Skip to main content
Glama

Disney Lorcana MCP Server

MCP Badge

An MCP server that lets Claude (or any MCP client) search, analyze, and build decks for the Disney Lorcana TCG. Ask for cards in plain English, get rules-checked deck validation, ink curves and keyword breakdowns, and round-trip deck lists with Dreamborn/Pixelborn.

2,506 unique cards (3,192 printings including alternate arts and promos), refreshed daily. No API key, no database, no rate limits — the server ships against a snapshot published from our own pipeline, so it never depends on a third-party API being up at query time.

Tools

Tool

What it does

search_cards

Filter and retrieve cards by name, color, cost, rarity, type, keyword, stats, set, and body text. Supports response_format="toon" for ~10% fewer tokens

count_cards

Count cards matching a filter without paying for the card objects

aggregate_cards

Group counts by cost (ink curve), rarity, color, set_code, or type

resolve_card

Fuzzy-match an informal, partial, or misspelled card name to the closest cards

top_traits

Most common traits (Storyborn, Hero, Villain, Ally, ...) across all cards

validate_deck

Check a deck against format rules (≥60 cards, max 4 copies, ≤2 inks, dual-ink requirements); returns {legal, total_cards, inks, violations}

deck_stats

Ink curve, color split, inkable count, type breakdown, keyword counts, subtype counts, and per-card keyword tags

import_deck

Parse a Dreamborn/Pixelborn deck list into resolved cards, with fuzzy candidates for unresolved lines

export_deck

Render a deck back out as a Dreamborn/Pixelborn-compatible text list

server_status

Startup metadata (card count, configuration)

Related MCP server: lorcana-oracle

Install

uvx lorcana-cards-mcp

Claude Desktop / claude_desktop_config.json:

{
  "mcpServers": {
    "lorcana": {
      "command": "uvx",
      "args": ["lorcana-cards-mcp"]
    }
  }
}

Claude CLI:

claude mcp add --scope user -- lorcana uvx lorcana-cards-mcp

pip / pipx

pipx install lorcana-cards-mcp   # then run: lorcana-cards-mcp

Docker

The server is also published to GHCR and the MCP Registry.

docker run --rm -i ghcr.io/danielenricocahall/lorcana-mcp:latest
{
  "mcpServers": {
    "lorcana": {
      "command": "docker",
      "args": ["run", "--rm", "-i", "ghcr.io/danielenricocahall/lorcana-mcp:latest"]
    }
  }
}

To persist the card cache across container restarts, mount a volume:

docker run --rm -i \
  -e LORCANA_CACHE_PATH=/data/cards.json \
  -e LORCANA_SKIP_IF_DB_EXISTS=true \
  -v lorcana_mcp_data:/data \
  ghcr.io/danielenricocahall/lorcana-mcp:latest

From a clone

uv run python main.py
docker build -t lorcana-mcp:latest .
docker run --rm -i lorcana-mcp:latest

# or via compose
docker compose build
docker compose run --rm -T lorcana-mcp

No port is exposed; MCP communication is over stdio.

Example questions

Once connected to an MCP client, you can ask natural language questions like:

Card lookup

  • "Show me all cards named Moana"

  • "What does the card Maui - Hero to All do?"

  • "Find all legendary amber cards"

Deck building

  • "What are the cheapest ruby characters with at least 3 strength?"

  • "Show me inkable sapphire cards that cost 4 or less"

  • "Find steel characters with 5 or more willpower"

  • "What 3-lore characters exist in emerald?"

Keyword & ability search

  • "How many Singer cards cost exactly 5?"

  • "How many Evasive characters are there in the first set?"

  • "How many ruby cards have Reckless?"

  • "Find all cards with Ward in their text"

  • "Show me Shift cards in amethyst"

Stats & aggregations

  • "How many cards are in each set?"

  • "What's the color distribution across all cards?"

  • "What are the most common traits?"

  • "Show me the ink curve — how many cards exist at each cost?"

  • "How many legendary cards are inkable?"

Cross-filter queries

  • "How many amber characters have 3 or more lore?"

  • "Find cheap (cost 2-3) characters with high strength (4+) in steel"

  • "How many cards in set 1 have Evasive and cost less than 4?"

