twine-play-mcp
Server Configuration
Describes the environment variables required to run the server.
| Name | Required | Description | Default |
|---|---|---|---|
| TWMCP_VIEW_HOST | No | Live-view server bind host (default `127.0.0.1`) | 127.0.0.1 |
| TWMCP_VIEW_PORT | No | Live-view server port (default `4571`, auto-increments if busy) | 4571 |
| TWMCP_CHROME_PATH | No | Chrome executable if `channel: 'chrome'` cannot find it | |
| TWMCP_DOWNLOAD_DIR | No | Folder where browser downloads are captured | ~/.cache/twine-play-mcp/downloads |
Instructions
Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.
This server publishes no instructions, or was last inspected before Glama recorded them.
Capabilities
Features and capabilities supported by this server
Protocol revision2025-11-25
| Capability | Details |
|---|---|
| tools | {
"listChanged": true
} |
Tools
Functions exposed to the LLM to take actions
| Name | Description |
|---|---|
| open_gameA | Open a Twine / interactive-fiction HTML game and return the first observation (passage text, numbered choices, inputs, dialog). Accepts a local .html file, a game folder (index.html or a single html is picked), or an http(s) URL. Local games are served over 127.0.0.1 so saves work. Returns a formatted text observation (string); pass format:"json" for a JSON string. Inputs are listed in windows of 40 (see inputs_offset/inputs_limit on observe) and find_ui(text) locates any control by label. |
| observeA | Read the current passage: text, numbered choices, input fields, dialog state and status text. Returns a formatted text observation (string); pass format:"json" for a JSON string. Inputs are paginated in windows of 40: when truncated, the header says e.g. "Inputs (41-80 of 132)" — call again with inputs_offset=80. Use find_ui(text) to jump to a specific control, get_variables for specific story variables, and since_last=true when polling to save tokens. |
| chooseA | Click a passage choice by its 1-based number from the last observation, or by (partial) label text. Numbered choices include dialog buttons (tagged [dialog] in observations) — so choose() answers dialogs too. Waits for the game to settle and returns the new observation. Pass expected to guard against clicking the wrong link. |
| waitB | Wait for time (ms), for a text to appear, and/or for the page to settle, then return the new observation. Useful for timed passages and animations. Returns text; pass format:"json" for a JSON string. |
| interactA | Interact with input fields or the keyboard: set a value on an input/textarea/select by ref, or press a key (e.g. "Enter", "ArrowUp"). For radios/checkboxes pass value "true" or "false". Inputs are refs from the last observation (or find_ui). Returns the new observation; pass format:"json" for a JSON string. |
| backB | Undo the last passage navigation when the story format supports it (SugarCube: Engine.backward). Returns the new observation (text; format:"json" for JSON). |
| restartB | Restart the story from the beginning (optionally with a new PRNG seed). Returns the first observation (text; format:"json" for JSON). |
| save_stateA | Save the full game state under a name so you can branch: save -> try a path -> load_state -> try another path. Supported natively by SugarCube; other formats report unsupported. |
| load_stateB | Restore a snapshot created by save_state and return the resulting observation (text; format:"json" for JSON). |
| click_uiA | Click dialogs, sidebar buttons and menus (SAVES, OPTIONS, ModLoader banner, modal buttons) by ref, CSS selector or visible text. Text matching covers -based controls too (SugarCube radio/checkbox options like "Jet black" or "Punch"); shortest match wins, exact=true for exact text. Returns the new observation (text; format:"json" for JSON). Use choose() for numbered passage choices and dialog buttons. |
| find_uiA | Search visible controls (buttons, links, labels, inputs) by label text and/or input name; returns refs usable with click_ui(ref), interact(ref, value) and upload_file(ref). This is the fastest way to reach radio/checkbox options (e.g. "Jet black", "Punch") and any input beyond the 40-item observation window. Results are capped by limit; no game state changes. |
| get_variablesA | Read specific story variables (SugarCube State.variables) by dot path, e.g. ["haircolour", "background", "player.background"]. Accepts "V.x", "variables.x" or plain "x". With no paths, returns a shallow summary of the top-level keys. Output is JSON. |
| upload_fileA | Upload a local file (mod .zip, exported .save, image) into an in the game. Provide trigger_text/trigger_ref for buttons that open a picker ("Load from File…", "Import"), or let it target the file input directly. If a hardcoded selector like #saves-import does not exist in the build, use find_ui("Load from File") and pass its ref as trigger_ref. Returns the new observation (text; format:"json" for JSON). |
| download_fileA | Browser file control for any game: every download is captured into the tool's download folder (see list_downloads). Three modes: (a) pass trigger_text/trigger_ref/trigger_selector to click the game's download button and take that file; (b) pass name or index to take an already-captured file from the folder; (c) pass none of those to take the newest file in the folder. Without |
| list_downloadsA | List files captured from the browser into the tool's download folder (any game; the folder persists across sessions and MCP restarts). Use download_file(name|index, path) to copy one anywhere. Shows name, size, capture time and the absolute folder path. |
| inspect_uiA | Inspect DOM outside the passage. Pass a CSS selector to get its text, buttons (with refs usable in click_ui) and inputs (file inputs usable in upload_file). Without a selector, lists overlay panels (mod GUIs, dev panels) that contain buttons or file inputs. |
| screenshotA | Take a PNG screenshot of the game viewport. Useful for canvas/image-driven games and visual QA. Pass path to save it to a file (returns the path instead of the image). One-shot: this is not a live view — to let the user watch the game, call live_view once instead of taking screenshots repeatedly. |
| get_console_errorsB | Return JavaScript errors/warnings captured from the page (useful for playtesting / QA). |
| get_journalA | Return this session's action history: every passage visited and every choice taken (including back/load events). Useful to summarise a playthrough, resume a run, or report coverage for QA. |
| list_gamesA | List the currently open game sessions with their id, source, story title and step count. |
| live_viewA | Give the user eyes on the actual page the agent is controlling: starts a tiny local web server (once per MCP process) that streams JPEG screenshots of the real Playwright tab (~1/s) together with passage, step, engine state, recent actions and the passage text. Returns a URL like http://127.0.0.1:4571/v/game_abc — open it in any browser (works with headless games too). Frames are captured only while someone is watching. Set open:true to also launch the URL in the default browser. DISPLAY POLICY: this is the default — and usually the only — way to show a game. One view per game: do not also switch to a headed window or loop screenshot; repeated calls return the same URL and never launch a second browser tab. |
| close_gameB | Close the browser tab and static server for a game session. |
Prompts
Interactive templates invoked by user choice
| Name | Description |
|---|---|
No prompts | |
Resources
Contextual data attached and managed by the client
| Name | Description |
|---|---|
No resources | |
TDQS
Scored across 22 tools
Most tools target distinct actions (observe, choose, save/load, screenshots, files), but there are ambiguous pairs: choose() explicitly answers dialog buttons while click_ui() claims to 'click dialogs', and find_ui vs inspect_ui both locate controls. These overlaps require careful description reading to pick correctly.
All names are snake_case and follow a verb_noun pattern for resource-targeting tools (save_state, open_game, get_variables, list_downloads). The spread of bare verbs (observe, choose, wait, back, restart, find_ui, inspect_ui, click_ui) deviates slightly, though it reads as an intentional convention for acting on the current game state.
22 tools is on the heavy side for a game-automation server. Several functions arguably overlap (choose/click_ui, find_ui/inspect_ui, screenshot/live_view), suggesting some consolidation is possible, though most tools do earn their place.
The surface spans the full play lifecycle: open/close, observe, choose/interact, wait, back/restart, save/load state, screenshots/live view, file upload/download, variables, console errors, journal, and UI inspection. Minor gaps exist (no direct story-variable setter, no passage search/jump), but these are workaroundable via interact and find_ui.