Content
# autoSpire — AI Automated Tower Climbing
[Slay the Spire 2](https://store.steampowered.com/app/2868840) AI Mod. Embedded HTTP service + MCP protocol for Claude Code driven game decisions.
## Architecture
```
AI (Claude Code) ←─ MCP stdio ─→ mcp/server.py ←─ HTTP ─→ C# GameHookServer (In-Game)
```
- **C# GameHookServer**: Embedded in-game process, refreshing state snapshots and consuming HTTP action queues every frame.
- **Python MCP Adapter**: Wrapping HTTP API as MCP tools (`get_state` / `take_action`), pure transmission without business logic.
- **Godot Mod**: `[ModInitializer]` entry, registering Harmony patch and starting HTTP service.
## Quick Start
### Prerequisites
- [Slay the Spire 2](https://store.steampowered.com/app/2868840)
- Python 3.10+
- [Claude Code](https://claude.ai/code) (recommended), [Codex](https://github.com/openai/codex), or [OpenCode](https://github.com/ssttuu/opencode)
### Three Steps to Get Started
**① Unzip and Install**
Download `autoSpire-vX.X.X.zip` from [Releases](../../releases) and unzip. Place `autoSpire/` in the game mod directory, then install Python dependencies:
```
Copy autoSpire/ to <Steam Library>\...\Slay the Spire 2\mods\autoSpire\
pip install -r mcp/requirements.txt
```
Unzipped structure:
```
├── .mcp.json # MCP configuration (Claude Code auto-detects)
├── autoSpire/
│ ├── autoSpire.dll # Mod本体
│ └── autoSpire.json # Mod清单
└── mcp/
├── server.py # MCP adapter
└── requirements.txt # Python dependencies
```
**② Agent Open Folder**
The unzipped directory already contains `.mcp.json`. Claude Code opens the folder and automatically discovers the MCP Server.
Other agents refer to this file configuration, essentially registering a stdio MCP Server:
```json
{
"mcpServers": {
"autospire": {
"type": "stdio",
"command": "python",
"args": ["mcp/server.py"]
}
}
}
```
> **Co-op Mode**: `.mcp.json` already includes `autospire2` (port 8766). Launch the second game client for co-op tower climbing.
**③ Start Using**
Launch the game, confirm the log `[autoSpire] autoSpire mod initialized!`, then in the Agent:
```
> Start a standard single-player game
```
### Build from Source
```bash
# Modify autoSpire.csproj's <Sts2Dir> to the game path, then:
dotnet build
```
## API
### GET `/state` — Game State Snapshot
Returns `GameStateSnapshot` JSON. Based on the `phase` field, only the current scene's corresponding sub-snapshot is non-null:
| Phase | Field | Content |
|-------|------|------|
| `combat` | `combat` | Hand/energy/enemies (intent + buff)/potions/deck count/card selection state |
| `map` | `map` | Current node + next layer options + all map nodes |
| `shop` | `shop` | Gold + item list (cards/relics/potions/remove card) |
| `reward` | `reward` | Reward item list + optional cards/relics |
| `rest` | `rest` | Rest options + upgrade/remove card interface |
| `event` | `event` | Event name/description/options (including associated card/relic preview) |
| `treasure` | `treasure` | Treasure state/optional relics/voting |
| `game_over` | — | Game over settlement interface (death/victory) |
| `menu` | `menu` | Out-of-game interface (main menu/character selection/submenu/etc.) |
All phases have a `run` field: chapter/layer/gold/deck.
### POST `/action` — Submit Action
Returns `ActionResult` JSON:
```json
{
"success": true,
"error": null,
"played_card_name": "Strike", // only play_card
"played_card_target": { // only play_card
"type": "enemy", // enemy / self / none
"combat_id": 2,
"name": "Small Behemoth"
},
"hand_card_selection": { ... }, // only pick_card / confirm_selection
"card_selection": { ... } // only pick_card / confirm_selection
}
```
| Action | Required Parameters | Optional Parameters | Description |
|------|----------|----------|------|
| `play_card` | `hand_index` | `target_id` | Play a card. Supports AOE/random target (no target_id). Returns `played_card_name` + `played_card_target` |
| `multi_play` | `cards` | — | Play multiple cards at once. `cards` is an array of `[{hand_index, target_id?}]`. Returns `played_card_name` (attempt list) |
| `end_turn` | — | — | End turn |
| `use_potion` | `slot_index` | `target_id` | Use a potion |
| `pick_card` | `card_index` | — | General card selection (hand selection/overlay selection). Returns the latest selection state |
| `confirm_selection` | — | — | Confirm hand selection (e.g., purification) |
| `move_to_map_coord` | `col`, `row` | — | Select a path |
| `pick_reward` | `choice_index`, `choice_type` | `card_index` | Select a reward (card/relic/gold/skip); if CardSelection sub-interface is present, pass `card_index` |
| `rest_action` | `option_index` | `card_index` | Rest point operation |
| `shop_action` | — | — | Shop operation (buy/remove card/leave) |
| `treasure_action` | — | — | Treasure operation (pick relic/skip/leave, auto-open) |
| `event_action` | `option_index` | — | Event option (supports multi-player voting, card selection) |
| `menu_action` | — | — | Out-of-game operation: continue_run/singleplayer/multiplayer/standard/daily/custom/select_character/set_ascension/embark/back/abandon_run/confirm/cancel, etc. |
## Feature Status
### Completed & Tested (Both Host and Client)
- Combat state acquisition: hand (including Damage/Block/description)/energy/enemies (HP+intent+buff)/potions (including description)/deck count/card selection state
- Playing cards: supports targeted (single enemy), self-targeted (skills/abilities), AOE/random target (AllEnemies/RandomEnemy/AllAllies), and card selection triggers (purification/headbutt/cleansing)
- **Batch playing cards**: `multi_play` plays multiple cards at once, reducing AI calls. Two-stage design (first parsing all CardModel references, then queuing) avoids index offset issues
- Using potions: self-target + enemy target, potion selection triggers (grey water)
- Card selection: overlay selection/hand selection mode/reward selection/shop remove card selection, with correct numerical descriptions
- Instant return of selection state: `pick_card` returns updated card list in ActionResult
- Playing card return value: `played_card_name` + `played_card_target` (including target type/ID/name)
- Map state + path selection (VoteForMapCoordAction)
- Reward selection: card/gold/relic/potion/skip
- Rest point: recovery/crafting/remove + upgrade/remove card interface
- **Full shop chain**: item list (character cards/colorless cards/relics/potions/remove card with price and inventory) + buy / remove_card / leave (auto-open/close interface)
- **Full event chain**: event description + options (including associated card/relic preview) + single-player/co-op multi-player voting + event card selection + ancient event
- **Treasure room**: treasure opening + relic selection (single-player/co-op voting, auto-distribution)
- **Game over detection**: correct identification of `game_over` phase after death/victory (no longer misjudged as map)
- **Out-of-game interface detection**: main menu/character selection/single-player submenu/co-op submenu/room creation/daily/custom game/ popup, etc. `menu` phase, with 20+ interface type identifications
- **Out-of-game operation**: `menu_action` supports the entire process from main menu to game start (character selection/ascension settings/daily challenge/custom game), including abandoning save confirmation popup
- Icon cleanup: `[img]` energy/star icons replaced with text
- Run-level information: relics (name+description), deck (combined count)
- JSON output optimization: omitting all null fields, significantly reducing token consumption
## Multi-Platform Support
The same MCP adapter distinguishes between:
- `autospire` (8765) — Host
- `autospire2` (8766) — Client
If the port is occupied, GameHookServer automatically increments (attempts up to 10 ports).
## Known Limitations
- **Asynchronous card playing**: `play_card` queues through `ActionQueueSynchronizer.RequestEnqueue`, and card selection triggers are processed in subsequent frames. AI needs to check with `get_state` afterwards
- **Card selection trigger changes hand index**: e.g., purification reorders hand index after each card selection. AI relies on `pick_card` return value for the latest index
- **Card descriptions**: `:energyIcons` / `:starIcons` special formatters output `[img]` tags replaced with text
- **Event options**: some dynamic variables (e.g., `{BatheCurses}`) might not be injected in option descriptions. `SafeFormat` falls back to original text
- **Co-op mode**: room creation/joining menu operations are implemented, but actual networking requires Steam network layer, not verified in real co-op environment
- **`multi_play` is asynchronous**: `RequestEnqueue` queues and processes cards asynchronously. `played_card_name` only reflects the attempt list, not actual results. AI should follow `multi_play` with `get_state` to confirm hand and selection state
- **`multi_play` + selection triggers**: if a batch contains cards that trigger selection (e.g., survivor/purification), subsequent cards in the batch are blocked. AI should handle such cards separately or place them at the end of the batch