MCP server providing tools to manage and inspect a chess game (no move suggestions).
This server provides 9 chess management tools with clear, descriptive docstrings and well-structured game state handling. Tool names follow verb_noun patterns (create_or_reset_game, add_move, get_status, is_legal, list_moves, etc.), making intent clear. Descriptions are detailed and explain what each tool does, when to use it, and what it returns, well above the 10-1024 character baseline and averaging ~250-400 chars per tool. However, there are significant gaps: (1) Input parameters lack explicit JSON Schema type declarations and enums in several tools. For example, add_move accepts 'uci' with a basic string description but no format constraint, pattern, or example of valid UCI notation beyond prose. (2) Output schemas are documented in prose within docstrings but not formally declared in the MCP tool schema, the server relies on free-form descriptions rather than structured JSON Schema for return types, forcing LLMs to parse unstructured text. (3) No explicit error handling guidance, tools like add_move return {'accepted': false, 'reason': 'illegal'} but don't guide the LLM on recovery steps or what to do next. (4) Parameters like 'n' in last_moves lack numeric bounds (min/max), the description says 'n >= 1' but this is not enforced via JSON Schema constraints. (5) No tool annotations (readOnlyHint, destructiveHint, idempotentHint) despite clear read/write semantics. The codebase shows correct FastMCP tool registration via @server.tool() decorators with docstrings, but the schema definitions are incomplete compared to production baselines.
Apply a move in UCI format if legal. Parameters: - uci: string like "e2e4", "g1f3", promotions like "e7e8q". Returns (in result): - On success: { accepted:true, status: Status, moves:[...], moves_detailed:[...] } where Status is the same shape returned by get_status(), including last_move_{uci,san}. - On failure: { accepted:false, reason:"illegal", expected_turn:"white"|"black", status: Status } Notes: - This tool validates legality only; it does not suggest or score moves.
Return an ASCII representation of the board from White's perspective. Notes: - This is a human-oriented view, suitable for displaying the board to users in UIs, logs, or chat. - For model reasoning, prefer the JSON map in get_status().pieces.
Create a new game or reset the current one to the initial position. Returns (in result): - ok: boolean - status: object with the current position metadata: - fen: string (Forsyth–Edwards Notation) - side_to_move: "white" | "black" - fullmove_number: int - halfmove_clock: int - ply_count: int (number of half-moves made) - castling_rights: string like "KQkq" or "-" - en_passant_square: algebraic square like "e3" or "-" - last_move_uci: string | null - last_move_san: string | null - who_moved_last: "white" | "black" | null - is_check: boolean - is_game_over: boolean - result: string like "1-0", "0-1", "1/2-1/2" or null - pieces: object mapping squares to piece symbols, e.g. {"e4":"P", "e5":"p"} - moves: array of UCI strings for all moves played so far (empty after reset) - moves_detailed: array of { ply:int, side:"white"|"black", uci:string, san:string } Notes: - This tool does not suggest moves; it only manages state.
Input parameters lack formal JSON Schema type constraints and enums. The 'uci' parameter in add_move is described as a string but has no pattern constraint, format, or enum to guide LLM input validation. Similarly, 'n' in last_moves has no numeric min/max bounds in the schema, only in prose description.
Output schemas are documented only in prose docstrings, not in formal JSON Schema. LLMs must parse unstructured text to understand the shape of returned status objects, move arrays, and nested fields. Production tools should declare return types with explicit field types and descriptions.
No error handling guidance for LLM recovery. Tools like add_move return {'accepted': false, 'reason': 'illegal'} but do not guide the agent on next steps (e.g., 'Call list_moves_detailed to review the game state and try a different move'). Error responses should be actionable per pattern:recovery-guide.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-22 | D | 52 | 2026-07-28+ | v2 |
| 2026-03-09 | D | 57 | - | v1 |
Get current position metadata for model-friendly planning. Returns (in result): - fen, side_to_move, fullmove_number, halfmove_clock, ply_count - castling_rights, en_passant_square - last_move_uci, last_move_san, who_moved_last - is_check, is_game_over, result - pieces: square-to-piece map (e.g., {"a2":"P", "e1":"K"})
Check if a UCI move is legal in the current position. Returns (in result): - { legal:boolean }. - If the UCI string cannot be parsed, { parse_error:string, legal:false }.
Return the last N moves in UCI (default 1). Parameters: - n: integer >= 1. If n <= 0, returns an empty list.
Return the last N moves with details (default 1). Each item: { ply:int, side:"white"|"black", uci:string, san:string }. Parameters: - n: integer >= 1. If n <= 0, returns an empty list.
Return all moves played so far in UCI, ordered from the start of the game.
Return detailed move history. Each item: { ply:int, side:"white"|"black", uci:string, san:string }
No tool annotations (readOnlyHint, destructiveHint, idempotentHint) despite clear semantics. create_or_reset_game is destructive (clears the game); add_move is destructive; get_status, is_legal, list_*, and board_ascii are read-only. Annotations enable LLMs to reason about side effects and plan safely.
Parameter 'n' in last_moves and last_moves_detailed lacks formal numeric bounds. Description states 'integer >= 1' but no JSON Schema minValue/maxValue constraint. This invites the LLM to pass invalid values (n=0, n=-1, n=999999) which the tool silently handles rather than rejecting.