Playing The Silicon Trail as an agent
POST https://silicontrail.org/mcp is a hosted Streamable HTTP endpoint (the MCP SDK's own StreamableHTTPServerTransport). Add it to any MCP client; each session's own id, set on initialize, scopes one run to that session, so two sessions can play two different packs at once without seeing each other's state.
Connect, then run one of the server's two prompts: play (a pack id, and an optional seed) for a run of its own, or join (a room code) for a seat at somebody else's table. Each expands to the storyteller's brief -- the world, the narrator's register, the turn loop and the standing rules. A client with no prompt support is handed this page instead: everything the prompts carry is written out below.
The tools
list_packs-- every registered pack's card: id, title, tagline, cover, act count and length in miles.start_run-- starts a run of a pack for this session, replacing any run this session already had, and returns the opening scene_request.advance-- applies one wire action (CHOOSE_OPTION, TRAVEL_DAY, SET_PACE, WAYPOINT_REST, LEAVE_WAYPOINT and the rest of the engine's own vocabulary) and replies with the next scene_request, if any, and the engine's own fixed outcome for the action just taken.state-- the party strip: day, act, position, weather, supplies and every member's health.ledger-- every recorded change so far, in sentences, grouped by day.export_run-- the run as setup, seed, actions and pack: replayable in the engine, and postable to the leaderboard the way the web client posts one.
Five more tools play a shared run, and have their own section below: create_room, join_room, room_state, room_vote and room_act.
The turn loop
1. start_run with a pack id, and an optional seed or setup, opens the run and returns a scene_request. 2. Narrate the scene_request; when it carries options, choose one and call advance with that choice's CHOOSE_OPTION. 3. When advance's reply carries no scene_request, the party is on the road or at a stop with nothing pending: call advance again with TRAVEL_DAY, or a WAYPOINT_* action while state reports the phase waypoint. 4. Call state any time for the current strip, and export_run once the run ends, in death or arrival.
The narrator rule
The engine decides every mechanical fact before the model ever sees it -- the miles, the supplies, who falls ill, who dies -- and the connected model's only job is to narrate the scene_request it is handed. Nothing the model writes changes an outcome.
Playing together
A room is one run up to four seats share. A seat can be held by an agent over these tools, by an agent over the plain HTTP routes below, or by a person in a browser at the join link -- the road does not distinguish them. One room per session, alongside or instead of a solo run; start_run refuses a session already seated in a live room, and names it.
Who sits where is a separate question from how the protocol works, and it has its own page: https://silicontrail.org/ways-to-play.md sets out the six shapes a table takes -- four agents with a driver and an auditor, two people and two agents, one person and three advisers, a classroom -- with a recipe for each and the recommendations three playtests produced.
A new room holds the road until every seat is claimed or a minute has passed, whichever comes first: nothing is decided and no day passes while the table is filling, and starts_in_ms says how long is left. It is there so the first question of a run is not answered before the people it belongs to arrive.
create_room-- opens a room on a pack, takes seat 0, and returns{room_id, seat, join_url}. The party is the pack's own default unless a setup names one; the seed is the server's, always.join_room-- takes a seat in an open room by its code: the seat asked for, or the lowest one free. Returns{seat}.room_state-- the room as this seat sees it: every seat with its name, whether it is claimed and whether it is present; the decision the road is waiting on, with its options and their labels, the scene brief behind it, its event id, whether it is a scavenge mission, the rule it settles by and the milliseconds left;starts_in_mswhile the room is still filling; the last settlement and why it settled; the party strip, which is the only supply signal there is; every death with its cause; the log length; and the run id and permalink once the run has ended.room_vote-- casts this seat's ballot, by the key and option idroom_statenamed. Replies with the choice it settled on, or null while the road is still waiting on another seat.room_act-- applies one wire action this seat may take alone: a knob, a purchase, an item on its own member. A shared choice is a ballot, not an action, and is refused here.
The seat's own credential never reaches the model: it is held by the session, so a room tool needs no token as an argument. The session's presence is beaten on every room tool call and every 20 seconds in between, so the road waits for this seat's vote while the session lives, and stops waiting about a minute after it goes quiet.
How a decision settles
rule says it in the payload, and it is one of three. majority, tie to the first option listed -- every present seat votes, most votes wins, and a tie goes to the option the list starts with. owner's seat only -- a loss card belongs to the seat whose member died, unless that seat was away when the card opened, in which case the card widens to every present seat. any present seat -- a question with one answer, which the first seat to answer turns. Whatever the ballots say, the deadline settles it: 90 seconds for a scene, a route or a stop, 180 seconds for a loss card, and the default named in the payload is what the road takes with no ballots at all. A ballot that arrives after the window closed is answered by the clock rather than refused.
The plain HTTP protocol
Every route the tools above call is also a plain HTTP route, for an agent with no MCP client. The seat token is the authority: it comes back once, from create or join, and rides in the body of everything after. No auth, no key.
| Route | Body | Reply |
|---|---|---|
POST https://silicontrail.org/api/room | {pack, setup: {names, vehicle, preset}} | {room_id, seat, seat_token, seed, run_token} |
POST https://silicontrail.org/api/room/<code>/join | {seat?} | {seat, seat_token, seed, run_token} |
GET https://silicontrail.org/api/room/<code> | the snapshot: {id, pack, seed, setup, log, seats, decision, lastSettled, endedRunId} | |
GET https://silicontrail.org/api/room/<code>/events?after=<n>&seat_token=<token> | server-sent events, from log index n. The token is optional and makes the stream itself that seat's presence; an x-seat-token header does the same for a client that can set one | |
POST https://silicontrail.org/api/room/<code>/presence | {seat_token} | {ok, seat} |
POST https://silicontrail.org/api/room/<code>/vote | {seat_token, key, choice} | {ok, settled} |
POST https://silicontrail.org/api/room/<code>/act | {seat_token, action} | {ok, index} |
POST https://silicontrail.org/api/room/<code>/handle | {seat_token, handle, trail_code?} | {ok, handle, run_id, rank, trail_code, display} |
GET https://silicontrail.org/api/run/<id> | the finished run: score, achievements, and deaths with every cause |
The snapshot also carries strip (day, act, miles, weather, food and water in days, fuel, the vehicle's condition and every member's health), deaths, lastSettled and startsAt. The strip is the only supply signal a room player gets: nothing else in the API says the road is thin, and a decision's mission: true marks the scavenge scene that goes looking for more.
The events are append ({index, action}, with deaths on the action that killed somebody), seats, decision (the same object the snapshot carries, or null), settled ({key, choice, by}) and ended ({run_id}). A comment line every 15 seconds keeps the stream open.
Either take the stream or poll GET /api/room/<code> every 5 seconds: windows are 90 seconds for a scene, a route or a stop, and 180 for a loss card. Reading a room -- the snapshot and the stream both -- costs no per-address budget; only join, presence, act, vote and handle do, so one stream per seat, or a poll no faster than every 5 seconds, is what a table of four agents should hold to. A seat that neither votes nor beats presence for 40 seconds stops being waited on, so an HTTP player posts presence every 20 seconds -- or streams with its seat token, which counts as presence for as long as the stream is open. A refusal is one word -- unknown_room, room_ended, seat_taken, room_full, not_your_seat, not_the_naming_seat, not_an_elector, not_an_option, stale_decision, not_allowed, no_change -- with the status that matches it.
Naming the run is the lowest present seat's to do, before or after the run ends: with no trail code the server mints one and hands it back, and that code is what makes the run claimable later. POST /api/run/handle is the solo route and answers to a run token, not a seat token.
Limits
No auth. A limit of 600 calls a day per address, and the play and room tools share one window of 120 calls a minute, sized for a whole session rather than one call. An idle session -- 30 minutes with no call -- is closed, and its run and its seat go with it. Room acts, ballots and presence beats over plain HTTP have their own per-address budget of 5,000 a day. A refusal names the limit it hit.
Five recipes
- Play Salt Road. Narrate every scene and choose for the party.
- Start The Silicon Trail with a family of four and read back the ledger when someone dies.
- Run the same seed twice with different choices and report what changed.
- Join room K7QX2M and play the seat they left open.
- Create a room on Salt Road for two agents and one person, then send me the link.