# 03 — STS2MCP interface A community mod by `kunology` that exposes the running game over a local HTTP API. Repository: `github.com/Gennadiyev/STS2MCP`, MIT. It is the actuator for this project: it reads state and performs one action at a time. It does **not** alter gameplay (`affects_gameplay: false`). ## Endpoints Base URL `http://localhost:15526`, no authentication. | Method | Path | Purpose | |---|---|---| | GET | `/api/v1/singleplayer?format=json\|markdown` | Read state | | POST | `/api/v1/singleplayer` | Perform one action | | GET | `/api/v1/multiplayer` | Read multiplayer state | | POST | `/api/v1/multiplayer` | Multiplayer action | | GET | `/api/v1/profile` | Persistent profile progress | | GET | `/api/v1/compendium` | Compendium-shaped progress | | GET | `/api/v1/wiki?query=&item_type=&limit=` | Fuzzy card/relic lookup | | GET/POST | `/api/v1/profiles` | List / switch / delete profile slots | All POST bodies carry an `"action"` field. Responses carry `{"status": "ok"|"error", "message": ...}`. ## `state_type` is the action enumerator Every response carries `state_type`, and each type has a small fixed action set. **This is the action space.** There is no need to write one. | `state_type` | Legal actions | |---|---| | `menu`, `game_over` | `menu_select` | | `monster` / `elite` / `boss` | `play_card`, `use_potion`, `end_turn` | | `hand_select` | `combat_select_card`, `combat_confirm_selection` | | `rewards` | `claim_reward`, `proceed` | | `card_reward` | `select_card_reward`, `skip_card_reward` | | `map` | `choose_map_node` | | `event` | `choose_event_option`, `advance_dialogue` | | `rest_site` | `choose_rest_option`, `proceed` | | `shop`, `fake_merchant` | `shop_purchase`, `proceed` | | `treasure` | `claim_treasure_relic`, `proceed` | | `card_select` | `select_card`, `confirm_selection`, `cancel_selection` | | `bundle_select` | `select_bundle`, `confirm_bundle_selection`, `cancel_bundle_selection` | | `relic_select` | `select_relic`, `skip_relic_selection` | | `crystal_sphere` | `crystal_sphere_set_tool`, `_click_cell`, `_proceed` | | `overlay`, `unknown` | none (transitions, or manual) | `use_potion` and `discard_potion` work in any state where potions are reachable, including outside combat. ## VERSION DRIFT — release 0.4.0 is broken on this build ### Symptom `GET /api/v1/singleplayer` returns **HTTP 500** as soon as combat starts: ```json { "error": "Failed to read game state: Method not found: 'Boolean MegaCrit.Sts2.Core.Combat.CombatManager.get_IsPlayPhase()'.", "exception_type": "System.MissingMethodException", "stack_trace": " at STS2_MCP.McpMod.BuildBattleState(RunState, CombatRoom)" } ``` Menu, character select, map, rewards, and events all work. **Only combat fails** — the one state that matters most. ### Root cause `CombatManager.IsPlayPhase` was removed in game `v0.107.1`. Verified: ```bash grep -c IsPlayPhase sts2.xml # -> 0 ``` Replacements present in `v0.107.1`: - `MegaCrit.Sts2.Core.Combat.PlayerTurnPhase.Play` (per-player) - `PlayerCombatState.Phase` - `ActionSynchronizerCombatState.PlayPhase` / `.NotPlayPhase` - `CombatManager.DebugOnlyGetState().CurrentSide == CombatSide.Player` ### Timeline | Event | Date | |---|---| | Release `0.4.0` (broken) | 2026-05-05 | | Fix commit `55e06485`, PR #123 | 2026-07-29 | | Newer tag | none | | CI build artifact | none | | Fork shipping a prebuilt DLL | none (checked 12 forks) | The fix exists in source but was never released. ### Resolution Build from upstream `main`. The fix is present and documented in the code: ```csharp // STS2 v0.107 removed CombatManager.IsPlayPhase. The turn phase now lives per-player on // PlayerCombatState.Phase internal static bool IsPlayPhase(Player? player) { if (!IsPlayerSideTurn()) return false; return player?.PlayerCombatState?.Phase == PlayerTurnPhase.Play; } ``` ```bash nix shell nixpkgs#dotnet-sdk_9 --command bash -c ' dotnet build STS2_MCP.csproj -c Release -o out/STS2_MCP \ -p:STS2GameDir="$HOME/Library/Application Support/Steam/steamapps/common/Slay the Spire 2"' ``` `Build succeeded. 0 Warning(s) 0 Error(s).` Result: 236,544 B DLL, replacing the 194,560 B release. The release DLL is kept at `vendor/STS2_MCP.dll.release-0.4.0`. **Lesson:** the game moves faster than the mod's releases. Upstream open issues #131 and #132 already track `v0.111` compatibility. Expect this class of break to recur after any game update. Pin the mod build to the game build. ## Known doc drift inside the mod `AGENTS.md` in the repo uses MCP **tool** names; `docs/raw-simplified.md` uses the HTTP **action** names. They do not match: | AGENTS.md | HTTP action | |---|---| | `combat_end_turn` | `end_turn` | | `event_choose_option` | `choose_event_option` | | `rewards_pick_card` | `select_card_reward` | | `rest_choose_option` | `choose_rest_option` | | `proceed_to_map` | `proceed` | Trust `raw-simplified.md` and the live API for direct HTTP use. ## Hard blocker: Timeline cannot be automated After a run ends with a pending epoch, the mod refuses: ```json {"status":"error", "error":"Timeline has obtained epochs that still need to be revealed manually; not opening Timeline because this game state logs invalid unlock-state errors when entered through automation", "pending_epoch_ids":["NEOW_EPOCH"], "manual_action_required":true} ``` This is a **deliberate guard**, not a bug. Entering Timeline through automation would corrupt unlock state. A human must open the Timeline and reveal the epoch. Until then the main menu offers only `settings` and `quit` and no run can be started. ## Mod config The mod writes `mods/STS2_MCP.conf`: ```json { "port": 15526 } ``` It also injects an **"Instant Mode"** checkbox into the game settings, which shortens animations. Worth enabling for batch runs.