@chessceo/mcp 0.6.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -8,6 +8,9 @@
8
8
  // No API key, no auth, no state; the API's own rate limits apply.
9
9
  import { createServer as createHttpServer } from "node:http";
10
10
  import { AsyncLocalStorage } from "node:async_hooks";
11
+ import { readFileSync } from "node:fs";
12
+ import { fileURLToPath } from "node:url";
13
+ import { dirname, join } from "node:path";
11
14
  import { Chess } from "chess.js";
12
15
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
13
16
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -15,6 +18,23 @@ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/
15
18
  import { CallToolRequestSchema, GetPromptRequestSchema, ListPromptsRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
16
19
  const BASE = process.env.CHESSCEO_BASE_URL ?? "https://chess.ceo";
17
20
  const UA = `chessceo-mcp/${process.env.npm_package_version ?? "0.1.0"} (+https://chess.ceo)`;
21
+ // The engine-usage guide ships in the package (see package.json "files").
22
+ // Loaded once at startup and returned verbatim by the engine_usage_primer
23
+ // prompt — LLM hosts surface it in their slash menu so a user can push the
24
+ // full doc into the conversation on demand.
25
+ function loadBundledDoc(filename, fallbackLabel) {
26
+ try {
27
+ const here = dirname(fileURLToPath(import.meta.url));
28
+ // dist/index.js → ../docs/*.md when packaged; src/index.ts →
29
+ // ../docs/*.md during dev. Same resolution either way.
30
+ return readFileSync(join(here, "..", "docs", filename), "utf8");
31
+ }
32
+ catch {
33
+ return `${fallbackLabel} not bundled with this install of @chessceo/mcp.`;
34
+ }
35
+ }
36
+ const ENGINE_USAGE_DOC = loadBundledDoc("engine-usage.md", "Engine usage guide");
37
+ const PREP_STRATEGY_DOC = loadBundledDoc("prep-strategy.md", "Prep strategy guide");
18
38
  // ── HTTP ────────────────────────────────────────────────────────────
19
39
  //
20
40
  // Two auth flavours coexist:
@@ -145,7 +165,14 @@ const TOOLS = [
145
165
  },
146
166
  {
147
167
  name: "get_player_preparation",
148
- description: "For a given player, colour and starting position, return both the moves the player actually chose (frequency + win rate) and the underlying games. Position is specified either as a move sequence in SAN (`line`) or a raw FEN. Use `line` iteratively to walk the opening tree: call once with empty `line`, pick a move, call again with `line` extended by that move, etc. When preparing for a real game, weight recent games (last 12-24 months) more heavily than old ones, classical over-the-board > rapid/blitz > online, and look for variations the player scores poorly in (below ~40%) or plays with less variety (shallower prep).",
168
+ description: "For a given player, colour and starting position, return both the moves the player actually chose (frequency + win rate) and the underlying games. Position is specified either as a move sequence in SAN (`line`) or a raw FEN. Use `line` iteratively to walk the opening tree: call once with empty `line`, pick a move, call again with `line` extended by that move, etc.\n\n" +
169
+ "Reading the response — CRITICAL:\n" +
170
+ "• Win % is one weight, not a verdict. Recommend 1.b3 over 1.d4 because 60% > 50% is wrong. Sample size matters (3 games at 66% is noise; 300 at 55% is signal); avgWhite / avgBlack per move show the rating context (a big score often means a rating gap, not repertoire truth).\n" +
171
+ "• Prep is symmetric information — both sides see the same history. Assume the opponent knows the weakness you spotted; a weak opponent won't patch it, a strong or improving one already has (but structural weaknesses like 'bad in Catalan structures' hold anyway).\n" +
172
+ "• Recency > career. The last 12-24 months dominate. This endpoint's compact/LLM view deliberately omits per-move `hotness` — at the individual level it's trailing noise. The general DB endpoint keeps it (there it's fashion signal).\n" +
173
+ "• Opponent will deviate early. Prep is a tree, not a line — cover the 2 most likely replies at each real branching point, not one 20-move line.\n" +
174
+ "• Surprise is a scalpel. Don't tell a lifelong 1.e4 player to switch to 1.d4 — meta-signal screams prep. Rare secondary lines within the user's existing repertoire (e.g. 6.Bc4 instead of usual 6.Bg5 vs the Najdorf) are where surprise is real.\n\n" +
175
+ "For the full guide call the `read_prep_strategy_guide` tool.",
149
176
  inputSchema: {
150
177
  type: "object",
151
178
  properties: {
@@ -258,15 +285,22 @@ const TOOLS = [
258
285
  required: ["fide_id"],
259
286
  },
260
287
  },
288
+ {
289
+ name: "list_cloud_machine_options",
290
+ 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.",
291
+ inputSchema: { type: "object", properties: {} },
292
+ },
261
293
  {
262
294
  name: "start_cloud_engine",
263
- 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. Requires an MCP token with agent access. Use `list_cloud_engines` first to see if the user already has one running; don't start a second combo unless the user asked for it.",
295
+ 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\n" +
296
+ "CRITICAL: `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\n" +
297
+ "Use 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.",
264
298
  inputSchema: {
265
299
  type: "object",
266
300
  properties: {
267
301
  machine_type: {
268
302
  type: "string",
269
- description: "SKU picked from the user's cloud-engine options (e.g. 'rtx-5090', 'rtx-5090-dual'). Ask the user if you don't already know which one they want.",
303
+ 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.",
270
304
  },
271
305
  },
272
306
  required: ["machine_type"],
@@ -293,7 +327,15 @@ const TOOLS = [
293
327
  },
294
328
  {
295
329
  name: "cloud_analyse",
296
- 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 (centipawns from side-to-move POV or mate distance), and each engine's principal variation. Auto-picks the caller's only running combo instance; errors clearly if there are zero (start one first) or more than one (destroy the extras first). Much deeper than `analyse` (the CPU-only public endpoint) — use this when the user is genuinely doing prep work and has cloud engines running.",
330
+ 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 (centipawns from side-to-move POV, or mate distance), and each engine's principal variation.\n\n" +
331
+ "Auto-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\n" +
332
+ "How to read the response:\n" +
333
+ "• 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" +
334
+ "• 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" +
335
+ "• 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\n" +
336
+ "Contempt (`contempt`) skews Lc0 (only Lc0 — Stockfish always stays objective) toward White (positive) or Black (negative). Practical range -20..+20. Use it to find non-objective 'practical' ideas or when the user needs to steer toward fighting/solid lines with a specific colour. Do NOT quote a contempt-biased eval as objective — cross-check with Stockfish.\n\n" +
337
+ "For the full guide including worked examples, call the `read_engine_usage_guide` tool.\n\n" +
338
+ "Not for casual questions — this costs real money per second. Use the free `analyse` tool (single Stockfish, 2s) or `get_position_stats` for anything that doesn't require deep prep.",
297
339
  inputSchema: {
298
340
  type: "object",
299
341
  properties: {
@@ -310,10 +352,26 @@ const TOOLS = [
310
352
  maximum: 10,
311
353
  description: "Number of candidate lines per engine (default 3).",
312
354
  },
355
+ contempt: {
356
+ type: "integer",
357
+ minimum: -100,
358
+ maximum: 100,
359
+ description: "Lc0 contempt bias. 0 = objective (default). Positive favours White, negative favours Black; stay within -20..+20 in practice. Not applied to Stockfish. See engine_usage_primer prompt for when to use.",
360
+ },
313
361
  },
314
362
  required: ["fen"],
315
363
  },
316
364
  },
365
+ {
366
+ name: "read_engine_usage_guide",
367
+ description: "Returns the full chess.ceo engine-usage guide: when to trust Stockfish (objective truth) vs Lc0 (practical eval), how to read disagreements between them, and how to use Lc0 contempt to find non-objective 'practical' ideas. Call this ONCE per session before running expensive `cloud_analyse` calls or when the user asks WHY the engines gave certain scores. Same content is also available as the `engine_usage_primer` prompt (for clients that surface prompts as slash commands), but many clients do not expose prompts to the model — this tool works everywhere.",
368
+ inputSchema: { type: "object", properties: {} },
369
+ },
370
+ {
371
+ name: "read_prep_strategy_guide",
372
+ description: "Returns the full chess.ceo prep-strategy guide: why win% is one weight not a verdict, why prep is a two-player game with symmetric information (opponent sees your history too), how sample size and recency change the reading, when 'revealed weaknesses' are actionable vs already patched, how to use move-order tricks with the `trs` field, and how to calibrate surprise (rare secondary lines inside the existing repertoire, not big first-move switches). Call this ONCE per session before recommending an opening plan, especially when the user is preparing for a specific real opponent.",
373
+ inputSchema: { type: "object", properties: {} },
374
+ },
317
375
  {
318
376
  name: "prep_snapshot",
319
377
  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).",
@@ -386,6 +444,8 @@ async function callTool(name, args) {
386
444
  case "list_player_live_tournaments":
387
445
  // Note: snake_case fide_id, unlike the prep endpoints. Documented quirk.
388
446
  return get("/api/chess/live/player", { fide_id: Number(args.fide_id) });
447
+ case "list_cloud_machine_options":
448
+ return authedRequest("GET", "/api/agent/cloud-engines/options");
389
449
  case "start_cloud_engine":
390
450
  return authedRequest("POST", "/api/agent/cloud-engines", {
391
451
  machineType: String(args.machine_type),
@@ -400,8 +460,14 @@ async function callTool(name, args) {
400
460
  body.movetime_ms = args.movetime_ms;
401
461
  if (typeof args.multipv === "number")
402
462
  body.multipv = args.multipv;
463
+ if (typeof args.contempt === "number")
464
+ body.contempt = args.contempt;
403
465
  return authedRequest("POST", "/api/agent/cloud-engines/analyse", body);
404
466
  }
467
+ case "read_engine_usage_guide":
468
+ return { guide: ENGINE_USAGE_DOC };
469
+ case "read_prep_strategy_guide":
470
+ return { guide: PREP_STRATEGY_DOC };
405
471
  case "prep_snapshot": {
406
472
  const me = Number(args.fide_id_me);
407
473
  const opp = Number(args.fide_id_opponent);
@@ -487,6 +553,16 @@ const PROMPTS = [
487
553
  { name: "player_b", description: "Second player (name or FIDE ID)", required: true },
488
554
  ],
489
555
  },
556
+ {
557
+ name: "engine_usage_primer",
558
+ description: "Full guide on how to use the chess.ceo cloud engines — Stockfish vs Lc0 tradeoffs, when to trust which, how to read disagreements, and how to use Lc0 contempt to find practical ideas. Read before running expensive cloud_analyse calls or when the user asks WHY the engines gave certain scores.",
559
+ arguments: [],
560
+ },
561
+ {
562
+ name: "prep_strategy_primer",
563
+ description: "Full guide on how to reason about opening preparation — why win% is one weight not a rule, why prep is a two-player game with symmetric information, when 'revealed weaknesses' are actionable, how to use move-order tricks, and how to calibrate surprise. Read before recommending an opening plan, especially when the user is preparing for a specific real opponent.",
564
+ arguments: [],
565
+ },
490
566
  ];
491
567
  // The workflow text for prepare_for_game. Kept in one place so both the
492
568
  // MCP prompt handler and the /prepare fallback can share it.
@@ -559,6 +635,12 @@ server.setRequestHandler(GetPromptRequestSchema, async (req) => {
559
635
  5. Deliver: current strength, characteristic openings, one-sentence style read, biggest wins, biggest losses / recurring weakness. Cite the numbers.`;
560
636
  break;
561
637
  }
638
+ case "engine_usage_primer":
639
+ text = ENGINE_USAGE_DOC;
640
+ break;
641
+ case "prep_strategy_primer":
642
+ text = PREP_STRATEGY_DOC;
643
+ break;
562
644
  case "head_to_head_briefing": {
563
645
  const a = promptArgs.player_a ?? "player A";
564
646
  const b = promptArgs.player_b ?? "player B";
@@ -0,0 +1,76 @@
1
+ # Engine usage guide
2
+
3
+ When you call `cloud_analyse`, chess.ceo runs Stockfish and Lc0 in parallel on the user's rented combo instance and returns both engines' final read. This doc explains what each engine is good for and how to interpret the numbers you get back — the difference between "this line is a draw" and "this line is easy to draw" is central to real prep and both engines are needed.
4
+
5
+ ## Two engines, two truths
6
+
7
+ ### Stockfish — objective source of truth
8
+
9
+ Stockfish is calculation. Its evaluation is objective: *"is this position a draw, a win, or a loss with best play from both sides?"*
10
+
11
+ Trust Stockfish for questions like:
12
+ - Does this defensive line actually hold?
13
+ - Is there a concrete tactic here that the human would find?
14
+ - Is this endgame drawn?
15
+ - Is White's initiative worth a piece?
16
+
17
+ **Watch out for:** Stockfish gives 0.00 to a *lot* of positions in the opening and early middlegame. 0.00 does not mean "trivial draw" — it means "objectively drawn with best play." Practically, one side can still be much harder to defend for a human. Every top-level classical game past move 8 typically shows 0.00 in Stockfish's eyes, yet real players win and lose those games all the time.
18
+
19
+ ### Lc0 — practical eval, human-like feel
20
+
21
+ Lc0 is a neural net trained on self-play games. Its evaluation is closer to how a strong human sees the position — it weighs long-term positional factors, piece activity, space, and initiative in a way Stockfish's fixed search often can't reach.
22
+
23
+ Where Stockfish says 0.00, Lc0 might say +0.15 — meaning *"White still has a small but real practical edge over the board."* That's exactly the signal you want for opening prep, where 95% of positions are within the objective drawing margin and the real question is *"which side is easier to play?"*
24
+
25
+ Trust Lc0 for:
26
+ - Which side has practical chances in an opening structure
27
+ - Which pawn structure will be easier to handle
28
+ - Whether a slow positional idea has long-term venom
29
+ - Ranking candidate moves when Stockfish sees several as equal
30
+
31
+ **Watch out for:** Lc0 can miss very deep tactical shots — its search is guided by intuition, not depth. If Lc0 loves a line but Stockfish doesn't, look for a concrete tactical justification (or a refutation).
32
+
33
+ ### Rule of thumb
34
+
35
+ - Objective truth ("does this hold?", "is this a mate?") → **trust Stockfish**
36
+ - Practical prep ("which side is easier?", "which candidate is best?") → **trust Lc0**
37
+ - Both agree → high confidence, ship the recommendation
38
+ - They disagree → look at both scores together and reason about *why*:
39
+ - Stockfish sharply higher: probably a tactic Lc0 didn't calculate
40
+ - Lc0 higher: probably a long-term positional factor beyond Stockfish's horizon
41
+ - Never dismiss either engine — the disagreement itself is the signal
42
+
43
+ ## Lc0 contempt
44
+
45
+ Contempt is an Lc0 option that skews its evaluation and move choice toward one side. Passing `contempt` to `cloud_analyse` sets it on the Lc0 leg only; Stockfish always analyzes objectively.
46
+
47
+ **Range:** -100 to +100 accepted; **practical range: -20 to +20**. Beyond ±20 Lc0 starts picking objectively bad moves that only look good under an extreme bias.
48
+
49
+ - **Positive contempt** (e.g. `+15`): Lc0 assumes it's playing *from White's side* against equal or slightly weaker opposition. It picks more ambitious, complicated moves and avoids quick simplifications. The eval reads higher than pure objective. Concrete effect: with White, Lc0 will less often steer into the Exchange Slav or a symmetrical Berlin.
50
+ - **Negative contempt** (e.g. `-15`): Same but from Black's side. Lc0 will play sharper, more provocative lines with Black — more likely to pick a Sveshnikov over a Petroff, or a Grünfeld over a Slav.
51
+ - **Zero** (default): pure objective Lc0.
52
+
53
+ The eval Lc0 returns when contempt is set is **not** the objective eval — it's Lc0's assessment under the contempt bias. If you also want the objective read, look at the Stockfish leg of the same response.
54
+
55
+ ### When to set contempt
56
+
57
+ - **Finding new ideas** — set contempt in the direction you're preparing to see moves the objective engine wouldn't consider "safe enough." Lines Lc0 rejects at contempt=0 but likes at contempt=±15 are candidate surprises.
58
+ - **Building a solid repertoire** — small positive contempt for your color if the user wants to take positions seriously (real, competitive games) without gambling on unclear complications.
59
+ - **Playing for a win with the "worse" side** — negative contempt if the user needs to avoid drawing lines with Black in a must-win situation.
60
+ - **Never with zero direction** — always know *why* you're skewing before you set contempt. It's a specific tool for a specific question, not a default knob.
61
+
62
+ ## Worked example
63
+
64
+ User is preparing Black against a 2600 opponent who plays 1.e4 c5 2.Nf3 d6 3.d4 cxd4 4.Nxd4 Nf6 5.Nc3 a6 6.Be3 e5. You want to know if 7.Nb3 or 7.Nf3 is more testing.
65
+
66
+ Two calls:
67
+ 1. `cloud_analyse(fen=<position after 6...e5>, multipv=2)` — get both engines' top choices with movetime=2000.
68
+ 2. If Stockfish scores them equal but Lc0 prefers one by 0.10-0.20, that's your practical answer. The user will find that line harder to face.
69
+
70
+ If the user is specifically preparing to *play* the black side in a must-win, add `contempt=-15` on a follow-up call to see which lines Lc0 finds most fighting for Black. Compare against Stockfish's objective read to make sure the fighting choice isn't just losing.
71
+
72
+ ## What NOT to do
73
+
74
+ - **Don't quote Lc0's contempt-biased eval as objective.** If you tell the user "Lc0 gives Black +0.30 here" without disclosing you set contempt=-15, that's misleading.
75
+ - **Don't run cloud analysis just for casual questions.** `cloud_analyse` costs the user real money per second. If the question is "is 1.e4 or 1.d4 better?", the free `analyse` (single Stockfish, 2s) or `get_position_stats` (11.7M-game database) is enough.
76
+ - **Don't ignore the disagreement.** When Stockfish and Lc0 diverge sharply, that's exactly when you should explain *why* to the user — not paper over it.
@@ -0,0 +1,79 @@
1
+ # Prep strategy for chess.ceo agents
2
+
3
+ Opening prep is not a monologue with the position-stats and player-prep endpoints as sources of truth. It's a two-player adversarial game with **symmetric information** — both sides can see the same history, both know what the other has played, both have their own weaknesses and their own idea of the other's weaknesses. Everything below flows from that.
4
+
5
+ ## Numbers are inputs, not verdicts
6
+
7
+ The move statistics endpoints return win %, game counts, and (in the big DB) hotness. Treat every one of these as a *weight*, not a *rule*.
8
+
9
+ - **Sample size scales trust.** 3 games at 66% is noise; 300 at 55% is signal. A great score is nice — with volume. When the opponent has only 2-4 games in a variation, the "opponent-specific" score is basically the general-DB score anyway.
10
+ - **Score doesn't automatically indicate a level gap.** A 60% variation isn't necessarily "stronger players crushing weaker ones." Look at the per-move `avgWhite` / `avgBlack` fields (returned on every move statistic) before drawing conclusions about who is playing whom.
11
+ - **Don't recommend the higher-percentage move just because it's higher.** If 1.b3 scores 60% and 1.d4 scores 50% against a given opponent, that is *not* on its own a case for playing 1.b3 — style, prep depth, transposition risk, and the practical questions below all matter more.
12
+
13
+ ## Prep is symmetric — both sides know the same things
14
+
15
+ The single biggest LLM error in prep is treating it like writing a book: "here's what you should play against this opponent's weakness." A real chess opponent:
16
+
17
+ - Knows the same weakness the LLM found (or worse, has already patched it since the game where it showed).
18
+ - Knows the user's own repertoire the same way the LLM does — by looking at the user's games.
19
+ - Will prepare against the user just as hard as the LLM is preparing against them.
20
+
21
+ Every recommendation should be filtered through: "does this survive the fact that the opponent could easily anticipate it?"
22
+
23
+ ## Recency > career
24
+
25
+ The opponent's last 12-24 months matter far more than a 10-year career average. Repertoires evolve — a lifelong Najdorf player might have quietly become a Petroff player last year, and their old career stats will lie to you if you skim.
26
+
27
+ **Related product note (2026-07-23):** the `get_player_preparation` endpoint (compact / LLM view) deliberately strips the per-move `hotness` field. At the individual-player level, "hotness" is trailing noise — the opponent's opening trend is already captured in game dates. Hotness stays on the general-DB endpoint, where it's genuinely useful: it means fashion (what the whole top field is playing this month).
28
+
29
+ ## Prep is a tree, not a line
30
+
31
+ The opponent will deviate somewhere in the first 10 moves — earlier if you surprise them. A single 20-move line with no answer at move 4 is much worse than a shallow tree with concrete plans at each real branching point.
32
+
33
+ - Cover the 2 most likely replies at each branch, not just the modal one.
34
+ - Depth is a resource — spend it where it matters (main lines, opponent's actual repertoire).
35
+ - **Caveat:** some opponents just play their repertoire no matter what. Read the opponent's rigidity from their game history — look at move-2 variance across their last 30 games. Narrow variance = fixed repertoire; wide variance = they adapt.
36
+
37
+ ## Style clash matters (but don't overdo it)
38
+
39
+ Steer toward positions where the *user* is better than the opponent. That gap is usually bigger than any objective evaluation edge you can find.
40
+
41
+ - Positional user vs sharp tactician: keep it quiet, avoid the opponent's calculation edge.
42
+ - Sharp user vs positional opponent: fight for complications.
43
+ - High draw rate opponent: unbalance early (KID, Benoni, gambit).
44
+ - Endgame-strong opponent: keep queens on, keep complications live.
45
+
46
+ Don't ride this signal so hard that you recommend an objectively bad line just because "it fits the user's style." Style is a weight, not an override.
47
+
48
+ ## Novelty burns
49
+
50
+ Deep prep is a weapon you spend, not one you own. A prepared novelty against a strong opponent gets analyzed after the game and never fools them again — even against different opponents in that circle, since strong players share prep. Save deepest prep for the games that matter most (championship match, decisive round, tiebreak), not "just in case."
51
+
52
+ ## Move-order tricks aren't visible in raw win rates
53
+
54
+ The `trs` (transpositions) field on move statistics tells you how much a given position folds into related structures. Move-order tricks are legitimate prep and position stats alone can't recommend them.
55
+
56
+ - Considering 1.Nf3 or 1.c4 as a duck around 1.d4? First check the opponent's repertoire *against 1.d4*. If they play the King's Indian, most 1.Nf3/1.c4 lines transpose anyway — the trick doesn't help.
57
+ - Move orders are useful when the opponent plays something that specifically depends on the move order — e.g. a Nimzo player who never gets to play the Nimzo because you go 1.Nf3-2.g3.
58
+
59
+ ## Revealed weaknesses need context
60
+
61
+ If the opponent lost 3 rapid games in a specific Bg5 Najdorf line last month, that's actionable — but not for all opponents equally:
62
+
63
+ - **Weak / casual opponent:** they won't have patched it. Safe to exploit.
64
+ - **Strong / improving opponent:** they will have patched *that specific line* — but the underlying *type* of weakness (e.g. bad in Catalan pawn structures) still holds, even when the surface leak is gone. Look for structural weaknesses that survive one bugfix.
65
+ - **Symmetric information reminder:** they know you know. Assume the strong opponent has already prepared the counter. Plan for that layer too.
66
+
67
+ ## Surprise is a scalpel, not a hammer
68
+
69
+ Meta-signal matters. Big changes in the user's repertoire are transparent to strong opponents — they'll see "opponent has played 1.e4 for 5 years, is playing 1.d4 this game" and immediately know you prepared something specific.
70
+
71
+ - **Wrong:** tell a 100% 1.e4 player to switch to 1.d4 as a "surprise." That signals prep, doesn't hide the intent, and puts the user in an opening they don't know as well as the opponent does.
72
+ - **Right:** stay inside the user's normal repertoire, pick a rare secondary line. Classic example: a 1.e4 player who always plays 6.Bg5 vs the Najdorf switching to 6.Bc4 for one game. The opponent recognizes the opening; they don't have prep on this specific line; the user's meta-signature ("I play 1.e4 vs Najdorf") stays intact.
73
+ - Same logic for candidate move choice: the opponent's expectation of *what the user plays* is itself a variable to be manipulated.
74
+
75
+ ## How to combine these
76
+
77
+ None of these are rules; they're weights. In one game against a specific opponent one factor dominates (they clearly hate Catalan structures); in another it's a different one (they're rigid and just play their repertoire, so tree-depth matters more than surprise). Reason through them explicitly when recommending, and cite the concrete numbers (game counts, dates, win rates) so the user can trust or overrule.
78
+
79
+ Above all: **assume the opponent is doing the same reasoning**, and don't be lulled by a big number.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chessceo/mcp",
3
- "version": "0.6.0",
3
+ "version": "0.10.0",
4
4
  "description": "Model Context Protocol server for chess.ceo — 11.7M+ games, ~1.5M FIDE player profiles, opening preparation, live broadcasts.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -9,6 +9,7 @@
9
9
  "main": "dist/index.js",
10
10
  "files": [
11
11
  "dist",
12
+ "docs",
12
13
  "README.md"
13
14
  ],
14
15
  "scripts": {