sts2-mcp-bridge
by brmarcosbr
README.md
[English](README.md) · [Português (BR)](README.pt-BR.md)
# sts2-mcp-bridge
MCP bridge + autopilot for **Slay the Spire 2** (Early Access). The game (via the
`Communication_Mod` mod, BaseLib + Harmony) pushes match state over HTTP to this Python
bridge; the bridge exposes MCP tools and, optionally, plays combat on its own with a
local deterministic policy — **no LLM in the critical path**.
Project write-up: *"I made an agent play Slay the Spire 2 on its own — and what unlocked it was the game saying 'no'"* (dev.to). This repo is the public proof of the part that is mine: the bridge and the policy test harness.
> ⚠️ **What this repo does NOT contain:** the game mod (`Communication_Mod`).
> The mod code belongs to [Manuelbbl/Communication_Mod_STS2](https://github.com/Manuelbbl/Communication_Mod_STS2)
> (no explicit license — all rights reserved), and my patches on top of it
> (selector v3, rejection channel) are versioned as zips with md5, not redistributed here.
## Architecture
```
Slay the Spire 2 (C#/Godot, mod BaseLib+Harmony)
└─ POST http://127.0.0.1:5000/update_state ← loopback, fire-and-forget
▼
sts2_bridge.py (this project)
│ the HTTP response to each push IS the command channel
│ {"Type":"PlayCard","HandIndex":N,"TargetIndex":M} | {"Type":"EndTurn"} | OK
▼
MCP (10 tools) → agent (operator/observer)
```
- **Loopback-only:** the mod only talks to `127.0.0.1:5000`; the bridge rejects any origin
that is not localhost. The game never opens a port.
- **Fire-and-forget:** the mod does not wait for the response to keep running, so the HTTP
response of each push is the only command channel (no queue, no polling).
- **Rejection channel:** when the game refuses a play, the mod POSTs to `/rejection` (a
dedicated endpoint, outside the state queue) — that is how the agent finds out it was wrong.
## The autopilot (local policy, no LLM)
`_autopilot_decide(gate, trigger)` inside `sts2_bridge.py` decides the play with greedy
score-based rules: damage/cost with a kill bonus, AOE, preventive and desperate block,
scaling powers in long fights, poison when the hand does not kill, target chosen from the
enemy's intent, `EndTurn` when there is no useful play. Zero LLM calls in the loop
(verified by grep).
The game saying "no" becomes state: cards rejected as `NotPlayable` go into an `unplayable`
set that filters the hand in the following decisions — this was the fix for the GRAND_FINALE
softlock (the bot kept retrying a card it could not play and froze the game). No retraining:
the environment's error becomes a constraint in the agent's state.
## How to run
```bash
pip install -r requirements.txt # fastmcp, uvicorn, httpx
STS2_MCP_PORT=5000 python3 sts2_bridge.py
```
Healthy print: `[sts2-bridge v0.2.7]` + `MCP tools -> http://0.0.0.0:5000/mcp`.
The game mod must be compiled and active (`mods/Communication_Mod`), with the game open.
The bridge does not need to run on the same machine as the game if there is a tunnel/route
to port 5000 of the game PC (but the mod always talks to **its own** loopback).
### Testing the policy without the game (and without fastmcp)
```bash
python3 autopilot_harness.py sts2_bridge.py
```
Loads the bridge up to `import fastmcp`, runs 5 synthetic scenarios and one full turn
simulation. **This is a QA process, not certification** — it proves the policy responds as
expected on synthetic cases, not that it wins runs.
## Honest limitations
- **1 combat validated end-to-end** (2026-09-08: 7 turns, 35 actions, 0 rejections — session
log). **There is no winrate metric.**
- Multi-class (Silent/Defect/Necrobinder/Regent) is mapped by state mechanics through
defensive reflection — **not validated end-to-end**.
- Slay the Spire 2 is Early Access: every update can break the mod patches (they target
methods by name). The bridge survives state changes by reading through defensive
reflection; the mod, not always.
- Dynamic card costs (e.g. Stomp) and setup powers are only partially modeled in the policy.
## Integrity (md5, verified 2026-09-09)
| Artifact | md5 |
|---|---|
| `sts2_bridge.py` (this repo, v0.2.7) | `b4286ec5522e0edfc31591b644dd6449` |
| `sts2-bridge-v0.2.7.zip` | `5a555e906369d821dd0e6270a1236196` |
| `Communication_Mod_v3selector_v0.3.2.zip` (mod, not redistributed) | `516a4797f21c36f06a2b140bf94661af` |
## License
MIT — see [LICENSE](LICENSE). The code in this repo (bridge, harness) was written from
scratch for this project.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues