Skip to main content
Glama
croissant-mcp

croissant-mcp

croissant-mcp

An MCP server that gives Claude programmatic access to a local En Croissant installation, so opening prep can be driven from a conversation instead of by hand in the GUI.

Quickstart

Requires Python 3.13+, uv, and a local En Croissant install (developed against 0.15.0).

git clone https://github.com/cheungbrenden/croissant-mcp
cd croissant-mcp
uv sync

Register the server once, user-level, so it's available in any session:

claude mcp add --scope user croissant-mcp -- uv run --directory /path/to/croissant-mcp croissant-mcp

Then ask for prep in a conversation:

What am I due to review today, and what do opponents actually play there?

Nothing else to configure — the Lichess handle is derived from the <handle>_lichess.db3 filename and paths from the standard macOS locations. USAGE.md has the full tool list, with honest status marks for what is built and what isn't.

Related MCP server: Chess MCP

Why this exists

En Croissant is a Tauri desktop app. Its 64 backend commands are registered via tauri_specta::collect_commands!() and reachable only over Tauri's IPC bridge — there is no CLI, HTTP API, or headless mode. (tauri_plugin_cli is registered, but its config declares a single positional file arg: that's file-association handling, not a scripting interface.)

It doesn't need one. Everything the app persists is in open formats:

~/Library/Application Support/org.encroissant.app/
  db/<name>.db3      SQLite — Games, Players, Events, Sites, Info
  db/<name>.pgn      the PGN the database was imported from
  db/<name>.ecsi     proprietary position index (magic bytes "ECSI", v4)
  engines/stockfish/ the UCI engine binary
