Skip to main content
Glama
slothingaway

stockfish-mcp

by slothingaway
README.md
# stockfish-mcp

A tiny, opinionated [Model Context Protocol](https://modelcontextprotocol.io) server that gives an LLM the **best move for any chess position** — powered by [Stockfish](https://stockfishchess.org).

Hand it a position as **FEN** or **PGN** (or nothing, for the starting position) and it returns the best move, the engine's evaluation, and — optionally — the predicted best line, as clean structured JSON. One tool. One call. No UCI knowledge required.

```json
{
  "turn": "black",
  "bestmove": { "uci": "f6e4", "san": "Nxe4" },
  "ponder":   { "uci": "d2d4", "san": "d4" },
  "score": { "type": "cp", "value": -371, "perspective": "side to move" },
  "evaluation": "White is better (+3.71)",
  "fen": "r1bqk2r/ppp2ppp/2np1n2/P3p3/1PB1P3/5N2/2PP1PPP/RNBQ1RK1 b kq - 0 7"
}
```

## Requirements

- **Node.js ≥ 18**
- **Stockfish** installed and on your `PATH` (or point `STOCKFISH_PATH` at the binary). This project does **not** bundle Stockfish.

Install Stockfish:

```sh
# Debian / Ubuntu
sudo apt install stockfish

# macOS (Homebrew)
brew install stockfish

# or download a binary from https://stockfishchess.org/download/
```

## Usage

### With any MCP client (Claude Desktop, etc.)

Add to your client's MCP config:

```json
{
  "mcpServers": {
    "stockfish": {
      "command": "npx",
      "args": ["-y", "stockfish-mcp"]
    }
  }
}
```

Or run it from a local checkout:

```json
{
  "mcpServers": {
    "stockfish": {
      "command": "node",
      "args": ["/path/to/stockfish-mcp/index.mjs"]
    }
  }
}
```

### From the command line (manual testing)

```sh
npm install
npm start          # speaks MCP over stdio
npm test           # end-to-end smoke test (needs stockfish installed)
```

## The `analyze` tool

| Parameter     | Type    | Default | Description |
| ------------- | ------- | ------- | ----------- |
| `position`    | string  | start position | A FEN string or PGN/move list. Omit for the starting position. |
| `depth`       | integer | `18`    | Search depth in plies. Higher = stronger but slower. |
| `movetime`    | integer | —       | If given, search this many milliseconds instead of a fixed depth. |
| `includeLine` | boolean | `false` | Include the engine's predicted best line (PV) in SAN. |

### Response

| Field        | Description |
| ------------ | ----------- |
| `turn`       | `"white"` or `"black"` — side to move in the given position. |
| `bestmove`   | `{ uci, san }` — the best move (`null` if the position is already terminal). |
| `ponder`     | `{ uci, san }` or `null` — the reply the engine expects. |
| `score`      | `{ type: "cp" \| "mate", value, perspective: "side to move" }` — raw engine score. |
| `evaluation` | Human-readable, White-perspective summary, e.g. `"White is better (+3.71)"`, `"Mate in 1 for Black"`, `"roughly equal"`. |
| `fen`        | The FEN actually analyzed (after PGN conversion / normalization). |
| `line`       | *(only with `includeLine`)* Numbered SAN principal variation, truncated with `…`. |

### Examples

**FEN (black to move):**

```json
// analyze({ "position": "r1bqk2r/ppp2ppp/2np1n2/P3p3/2B1P3/5N2/2PP1PPP/RNBQ1RK1 b kq - 0 7", "depth": 14 })
{
  "turn": "black",
  "bestmove": { "uci": "f6e4", "san": "Nxe4" },
  "ponder": { "uci": "d2d4", "san": "d4" },
  "evaluation": "White is better (+3.71)"
}
```

**PGN:**

```json
// analyze({ "position": "1. e4 e5 2. Nf3 Nc6 3. Bb5 a6", "depth": 12 })
{
  "turn": "white",
  "bestmove": { "uci": "b5c6", "san": "Bxc6" },
  "ponder": { "uci": "d7c6", "san": "dxc6" },
  "evaluation": "White is better (+0.36)"
}
```

**Best line + mate detection:**

```json
// analyze({ "position": "6k1/5ppp/8/8/8/8/8/R6K w - - 0 1", "includeLine": true })
{
  "turn": "white",
  "bestmove": { "uci": "a1a8", "san": "Ra8#" },
  "evaluation": "Mate in 1 for White",
  "line": "1. Ra8#"
}
```

## Configuration

Environment variables:

| Variable                  | Default     | Description |
| ------------------------- | ----------- | ----------- |
| `STOCKFISH_PATH`          | `stockfish` | Path to the Stockfish binary. |
| `STOCKFISH_TIMEOUT_MS`    | `60000`     | Per-search safety timeout, in milliseconds. |
| `STOCKFISH_DEFAULT_DEPTH` | `18`        | Search depth used when the caller omits `depth`. |

Example:

```json
{
  "mcpServers": {
    "stockfish": {
      "command": "npx",
      "args": ["-y", "stockfish-mcp"],
      "env": { "STOCKFISH_PATH": "/usr/games/stockfish", "STOCKFISH_DEFAULT_DEPTH": "20" }
    }
  }
}
```

## How it works

The server spawns a fresh `stockfish` process for each `analyze` call, sends `position fen …` followed by `go depth N` (or `go movetime N`), reads the engine's `info` lines to capture the latest score and principal variation, and resolves on `bestmove`. Moves are converted to SAN with [`chess.js`](https://github.com/jhlywa/chess.js), which also handles PGN→FEN conversion and FEN validation.

Two UCI subtleties worth noting (both handled here): `go` is **asynchronous**, so stdin is left open and no `quit` is sent before the search finishes — closing the pipe would abort it. And `score cp`/`score mate` are reported from the **side-to-move**'s perspective, so we flip them when Black is to move to produce a consistent White-perspective evaluation.

## AI usage in this project

Entirely written by Qwen3.8-Preview-Max

## License

[MIT](LICENSE).

**A note on Stockfish:** Stockfish itself is licensed [GPL-3.0](https://github.com/official-stockfish/Stockfish/blob/master/LICENSE). This project does not include, link against, modify, or distribute Stockfish — it only *spawns* a Stockfish binary that you install separately and communicates with it over the public UCI protocol via a pipe. The two are separate programs, so this wrapper is independently licensed under MIT. You are responsible for obtaining Stockfish and complying with its license.

TDQS

A4.4/5.0

Scored across 1 tool

Disambiguation5/5

Only one tool exists, so there is no possibility of confusion or overlap with other tools.

Naming Consistency5/5

With a single tool, naming is trivially consistent and fits a clear verb-noun pattern (analyze_? but the tool name is just 'analyze', which is appropriate).

Tool Count3/5

A single tool is borderline for a chess engine server; it bundles position setup and search, which works but limits flexibility (e.g., no separate depth control or multiple analysis modes).

Completeness4/5

The tool covers the main use case of analyzing a position with evaluation and best move. It accepts FEN/PGN, making it versatile, but lacks advanced options like depth parameter or multi-move output.

Maintenance

ActivitySlowing
ResponsivenessNo issues