In addition to running as a MUD server, you can create general-purpose applications in LPC and execute the program via neolith command.
The usage is simple:
neolith [options] <lpc-file>Traditionally, a MudOS LPMud driver requires a configuration file that supplies settings for mudlib directory, master file, ... etc. when starts up.
If you take a look at the core of a LPMud, there are only three essential components required:
- A mudlib directory that defines the filesystem boundary the LPMud code can access.
- A master object that is the source of authorizations (policies) when LPC programs interact with each other and the filesystem or network.
- Settings for incoming port from which user can enter the MUD.
Neolith has enhanced the LPMud driver starting-up path and make the configuration file optional. This allows standalone LPC programs or MUD applications to be created.
For a minimal viable MUD application:
- Single LPC file and implicit mudlib directory
- No configuration file (with default settings)
- Run with console mode.
Example:
// hello_world.c - a minimal MUD application
object connect(int port) {
return this_object();
}
void logon() {
write ("Hello World!\n");
shutdown();
}To run the MUD application:
neolith -c hello_world.cThe example prints the message to console and exits.
- The file
hello_world.cis used as the master file to create a minimal LPMud that does not listen on any TCP port. - Without specifying a configuration file with
-foption, Neolith uses the parent directory ofhello_world.cas the mudlib directory. - With
-coption, the driver connects the console user on start up. - In the master apply
connect, the master object itself is returned as the user object for the console user. - The driver calls
logonapply on the user object, which is master object itself. The LPC code prints the message to console user and shutdown the MUD.
With configuration file (neolith.conf), a MUD application is defined by specifying a particular master file via the driver command line argument (overriding the MasterFile setting in the configuration file).
For example:
neolith -f neolith.conf -c mudlib/adm/apps/migrate_player_file.cNote
To prevent ambiguity, if configuration file is specified, neolith requires the mudlib directory be explicitly defined with MudlibDir.
Specifying a MUD application outside the mudlib directory will be rejected.
A regular MUD application shares the same configuration settings with the production MUD server (e.g. simul efuns), while starting with its own epilog() stage, its own connect() interface, and all the policies controlled by master file.
Example of regular MUD application use cases:
- Experiment mass mudlib refactoring
- Sandboxing access to the MUD for agentic AI users
- Running as MCP server for AI coding agents
- Exercise maintenance tasks with tailor-made master file in console mode
- Automate LPC code testings or performance testings.
If the MUD application is an archive, it is used as a packaged (read-only) mudlib serving as MUD applications.
- When used alone, the archive provides a file tree acting as the mudlib directory. The driver shall look for a
config.jsonfile in the archive for labelled configuration settings. For example:{ ".defaults": { "SimulEfunFile": "/adm/obj/simul_efun.c" }, "production": { "inherits": [".defaults"], "MasterFile": "/adm/obj/master.c", "Port": [[4000, "telnet"]] }, "migrate-player-file": { "inherits": [".defaults"], "MasterFile": "/adm/apps/migrate_player_file.c" } }- Each label name a MUD application in the package.
- If the label name start with a dot(
.), it is hidden from the UI.
- When used with a configuration file, it overrides the
MudlibDirsetting and restricts the driver to load LPC files only from the archive.- Settings in the configuration file overrides
config.json.
- Settings in the configuration file overrides
To launch particular MUD application in an archive, add the label after archive name. For example:
neolith -c package.zip migrate-player-fileFor console-mode applications, Neolith behaves like an LPC runtime hosted by the driver backend. Even in console mode, the program runs as a real LPMud session with one interactive user.
For runnable examples (including hello_openai.c) and the current
environment/configuration requirements, use the examples/apps README as the
source of truth.
Extra arguments after the LPC file are forwarded to the application and made available at compile time through the __ARGV__ predefined macro:
neolith [options] <lpc-file> [arg1 arg2 ...]Inside the LPC file, retrieve the arguments as a string array:
void logon() {
string* argv = __ARGV__;
if (sizeof(argv) == 0) {
write("No arguments.\n");
} else {
write("Arguments: " + implode(argv, ", ") + "\n");
}
shutdown();
}Running with:
neolith -c my_app.c foo barproduces __ARGV__ expanding to ({"foo","bar"}) at compile time, so argv holds ({ "foo", "bar" }) at runtime.
When no extra arguments are supplied, __ARGV__ expands to ({}) (an empty array), so sizeof(__ARGV__) == 0 is a safe empty-check.
Note
A maximum of 16 arguments is accepted. Arguments beyond that limit are silently ignored.
A MUD application keeps core LPMud semantics:
- Application entry points:
epilog()for optional preload logicconnect()to bind an incoming user to an objectlogon()for per-user startup
- Independent sessions: Each user session is isolated; disconnect a user with
destruct(). - Service lifecycle: The backend loop continues until
shutdown()is called. - Sandbox boundary: File access remains restricted to the mudlib directory.
- Piped I/O: In console mode, standard input and output can be piped for automation.
- Configurable deployment: With
-f, you can run the same app with production mudlib settings and open network ports. - Recoverable coding loop: LPC compile/runtime errors do not crash the driver process; diagnostics are surfaced through standard error/log paths (including
log_error()policy hooks).
Use this pattern when moving from one-shot scripts to production-style tools.
- Keep master applies (
epilog(),connect(),logon()) minimal. - Delegate domain behavior to service objects and helper modules.
- In interactive apps, return a dedicated user object from
connect()instead of reusing the master object.
- Prefer non-blocking efuns (
perform_using()+perform_to()) for network access. - Treat callbacks as state transitions; store request context explicitly.
- Add timeout and retry policy for external APIs.
- Build payloads as LPC mappings/arrays, serialize with
to_json(). - Parse external responses with
from_json()before business logic. - Validate expected schema (
mappingp,arrayp, key existence) before indexing nested values.
- Keep secrets out of source files; load from local files or deployment-managed secrets.
- Fail fast with clear operator-facing messages when required configuration is missing.
- When using
-f neolith.conf, keep app paths insideMudlibDir.
- In LPC all variables are bound to an object; there are no global variables. Per-user state belongs on the user/session object, shared state belongs on a dedicated service object.
- Separate console automation flows from telnet/websocket user flows.
- Implement explicit teardown paths so abandoned sessions do not leak resources.
- Live repair without restart: When command-processing or other LPC code has bugs, the normal fix cycle is: edit the source file,
destruct()the live object(s) carrying the old program, then let the driver recompile on the next access. No MUD restart is required. This is especially important for remote clients where downtime is disruptive.- Destructing a parent object does not affect clones that have already inherited its program; destruct those independently if needed.
- Keep application logic in separate objects from the master file so bugs can be fixed without touching the master.
- Test console-mode behavior with scripted stdin/stdout.
- Add interaction tests for multi-user flows (for example with
examples/m3_testbots). - Verify failure paths: network errors, invalid JSON, missing config, and callback object destruction.