@chessceo/mcp 0.2.0 → 0.3.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.
Files changed (3) hide show
  1. package/README.md +12 -0
  2. package/dist/index.js +127 -3
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -71,6 +71,18 @@ Ask your model:
71
71
  - *"Are there any live tournaments right now with Hikaru Nakamura?"*
72
72
  - *"From the position after 1.e4 c5 2.Nf3 d6 3.d4 cxd4 4.Nxd4 Nf6 5.Nc3 a6, what's the top continuation across the whole database?"*
73
73
 
74
+ ## Prep workflow (built-in prompts)
75
+
76
+ For MCP hosts that show prompts in a slash-menu (Claude Desktop, Cursor, Claude Code), three pre-baked prompts are included so users get a proper preparation workflow without prompt-engineering their own:
77
+
78
+ | Prompt | Purpose |
79
+ |---|---|
80
+ | `prepare_for_game(me, opponent, my_color?, time_control?)` | Full pre-match workflow: resolves both players, weights games by recency + format (classical OTB > rapid/blitz > online), walks the opponent's repertoire looking for lines where they score under 40%, checks head-to-head, and delivers a concrete plan with the moves to steer toward the opponent's weak points. |
81
+ | `scout_player(player)` | Deep scouting report on one player — style, top openings, recent form, biggest wins and losses, recurring weaknesses. |
82
+ | `head_to_head_briefing(player_a, player_b)` | One-paragraph read on the history between two players — who has the edge, dominant openings, style clash, current form. |
83
+
84
+ Pick one from the host's slash-menu, fill in the arguments, and the model does the rest.
85
+
74
86
  ## Remote MCP (chess.ceo-hosted)
75
87
 
76
88
  You can also connect to chess.ceo's hosted instance and skip installing anything:
