mtg-search
Semantic search over every Magic: The Gathering card, as an MCP server for deck-building agents.
An LLM building a Commander deck from memory gets the rules wrong (off-colour cards, cards that are not legal, cards already in the list) and keeps reaching for the same popular staples. mtg-search gives the agent the official rules text of all 34,646 cards, a search engine over it, and the filters to check its picks.
Write-up: Does a search engine help a deck-building agent?
Install
One line for Claude Code, with uv installed. It downloads the card index once (about 1.9 GB) and registers the server:
uvx mtg-search-mcp --download && claude mcp add --scope user mtg-search -- uvx mtg-search-mcp
Any other MCP client runs uvx mtg-search-mcp over stdio:
{
"mcpServers": {
"mtg-search": { "command": "uvx", "args": ["mtg-search-mcp"] }
}
}
The server uses about 2.7 GB of RAM and keeps the index in ~/.cache/mtg-search (set MTG_SEARCH_HOME to
move it). To update, run uvx mtg-search-mcp@latest --download; a new card snapshot that has not been
downloaded yet is fetched in the background while the previous one keeps serving.
With Docker instead (no Python needed; the image already contains the index):
docker pull ghcr.io/kasperg3/mtg-search && claude mcp add --scope user mtg-search -- docker run -i --rm ghcr.io/kasperg3/mtg-search
Then ask for cards the way a player would: "upgrade my Auntie Ool precon", "cheap ways to protect my commander in Selesnya", "what else works like Smothering Tithe?", or point it at your own list: "swap ten cards in https://moxfield.com/decks/…"
Tools
| tool | what it does |
|---|---|
search_cards |
1–4 rules-text queries, fused, with filters: color_identity, format, types, mana_value_min / mana_value_max. Only cards that pass the filters compete, so a filtered search never comes back empty because the best matches were cut. |
get_card |
One card by (fuzzy) name: full text, legality in every format, roles, official rulings and the Scryfall link. |
find_similar |
Cards whose text is closest to a given card's, with the same filters. |
get_deck |
Loads a deck to work on: a Moxfield or Archidekt link, an official precon by name or commander (lists from MTGJSON), or a pasted decklist. Returns every card with its roles and checks the deck against its format's rules (size, copies, commander and colour identity, bans, Game Changers). |
What the agent gets back, for search_cards(queries=["Deals 3 damage to any target."], color_identity="R", types=["instant"], mana_value_max=1, format="commander", limit=3):
search: "Deals 3 damage to any target." · filters: color_identity=R, format=commander, types=['instant'], mana_value_max=1 · 3 shown of 152 matching cards
1. Lightning Bolt — {R} (MV 1) · Red · Instant · uncommon
Lightning Bolt deals 3 damage to any target.
roles: burn-any
legal: commander ✓
2. Collateral Damage — {R} (MV 1) · Red · Instant · common
As an additional cost to cast this spell, sacrifice a creature.
Collateral Damage deals 3 damage to any target.
roles: bombard, sacrifice-outlet-creature, burn-any, lightning-bolt-redux
legal: commander ✓
3. Shard Volley — {R} (MV 1) · Red · Instant · common
...
Results are ordered by text relevance only. Prices, EDHREC rank and other popularity signals are not in the image at all, so the agent picks cards for what they do, not for how often they are played.
How well it works
All runs use Claude Sonnet 5.5 in Claude Code, and cards were graded blind by a separate Claude
model. Details, intervals and every caveat: write-up
and eval/experiments/.
Search vs grep, same agent, same minimal instructions (113 tasks; relevance over the 80 deck-need and card-attribute tasks):
| relevant cards in the top 10 | card-attribute tasks | tokens per task | |
|---|---|---|---|
| mtg-search | 98.3% | 100% | 10.6k |
| grep over the card text | 86.5% | 48.0% | 14.3k |
| grep, with a prompt tuned to the card file | 96.8% | 92.7% | 20.9k |
| reading every card (12-task sample) | 2.91M |
Real decks. Asked to add 20 cards to each of 14 Commander precons released after 1 November 2025, the agent's adds fit the deck better with mtg-search than from memory: deck-fit score 0.45 → 0.65 (0 to 1), better on 13 of 14 decks, p = 0.002.
The engine. On 46 rules-text queries, nDCG@10 is 70.7 for lightonai/mLateOn
(late interaction), 61.1 for BM25 and 36.7 for a dense embedding (EXP-001).
How it works
- Data: Scryfall's Oracle cards (one entry per card, current official wording), Tagger function
tags, the official rulings and MTGJSON's official precon decklists, refreshed weekly by
scripts/refresh.py. - Search: exact late interaction with mLateOn. Queries go through the model's fp32 ONNX encoder;
every card is scored by exact MaxSim against its precomputed token vectors, about 100–150 ms per query
on a 6-core desktop CPU. Nothing is approximated, so the shipped server ranks exactly like the
benchmarks' reference (
docker/README.md). - Server: Python, MCP over stdio, one Docker image.
Datasets
All on Hugging Face; cite a tagged revision (BibTeX).
| dataset | contents |
|---|---|
kasper-cbx/mtg-search-index |
the card tables, tags, rulings, precon decklists and mLateOn embeddings the server searches |
kasper-cbx/mtg-search-bench |
363 search queries, 113 agent tasks, every relevance grade and the final labels |
kasper-cbx/mtg-precon-bench |
Commander precon decklists, EDHREC add/cut rates and deck-fit grades |
kasper-cbx/mtg-search-experiments |
raw run outputs: episodes and transcripts |
Repository
src/mtg_search/ the MCP server, ingest, filters and the mLateOn search
docker/ the image (Dockerfile, build script, smoke test)
eval/ search benchmark, agent tasks and harness, experiment write-ups (eval/experiments/)
bench/precon/ the Commander precon benchmark: data build, agent harness, scorer, deck-fit judge
scripts/ weekly index refresh, dataset publishing, result sync
jobs/ the GPU encoding job for the card embeddings
docs/ dataset cards, findings page, plan and design notes
tests/ unit tests and the Docker integration test
Development
uv sync
uv run pytest # unit tests
docker/build.sh # build the image from the HF dataset
uv run pytest -m docker # run the built image over stdio
python docker/mcp_smoke.py # sample calls with output and latency
Run the server from a checkout with uv run mtg-search-mcp; on first run it downloads the index into
~/.cache/mtg-search, like the published package.
Releases
Every version bump on main is published automatically
(publish.yml): tests,
then PyPI, a vX.Y.Z GitHub release and the matching Docker
image. Each release pins one card snapshot; when the index refresh tags a new one,
scripts/bump_index.py points the
package at it and bumps the patch version, which releases it.
Data and terms
Card names and rules text are © Wizards of the Coast, used under the Wizards of the Coast Fan Content Policy. This is unofficial Fan Content, not approved or endorsed by Wizards. Card data comes from Scryfall, function tags from Scryfall Tagger, precon decklists from MTGJSON and precon add/cut rates from EDHREC. This project is not affiliated with any of them.
License
The code is MIT licensed (LICENSE). The card data and the benchmarks keep their sources' terms, described above.
Metadata
Release files for mtg-search-mcp 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| mtg_search_mcp-0.2.0.tar.gz | 55.3 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| mtg_search_mcp-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 118.1 kB
Release files / mtg_search_mcp-0.2.0.tar.gz
| Download URL | mtg_search_mcp-0.2.0.tar.gz |
|---|---|
| Size | 55.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e1a09ceadf02a5e408d3e06c8d817422e46e89a1e5bdf31b35d1826edaf55444
|
|
BLAKE2b-256 checksum How to use checksums |
f4c29ff748c387f38920887d6a05bfe01055b4c00a9fc79ed1393d5ba2120eaf
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.
Transparency logRelease files / mtg_search_mcp-0.2.0-py3-none-any.whl
| Download URL | mtg_search_mcp-0.2.0-py3-none-any.whl |
|---|---|
| Size | 62.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
9a141c37b04caff902984a60e7d94e78bbcea5901dee8a0dd64bfe4dc197198a
|
|
BLAKE2b-256 checksum How to use checksums |
71d47cd46082e386a36fb7a6fa77241e15f8af07f1bfbffb07117cf3ce324e3b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.
Transparency logSecurity for mtg-search-mcp 0.2.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for mtg_search_mcp-0.2.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.