Public log

@chessceo/mcp

Model Context Protocol server for chess.ceo — 11.7M+ games, ~1.5M FIDE player profiles, opening preparation, live broadcasts.

npm · fingerprint 0a69051c96a1f26ec312bfb2 · repository · RSS

59tools
5recorded versions
45dtracked
last checked 7 Oct

What changed

Changed 2026-10-07 13:30 UTC

2 descriptions rewritten, 1 schema or annotation change with the description left byte-identical, 2 tools added, 1 tool removed.

+ tool added: file_versions

--- pinned/get_prep_position
+++ observed/get_prep_position
  {
-   "description": "A player's moves at one position: move statistics (frequency + win rate + last-played date per move) plus the actual games from that position, in one call.\n\nTwo ways in: `fide_id` + `color` reads one FIDE player's games directly, no session (the usual case). `session_token` reads a session from `prepare_opponent` (Chess.com / Lichess accounts, combined sources, or date / time-control filters).\n\nPosition input: prefer `file_id`+`node_id` when inside a prep file (server derives FEN from the tree). Otherwise pass `fen`.\n\nAUTO-EVAL: if a cloud engine instance is running, the response includes `.eval` (whichever engines that rental provides — Stockfish, Lc0, or both) so you don't need a separate cloud_analyse.\n\nReading the response — CRITICAL:\n• Win % is one weight, not a verdict. Sample size matters (3 games at 66% is noise; 300 at 55% is signal).\n• Prep is symmetric information — both sides see the same history. Assume the opponent knows the weakness you spotted.\n• Recency > career. The last 12-24 months dominate — filter your session with `start_month` if the player's repertoire shifted.\n• Opponent will deviate early. Prep is a tree — cover the 2 most likely replies at each real branching point, not one 20-move line.\n\nFor the full guide call `read_opening_prep_guide`.",
+   "description": "A player's moves at one position: move statistics (frequency + win rate + last-played date per move) plus the actual games from that position, in one call.\n\nTwo ways in: `fide_id` + `color` reads one FIDE player's games directly, no session (the usual case). `session_token` reads a session from `prepare_opponent` (Chess.com / Lichess accounts, combined sources, or date / time-control filters).\n\nCOMPARE: `compare` reads up to 4 sources at the same position in one call, e.g. the opponent and the user (FIDE id from their memory), two candidate opponents, or one player's FIDE games next to a session of their Chess.com games or a classical-only session. Each entry is `fide_id` + `color` or `session_token`, plus a `label`; the response has one result per source (5 games each by default) under `sources`.\n\nPosition input: prefer `file_id`+`node_id` when inside a prep file (server derives FEN from the tree). Otherwise pass `fen`.\n\nAUTO-EVAL: if a cloud engine instance is running, the response includes `.eval` (whichever engines that rental provides — Stockfish, Lc0, or both) so you don't need a separate cloud_analyse.\n\nReading the response — CRITICAL:\n• Win % is one weight, not a verdict. Sample size matters (3 games at 66% is noise; 300 at 55% is signal).\n• Prep is symmetric information — both sides see the same history. Assume the opponent knows the weakness you spotted.\n• Recency > career. The last 12-24 months dominate — filter your session with `start_month` if the player's repertoire shifted.\n• Opponent will deviate early. Prep is a tree — cover the 2 most likely replies at each real branching point, not one 20-move line.\n\nFor the full guide call `read_opening_prep_guide`.",
    "inputSchema": {
      "properties": {
        "color": {
+       "compare": {
+         "description": "Several sources at the same position, instead of `fide_id` / `session_token`.",
+         "items": {
+           "properties": {
+             "color": {
+               "enum": [
+                 "white",
+                 "black"
+               ],
+               "type": "string"
+             },
+             "fide_id": {
+               "type": "integer"
+             },
+             "label": {
+               "description": "Name for this source in the response, e.g. 'opponent', 'me', 'chess.com blitz'.",
+               "type": "string"
+             },
+             "session_token": {
+               "type": "string"
+             }
+           },
+           "type": "object"
+         },
+         "maxItems": 4,
+         "type": "array"
+       },
        "fen": {
          "description": "Position as FEN. Only used if `node_id` is not set.",
          "type": "string"
-         "description": "Games to return (default 10; at most 10 with `fide_id`).",
+         "description": "Games to return per source (default 10, or 5 per source with `compare`; at most 10 with `fide_id`).",
          "maximum": 50,
          "minimum": 1,
          "type": "integer"
--- pinned/memory
+++ observed/memory
  {
    "description": "What the user told you about themselves, saved on their chess.ceo account and shown to you at the start of every chat (all clients). Call with no arguments to read; pass fields to change them.\n\nSAVE WHEN the user states something about themselves ('this is my FIDE id', 'my chess.com is X', 'I like concrete engine lines', 'I struggle in IQP positions', 'I play the Najdorf') or asks you to remember it. Your own inference (e.g. a weakness you noticed in their games) only after the user agrees. Never save other people's data. Tell the user in one line what you saved.\n\nKinds: preference (how they like analysis), weakness, strength, repertoire, goal, other. One fact per note, max 500 characters, max 50 notes; update or merge an existing note (ids are shown as [n]) instead of adding a near-duplicate. To forget something, delete the note or set the account field to null.",
    "inputSchema": {
+       "rating": {
+         "description": "The level to explain at, Elo-like (null clears). The user's choice: it may differ from their FIDE rating or exist without one ('I'm about 1200 online').",
+         "maximum": 3500,
+         "minimum": 100,
+         "type": [
+           "integer",
+           "null"
+         ]
+       },
        "update": {
          "items": {
            "properties": {

+ tool added: natural_moves

--- pinned/read_prep_file
+++ observed/read_prep_file
  {
-   "description": "Read one prep file. Response always includes `id`, `version`, `tags`. The tree/PGN part is controlled by `view` and `node_id`/`max_depth` — large files (500+ nodes) can otherwise blow the LLM's token limit.\n\n**Views** (pick the smallest one that answers your question):\n  • `compact` (default) — per-node: `id`, `san`, `ply`, `nags`, `comment`, `ceoEval`, `children`. Drops `fen` and `annotations`. Typical size: ~120 chars/node vs ~330 in `full`.\n  • `full` — everything (`fen`, `annotations` too). Use when you actually need the FEN inline or want to inspect arrows/highlights. On a 500+-node file this can exceed token limits.\n  • `spine` — mainline only (children[0] recursively). Great for a 'what does this repertoire cover' summary.\n  • `pgn` — subtree as raw PGN text (comments, NAGs, [%cal] arrows all preserved). Useful for sanity-checking formatting against reference material.\n\n**Node addressing.** Every node has a stable `id` — root is `'r'`, every other node is an 8-hex-char content hash of parent-id + SAN. Sibling insertions, deletions, variation promotions never shift ids. Pass as `node_id` (or `parent_id` for add_move / add_line) to every mutation and engine/DB tool.\n\n**Scoping.** `node_id` starts the tree from a subtree root (default `'r'`). `max_depth` caps the tree at that many plies below the anchor (default unlimited). Use both to drill into a specific branch without dumping the whole file — the LLM never needs to see the full 800-node tree at once.\n\nFor querying the tree without reading it (\"which nodes have no ceoEval?\", \"give me the mainline spine\") call `list_nodes` — cheaper than parsing a full read.\n\n**Every engine/DB tool accepts `file_id`+`node_id`** (get_position_stats, cloud_analyse, describe_position, predict_human_move, prep_snapshot, get_prep_position, quote_engine_eval). Use it whenever a file is open — the server derives the FEN from the tree, so you can't 'analyse the wrong position' by mis-typing a FEN.",
+   "description": "Read one prep file. Response always includes `id`, `version`, `tags`. The tree/PGN part is controlled by `view` and `node_id`/`max_depth` — large files (500+ nodes) can otherwise blow the LLM's token limit.\n\n**Views** (pick the smallest one that answers your question):\n  • `compact` (default) — per-node: `id`, `san`, `ply`, `nags`, `comment`, `ceoEval`, `children`. Drops `fen` and `annotations`. Typical size: ~120 chars/node vs ~330 in `full`.\n  • `full` — everything (`fen`, `annotations` too). Use when you actually need the FEN inline or want to inspect arrows/highlights. On a 500+-node file this can exceed token limits.\n  • `spine` — mainline only (children[0] recursively). Great for a 'what does this repertoire cover' summary.\n  • `pgn` — subtree as raw PGN text (comments, NAGs, [%cal] arrows all preserved). Useful for sanity-checking formatting against reference material.\n\n**Node addressing.** Every node has a stable `id` — root is `'r'`, every other node is an 8-hex-char content hash of parent-id + SAN. Sibling insertions, deletions, variation promotions never shift ids. Pass as `node_id` (or `parent_id` for add_move / add_line) to every mutation and engine/DB tool.\n\n**Scoping.** `node_id` starts the tree from a subtree root (default `'r'`). `max_depth` caps the tree at that many plies below the anchor (default unlimited). Use both to drill into a specific branch without dumping the whole file — the LLM never needs to see the full 800-node tree at once.\n\nFor querying the tree without reading it (\"which nodes have no ceoEval?\", \"give me the mainline spine\") call `list_nodes` — cheaper than parsing a full read.\n\n**Every engine/DB tool accepts `file_id`+`node_id`** (get_position_stats, cloud_analyse, describe_position, natural_moves, prep_snapshot, get_prep_position, quote_engine_eval). Use it whenever a file is open — the server derives the FEN from the tree, so you can't 'analyse the wrong position' by mis-typing a FEN.",
    "inputSchema": {
      "properties": {
        "id": {
+       "version": {
+         "description": "Read an earlier saved version of the file (numbers from file_versions). Omit for the current file. Read-only: edits always go to the current file.",
+         "minimum": 1,
+         "type": "integer"
+       },
        "view": {
          "description": "Response shape. Default `compact` — drops fen + annotations to keep token count sane. See tool description for when to use each.",
          "enum": [

tool removed: predict_human_move

Changed 2026-10-06 15:01 UTC

3 descriptions rewritten.

--- pinned/auto_evaluate
+++ observed/auto_evaluate
  {
-   "description": "Analyse a whole prep file, or the subtree under `node_id`, on the engines you choose and store the evals on every node. Example: \"analyse the whole tree with Stockfish and the human engine and save the evals\" → `{id, engines: [\"stockfish\", \"human\"]}`.\n\nEach node gets only the requested engines it is missing (`only_missing`, default true), so adding an engine later runs just that engine. Transpositions are analysed once and stamped on every node that reaches the position. Stored evals merge per engine; other engines' stored reads are kept. Results are saved after every batch of up to 10 positions.\n\n**Async job, returns immediately:** `{ job_id, target_count, already_complete, estimated_seconds }`. Poll auto_evaluate_status(job_id); cancel with auto_evaluate_cancel. Do other work between polls.\n\nNeeds running rentals for every engine you ask for (ensure_engines). Wall time ≈ target_count × movetime (engines run in parallel per position). Does not set visible NAGs: the eval symbol is your call.",
+   "description": "Analyse a whole prep file, or the subtree under `node_id`, on the engines you choose and store the evals on every node. Example: \"analyse the whole tree with Stockfish and the human engine and save the evals\" → `{id, engines: [\"stockfish\", \"human\"]}`.\n\nEach node gets only the requested engines it is missing (`only_missing`, default true), so adding an engine later runs just that engine. Transpositions are analysed once and stamped on every node that reaches the position. Stored evals merge per engine; other engines' stored reads are kept. Results are saved after every batch of up to 10 positions. Positions you analysed in the last 24 h (cloud_analyse, deep_analyse) are filled in first and not run again (`filled_from_recent_analysis`). Only sweep when the user asks for it; usually it is enough to analyse positions as you work.\n\n**Async job, returns immediately:** `{ job_id, target_count, already_complete, estimated_seconds }`. Poll auto_evaluate_status(job_id); cancel with auto_evaluate_cancel. Do other work between polls.\n\nNeeds running rentals for every engine you ask for (ensure_engines). Wall time ≈ target_count × movetime (engines run in parallel per position). Does not set visible NAGs: the eval symbol is your call.",
    "inputSchema": {
      "properties": {
        "contempt": {
--- pinned/get_prep_position
+++ observed/get_prep_position
  {
-   "description": "Query one position within a prep session created by `prepare_opponent`. Returns move statistics (frequency + win rate + last-played date per move) plus the actual games played from that position, in one call.\n\nPosition input: prefer `file_id`+`node_id` when inside a prep file (server derives FEN from the tree). Otherwise pass `fen`.\n\nAUTO-EVAL: if a cloud engine instance is running, the response includes `.eval` (whichever engines that rental provides — Stockfish, Lc0, or both) so you don't need a separate cloud_analyse.\n\nReading the response — CRITICAL:\n• Win % is one weight, not a verdict. Sample size matters (3 games at 66% is noise; 300 at 55% is signal).\n• Prep is symmetric information — both sides see the same history. Assume the opponent knows the weakness you spotted.\n• Recency > career. The last 12-24 months dominate — filter your session with `start_month` if the player's repertoire shifted.\n• Opponent will deviate early. Prep is a tree — cover the 2 most likely replies at each real branching point, not one 20-move line.\n\nFor the full guide call `read_opening_prep_guide`.",
+   "description": "A player's moves at one position: move statistics (frequency + win rate + last-played date per move) plus the actual games from that position, in one call.\n\nTwo ways in: `fide_id` + `color` reads one FIDE player's games directly, no session (the usual case). `session_token` reads a session from `prepare_opponent` (Chess.com / Lichess accounts, combined sources, or date / time-control filters).\n\nPosition input: prefer `file_id`+`node_id` when inside a prep file (server derives FEN from the tree). Otherwise pass `fen`.\n\nAUTO-EVAL: if a cloud engine instance is running, the response includes `.eval` (whichever engines that rental provides — Stockfish, Lc0, or both) so you don't need a separate cloud_analyse.\n\nReading the response — CRITICAL:\n• Win % is one weight, not a verdict. Sample size matters (3 games at 66% is noise; 300 at 55% is signal).\n• Prep is symmetric information — both sides see the same history. Assume the opponent knows the weakness you spotted.\n• Recency > career. The last 12-24 months dominate — filter your session with `start_month` if the player's repertoire shifted.\n• Opponent will deviate early. Prep is a tree — cover the 2 most likely replies at each real branching point, not one 20-move line.\n\nFor the full guide call `read_opening_prep_guide`.",
    "inputSchema": {
      "properties": {
+       "color": {
+         "description": "The colour the player had (with `fide_id`).",
+         "enum": [
+           "white",
+           "black"
+         ],
+         "type": "string"
+       },
        "fen": {
          "description": "Position as FEN. Only used if `node_id` is not set.",
          "type": "string"
+       "fide_id": {
+         "description": "FIDE player to read directly (no session). Needs `color`.",
+         "type": "integer"
+       },
        "file_id": {
          "description": "Prep file id — combine with `node_id` for tree-addressed position lookup.",
          "type": "string"
-         "description": "Games to return (default 10).",
+         "description": "Games to return (default 10; at most 10 with `fide_id`).",
          "maximum": 50,
          "minimum": 1,
          "type": "integer"
+       "moves": {
+         "description": "SAN moves from the start, e.g. 'e4 c5 Nf3'. Alternative to `fen`.",
+         "type": "string"
+       },
        "node_id": {
          "description": "Node id inside `file_id`. Root is 'r'. When set, overrides `fen`.",
          "type": "string"
-         "description": "Session token from `prepare_opponent`.",
+         "description": "Session token from `prepare_opponent` (instead of `fide_id`).",
          "type": "string"
        }
      },
-     "required": [
-       "session_token"
-     ],
      "type": "object"
    },
    "name": "get_prep_position"
--- pinned/prepare_opponent
+++ observed/prepare_opponent
  {
-   "description": "Create a prep SESSION combining games from one or more sources — FIDE database, Chess.com account, Lichess account — with optional filters (colour, date range, time control). Returns a session `token` you pass to `get_prep_position` to query stats at any position within that filtered corpus.\n\nThis is the main opponent-prep tool. Use it whenever a user asks 'prep me against X' — call once with the right sources+filters, then walk the tree with `get_prep_position(session_token, ...)`. Sessions are cached on the server (list existing ones with `list_prep_sessions` to avoid rebuilding).\n\nSOURCES (1-10 per call, combined into one gameset):\n- `fide` — needs `fideId`. Optional filters: `color`, `startMonth`/`endMonth`, `timeControl` (`classical`|`rapid`|`blitz`), `excludeOnline`.\n- `chesscom` — needs `username`. Filters: `color`, `startMonth`/`endMonth` (**required** for chesscom/lichess), `timeControl`.\n- `lichess` — needs `username`. Same filters as chesscom; `timeControl` also accepts `bullet` on Lichess.\n\nMulti-source example: one player with both a FIDE ID and a Lichess account → two sources in one call, all their games combined into one session.\n\nGROUNDING: every claim about the opponent's repertoire must trace back to a `get_prep_position` call on this session. Don't assert 'they play sharply' or 'they hate isolated queen pawn' without pointing at actual game counts / win rates in the response. Prep is a two-player game — see `read_opening_prep_guide` before recommending an opening plan.",
+   "description": "Create a prep SESSION combining games from one or more sources — FIDE database, Chess.com account, Lichess account — with optional filters (colour, date range, time control). Returns a session `token` you pass to `get_prep_position` to query stats at any position within that filtered corpus.\n\nWHEN YOU NEED IT: a Chess.com or Lichess account, several sources combined, or a FIDE player with filters (date range, time control, exclude online). For ONE FIDE player and a colour with no filters, skip this: call `get_prep_position` with `fide_id` + `color` directly (no session). Sessions are owned by the user's account and cached on the server; check `list_prep_sessions` first to reuse one instead of rebuilding.\n\nSOURCES (1-10 per call, combined into one gameset):\n- `fide` — needs `fideId`. Optional filters: `color`, `startMonth`/`endMonth`, `timeControl` (`classical`|`rapid`|`blitz`), `excludeOnline`.\n- `chesscom` — needs `username`. Filters: `color`, `startMonth`/`endMonth` (**required** for chesscom/lichess), `timeControl`.\n- `lichess` — needs `username`. Same filters as chesscom; `timeControl` also accepts `bullet` on Lichess.\n\nMulti-source example: one player with both a FIDE ID and a Lichess account → two sources in one call, all their games combined into one session.\n\nGROUNDING: every claim about the opponent's repertoire must trace back to a `get_prep_position` call on this session. Don't assert 'they play sharply' or 'they hate isolated queen pawn' without pointing at actual game counts / win rates in the response. Prep is a two-player game — see `read_opening_prep_guide` before recommending an opening plan.",
    "inputSchema": {
      "properties": {
        "sources": {

Changed 2026-10-06 13:32 UTC

6 tools added.

+ tool added: get_games

+ tool added: memory

+ tool added: puzzle

+ tool added: puzzle_move

+ tool added: search_games

+ tool added: share

Changed 2026-10-05 14:40 UTC

15 descriptions rewritten, 4 tools added.

--- pinned/auto_evaluate
+++ observed/auto_evaluate
  {
-   "description": "Walk the tree from `node_id` (default `'r'` = whole file) and populate the persistent `ceoEval` on every descendant via cloud_analyse. Requires a running cloud combo instance.\n\n**Async job — returns immediately.** Response: `{ job_id, target_count, status: 'running', estimated_seconds }`. Then poll `auto_evaluate_status(job_id)` until `done: true`. Cancel a run with `auto_evaluate_cancel(job_id)` — partial progress is preserved. Do useful other work between polls (write more of the tree, walk the opponent's repertoire) — the engine runs in the background.\n\nProgress is checkpointed to the prep file every 8 successfully-evaluated nodes, so a cancel / crash / MCP restart mid-run leaves the tree partially populated rather than losing everything. On MCP restart the job record disappears; re-run auto_evaluate and `only_missing=true` naturally skips what was already saved.\n\n**Does NOT set visible NAGs.** NAG placement is your call, not the engine's — an opening tree full of 0.00 positions doesn't need a `$10` (=) glyph on every move. Use quote_engine_eval on individual nodes before writing prose that references engine numbers.\n\nCosts real money — one cloud_analyse per node. A 200-node walk at default movetime is ~5 min of engine time (calls serialise on the per-combo semaphore in the backend).",
+   "description": "Analyse a whole prep file, or the subtree under `node_id`, on the engines you choose and store the evals on every node. Example: \"analyse the whole tree with Stockfish and the human engine and save the evals\" → `{id, engines: [\"stockfish\", \"human\"]}`.\n\nEach node gets only the requested engines it is missing (`only_missing`, default true), so adding an engine later runs just that engine. Transpositions are analysed once and stamped on every node that reaches the position. Stored evals merge per engine; other engines' stored reads are kept. Results are saved after every batch of up to 10 positions.\n\n**Async job, returns immediately:** `{ job_id, target_count, already_complete, estimated_seconds }`. Poll auto_evaluate_status(job_id); cancel with auto_evaluate_cancel. Do other work between polls.\n\nNeeds running rentals for every engine you ask for (ensure_engines). Wall time ≈ target_count × movetime (engines run in parallel per position). Does not set visible NAGs: the eval symbol is your call.",
    "inputSchema": {
      "properties": {
+       "contempt": {
+         "description": "lc0 only. Stored evals then carry the bias; use for idea finding, not for the stored record.",
+         "maximum": 100,
+         "minimum": -100,
+         "type": "integer"
+       },
+       "engines": {
+         "description": "Engines to run. Default [\"stockfish\"].",
+         "items": {
+           "enum": [
+             "stockfish",
+             "lc0",
+             "human"
+           ],
+           "type": "string"
+         },
+         "type": "array"
+       },
        "id": {
+         "description": "Prep file id.",
          "type": "string"
        },
        "movetime_ms": {
-         "description": "Per-node cloud_analyse think time (default 1500).",
-         "maximum": 5000,
+         "description": "Think time per position in ms (default 2000).",
+         "maximum": 15000,
          "minimum": 500,
          "type": "integer"
        },
-         "description": "Skip nodes that already carry a stored ceoEval (default true).",
+         "description": "Run only the engines a node has no stored eval for (default true). false re-analyses everything.",
          "type": "boolean"
+       },
+       "rentals": {
+         "description": "Only when two running rentals provide the same engine: engine → contract_id.",
+         "properties": {
+           "human": {
+             "type": "string"
+           },
+           "lc0": {
+             "type": "string"
+           },
+           "stockfish": {
+             "type": "string"
+           }
+         },
+         "type": "object"
        }
      },
      "required": [
--- pinned/auto_evaluate_cancel
+++ observed/auto_evaluate_cancel
  {
-   "description": "Ask a running auto_evaluate job to stop as soon as its current node finishes. Whatever progress was completed before cancellation is durably saved (checkpoint on cancel). Idempotent — cancelling an already-finished job is a no-op with a clear note in the response.",
+   "description": "Ask a running auto_evaluate job to stop after the batch in flight. Every batch already analysed is saved in the file. Idempotent — cancelling an already-finished job is a no-op with a clear note in the response.",
    "inputSchema": {
      "properties": {
        "job_id": {
--- pinned/auto_evaluate_status
+++ observed/auto_evaluate_status
  {
-   "description": "Poll the status of an auto_evaluate job. Response: `{ status: 'running' | 'done' | 'cancelled' | 'error' | 'not_found', target_count, evaluated, errored, remaining, done, error?, version? }`. When `status: 'not_found'` the job either expired (kept ~15 min after completion), never existed, or the MCP restarted since it was created — re-run auto_evaluate.\n\nTypical poll cadence: every 3-5 s for small walks, every 10-30 s for large ones. Don't hammer — status is a pure in-memory read but polling doesn't speed the engine up.",
+   "description": "Poll the status of an auto_evaluate job. Response: `{ status: 'running' | 'done' | 'cancelled' | 'error' | 'not_found', engines, target_count, processed, remaining, evaluated: {engine: n}, failed: {engine: n}, failed_node_ids: {engine: [ids]}, last_error?, aborted_reason?, done }`. Retry failures with cloud_analyse({file_id, node_ids}) or a new auto_evaluate. When `status: 'not_found'` the job either expired (kept ~15 min after completion), never existed, or the MCP restarted since it was created — re-run auto_evaluate.\n\nTypical poll cadence: every 3-5 s for small walks, every 10-30 s for large ones. Don't hammer — status is a pure in-memory read but polling doesn't speed the engine up.",
    "inputSchema": {
      "properties": {
        "job_id": {
--- pinned/cloud_analyse
+++ observed/cloud_analyse
  {
-   "description": "Runs a synchronous ~2s analysis on the user's running combo instance and returns both Stockfish and Lc0's final read for the FEN — depth, top-N candidate moves with scores (**scoreCp is White-POV centipawns**: +20 = White is +0.20 pawns better regardless of whose turn it is; matches the sign convention used everywhere else in this MCP, including the stored ceoEval). Mate is White-POV plies-to-mate (+5 = White mates in 5). Also returns each engine's principal variation.\n\nGROUNDING: every claim you make about a position must trace back to actual engine output from a call in THIS session. Don't invent evaluations, don't name 'best moves' you haven't seen the engine list, don't fabricate variations that 'look plausible.' Compute is cheap — call this 5-10 times while walking a tree rather than pattern-matching from your training data. When you don't have data for the position, either run the tool or say so; don't fill the gap with chess prose the user can't distinguish from measured output.\n\nAuto-picks the caller's only running combo instance; errors clearly if there are zero (start one first with start_cloud_engine) or more than one (destroy the extras first).\n\nHow to read the response:\n• Stockfish is objective truth — trust it for 'does this line hold?' 'is there a tactic?' 'is this endgame drawn?' A Stockfish 0.00 means 'objectively equal', NOT 'trivial draw' — one side can still be much harder to play in practice.\n• Lc0 is practical eval — trust it for 'which side is easier?' 'which candidate is best when Stockfish shows several as equal?' Lc0 sees long-term positional factors Stockfish's fixed search can miss.\n• When they agree → high confidence. When they disagree → look at both scores and reason WHY (Stockfish sharply higher = tactic Lc0 missed; Lc0 higher = long-term positional edge past Stockfish's horizon). Never dismiss either — the disagreement is the signal.\n\nContempt (`contempt`) skews Lc0 (only Lc0 — Stockfish always stays objective) toward White (positive) or Black (negative). Signed 0-100 strength — same scale as the web UI's ContemptStrength slider (the server multiplies by 8 to produce Lc0's internal cp bias). Typical values: ±15 for a light nudge, ±30-60 for real fighting play, ±80-100 for maximum steer. Use it to find non-objective 'practical' ideas or when the user needs to lean toward fighting/solid lines with a specific colour. Do NOT quote a contempt-biased eval as objective — cross-check with Stockfish.\n\nAlso useful: pass `moves` on top of `fen` to explore a variation without computing FENs yourself (e.g. fen='<tabiya>', moves='b4 a5 c3'). And the flip-side-to-move threat check documented in the guide is a great free trick.\n\n**PVs are capped at 6 plies by default (3 full moves), and lines that got truncated are marked with `pv_truncated: true`.** This is deliberate: the tail of a PV is where the engine's confidence collapses, AND pasting a long PV into `add_line` as if it were prepared repertoire is the #1 documented anti-pattern of this MCP — a 15-move PV is one line of engine output through positions where both sides had real choices, not a repertoire. To see further, don't raise `pv_max_plies`; instead, walk the tree one branch at a time with a fresh `cloud_analyse` at each position where the opponent has real alternatives — that's what makes it prep instead of pasted output. Only raise the cap when you're verifying a forcing sequence (a mate, a forced tactical resolution), not to build lines.\n\nFor the full guide including worked examples, call the `read_engine_usage_guide` tool.\n\nNot for casual questions — this costs real money per second. Use `get_position_stats` for anything that doesn't require deep prep.\n\n**When called with `file_id`+`node_id` (preferred inside a prep file), the resulting eval is auto-stored on that node's `ceoEval` — you can then quote it with quote_engine_eval on any later call.** This is what makes engine attribution trustworthy: prose that says 'engines say X on node Y' can only be true if a call was actually made against node_id=Y.",
+   "description": "Analyse up to 10 positions on the engines you choose (`stockfish`, `lc0`, `human`), each engine on whichever running rental provides it. Engines run in parallel per position. Returns, per position, each engine's depth, candidate lines and best move.\n\nPositions: `fens` (list), `lines` (list of SAN move sequences from `fen` or the start), `file_id` + `node_ids` (list), or a single `fen` / `moves` / `file_id`+`node_id`. Examples: analyse three FENs with Stockfish → `{fens: [a, b, c], engines: [\"stockfish\"]}`; with the human engine and Stockfish → `engines: [\"stockfish\", \"human\"]`. For a whole file or subtree use auto_evaluate instead.\n\nScores: `scoreCp` and `mate` are White's point of view (+20 = White +0.20; mate +5 = White mates in 5). lc0 and human lines carry `wdl`: win/draw/loss percent, White's point of view. Read the response's `eval_scale` before calling a position equal.\n\nGROUNDING: every claim about a position must trace back to engine output from this session. Don't invent evaluations, best moves or variations. When you have no data for a position, run it or say so.\n\nStoring: with `file_id`, each result is merged into the `ceoEval` of every node that reaches that position (`stored_on` in the response), keeping other engines' stored reads. quote_engine_eval cites them later. Without `file_id` nothing is lost either: results are remembered for 24 h and filled into any node reaching that position the next time you write to a file (`evals_filled` in add_move / add_line / apply_mutations responses).\n\nContempt (`contempt`, lc0 only): signed -100..100, positive favours White. Use it to find ideas (e.g. -20 makes Black play for a win). Never quote a contempt eval as objective.\n\nPVs are capped at 6 plies (`pv_truncated: true` when cut). To see further, analyse the position at the end of the line; raise `pv_max_plies` only to verify a forcing line. Don't paste PVs into add_line as prep.\n\nNeeds running rentals for the engines you ask for (see ensure_engines). Costs money while rentals run; use get_position_stats for casual questions.",
    "inputSchema": {
      "properties": {
        "contempt": {
-         "description": "Lc0 contempt bias. Signed 0-100 strength (same scale as the web UI's ContemptStrength slider — server multiplies by 8 to get the internal cp bias). 0 = objective (default). Positive favours White, negative favours Black. Typical: ±15 light nudge, ±30-60 real fighting play, ±80-100 maximum steer. Not applied to Stockfish. See engine_usage_primer for when to use.",
+         "description": "lc0 only. Positive favours White, negative Black. 0 = objective (default).",
          "maximum": 100,
          "minimum": -100,
          "type": "integer"
-         "description": "Which engines to run. Default = both. Use `[\"lc0\"]` to skip Stockfish (e.g. while a deep_analyse job is holding the SF slot on the same combo). Use `[\"stockfish\"]` when only the objective read matters. The skipped engine's field is omitted from the response.",
+         "description": "Engines to run. Default [\"stockfish\"]. Each needs a running rental that provides it.",
          "items": {
            "enum": [
              "stockfish",
-             "lc0"
+             "lc0",
+             "human"
            ],
            "type": "string"
          },
-         "description": "Starting position as FEN. Only used if `node_id` is not set.",
+         "description": "One position, or the start position for `moves` / `lines`.",
          "type": "string"
        },
+       "fens": {
+         "description": "Positions as FENs (up to 10 in total with the other inputs).",
+         "items": {
+           "type": "string"
+         },
+         "type": "array"
+       },
        "file_id": {
-         "description": "Prep file id. **Prefer file_id+node_id over fen** when inside a prep file — the FEN comes from the tree AND the result is stored on the node.",
+         "description": "Prep file id. With node_ids/node_id the FEN comes from the tree; with any input, results are stored on matching nodes.",
          "type": "string"
        },
-       "lc0_multipv": {
-         "description": "Lc0 candidate lines (default 8). Kept wide because multipv doesn't degrade Lc0's strength the way it does Stockfish's — Lc0 is the 'find inspiration / explore practical tries' leg, use a high multipv to get a full slate of ideas.",
-         "maximum": 10,
-         "minimum": 1,
-         "type": "integer"
+       "lines": {
+         "description": "SAN move sequences, each applied from `fen` (or the start position), e.g. [\"e4 c5 Nf3\", \"e4 e5 Nf3\"].",
+         "items": {
+           "type": "string"
+         },
+         "type": "array"
        },
        "moves": {
-         "description": "Optional SAN moves to apply on top of `fen` (or startpos). Only used if `node_id` is not set.",
+         "description": "SAN moves applied on top of `fen` (or the start position) for a single position.",
          "type": "string"
        },
        "movetime_ms": {
-         "description": "Think time in milliseconds (default 2000).",
-         "maximum": 10000,
+         "description": "Think time per position in ms (default 2000).",
+         "maximum": 15000,
          "minimum": 100,
          "type": "integer"
        },
+       "multipv": {
+         "description": "Candidate lines per engine. Defaults: stockfish 2 (extra lines cost it strength), lc0 and human 8 (wide slate of ideas, no strength cost).",
+         "properties": {
+           "human": {
+             "maximum": 10,
+             "minimum": 1,
+             "type": "integer"
+           },
+           "lc0": {
+             "maximum": 10,
+             "minimum": 1,
+             "type": "integer"
+           },
+           "stockfish": {
+             "maximum": 10,
+             "minimum": 1,
+             "type": "integer"
+           }
+         },
+         "type": "object"
+       },
        "node_id": {
-         "description": "Node id inside `file_id`. Root is 'r'. When set, overrides `fen`/`moves`.",
+         "description": "One node id inside `file_id`. Root is 'r'.",
          "type": "string"
        },
+       "node_ids": {
+         "description": "Node ids inside `file_id`.",
+         "items": {
+           "type": "string"
+         },
+         "type": "array"
+       },
        "pv_max_plies": {
-         "description": "Cap each returned PV to this many plies (default 6 = 3 full moves). PVs beyond ~6 plies are speculative and are the anti-pattern behind pasted-engine-line 'prep' — don't raise unless you're specifically checking a forcing tactic or verifying a mate. When a line was truncated, the response marks it with `pv_truncated: true`.",
+         "description": "Cap each PV (default 6 plies). Raise only to verify a forcing line.",
          "maximum": 40,
          "minimum": 1,
          "type": "integer"
-       "stockfish_multipv": {
-         "description": "Stockfish candidate lines (default 2). Kept tight because each extra PV steals search bandwidth from the top choice — SF is the 'what's objectively best' leg, use a low multipv to keep it strong. Raise only when you specifically need SF's take on a wide range of candidates.",
-         "maximum": 10,
-         "minimum": 1,
-         "type": "integer"
+       "rentals": {
+         "description": "Only when two running rentals provide the same engine: engine → contract_id from list_cloud_engines.",
+         "properties": {
+           "human": {
+             "type": "string"
+           },
+           "lc0": {
+             "type": "string"
+           },
+           "stockfish": {
+             "type": "string"
+           }
+         },
+         "type": "object"
        }
      },
      "type": "object"

+ tool added: create_collection

--- pinned/deep_analyse
+++ observed/deep_analyse
  {
-   "description": "Start a long Stockfish think on a single position (up to 5 min movetime). Returns a `job_id` immediately; poll `deep_analyse_status(job_id)` for the result, cancel with `deep_analyse_cancel(job_id)`. Runs SF only — Lc0 doesn't benefit from long thinks past a handful of seconds — and **holds only the SF engine slot on the combo, so `cloud_analyse(..., engines: [\"lc0\"])` stays available for other work in parallel**.\n\nUse this when a specific critical position deserves depth — a novelty candidate, a hairy tactical shot, a difficult endgame — and you want Stockfish at depth 35+ rather than the ~depth 22 you get from a 2s cloud_analyse. Movetime is in ms; typical: 30_000-60_000 for 'careful check', 120_000-300_000 for 'find the truth'.\n\nResult shape when done matches cloud_analyse's Stockfish leg (depth, top-N candidates with scoreCp/mate, best move, PV). Auto-stores the eval on `file_id`+`node_id` when both are supplied, same as cloud_analyse.",
+   "description": "Start one long think (up to 5 min) on one position, on one engine (`engine`, default stockfish). Returns a `job_id` immediately; poll deep_analyse_status, cancel with deep_analyse_cancel. It holds only that engine, so cloud_analyse on the other engines keeps working meanwhile.\n\nUse it when a critical position deserves depth: a novelty candidate, a sharp tactic, a hard endgame. Typical movetime: 30_000-60_000 for a careful check, 120_000-300_000 to find the truth.\n\nWith `file_id`+`node_id`, the result is merged into that node's ceoEval (other engines' stored reads are kept) and stamped on its transpositions.",
    "inputSchema": {
      "properties": {
+       "contempt": {
+         "description": "lc0 only.",
+         "maximum": 100,
+         "minimum": -100,
+         "type": "integer"
+       },
+       "engine": {
+         "description": "Default stockfish.",
+         "enum": [
+           "stockfish",
+           "lc0",
+           "human"
+         ],
+         "type": "string"
+       },
        "fen": {
          "description": "Position as FEN. Only used if `node_id` is not set.",
          "type": "string"
-         "description": "Prep file id. Combine with `node_id` to derive FEN from the tree AND persist the result on the node's ceoEval.",
+         "description": "Prep file id. With `node_id`, the FEN comes from the tree and the result is stored on the node.",
          "type": "string"
        },
        "moves": {
-         "description": "Number of candidate lines (default 2). Stockfish gets weaker as multipv grows — each extra PV steals search bandwidth from the top choice — so keep this low unless you specifically want to see several candidates ranked deep.",
+         "description": "Candidate lines. Default 2 for stockfish (extra lines cost it strength), 8 for lc0/human.",
          "maximum": 10,
          "minimum": 1,
          "type": "integer"
+       },
+       "rental": {
+         "description": "Only when two running rentals provide this engine: the contract_id to use.",
+         "type": "string"
        }
      },
      "type": "object"
--- pinned/deep_analyse_status
+++ observed/deep_analyse_status
  {
-   "description": "Poll a deep_analyse job. Response: `{ status: 'running' | 'done' | 'cancelled' | 'error' | 'not_found', elapsed_ms, movetime_ms, result?, error? }`. `result` shape when done: `{ engine, depth, timeMs, bestMove, lines: [{rank, depth, scoreCp?, mate?, pv, nodes?}] }` — the SF leg of a cloud_analyse response.\n\nPoll cadence: every ~15-30s for long thinks; there's no penalty for polling more often but the engine progresses at its own pace.",
+   "description": "Poll a deep_analyse job. Response: `{ status: 'running' | 'done' | 'cancelled' | 'error' | 'not_found', elapsed_ms, movetime_ms, result?, error? }`. `result` when done: that engine's block from a cloud_analyse response (`{ depth, bestMove, lines: [{rank, depth, scoreCp?, mate?, wdl?, pv}] }`, PVs in SAN, wdl in percent); `stored_on` lists the nodes it was saved to.\n\nPoll cadence: every ~15-30s for long thinks; there's no penalty for polling more often but the engine progresses at its own pace.",
    "inputSchema": {
      "properties": {
        "job_id": {

+ tool added: ensure_engines

--- pinned/get_position_stats
+++ observed/get_position_stats
  {
-   "description": "Move statistics + example games at a position. Answers 'how often is 4.O-O vs 4.d3 played here and which scores better'.\n\nSOURCE (default: `gm-classical`) selects a pre-aggregated database shard:\n- `gm-classical` — GM classical games (both players ≥2500, real thinking-time). BEST for opening prep — every move is signal, avgElo ~2600 across all listed moves.\n- `main` — the whole 11.7M-game DB. Widest coverage but noisiest (includes 1000-Elo blunder-fests in the move stats). Use as fallback when gm-classical's totalCount is too small to be informative.\n\nGame movetext is trimmed to the moves AFTER the queried position (using each game's plyNumber). Saves ~70% of the bytes vs full movetext.\n\nAUTO-EVAL: if a cloud combo instance is running, the response includes `.eval` with a compact Stockfish + Lc0 read and the corresponding NAG. Do NOT fire cloud_analyse separately for the same FEN. When called with `file_id`+`node_id`, the eval is also auto-stored on that node's `ceoEval` — later readable via quote_engine_eval.",
+   "description": "Move statistics + example games at a position. Answers 'how often is 4.O-O vs 4.d3 played here and which scores better'.\n\nSOURCE (default: `gm-classical`) selects a pre-aggregated database shard:\n- `gm-classical` — GM classical games (both players ≥2500, real thinking-time). BEST for opening prep — every move is signal, avgElo ~2600 across all listed moves.\n- `main` — the whole 11.7M-game DB. Widest coverage but noisiest (includes 1000-Elo blunder-fests in the move stats). Use as fallback when gm-classical's totalCount is too small to be informative.\n\nGame movetext is trimmed to the moves AFTER the queried position (using each game's plyNumber). Saves ~70% of the bytes vs full movetext.\n\nAUTO-EVAL: if a Stockfish rental is running, the response includes `.eval` with a short Stockfish read (cp/mate White's point of view, best move, PV). Not stored on any node; for a stored eval or other engines use cloud_analyse.",
    "inputSchema": {
      "properties": {
        "fen": {
--- pinned/get_prep_position
+++ observed/get_prep_position
  {
-   "description": "Query one position within a prep session created by `prepare_opponent`. Returns move statistics (frequency + win rate + last-played date per move) plus the actual games played from that position, in one call.\n\nPosition input: prefer `file_id`+`node_id` when inside a prep file (server derives FEN from the tree). Otherwise pass `fen`.\n\nAUTO-EVAL: if a cloud combo instance is running, the response includes `.eval` (Stockfish + Lc0 read at the position) so you don't need a separate cloud_analyse.\n\nReading the response — CRITICAL:\n• Win % is one weight, not a verdict. Sample size matters (3 games at 66% is noise; 300 at 55% is signal).\n• Prep is symmetric information — both sides see the same history. Assume the opponent knows the weakness you spotted.\n• Recency > career. The last 12-24 months dominate — filter your session with `start_month` if the player's repertoire shifted.\n• Opponent will deviate early. Prep is a tree — cover the 2 most likely replies at each real branching point, not one 20-move line.\n\nFor the full guide call `read_opening_prep_guide`.",
+   "description": "Query one position within a prep session created by `prepare_opponent`. Returns move statistics (frequency + win rate + last-played date per move) plus the actual games played from that position, in one call.\n\nPosition input: prefer `file_id`+`node_id` when inside a prep file (server derives FEN from the tree). Otherwise pass `fen`.\n\nAUTO-EVAL: if a cloud engine instance is running, the response includes `.eval` (whichever engines that rental provides — Stockfish, Lc0, or both) so you don't need a separate cloud_analyse.\n\nReading the response — CRITICAL:\n• Win % is one weight, not a verdict. Sample size matters (3 games at 66% is noise; 300 at 55% is signal).\n• Prep is symmetric information — both sides see the same history. Assume the opponent knows the weakness you spotted.\n• Recency > career. The last 12-24 months dominate — filter your session with `start_month` if the player's repertoire shifted.\n• Opponent will deviate early. Prep is a tree — cover the 2 most likely replies at each real branching point, not one 20-move line.\n\nFor the full guide call `read_opening_prep_guide`.",
    "inputSchema": {
      "properties": {
        "fen": {
--- pinned/list_cloud_engines
+++ observed/list_cloud_engines
  {
-   "description": "List the user's currently running cloud engines. Use before starting a new one, or to find the contract_id for stop_cloud_engine. `cloud_analyse` auto-picks the only running combo, so listing is only necessary when the user might have zero or several.",
+   "description": "List the user's running cloud engines, each with its `contractId` and `engines` (what it provides). A row with empty `engines` is still starting. Use the contractId for stop_cloud_engine, or in `rentals` when two running rentals provide the same engine.",
    "inputSchema": {
      "properties": {},
      "type": "object"
--- pinned/list_cloud_machine_options
+++ observed/list_cloud_machine_options
  {
-   "description": "Returns the catalog of combo cloud-engine machine types the user can start (SKU, human display name, cost per hour, availability). ALWAYS call this before start_cloud_engine — SKU strings like 'rtx-5090-64' do not match the display names ('Stockfish 32 CPUs + Lc0 1× RTX 5090') and are NOT guessable. Present the user the display names + prices; pass the SKU to start_cloud_engine.",
+   "description": "Returns the catalog of cloud-engine SKUs the user can start, same visibility as the chess.ceo app: SKU (`machineType`), the one engine it runs (`engine`: stockfish, lc0 or human), display name, cost per hour, availability. Also returns `engine_guide`, `eval_scale`, `eval_symbols` and `workflow`. ensure_engines already picks the cheapest SKU per engine; use this when the user wants to choose hardware. SKU strings are NOT guessable from display names; pass them verbatim to start_cloud_engine.",
    "inputSchema": {
      "properties": {},
      "type": "object"
--- pinned/list_transpositions
+++ observed/list_transpositions
  {
-   "description": "Group every position in a prep file that appears more than once — the same piece placement + side-to-move + castling rights reached by different move orders. Chess move orders diverge and re-converge constantly (1.d4 Nf6 2.c4 e6 3.Nc3 vs 1.c4 e6 2.Nc3 Nf6 3.d4 land on the same position); if you analyse both branches independently or write the same commentary twice, you're wasting engine time and inviting inconsistency.\n\nCall this BEFORE `auto_evaluate` on a big subtree to see how much work will actually be new, and BEFORE writing prose to know which nodes can share a comment or should point at each other with 'transposes to line X'.\n\nNote: engine evals auto-propagate — when `cloud_analyse({file_id, node_id})` stores `ceoEval` on a node, it also stamps every transposition of that position in the same file (see the response's `also_stored_on`). And `auto_evaluate({only_missing: true})` naturally skips the twin because it now has an eval. So detection is cheap AND propagation is automatic; this tool is for prose planning and one-shot audits, not for gating engine work.\n\nResponse: `{ file_id, group_count, node_count, groups: [{ position_key, size, node_ids, sans }] }`. `position_key` is the 3-field FEN prefix used as the match key; `size` is how many nodes share it; `sans` are the moves that led to each occurrence (parallel with `node_ids`, DFS order — first entry is the earliest/mainline-preferred occurrence). Only groups with size ≥ 2 are returned; sorted by size descending.",
+   "description": "Group every position in a prep file that appears more than once — the same piece placement + side-to-move + castling rights reached by different move orders. Chess move orders diverge and re-converge constantly (1.d4 Nf6 2.c4 e6 3.Nc3 vs 1.c4 e6 2.Nc3 Nf6 3.d4 land on the same position); if you analyse both branches independently or write the same commentary twice, you're wasting engine time and inviting inconsistency.\n\nCall this BEFORE `auto_evaluate` on a big subtree to see how much work will actually be new, and BEFORE writing prose to know which nodes can share a comment or should point at each other with 'transposes to line X'.\n\nNote: engine evals auto-propagate — when `cloud_analyse({file_id, node_id})` stores `ceoEval` on a node, it also stamps every transposition of that position in the same file (see the response's `stored_on`). And `auto_evaluate` analyses each position once and skips nodes that already hold every requested engine. So detection is cheap AND propagation is automatic; this tool is for prose planning and one-shot audits, not for gating engine work.\n\nResponse: `{ file_id, group_count, node_count, groups: [{ position_key, size, node_ids, sans }] }`. `position_key` is the 3-field FEN prefix used as the match key; `size` is how many nodes share it; `sans` are the moves that led to each occurrence (parallel with `node_ids`, DFS order — first entry is the earliest/mainline-preferred occurrence). Only groups with size ≥ 2 are returned; sorted by size descending.",
    "inputSchema": {
      "properties": {
        "id": {
--- pinned/prep_snapshot
+++ observed/prep_snapshot
  {
-   "description": "One call, three parallel fetches at the same position: opponent's stats on their side, your stats on your side, and the 11.7M-game general database at that position. Use this while walking the opening tree — one round trip instead of three separate calls, and you can compare the three views directly (e.g. opponent has 2 games here but the general DB has 8k → prep candidate).\n\nAUTO-EVAL: if a cloud combo instance is running, the response includes a top-level `.eval` (Stockfish + Lc0 read at the shared position) so you get four signals in one call. Do NOT fire cloud_analyse separately for the same FEN.",
+   "description": "One call, three parallel fetches at the same position: opponent's stats on their side, your stats on your side, and the 11.7M-game general database at that position. Use this while walking the opening tree — one round trip instead of three separate calls, and you can compare the three views directly (e.g. opponent has 2 games here but the general DB has 8k → prep candidate).\n\nAUTO-EVAL: if a cloud engine instance is running, the response includes a top-level `.eval` (the engines that rental provides, read at the shared position) so you get four signals in one call. Do NOT fire cloud_analyse separately for the same FEN.",
    "inputSchema": {
      "properties": {
        "fen": {
--- pinned/quote_engine_eval
+++ observed/quote_engine_eval
  {
-   "description": "Return the stored engine eval for a node, or null if that node was never analysed. **Call this before writing prose or NAGs that quote engine numbers** — if it returns null, you have no measurement to cite. Do NOT infer an eval for the node from siblings or children; either analyse it (cloud_analyse with node_id) or omit the number from your prose.\n\nResponse: `{ ceoEval: { sf: {cp, depth}, lc0: {cp, depth}, nag } | null }`. `cp` is White-POV centipawns as an integer (+20 = +0.20). `nag` is the threshold-derived glyph as a SUGGESTION — promote to a visible NAG via set_nags only when a glyph on that move carries editorial signal.",
+   "description": "Return the stored engine eval for a node, or null if that node was never analysed. **Call this before writing prose or NAGs that quote engine numbers** — if it returns null, you have no measurement to cite. Do NOT infer an eval for the node from siblings or children; either analyse it (cloud_analyse with node_id) or omit the number from your prose.\n\nResponse: `{ ceoEval: { sf?: {cp | mate, depth}, lc0?: {w, d, l, depth}, human?: {w, d, l, depth} } | null }`. `cp` is White-POV centipawns (+20 = +0.20); `mate` White-POV moves to mate; `w/d/l` win/draw/loss percent from White's point of view. An engine missing from the object was never run on this position. Files analysed before 0.50 may hold an lc0 `cp` instead of w/d/l.",
    "inputSchema": {
      "properties": {
        "id": {

+ tool added: rename_collection

+ tool added: set_collection_folder

--- pinned/start_cloud_engine
+++ observed/start_cloud_engine
  {
-   "description": "Rent a combo GPU instance (Stockfish + Lc0 in the same container) on the user's chess.ceo account. Real money — billed per second while running.\n\nCRITICAL: `machine_type` must be an exact SKU from `list_cloud_machine_options` (e.g. 'rtx-5090-64', NOT 'rtx-5090'). Guessing SKUs will fail. Call list_cloud_machine_options first, show the user the display names + prices, get their confirmation, then pass the SKU here.\n\nUse list_cloud_engines first to check if the user already has one running; don't start a second combo unless the user asked for it. Requires an MCP token with agent access.",
+   "description": "Rent one cloud engine on the user's chess.ceo account. Each SKU runs exactly one engine (stockfish, lc0 or human), taken from the SKU. Real money: billed per minute from start until stop_cloud_engine.\n\n`machine_type` must be an exact SKU from ensure_engines or list_cloud_machine_options. Show the user the price and get their confirmation first. For several engines, start one rental per engine; cloud_analyse uses them together. Don't start an engine that is already running (ensure_engines / list_cloud_engines show what is).",
    "inputSchema": {
      "properties": {
        "machine_type": {
-         "description": "SKU from list_cloud_machine_options (e.g. 'rtx-5090-64', 'rtx-5090-dual-64'). MUST be the exact SKU, not the display name and not a guess.",
+         "description": "Exact SKU from ensure_engines or list_cloud_machine_options. Not a display name, not a guess.",
          "type": "string"
        }
      },
--- pinned/stop_cloud_engine
+++ observed/stop_cloud_engine
  {
-   "description": "Destroy a running cloud engine. Billing stops immediately. Use the contract_id from `list_cloud_engines` — don't guess.",
+   "description": "Destroy a running cloud engine. Billing stops immediately. Use the contract_id from `list_cloud_engines`, don't guess. Stop rentals when the analysis is done unless the user wants them kept.",
    "inputSchema": {
      "properties": {
        "contract_id": {

Current tools

Show all 59 tool fingerprints
add_line
21c522d89b668421
add_move
a1ebda0a534038dd
apply_mutations
97e7411e2e8eab26
auto_evaluate
22c34508c8f853ca
auto_evaluate_cancel
7649e02667e83b5a
auto_evaluate_status
fc1052c6da96da3a
cloud_analyse
337645a2bb5b45c4
create_collection
8a9bfd1e39603c72
create_prep_file
c90bb11fe81c2aff
deep_analyse
7688ee7c14a548ef
deep_analyse_cancel
6d5ca1d837eefcd4
deep_analyse_status
8a8c0e25d2b5bb9d
delete_prep_file
cecf1eddc78f5c6b
delete_prep_session
92f592c9ded60320
delete_subtree
1700e6f4bb5e6257
describe_position
9d18c31e9618671c
ensure_engines
cfc22a1b356fb3b1
file_versions
fedee4cd2ec13281
find_position_in_courses
b336b9e9641c75c0
find_position_in_files
5564ca7b9cefb12f
get_games
42c8830404cf739d
get_head_to_head
3000d93e93066a10
get_player_profile
bc3121ec0433ddaf
get_position_stats
26780655bc01b33c
get_prep_position
0411f4b49b827e86
list_cloud_engines
7b3798b874b3948d
list_cloud_machine_options
011bcfb57da8cf21
list_collections
bd59ed5399e74e58
list_live_tournaments
9ab978b56cf31b18
list_nodes
e290a472d3b181c9
list_player_live_tournaments
e28ed793390a0f97
list_prep_files
7ef4f6f163632cbb
list_prep_sessions
840ef998bbfbfbdd
list_tournament_players
6b2efb4dfb48f784
list_transpositions
9cf0a82d2a58657e
memory
12c167bc08ea0cd4
natural_moves
a0ad60a0349cb9b9
prep_snapshot
afc19bd84559691a
prepare_opponent
f455d23895b48aea
promote_variation
cb943bcb3acbb512
puzzle
5ff954e88a8a6e41
puzzle_move
a21dcac6b0228a66
quote_engine_eval
8a4f32c8ca620f31
read_course_at_position
380741cdbe37988b
read_docs
975dc8e994df1ea1
read_prep_file
e82f3813f010f912
rename_collection
f62aa34cabb50933
restore_prep_file
76b6002a8a6cdaa7
search_games
f47e2da0315df5d0
search_player
89dc7cdae05aa59c
search_prep_files
b864d3dc8057381d
set_annotations
d992008378a9ca92
set_collection_folder
878c1c1b28ba9627
set_comment
b287e20ea34991cf
set_nags
98f72eb19cbc5789
set_tag
dc02ce0ba9a273b1
share
ec61c944842f8967
start_cloud_engine
b3d1386af549c11b
stop_cloud_engine
3f7388e4870f734e

Watch this server yourself

If you run this server, put the proxy in front of it. It pins these exact fingerprints on first connect and stops the session if they move.

npx -y mcp-pin@0.2.3 -- <your @chessceo/mcp command>

Or subscribe to this page's RSS feed to be told when it changes.

Badge

mcp-pin status badge for @chessceo/mcp

The badge states one fact about time and nothing else. It never claims a server is safe.

[![mcp-pin](https://mcp-pin.gautamkhosla.com/badge/061c40aa09ba3434.svg)](https://mcp-pin.gautamkhosla.com/servers/061c40aa09ba3434.html)