package/dist/index.js CHANGED
@@ -10,7 +10,7 @@ import { createServer as createHttpServer } from "node:http";
10
10
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
11
11
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
12
12
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
13
- import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
13
+ import { CallToolRequestSchema, GetPromptRequestSchema, ListPromptsRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
14
14
  const BASE = process.env.CHESSCEO_BASE_URL ?? "https://chess.ceo";
15
15
  const UA = `chessceo-mcp/${process.env.npm_package_version ?? "0.1.0"} (+https://chess.ceo)`;
16
16
  // ── HTTP ────────────────────────────────────────────────────────────
@@ -71,7 +71,7 @@ const TOOLS = [
71
71
  },
72
72
  {
73
73
  name: "get_player_preparation",
74
- 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.",
74
+ 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).",
75
75
  inputSchema: {
76
76
  type: "object",
77
77
  properties: {
@@ -208,9 +208,133 @@ async function callTool(name, args) {
208
208
  throw new Error(`Unknown tool: ${name}`);
209
209
  }
210
210
  }
211
+ // ── Prompt templates ───────────────────────────────────────────────
212
+ //
213
+ // MCP prompts are pre-baked instructions the host surfaces in a slash-menu
214
+ // (Claude Desktop, Cursor, etc.). Users pick one and the templated content
215
+ // gets injected into the conversation. Perfect for workflows the LLM
216
+ // wouldn't reliably discover from tool descriptions alone — chess prep
217
+ // especially, where "call the right tools in the right order weighted by
218
+ // recency and format" is non-obvious.
219
+ const PROMPTS = [
220
+ {
221
+ name: "prepare_for_game",
222
+ description: "Prep workflow for an upcoming chess game. Walks both players' repertoires and identifies where the opponent is weakest.",
223
+ arguments: [
224
+ { name: "me", description: "Your name (or FIDE ID)", required: true },
225
+ { name: "opponent", description: "Opponent's name (or FIDE ID)", required: true },
226
+ { name: "my_color", description: "The color you'll be playing: 'white' or 'black'. Optional — if you don't know yet, ask.", required: false },
227
+ { name: "time_control", description: "Optional: 'classical', 'rapid', 'blitz'. Weights which of the opponent's games matter most.", required: false },
228
+ ],
229
+ },
230
+ {
231
+ name: "scout_player",
232
+ description: "Deep scouting report on one player. Their style, top openings, recent form, and where they've been beaten.",
233
+ arguments: [
234
+ { name: "player", description: "Player name or FIDE ID", required: true },
235
+ ],
236
+ },
237
+ {
238
+ name: "head_to_head_briefing",
239
+ description: "Briefing on the history between two players — who has the edge, what openings decide their meetings, style clash.",
240
+ arguments: [
241
+ { name: "player_a", description: "First player (name or FIDE ID)", required: true },
242
+ { name: "player_b", description: "Second player (name or FIDE ID)", required: true },
243
+ ],
244
+ },
245
+ ];
246
+ // The workflow text for prepare_for_game. Kept in one place so both the
247
+ // MCP prompt handler and the /prepare fallback can share it.
248
+ const PREP_WORKFLOW = (args) => {
249
+ const me = args.me ?? "the user";
250
+ const opp = args.opponent ?? "the opponent";
251
+ const color = args.my_color ? ` as ${args.my_color}` : "";
252
+ const tc = args.time_control ? ` in a ${args.time_control} game` : "";
253
+ return `You are preparing ${me} for a chess game against ${opp}${color}${tc}.
254
+
255
+ Preparation workflow — follow the steps in order and be explicit about which tools you called at each step:
256
+
257
+ 1. **Resolve both players.** Call \`search_player\` for "${me}" and "${opp}" to get their FIDE IDs. Confirm the identity — many players share names.
258
+
259
+ 2. **Understand the opponent.** Call \`get_player_profile\` for the opponent. Read out:
260
+ - Current classical / rapid / blitz ratings.
261
+ - Top openings as White and Black (from openingRepertoire).
262
+ - Career win / draw / loss splits — is the draw rate above ~40%? That's a stylistic hint (drawish opponents need to be unbalanced).
263
+ - Notable wins and worst losses — patterns?
264
+
265
+ 3. **Weight games by quality when interpreting the data.**
266
+ - Recent games (last 12-24 months) matter far more than old ones. Opening repertoires evolve.
267
+ - Classical over-the-board games are the strongest signal — that's what real preparation reveals.
268
+ - Rapid and blitz reveal what they play under time pressure but may include experiments.
269
+ - Online games are useful but noisier (blitz gambits, alt accounts).
270
+
271
+ 4. **Walk the opponent's repertoire against ${me}'s color.** Call \`get_player_preparation\` on the opponent for the color they'll have in this game. Iterate: start from move 1, pick the opponent's most-played reply, call again with \`line\` extended. Look for:
272
+ - **Weak lines**: variations where the opponent scores below 40% as their side.
273
+ - **Shallow lines**: openings the opponent has played only a few times — probably less deeply prepared.
274
+ - **Abandoned lines**: openings they used to play but stopped. Something went wrong; may not want to revisit.
275
+ - **Variety**: places where the opponent picks different moves game to game — those are branching points where they can't predict your prep.
276
+
277
+ 5. **Style considerations.**
278
+ - High draw rate → propose openings that unbalance early (Benoni, King's Indian, gambit lines).
279
+ - Sharp tactician → don't play their prepared attacks; steer toward quiet positional lines.
280
+ - Endgame strong → keep queens on and keep complications.
281
+
282
+ 6. **Cross-check head-to-head.** Call \`get_head_to_head\` on the two players. If they've met before, what openings decided those games? Anything the opponent showed only against ${me}?
283
+
284
+ 7. **Deliver a concrete plan.** Summarize:
285
+ - What the opponent will likely play on move 1 (with confidence level).
286
+ - The 2-3 branching points where the opponent is weakest for ${me}'s color.
287
+ - The concrete move sequence ${me} should aim for to steer into those positions.
288
+ - What to avoid — the opponent's strongest weapons.
289
+
290
+ Don't just dump data. Reason about it. Cite specific numbers (game counts, win rates, dates) so the user can trust your conclusions.`;
291
+ };
211
292
  // ── Server wiring ──────────────────────────────────────────────────
212
- const server = new Server({ name: "chessceo-mcp", version: process.env.npm_package_version ?? "0.1.0" }, { capabilities: { tools: {} } });
293
+ const server = new Server({ name: "chessceo-mcp", version: process.env.npm_package_version ?? "0.1.0" }, { capabilities: { tools: {}, prompts: {} } });
213
294
  server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
295
+ server.setRequestHandler(ListPromptsRequestSchema, async () => ({ prompts: PROMPTS }));
296
+ server.setRequestHandler(GetPromptRequestSchema, async (req) => {
297
+ const { name, arguments: args } = req.params;
298
+ const promptArgs = {};
299
+ if (args)
300
+ for (const [k, v] of Object.entries(args))
301
+ promptArgs[k] = String(v);
302
+ let text;
303
+ switch (name) {
304
+ case "prepare_for_game":
305
+ text = PREP_WORKFLOW(promptArgs);
306
+ break;
307
+ case "scout_player": {
308
+ const p = promptArgs.player ?? "the player";
309
+ text = `Produce a scouting report on ${p}. Steps:
310
+ 1. \`search_player\` to get their FIDE ID.
311
+ 2. \`get_player_profile\` — pull rating history, career splits by color and time control, opening repertoire, opponent analysis, top events, notable wins and losses.
312
+ 3. Weight the data: recent (last 12-24 months) > older, classical OTB > rapid/blitz > online.
313
+ 4. \`get_player_preparation\` for both colors from the starting position to summarise their opening choices with actual frequencies and win rates.
314
+ 5. Deliver: current strength, characteristic openings, one-sentence style read, biggest wins, biggest losses / recurring weakness. Cite the numbers.`;
315
+ break;
316
+ }
317
+ case "head_to_head_briefing": {
318
+ const a = promptArgs.player_a ?? "player A";
319
+ const b = promptArgs.player_b ?? "player B";
320
+ text = `Briefing on the ${a} vs ${b} history. Steps:
321
+ 1. Resolve both FIDE IDs with \`search_player\`.
322
+ 2. \`get_head_to_head\` for the pair — pull overall + per-color W/D/L (from ${a}'s perspective), splits by time format, first / last meeting, most-played openings between them, average game length.
323
+ 3. Read the pattern: who has the edge, in which colour, in which time format. Which openings decide the meetings? Anything unusual — very drawish, very sharp, big rating gap?
324
+ 4. If either player is currently live in a tournament, note it with \`list_player_live_tournaments\`.
325
+ 5. Deliver a one-paragraph read: score, dominant openings, one-line style clash, current form.`;
326
+ break;
327
+ }
328
+ default:
329
+ throw new Error(`Unknown prompt: ${name}`);
330
+ }
331
+ return {
332
+ description: `chessceo prompt: ${name}`,
333
+ messages: [
334
+ { role: "user", content: { type: "text", text } },
335
+ ],
336
+ };
337
+ });
214
338
  server.setRequestHandler(CallToolRequestSchema, async (req) => {
215
339
  const { name, arguments: args } = req.params;
216
340
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chessceo/mcp",
3
- "version": "0.2.0",
3
+ "version": "0.3.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": {