Note: For plain keyword queries (Evasive, Bodyguard, Shift, etc.) use the keyword parameter — it filters against the structured ability list and is more reliable than substring search. For value-specific queries like Singer 5 or Resist +2, use body_text (keyword values live in the card's full text, not the ability list).

MCP prompts

  • build_deck(colors, playstyle="balanced") — guides the model through assembling a legal Lorcana deck (60-card minimum, ≤2 inks, max 4 copies of any card) for the requested color(s) and playstyle (aggressive / control / lore-race / balanced). Uses the search/aggregate tools above plus the rules embedded in the server instructions.

Card data & startup behavior

On startup, the server fetches a JSON list of cards from https://danielenricocahall.github.io/lorcana-mcp/allCards.json. The snapshot is refreshed daily by data_pipeline/fetch_cards.py, which pulls from the Lorcast API, normalizes each card into our internal schema, and publishes the list to the gh-pages branch. That middle layer insulates running servers from Lorcast's availability and rate limits — the runtime never calls Lorcast directly.

Cards are kept in memory as a Python list for fast filtering. The dataset holds 2,506 unique cards, each carrying a printings array for its alternate sets, numbers, and rarities (3,192 printings in total). Consolidating printings onto one row per card means a search for "Elsa" returns each distinct Elsa once rather than repeating her for every promo reprint. A local JSON file cache (LORCANA_CACHE_PATH, default cards.json) lets the server skip the network fetch on subsequent startups.

Config

  • LORCANA_API (default: https://danielenricocahall.github.io/lorcana-mcp/allCards.json)

  • LORCANA_CACHE_PATH (default: cards.json) — local file for caching fetched cards

  • LORCANA_HTTP_TIMEOUT_SECONDS (default: 60)

  • LORCANA_REFRESH_ON_STARTUP (default: false) — true always fetches and repopulates storage

  • LORCANA_SKIP_IF_DB_EXISTS (default: true) — false fetches and repopulates even if the cache is populated

TOON response format

search_cards accepts a response_format argument:

  • "json" (default) — list of card objects, unchanged from prior versions.

  • "toon" — a TOON string with one column header line and one row per card, encoded by the toons Rust-backed library (the official community reference implementation).

Example (search_cards(name="elsa", limit=2, response_format="toon")):

cards[2]:
  - id: crd_01c4835a62df4960bb973aeff81f2bb2
    name: Elsa
    version: Ice Maker
    full_name: Elsa - Ice Maker
    cost: 7
    ...
    printings[3]{set_code,set_name,number,rarity}:
      "7",Archazia's Island,69,Super Rare
      C2,Lorcana Challenge Year 3,2,Promo
      C2,Lorcana Challenge Year 3,6,Promo
  - id: crd_04bca46a8e2d4e9ba0fbdbfc6c99e51e
    name: Elsa
    ...

The outer cards[2]: falls back to YAML-style per-card blocks (rather than a single tabular table) because card shapes vary — Actions and Items don't carry strength/willpower/lore, for example. The inner printings[N]{...}: block is fully tabular since every printing has the same four fields.

Benchmark

Measured with benchmarks/bench_toon.py against the live 2,506-card dataset, tokenizing with tiktoken cl100k_base (used as a proxy for Claude's tokenizer):

query

rows

JSON tokens

TOON tokens

Δ

color="amber", limit=200

200

44,101

39,680

−10.0%

color="ruby", limit=50

50

10,522

9,519

−9.5%

card_type="action", limit=50 (sparse cols)

50

10,154

9,250

−8.9%

body_text="when", limit=50 (long full_text)

50

11,602

10,391

−10.4%

name="elsa", limit=20

14

3,488

2,940

−15.7%

total

79,867

71,780

−10.1%

Note: TOON's relative savings are smaller here than they were before the printings consolidation (pre-PR-#29 the same queries showed ~50% reductions). That gap is structural to the nested printings array — TOON's columnar encoding wins on the top-level fields but falls back to JSON-style encoding inside the per-printing entries, so the array dilutes the relative gain. Absolute token counts are still down meaningfully versus the equivalent count of pre-consolidation rows, since each unique card is now represented once with a small printings list rather than as 1-3 separate full rows.

Reproduce with PYTHONPATH=. uv run python benchmarks/bench_toon.py (requires a populated cards.json cache).

Disclaimer

This is a personal, unofficial fan and engineering project. It is not affiliated with, endorsed by, sponsored by, or reviewed by Disney, Ravensburger, or the Disney Lorcana TCG team. It is built and distributed in accordance with Ravensburger's Disney Lorcana TCG Community Code, using only publicly available and community data sources. All Disney Lorcana TCG names, card text, trademarks, and related intellectual property belong to Disney and Ravensburger. This project is non-commercial and reflects my personal views only, not those of my employer.


MCP Registry ownership verification — the registry reads this line from the published package description to confirm this project owns the server name.

mcp-name: io.github.danielenricocahall/lorcana-mcp

Available Tools

10 tools
aggregate_cardsA

Return card counts grouped by a field. Valid fields: cost (ink curve), rarity, color, set_code, type. Examples: aggregate_cards('cost') for the ink curve, aggregate_cards('color') for color breakdown, aggregate_cards('rarity') for rarity breakdown, aggregate_cards('set_code') for set distribution.

Inputs: field (required). Returns: result

count_cardsA

Count cards matching the given filters. Use this for questions like 'how many ruby cards are there?' or 'how many evasive characters?'. Supports stat ranges: min_attack/max_attack, min_defence/max_defence, min_cost/max_cost. Use keyword to filter by Lorcana keyword (Bodyguard, Challenger, Evasive, Reckless, Resist, Rush, Shift, Singer, Sing Together, Support, Vanish, Voiceless, Ward, etc.) — matches against the structured ability list. Use body_text for value-specific keyword queries like 'Singer 5' or 'Resist +2', or for non-keyword phrases like 'gain 2 lore'. Use lore/min_lore/max_lore to filter by lore value. Use card_type to filter by card type: Character, Action, Item, Song, or Location. Use set_code to filter by set number (e.g. '1' for The First Chapter). Use set_name to filter by set name as a case-insensitive substring (e.g. 'Wilds Unknown', 'frozen') — preferred when the user names a set in plain English. Color must be one of: ruby, sapphire, emerald, amber, amethyst, steel.

Inputs: cost, lore, name, color, +16 more. Returns: result

deck_statsA

Compute summary stats for a Lorcana deck: ink curve (copies at each cost), color split (copies per color — dual-ink contributes to BOTH buckets, so the sum may exceed total_cards), inkable/uninkable counts, type breakdown (Character/Action/Item/Song/Location), keyword_counts (copies per keyword — Evasive, Ward, Bodyguard, Shift, Singer, ...), subtype_counts (copies per subtype — Floodborn, Hero, Puppy, ...), and card_keywords (per-card keyword tags, so archetype questions like 'how many Evasive bodies does this run?' are answerable without a follow-up search_cards call). Each entry is {name: str, count: int} where name is the card's full_name. Names that don't resolve are listed under unresolved and excluded from the per-card stats (but still counted in total_cards).

Inputs: deck (required). Returns: object

export_deckA

Render a Lorcana deck as a Dreamborn/Pixelborn-compatible text deck list (<count> <full_name> per line, no sections, no set codes). Each entry is {name: str, count: int} where name is the card's full_name from search_cards (e.g. 'Mickey Mouse - Brave Little Tailor'). Returns plain text suitable for pasting into Dreamborn, Pixelborn, untap.in, or a Limitless tournament submission.

Inputs: deck (required). Returns: result

import_deckA

Parse a Dreamborn/Pixelborn-style deck list (<count> <full_name> per line) into resolved card objects. Each line is matched to a card by exact full_name first; lines that don't match exactly are returned in unresolved with the top fuzzy candidates so the user can disambiguate. Returns {parsed: [{count, card}], unresolved: [{raw, candidates}]}. Blank lines, comments (#, //), totals (Total: 60), and bracketed section headers are silently skipped.

Inputs: text (required). Returns: object

resolve_cardA

Resolve an informal, partial, or misspelled card name to the closest matching cards. Returns full card data (stats, abilities, cost, etc.) for each candidate — no follow-up query needed. Use this as follows:

  • 'Tell me about / get stats for X' → call resolve_card only, use the result directly.

  • 'Find cards that synergize with / work well with X' → resolve_card to get X's traits and abilities, then use those as inputs to search_cards.

  • 'Build a deck with X' → resolve_card to identify X, then search_cards for supporting cards. Never call search_cards first when the user has named a specific card — resolve_card avoids failed searches and retry loops. Example: 'Maui Half Shark' resolves to 'Maui - Half-Shark' as the top result.

Inputs: query (required), limit. Returns: result

search_cardsA

Search Lorcana cards with optional filters. Returns card objects (not counts). Color must be one of: ruby, sapphire, emerald, amber, amethyst, steel. Use card_type to filter by card type: Character, Action, Item, Song, or Location. Use min_attack/max_attack and min_defence/max_defence for stat-based queries (e.g. 'characters with 4+ strength'). Use min_cost/max_cost for cost ranges. Use keyword to filter by Lorcana keyword (Bodyguard, Challenger, Evasive, Reckless, Resist, Rush, Shift, Singer, Sing Together, Support, Vanish, Voiceless, Ward, etc.) — matches against the structured ability list, more reliable than substring search. Use body_text to search card ability text — useful for value-specific keyword queries like 'Singer 5' or 'Resist +2', or for non-keyword phrases like 'gain 2 lore'. Use lore/min_lore/max_lore to filter by lore value. Use sort_by to order results (id, name, cost, strength, willpower, lore, rarity, set_code); use sort_order='asc' or 'desc'. Use set_code to filter by set number (e.g. '1' for The First Chapter). Use set_name to filter by set name as a case-insensitive substring (e.g. 'Wilds Unknown', 'frozen', 'Floodborn') — preferred when the user names a set in plain English so you don't have to look up the code first. Use offset to paginate through results (e.g. offset=20 for the next page). Use count_cards instead if you only need a total count. Set response_format='toon' to receive a TOON-encoded string instead of JSON objects (~10% fewer tokens; the printings array is nested so the savings are smaller than a purely flat schema would yield); default is 'json'.

Inputs: cost, lore, name, color, +21 more. Returns: result

server_statusA

Show startup metadata for this server instance.

Inputs: no named parameters. Returns: object

top_traitsC

Return most common traits.

Inputs: limit. Returns: object

validate_deckA

Validate a Lorcana deck against the format rules: at least 60 cards (no maximum — 60 is just the floor), max 4 copies of any card, max 2 distinct ink colors across the deck. Each entry is {name: str, count: int} where name is the card's full_name (e.g. 'Mickey Mouse - Brave Little Tailor'). Returns {legal: bool, total_cards, inks, violations: [...]}. Violation types: deck_size (under 60 — total below minimum), max_copies (>4 of one card), ink_limit (>2 distinct colors — also catches dual-ink cards adding a forbidden third color), unknown_card (name didn't resolve to a known card). If you have the output of import_deck, build the input as [{name: card.full_name, count: count}] for each parsed entry.

Inputs: deck (required). Returns: object

Tool Schema Changelog

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

Loading tool schema changelog…

    TDQS

    A3.7/5.0

    Scored across 10 tools

    Disambiguation5/5

    Each tool targets a distinct task: search returns card objects, count returns totals, aggregate returns grouped counts, top_traits returns trait frequencies, resolve_card handles fuzzy name matching, and the deck tools each handle import, export, validation, or stats. No two tools overlap in purpose, and the descriptions make the boundaries clear.

    Naming Consistency3/5

    Most tools follow a verb_noun pattern (search_cards, count_cards, aggregate_cards, resolve_card, export_deck, import_deck, validate_deck), but top_traits, server_status, and deck_stats use noun phrases. The mixed convention is still readable but not uniformly predictable.

    Tool Count5/5

    With 10 tools, the set is well-scoped for a Lorcana card and deck assistant. It covers both card querying (search, count, aggregate, resolve) and deck management (import, export, validate, stats) without unnecessary bloat.

    Completeness5/5

    The tool surface provides comprehensive coverage for the domain: card discovery, count/aggregation, fuzzy name resolution, deck import/export, format validation, and detailed deck statistics. Common workflows like 'search for a card -> build a deck -> validate -> analyze stats' are fully supported with no obvious dead ends.

    Maintenance

    ActivityNo data
    ResponsivenessOutdated data

    Related MCP Connectors

    Related MCP Servers