Files
turnstone/examples/door-game
Patrick Buckley d54110ffcb feat(examples): Understone v0.3 — the Watch (lobby TV) + a livelier Vale
A read-only CRT spectator page served by the game process itself, plus
content depth. Input never flows through the Watch — it is the wall-mounted
terminal in the BBS room; chat remains the only actuator, so there is no
input channel to deadlock and no cross-origin surface (the page polls the
same origin that served it).

- /watch: one self-contained page (inline CSS/JS, no external assets),
  phosphor CRT styling. The base map paints once from /watch/world.json
  (terrain glyph rows + a glyph->color legend — the palette the text
  renderer has deliberately ignored since v0.1 finally gets its first
  renderer); players overlay as positioned glyphs repainted from
  /watch/state.json every 2s; the sidebar carries the roster with win
  stars, the Hall of Legends, and the Herald. SIGNAL LOST on poll failure;
  the bootstrap retries so a spectator arriving during a server blip
  recovers without a reload.
- Routes ride FastMCP custom_route on the existing process — read-only
  handlers with no awaits between reads (handlers and sync tools
  interleave on one event loop, so every response is a consistent
  snapshot).
- door_join/door_help advertise the Watch URL in http mode (stdio: none).
- Content: +5 monsters (one per tier; the gauntlet's first-in-tier foes
  preserved), +3 items smoothing the gear curve, +6 events; fight weight
  retuned to hold ~55% of encounter rolls. Zero geography churn.
- Review round: the Herald window is a plain list tail (id arithmetic
  under-reported the feed when AUTOINCREMENT ids gap — regression-pinned
  with sparse ids), and the bootstrap-retry fix above.

Tests 149 -> 166.
2026-06-13 04:40:40 -07:00
..

Understone

A small, multiplayer, BBS-style ANSI door game served over the Model Context Protocol (MCP). It is a text RPG in the spirit of Legend of the Red Dragon — explore an overworld of box-drawing maps, fight wandering monsters, shop and rest in town, and descend a dungeon — except the "door" is an MCP server and the player drives it by talking to an AI assistant.

The server is the rules engine and the single source of truth. Players share one persistent world: your assistant calls tools, the server returns authoritative frames and facts, and the assistant narrates the story around them.

This is a self-contained reference example. It depends only on mcp — there is no dependency on Turnstone itself — so it runs against any MCP client.

How to play

There is no prompt to paste and no persona to configure. The tool schema is the whole interface. Once the server is registered with your assistant:

  1. Tell your assistant you'd like to play an ANSI door game / text dungeon RPG (it can discover the tools by name and description).
  2. The assistant calls door_help to learn how to run the world, then door_join with your adventurer's name.
  3. Play unfolds as a conversation: "head east", "fight it", "rest at the inn".

Everything the assistant needs to run the game well is returned by door_help.

Gameplay

A run is a little RPG loop, played a bit each day:

  • Explore the overworld of box-drawing maps. Walking is free, but the wild country has texture — a step may turn up a wandering monster, a purse of gold, a healing spring, a small trap (which can never kill you), or a scrap of old Vale lore. Only one such find happens per move, and the non-combat ones don't interrupt your walk.
  • Fight, shop, and heal in and around town. Fighting and descending the dungeon gauntlet each spend one of your daily turns; resting, shopping and moving do not.
  • Win the game by slaying the Wyrm Below. Once your hero is seasoned enough, challenge it at the dungeon. A victory frees the Vale, carves your run into the Hall of Legends, and — in the tradition of Legend of the Red Dragon — begins a new life: your character resets to first-day gear and stats but keeps a permanent ★ for every Wyrm slain, ready to do it all again.
  • Read the news. door_log is the Understone Herald, a shared broadsheet of notable deeds across the whole world — who joined, who rose a level, who was dragged home by a goblin, and who freed the Vale.

Installation

This example uses uv. From the example directory:

cd examples/door-game
uv venv
uv pip install -e .

That installs the understone entry point into the environment.

To run the tests and quality gates:

uv pip install -e ".[test,dev]"
uv run pytest
uv run ruff check .
uv run ruff format --check .
uv run mypy understone/

Running the server

