pure-llm-chess-mcp
by prepaser
README.md
# Pure LLM Chess MCP
A local MCP server that validates submitted chess moves. It does not expose legal-move lists, evaluation, recommendations, or search. Rules come from chessops, not a playing engine.
Requires Node.js 24 or newer.
```sh
npx -y pure-llm-chess-mcp
```
Or install the CLI globally:
```sh
npm install -g pure-llm-chess-mcp
pure-llm-chess-mcp
```
## Connect
For a stdio MCP client, use `npx`:
```json
{
"mcpServers": {
"chess": {
"command": "npx",
"args": ["-y", "pure-llm-chess-mcp"]
}
}
}
```
For Streamable HTTP:
```sh
npx -y pure-llm-chess-mcp --transport http --port 3000
```
Connect to `http://127.0.0.1:3000/mcp`. This is a local, single-user server. HTTP clients share games; game IDs are not authentication. Games live in memory until the process exits. Export JSON to save them.
Both transports support MCP `2026-07-28` and legacy 2025-era clients. HTTP uses stateless POST requests; standalone GET notification streams are not provided. Shutdown closes active connections instead of waiting for clients to disconnect.
Load a local rule plugin:
```sh
npx -y pure-llm-chess-mcp --plugin /absolute/path/to/variant.mjs
```
`--plugin` may be repeated. Plugins are trusted local JavaScript modules loaded at startup, with the same privileges as the server. The server does not execute code supplied through MCP or imported archives. See [the plugin guide](docs/plugins.md) to load the included three-player hex example or write your own rules.
## Tools
| Tool | Arguments | Result |
| --- | --- | --- |
| `list_variants` | none | Rule IDs, versions, descriptions, schemas, supported formats |
| `new_game` | `variant="standard"`, `options={}` | New game and state |
| `get_state` | `game_id`, `ascii=false`, `unicode=false` | Current state; optional `ascii` string with ASCII or Unicode pieces |
| `play_move` | `game_id`, `move`, `allow_illegal=false` | `legal`, `applied`, `reason`, resulting `state` |
| `edit_board` | `game_id`, `changes`, optional `turn`, `rule_state` | Updated state |
| `import_game` | `format`, `data`, `variant="standard"` for FEN, optional `options` for FEN/PGN | A new game |
| `export_game` | `game_id`, `format` | `{format, data}` |
Built-in variant IDs are `standard`, `chess960`, `antichess`, `atomic`, `crazyhouse`, `horde`, `kingofthehill`, `racingkings`, and `threecheck`. Chess960 accepts `options.index` from 0 to 959 (default 518).
Standard moves use UCI coordinates, such as `e2e4` and `e7e8q`. Inspect `list_variants` for each variant's move schema. The hex example uses objects such as `{"from":"-2,0","to":"-1,0"}`.
An edit maps cells to a piece or `null`:
```json
{
"game_id": "<game-id>",
"changes": {
"e1": null,
"e4": {"kind": "queen", "owner": "white"}
},
"turn": "black"
}
```
State includes cell IDs, a `pieces` map, registered `players`, `turn`, variant-specific `rule_state`, `valid`, `issues`, `check`, `outcome`, `claimable_draws`, and a revision. Normal chess pieces use `pawn`, `knight`, `bishop`, `rook`, `queen`, and `king`; owners are `white` and `black`.
`get_state` with `ascii=true` adds a board like this without replacing structured state:
```text
8 r n b q k b n r
7 p p p p p p p p
6 . . . . . . . .
5 . . . . . . . .
4 . . . . . . . .
3 . . . . . . . .
2 P P P P P P P P
1 R N B Q K B N R
a b c d e f g h
```
Set `unicode=true` to use Unicode chess pieces (`♔♕♖♗♘♙` and `♚♛♜♝♞♟`). This also requests board output, so `ascii=true` is optional. The rendered text remains in the `ascii` response field; coordinates and empty cells are unchanged. Plugins that do not implement Unicode rendering keep their ASCII output.
```json
{"game_id": "<game-id>", "unicode": true}
```
## Legality and editing
A rejected move leaves the game unchanged. `legal` is `true` or `false` for valid positions and `null` when rules cannot evaluate the position. `applied` independently reports whether the board changed.
With `allow_illegal=true`, legal moves still follow all normal rules. Otherwise the server relocates the source piece, overwrites the destination (including friendly pieces and kings), and advances the current turn. It does not infer illegal castling, en passant, or promotion effects. Empty sources, nonexistent cells, and identical source/destination remain errors. Use `edit_board` for creation, deletion, replacement, and compound edits.
Forced changes reset the repetition segment. Built-in chess variants also clear castling rights, en passant, and the halfmove counter. Edits preserve the turn unless explicitly changed and may explicitly replace `rule_state`. Historical events remain in JSON archives.
Rule-invalid boards, including boards with missing kings, remain editable and printable. Structurally invalid data, such as unknown cells or piece kinds, is rejected. Failed operations do not publish partial state changes.
The reserved object key and cell ID `__proto__` are rejected explicitly, including inside nested JSON data. The same text is allowed as an ordinary string value.
Standard automatic outcomes include checkmate, stalemate, insufficient material, fivefold repetition, and the 75-move rule. Claimable threefold/50-move draws are reported; claim and resignation tools are not included.
## Import and export
- **JSON** is the full-fidelity format: format version, exact variant ID/version, options, metadata, initial state, applied events, and final state. Imports replay events and compare the resulting state. The same plugin version must already be installed. Code is never embedded or loaded from an archive.
- **FEN** exchanges a current position, without history. A syntactically valid but rule-invalid position can be imported. Specify `variant` when importing a nonstandard position.
- **PGN** exchanges a single game's headers, mainline, and result for supported variants. Comments, annotations/NAGs, variations, and multiple games are rejected. Games with edits or actual forced moves require JSON. A PGN `Variant` header selects the rule set.
PGN headers require string values without control characters and valid tag names. JSON metadata that cannot be represented is rejected on PGN export rather than discarded. Position and variant headers are derived from the actual game state. Standard aliases such as `normal`, `classical`, and `wild/0` are accepted; an exact installed plugin ID takes precedence over an alias.
A new game ID is assigned on every import. Input/output is data, not server filesystem paths. No file is read or written through these tools. Unsupported formats fail explicitly instead of dropping information.
For FEN/PGN imports, pass any creation options required by the selected variant in `options`. They are validated, passed to the plugin's importer, and preserved in JSON exports. JSON imports restore their archived options and reject an `options` override.
Accepted FEN promoted-piece markers (`~`) survive unrelated moves and follow the marked piece when it moves; captures remove them. This changes replay semantics outside Crazyhouse, so those built-in adapters now use version `2`. Their version `1` JSON archives are rejected with `VERSION_MISMATCH` rather than silently replayed with different state. Crazyhouse remains version `1`.
PGN declarations such as resignation or an agreed draw are preserved as terminal results, even when the board itself is not terminal. Such declarations are recorded in JSON history. Forced moves and edits clear the declaration and resume from the changed position; a declared result contradicting a rules-based outcome is rejected.
Archive replay rejects another terminal result declaration unless an intervening edit or forced move has cleared the previous one.
## Development
From a source checkout:
```sh
npm ci
npm run check
npm test
npm start
```
`npm test` builds the project before running the tests. Use `npm run build` to build without testing.
`npm pack` builds automatically before creating the package. The package includes the runtime, type declarations, example plugin, documentation, license, and TypeScript source; tests and local workspace files are excluded.
The implementation separates the game service, rule adapters, and MCP transports. See [the plugin contract](docs/plugins.md) and [the hex example](examples/hex.ts) for nonrectangular boards, new pieces, and multiplayer rules.
Dependency versions are pinned in `package-lock.json`.
## License
Licensed under the GNU Affero General Public License v3.0 only (`AGPL-3.0-only`). See [LICENSE](LICENSE).
chessops is licensed separately under [GPL-3.0-or-later](https://github.com/niklasf/chessops/blob/main/LICENSE.txt).
TDQS
A3.6/5.0
Scored across 7 tools
Disambiguation5/5
Each tool targets a distinct action: creating, inspecting, moving, editing, exporting, importing, and listing variants. There is no meaningful overlap between them.
Naming Consistency5/5
All tool names follow a consistent verb_noun pattern (new_game, get_state, play_move, edit_board, export_game, import_game, list_variants). The naming is uniform and predictable.
Tool Count5/5
Seven tools is well-scoped for a chess MCP server: it covers game lifecycle, state inspection, move execution, board editing, import/export, and variant discovery without unnecessary bloat.
Completeness5/5
The tool surface covers the full chess workflow: create, inspect, move, edit, import/export, and discover variants. No obvious dead ends or missing core operations for the stated purpose.
Maintenance
ActivityMaintained
ResponsivenessNo issues