m59-harness
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@m59-harnesshave my cleric heal the party after the fight"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
m59-harness
Play Meridian 59 as a real player character, from an agent.
Characters log in over the same port humans use. They see the room and its
geometry, enforce the same walls, cliffs, headroom, and player radius as the
normal client even though the server does not, travel across the world, fight, shop, talk, rest, hand each other
items and money, and hear each other. who lists them beside the humans. Any
MCP client — Claude Code, Codex, a local model with a curl loop — can drive
one.
This is a baseline, not a running fleet. Nobody's roster, character state or chat history is in here; what is here is the protocol client, the world model, the behaviours, and a reference compendium compiled from the game's own source. Build your own fleet management on top.
Install it
From nothing to ten characters playing, on Windows or Linux:
git clone https://github.com/tpeppers/m59-harness
cd m59-harness
node tools/setup.mjs all 10That clones the Meridian 59 source,
builds the server in a container, bakes a local collision map from that exact
server's room resources, starts the broker, and creates ten
characters. Ten to fifteen minutes, mostly compiling. node tools/setup.mjs doctor reports what is present and what is missing without changing anything.
Or open the repository in Claude Code or Codex and ask it to install the game and
make you a fleet — CLAUDE.md and AGENTS.md are the
procedure, written for an agent.
The manual, with both platforms, the native build, and troubleshooting, is
docs/INSTALL.md.
The Steam client is optional. Agents log in over the wire; no Meridian.exe
is involved in running a fleet. You need
the client to watch a
character in first person, and for the compendium's sprite art.
If you do have it, node tools/setup.mjs shortcuts writes one shortcut per
character — host, port, account and password already filled in, so opening
m59-Aldric.desktop puts you in the world as Aldric with nothing to type. They
hold real passwords, so shortcuts/ is gitignored; the details are in
docs/INSTALL.md.
Related MCP server: Railroaded
What you need
Node 22+ | everything. Node 24 LTS is recommended; tools in |
Docker | building and running the server, the same way on both platforms |
git | fetching the server source |
Python 3 | the sprite puller and the source-analysis scripts. Optional |
the source tree | set |
One dependency exists, for the chat responder only: npm install.
Start here
node tools/m59-service.mjs startThis starts one supervised broker with a durable log. For foreground diagnostics,
node tools/m59-broker.mjs --http 8901 --dashboard 8902 runs the same broker directly.
Both select maps in the same order: explicit M59_MAP, then
substrate/m59-map.local.json when setup generated one, then the checked reference.
The selected map is fully decoded and validated before the broker reports healthy.
One broker endpoint, N characters. The production default keeps each
character's keeper in a child process for fault isolation; the optional
m59-lab-runtime gives an explicitly marked test fleet
event-driven scheduling and a lazy atlas. It defaults to a lowest-memory single process;
--shards N optionally isolates partner-preserving actor groups into hidden child
processes, each with its own V8 heap and one atlas, while a Meridian-free parent owns the
leases and aggregate control surface. Point a client at the broker:
{ "mcpServers": { "meridian59": {
"command": "node",
"args": ["C:/code/m59-harness/tools/m59-mcp-attach.mjs", "--port", "8901"]
} } }.mcp.json in this repo does exactly that — fix the path for your checkout.
Attach, do not spawn. m59-broker.mjs with no arguments serves stdio MCP
and resumes a fleet. With one broker already owning that fleet, a second is
refused before its HTTP or stdio listener opens and exits with status 3; it does
not attach to the existing process. m59-mcp-attach.mjs forwards stdio MCP to
the broker that already owns the fleet and holds no state itself.
Resource-efficiency modes
The production entry point and isolation model are unchanged: m59-service.mjs still
runs the standard broker and one keeper child per character. Its idle work is now
demand-driven. Rich keeper state is projected only when an MCP/dashboard reader needs it,
bursts reuse a two-second snapshot, and one broker deadline checks all keepers through the
small projection-free /live identity/connection reply. State-file persistence, the
flight recorder, failed-start retry, and client keepalive use bounded one-shot deadlines
instead of permanent polling intervals.
The opt-in lab runner shares one lazy atlas and scheduler across all actors by default;
--shards N trades additional heaps/atlases for smaller GC, synchronous-stall, and crash
domains. The offline 100-actor lower-bound smoke measured about 14 KiB of used heap per
unstarted actor shell after one shared import, whose repeated forced-GC floor was about
138 MiB RSS. This is not a connected-fleet memory promise. A checked exit atlas (used by every keeper since 2026-09-01, not only the lab runner)
removes the former multi-second fine-boundary derivation while exact tests compare every
projected approach. See the lab runtime guide for the safety
boundary, measurements, fallbacks, and shard accounting.
An isolated one-character live canary measured 122–129 MiB working set for the shared runtime versus 727 MiB for the ordinary broker plus one keeper on the same machine—about 82–83% less RAM—and replaced the standard broker's observed 8.5–8.7-second eager routing startup with 0–1 ms lazy graph registration. The canary also passed authenticated control shutdown, stale-lock reclaim, standard keeper resume in a dedicated 100-port band, and orderly service shutdown. These are test-server measurements, not a promise that connected actors remain constant-cost; the full setup and caveats are in the lab runtime guide.
A separate experimental image can scale only Blakod timers and the world-hour event;
anti-abuse timing (GetTime()), sockets, sessions, protocol pacing, logs, and save cadence
remain on wall time. It is never selected by the normal Dockerfile or production service:
node tools/m59-sim-server-build.mjs --check --scale 10
node tools/m59-sim-server-build.mjs --build --scale 10 --tag m59-blakserv-sim:lab-10xThe wrapper requires the manifest's exact pinned source checkout through M59_ROOT or
--source PATH; it verifies preimage hashes and patch applicability without modifying that
checkout.
The pinned patch contract and a native Windows RELEASE/Werror build pass, but the Docker
daemon was unavailable: the Linux image build, container attestation, save/restart smoke,
and a live canary have not been run. Treat it as an isolated test-first artifact, not a
production option; the exact guarded run/stop commands and save boundary are in the lab
runtime guide.
Then read docs/m59-agent-primer.md — the rules of
the world, written for something that is about to play it.
The map
tools/setup.mjs doctor / server / client / broker / fleet — the bootstrapper
tools/m59-makefleet.mjs make N characters that are worth growing
tools/m59-shortcuts.mjs a click-to-play client shortcut per character
tools/pull-client-assets.py decode the client's sprites into the compendium
docker/Dockerfile builds blakserv from either source tree, on any platform
docker/Dockerfile.sim-clock separate source-pinned experimental lab-clock image
server-patches/simulation-clock/ immutable manifest, hashes, and isolated server patch
docs/INSTALL.md the manual: both platforms, both build paths, traps
docs/m59-agent-primer.md the rules of the world, for an agent that will play it
docs/m59-progression.md how a character grows, how fast, how to tell it is working
docs/m59-mcp.md the broker: run it, wire it up, what the tools do
docs/m59-protocol-client.md the wire protocol — login, perception, message formats
docs/m59-coordination-research.md cited findings on trading, loot, PvP, kill credit
docs/m59-conversation.md the chat bridge: hearing players, answering them
docs/m59-proxy-handoff.md sitting between a human client and the server
docs/meridian59-bridge.md the admin-socket control plane, runbook, and traps
docs/m59-lab-runtime.md optional one- or multi-process event-driven test-fleet runtime
tools/m59-broker.mjs the MCP control plane over N keeper-backed characters
tools/m59-lab-runner.mjs opt-in event-driven runtime and shard supervisor for a dedicated test fleet
tools/m59-lab-roster.mjs safely marks an existing local named roster for the lab runtime
tools/m59-lab-shard-child.mjs minimal guarded child for optional process-sharded labs
tools/m59-sim-server-build.mjs verifies/builds the isolated simulation-clock image
tools/m59-sim-server.mjs guarded loopback lifecycle and clock attestation for that image
tools/runtime/shards/ ownership permits, IPC state/ACK control, aggregation, and exact stop
tools/m59-client.mjs a protocol client that logs in as a real player
tools/m59-parse.mjs the server→client parsers: perception and trading
tools/m59-world.mjs the joined world model: perception + graph + geometry
tools/m59-map.mjs the room graph — 264 rooms, 980 exits, both mechanisms
tools/m59-roo.mjs .roo geometry: walkability, walls, A*, the minimap
tools/m59-skills.mjs composite behaviours: fight, rest, escape, sell everything
tools/m59-autopilot.mjs the keeper — a background loop that holds baseline state
tools/m59-merchants.mjs who buys, sells and teaches what
tools/m59-spells.mjs spell costs, reagents and the karma gate, compiled from kod
tools/m59-safespots.mjs squares a character can hold, and against how many
tools/m59-rsc.mjs the resource table, id → text, straight off the wire
tools/m59.mjs say / listen / escort / follow, over the admin socket
tools/m59-fleets.mjs every roster on this machine: slots, server, who is holding it
tools/m59-proxy.mjs sit between a human client and the server and watch
tools/m59-tui.mjs interactive fleet terminal
tools/m59-dashboard.mjs the fleet web page — /, and the shared dashboard tab bar
tools/m59-observability-page.mjs DUM interventions plus opt-in 2h strategy drill-ins — /dum, /harness
tools/m59-strategy-stats.mjs rotating travel/fight/trade/vault detail spool (24h default)
tools/m59-deaths-page.mjs /deaths and /tougher: what killed them, what it took to gain
tools/m59-economy.mjs purses, bank balances and reagents — /economy
tools/m59-abilities.mjs every skill and spell number the fleet holds — /skills
tools/m59-stats-page.mjs /stats: the builds the fleet is made of, grouped by the roll
substrate/m59-map.json 264-room graph plus generated reference collision geometry
substrate/m59-merchants.json 70 merchants: who buys, sells and teaches what
substrate/m59-spells.json 175 spells: mana, reagents, level, karma requirement
substrate/m59-spawns.json 120 creatures across 183 rooms, with danger ratings
substrate/m59-safespots.json 11 rooms of proven standing squaressubstrate/ here is reference data, not a running fleet's state.
A setup also writes gitignored substrate/m59-map.local.json from the exact .roo
files used by its server. Ordinary restarts keep selecting that local artifact. If it
does not exist, the portable checked reference is used; movement still stops closed if
the live room security differs. Every map carries a semantic manifest over all room
security values and collision payloads. Corrupt or incomplete maps fail broker startup;
valid but server-mismatched/obsolete geometry fails movement closed on the live room's
security value.
A live broker writes fleet-state.json, history/ and recordings/ beside it;
all three are gitignored, because a roster carries account passwords in plain
text and recordings are one server's history rather than anything reusable.
Tests
Offline, no server needed:
node tools/m59-safespot-test.mjs # 91 tests — safe squares, errand pairing
node tools/m59-autopilot-policy-test.mjs # explicit keeper policy overrides, offline
node tools/m59-chat-test.mjs # 102 tests — sanitiser and leak detection
node tools/m59-escape-test.mjs # 29 tests — leaving and fighting from a sitting start
node tools/m59-collision-test.mjs # fine BSP collision, cliffs, walls, slopes, exits
node tools/m59-fleets-test.mjs # the roster inventory, against a fixture broker
node tools/m59-loadout-test.mjs # 109 tests — loadouts, and what reaches the counter
node tools/m59-stats-test.mjs # 60 tests — the builds board, and the pane it shares
node tools/m59-broker-demand-test.mjs # demand state and projection-free keeper /live seam
node tools/m59-keeper-idle-test.mjs # event-driven keeper persistence and join retry
node tools/m59-client-keepalive-test.mjs # one-shot idle/proof-of-life wire keepalive
node tools/m59-recorder-test.mjs # lazy event-driven flight recorder
node tools/m59-world-exit-atlas-test.mjs # exact lab atlas parity and cold-origin bound
node tools/runtime/server-clock-contract-test.mjs # isolated patch/image contract
node tools/m59-sim-server-test.mjs # offline simulation-server controller contractAgainst a live server, with test accounts:
node tools/m59-perception-test.mjs # the parser, in every room on the server
node tools/m59-play-test.mjs # the primer's rules, re-checked
node tools/m59-coop-test.mjs # two agents: see, walk, talk, trade, split
node tools/m59-skills-test.mjs # fight / rest / escape, end to end
node tools/m59-autopilot-test.mjs # three unattended minutes with nobody drivingThe compendium
A static reference site: every spell, skill, item and creature, plus guides to
the systems that connect them. 1,030 pages. Nothing in it is remembered or
estimated — every page is compiled from the server's own Blakod source, and every
quantitative claim carries a file:line citation into M59_ROOT.
cd compendium && node tools/serve.mjs # http://localhost:8099/The pages are committed. The 5,355 sprites are not — they are the client's own art, 40 MB of it. Decode them from any local client:
python tools/pull-client-assets.pyIt finds a source checkout or a shipped client (Steam, GOG) on its own; pass
--resource and --palette if it does not. Note that blakston.pal ships only
with the source tree, so a retail client alone is not enough. Until you run it,
pages render and images 404.
To rebuild the site itself from a changed source tree: node tools/build.mjs
inside compendium/. See compendium/README.md.
Telling the fleet what a character should be carrying
The compendium's planner is the page between the reference site and the live fleet. It rebuilds the client's own right-hand panel — inventory, spells, skills, stats, the same four tabs — and makes it editable, so what comes out is a loadout: one file per character saying what gear it should get back to, how many of each thing it should carry, and what it should sell on sight.
node tools/m59-compendium.mjs --open --to /planner/ # or press P in the fleet terminal
node tools/m59-loadout.mjs # every loadout on this machine
node tools/m59-loadout.mjs Kermit --check # ...against what Kermit holds now
node tools/m59-loadout.mjs Kermit --init # seed one from its character sheet
node tools/m59-loadout.mjs Kermit --gear-to-fleet # what giving everyone its gear would doThe gear half is the one part of a loadout that is about the fleet rather than
about a character — how many reagents a caster burns is its own business, but
"fight with a short sword and wear leather" is a decision about all of them. So
the planner's Apply gear to fleet, and --gear-to-fleet --apply, write that one
field into every character's loadout and change nothing else in any of them. Both
say what they would do first: it is one file per character, and an empty gear list
is refused rather than applied, because a loadout nobody has filled in is not an
instruction to strip the fleet.
The keeper reads substrate/loadouts/<character>.json every pass and acts on it:
it tops up to the minimums at a counter it is already standing at, holds back
what the floors protect, sheds what the ceilings and the sell list release, and
reaches for the weapon the list names rather than whichever one it happens to be
best with. Every rule in it used to be a constant shared by all twenty-one
characters.
A loadout adds rules; it never removes them. A character without one behaves
exactly as it did before loadouts existed, and a loadout that mentions only
elderberry changes nothing about anything else. That is the property
m59-loadout-test.mjs spends most of its 126 assertions on.
Source analysis
tools/*.py and experiments/*.py read the Blakod tree directly rather than
playing, because the interesting numbers are never sent over the wire — a spell's
mana cost, its reagents, its karma requirement, armour resistances, monster
difficulty, treasure tables are all declared in kod and enforced server-side.
Both honour M59_ROOT and write beside themselves.
python tools/extract_monsters.py # kod → monsters.json
python tools/xref2.py # which rooms spawn what
python tools/econ_shops.py # what merchants pay
python experiments/ladder.py # kills-to-next-HP by stamina and levelTwo things that made this harder than the protocol suggested
Silence. The server drops illegal and too-fast actions without saying so — three rate limits, a facing check, a range check, all quiet. The broker paces every request, so an agent trades visible latency for invisible failure.
The state a player actually has. A protocol client sees a list of objects
with coordinates, which is not enough to play. The human client has a minimap,
and that minimap is drawn from the room's .roo file — a per-square walkability
grid and a wall-segment list the protocol never mentions. All 264 rooms of it are
parsed and baked in, so look returns the room's shape, what is reachable and in
how many steps, and which square to stand on to leave.
A small model does not have to orchestrate any of that. fight("spider") finds
the nearest match, arms itself, walks there through the geometry, turns to face,
swings on the server's clock, breaks off if it is losing, and loots the drops —
one call, every stage reported. autopilot is a background keeper with no model
in it at all: it rests, withdraws, escapes the Underworld after a death, and
optionally farms one named creature, journalling each decision with a reason.
Licence
The tooling here is ours. Meridian 59 itself — its source, its art, its data — belongs to its owners; nothing of the client's is redistributed here, which is why the sprites are pulled rather than committed.
This server cannot be deployed
Maintenance
Related MCP Connectors
MCP server for building and testing AI agents with multi-model experimentation and insights.
Nifty's MCP server — exposes tasks, projects, messages, and files as tools for AI agents.
MCP server for AI dialogue using various LLM models via AceDataCloud
- UnifAPIOAuthcom.unifapi
Hosted MCP server for live public-data APIs and Skills for AI agents.
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceAn MCP server that enables AI agents to act as dynamic dungeon masters for text-based RPGs with dynamically generated rule systems and comprehensive game state management.10 npm11MIT
- AlicenseNot gradedqualityBmaintenanceAn MCP server enabling AI agents to autonomously play D\&D as players and Dungeon Masters, with real dice rolls and full campaign management.2MIT
- AlicenseNot gradedqualityDmaintenanceLocal MCP server that gives AI agents 44 engine tools to build, run, and debug real 2D and 3D games through conversation.MIT
- AlicenseBqualityCmaintenanceAn MCP server that enables AI agents to play Minecraft with full bot capabilities including pathfinding, block interaction, crafting, combat, and more.293 npmMIT