By default the server speaks the stdio transport, which is how MCP clients launch a per-session subprocess:

understone

To host one shared world over HTTP for several clients, run the streamable-http transport as a single long-lived process:

UNDERSTONE_TRANSPORT=streamable-http understone

Environment variables

Variable Default Description
UNDERSTONE_DB ./understone.db SQLite database file for the world's state.
UNDERSTONE_WORLD (packaged pack) Directory of a content pack to load instead of the bundled Vale of Understone.
UNDERSTONE_TRANSPORT stdio stdio or streamable-http.
UNDERSTONE_HOST 127.0.0.1 Bind host (streamable-http only).
UNDERSTONE_PORT 8077 Bind port (streamable-http only).
UNDERSTONE_PATH /mcp HTTP path for the MCP endpoint (streamable-http only).

The Watch — a live spectator view

When the server runs under the streamable-http transport, it also serves a read-only Watch page: the lobby TV of the Vale. Point a browser at

http://127.0.0.1:8077/watch

(the host and port follow UNDERSTONE_HOST / UNDERSTONE_PORT). It is a period CRT spectator console — a green-and-amber phosphor map of the whole world with every adventurer's @ marker, a live Understone Herald feed, the Hall of Legends, and a roster of who is currently abroad. It refreshes every couple of seconds; if it loses contact it dims and reads SIGNAL LOST until the server returns.

The Watch is strictly read-only. Input never flows through it — there are no controls, no forms, nothing that can change the world. It reads the same shared state the tools do and paints it; that is all. There is no authentication, in keeping with the rest of this easter-egg server (see the safety note below), so treat the page as you would the MCP endpoint itself.

Screenshot: the Watch console — a phosphor-green overworld map with amber @ markers, the Herald feed and Hall of Legends down the right-hand rail. (Image placeholder; run the server and open the URL to see it live.)

When the Watch is up, the door_join welcome and the door_help manual both print its URL so players (and the assistant narrating for them) know it exists. If you bind to 0.0.0.0 to share the world across a network, advertise a host that browsers can actually reach (your machine's LAN address or hostname) rather than 0.0.0.0 itself — the link is composed from UNDERSTONE_HOST.

Registering with Turnstone

Understone is an ordinary MCP server, so it plugs into Turnstone's MCP client config two ways.

Stdio (per-session subprocess). Turnstone launches the understone command for each session. Each session gets its own subprocess, so for a truly shared world prefer the HTTP form below; stdio is simplest for solo play.

[mcp.servers.understone]
command = "understone"

[mcp.servers.understone.env]
UNDERSTONE_DB = "/var/lib/understone/world.db"

Streamable-HTTP (one shared world). Run a single Understone process with UNDERSTONE_TRANSPORT=streamable-http and point every client at its URL. This is the right setup for multiplayer: one process, one database, one world that all adventurers share.

[mcp.servers.understone]
url = "http://localhost:8077/mcp"

Operator note. For multiplayer, start exactly one shared process — UNDERSTONE_TRANSPORT=streamable-http understone — and have all clients use the url form. The world lives in a single SQLite file written by that one process.

The tools

Tool What it does
door_help The game-master manual. Start here.
door_join Create or resume an adventurer; returns the opening map.
door_status The character sheet (read-only).
door_look Redraw the current view — overworld map or location menu.
door_move Walk the overworld (free; no daily turn spent).
door_action Context verbs: fight, flee, rest, buy, sell, heal, descend, challenge (the Wyrm), leave.
door_log The Understone Herald — the shared feed of notable deeds.
door_rank The leaderboard, plus the Hall of Legends (★ marks Wyrm kills).
door_bestow Game-master grant of a little gold/healing for a story beat.

A note on identity and safety

This example is an easter egg, not a hardened service. Identity is self-asserted: a "player" is just a name passed to the tools, and there is no authentication — anyone who can reach the server can act as any name. That is fine for a shared toy world among people who trust each other, and deliberately out of scope for a game. Do not store anything sensitive in it, and if you expose the HTTP transport beyond localhost, put it behind whatever access control your environment already provides.

The game master's door_bestow channel can only grant small, capped amounts of in-game gold and healing — never items, never turns — and every grant is written to the public in-world log, so its reach is bounded by design.