~/Documents/EnCroissant/*.pgn   repertoire files
~/Library/WebKit/org.encroissant.app/.../LocalStorage/localstorage.sqlite3
                   FSRS training state, keyed deck-<repertoire path>-<n>,
                   values UTF-16. Not in the PGN.

So this project reads En Croissant's files, never its code. It is not a fork and has no upstream relationship.

Start with USAGE.md — what you can ask for and what's built yet. Design records (build order, test seams, domain vocabulary) live in docs/.

Developed against En Croissant 0.15.0. The app registers tauri_plugin_updater and updates itself, and every format above is internal and undocumented. Startup asserts the shapes we depend on so a format change fails loudly instead of being misread.

The sidecar is not a live export. It's the file the database was imported from, so the two can drift: the Lichess pair matches exactly (190/190), the chess.com pair does not (3525 PGN events against a GameCount of 3521). Treat equivalence as an invariant to check, never a property to rely on. Because of this — and because a game played ten minutes ago isn't in it at all — recent games come from the Lichess API and the sidecar is the bulk/offline fallback.

How repertoires and decks work

Read from En Croissant's own source rather than inferred by experiment.

Start header — [Start "[0,0,0]"] is a JSON array of child indices from the tree root, written as JSON.stringify(game.start) and read as JSON.parse(Start ?? "[]"). It marks where training begins, not where the file begins: buildFromTree skips any node whose path is a prefix of start. The Vienna file starts at [0,0,0], which is why its deck drills from move 3 (f4, e5, Nf3) and never asks for 1.e4 or 2.Nc3.

Deck key suffix — deck-${file}-${game}, where game is the index of the game within the PGN file. -0 is the first game; a multi-game repertoire gets one deck per game.

Cards are built eagerly, and adding lines is safe. On open, if the deck is empty En Croissant builds cards for the whole tree. If the deck is non-empty it calls syncDeck, which reconciles against the tree, reports added/removed counts, and preserves existing cards and the review log. So writing new lines into a repertoire and reopening it schedules the new positions without disturbing existing scheduling. Logs are only wiped by the explicit Reset button.

A position's drilled answer is always its first child. buildFromTree uses item.node.children[0].san and ignores the rest. Whatever move we write first at a node is the move you'll be drilled on — variation order is semantic, not cosmetic.

Cards are only created for positions where it's your turn (halfmove parity against Orientation), leaf nodes are skipped, and positions are deduplicated by full FEN with the first occurrence winning — so transpositions collapse to one card.

Scope

Lichess only; chess.com is historical and opt-in. Blitz only by default, since that's the bulk of most players' game history.

Your own win rate is not a ranking signal anywhere. A typical two-year window holds only a few dozen blitz games per colour, which makes any per-position percentage noise. Ranking is on coverage instead: deterministic, and it needs no sample size.

What it deliberately does not do

Two things on disk are closed formats:

  • The move BLOB. length(Moves) equals PlyCount exactly — one byte per ply, an index into the generated legal moves for each position. Decoding it means reproducing En Croissant's move generator ordering byte-for-byte. Avoided entirely by reading the .pgn sidecar instead.

  • The .ecsi index. Powers fast position search over large databases. No substitute here; brute-force PGN scanning covers the personal databases (130 KB and 7.6 MB) and the Lichess explorer API covers reference statistics.

Indexed search over a multi-gigabyte master database is the one capability this project cannot reach. If that ever becomes a real need, it's a separate project (extract the Rust search core from En Croissant as a [[bin]] target) — not a requirement here.

What it does to your data

  • Read-only against .db3. The PawnHome column and the .ecsi index are derived; hand-written rows would desync them. To add games, write PGN and import through the GUI.

  • Repertoire files under ~/Documents/EnCroissant/ are the one place this project writes at all.

Development

uv sync
uv run pytest

Available Tools

8 tools
add_line_to_repertoireA

Merge a line of SAN moves (from the starting position) into a repertoire.

Joins branches that already exist instead of duplicating them, and never reorders existing variations — the first variation at a position is the answer the user gets drilled on, so reordering would change what they're tested on. New positions are scheduled automatically when the file is next opened; existing cards and review history are untouched.

En Croissant must be closed: it holds repertoire files open and would silently overwrite this change. The previous contents are backed up first.

Inputs: name (required), moves (required). Returns: name, path, main_line, move_count, +1 more

get_training_statusA

Read the FSRS training state for a repertoire: what's due, what's never been seen, and what the file would schedule that isn't in the deck yet.

Read-only — never modifies training state. Safe while En Croissant runs, though a session in progress there may not have flushed to disk yet.

Inputs: name (required). Returns: name, unseen, positions, practiced, +2 more

list_databasesA

List the En Croissant game databases installed on this machine.

Reports each database's title, game count, size, and whether it has a .pgn sidecar. Read-only, and safe to call while En Croissant is running.

Inputs: no named parameters. Returns: result

list_repertoiresA

List the En Croissant repertoire files and which colour each trains.

Inputs: no named parameters. Returns: result

my_results_from_positionA

The user's W/D/L record from a position, across their rated blitz games.

Position matching ignores move counters and non-capturable en-passant squares, so transpositions count. Sample sizes here are small — report them, and never rank or conclude from the percentage alone.

Inputs: fen (required). Returns: wins, draws, games, losses, +1 more

opponents_playA

What gets played from a position: by players at the user's own rating and speed (source='lichess', the default) or by masters (source='masters').

Defaults come from config so the pool matches the user's actual games — blitz around their rating band, not the site-wide average. Cached locally; a repeated query makes no network call.

Inputs: fen (required), source. Returns: moves, source, speeds, ratings, +1 more

read_repertoireA

Read one repertoire's tree: its main line, orientation, and size.

At every position, the FIRST variation listed is the move En Croissant drills; later variations are recorded alternatives.

Inputs: name (required). Returns: name, path, main_line, move_count, +1 more

search_gamesA

Search the user's own rated Lichess games (blitz by default).

Filters are from the USER's side: colour is the colour they played, result is their result ('win', 'loss', 'draw'). Dates are 'YYYY.MM.DD'. Fetches live from the Lichess API, falling back to the local export.

Inputs: eco, since, until, colour, +2 more. Returns: games, source, sample_size, failed_to_parse

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A4.3/5.0

    Scored across 8 tools

    Disambiguation5/5

    Each tool targets a distinct resource or action: databases vs. repertoires vs. training state vs. game queries. Even the three position-related tools differ clearly: one searches games, one reports user results, one reports opponent moves.

    Naming Consistency4/5

    Most tools follow a verb_noun pattern (list_databases, read_repertoire, add_line_to_repertoire, get_training_status, search_games). Two tools break the pattern with possessive noun phrases (my_results_from_position, opponents_play), which is a minor inconsistency.

    Tool Count5/5

    Eight tools is well within the ideal 3–15 range for a focused chess training server. Each tool addresses a distinct part of the workflow without unnecessary bloat.

    Completeness4/5

    The set covers listing, reading, and adding repertoire lines, plus training status and position analysis—solid coverage for the core domain. Missing explicit update/delete operations for repertoire lines are workable gaps but not fatal for typical use.

    Maintenance

    ActivityInactive
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      C
      quality
      B
      maintenance
      An MCP server that enables natural language interaction with the Lichess chess platform, allowing users to play games, analyze positions, manage their account, and participate in tournaments through Claude.
      90
      23 npm
      20
      MIT
    • A
      license
      Not graded
      quality
      D
      maintenance
      A powerful chess engine and game server built with the Model Context Protocol (MCP). Play chess against AI, analyze positions, and integrate chess functionality into your AI applications.
      48 npm
      1
      ISC
    • A
      license
      A
      quality
      D
      maintenance
      MCP server that exposes the Lichess public API through tools for querying player profiles, games, analysis, openings, puzzles, and tournaments, allowing natural-language chess questions without an API key.
      8
      MIT