Back to Discover

lorcana-mcp

connector

danielenricocahall

MCP server for searching, exploring, and aggregating Disney Lorcana cards.

View on GitHub
0 starsSynced Aug 3, 2026

Install to Claude Code

/plugin marketplace add danielenricocahall/lorcana-mcp

README

Lorcana MCP Server

MCP Badge

An MCP server for searching and aggregating Disney Lorcana cards.

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. The middle layer insulates running containers 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. With ~2,270 unique cards (each carrying a printings array for its alternate sets/rarities) this is lightweight and requires no external database. A local JSON file cache (LORCANA_CACHE_PATH, default cards.json) lets the server skip the network fetch on subsequent startups.

Startup data loading is controlled by:

  • LORCANA_REFRESH_ON_STARTUP:
    • true: always fetch from API and repopulate storage
    • false: use existing cache if available
  • LORCANA_SKIP_IF_DB_EXISTS:
    • true (default): skip API fetch if the cache file already contains cards
    • false: fetch and repopulate

Quick start (no clone required)

The server is published to GHCR and the MCP Registry. Pull and run it directly:

docker pull ghcr.io/danielenricocahall/lorcana-mcp:latest

docker 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

Run locally (stdio MCP)

uv run python main.py

Docker

Build image

docker build -t lorcana-mcp:latest .

Run as stdio MCP server

docker run --rm -i lorcana-mcp:latest

Docker Compose

Start with compose

docker compose build
docker compose run --rm -T lorcana-mcp

Notes:

  • No port is exposed; MCP communication is over stdio.
  • Use a volume to persist the JSON cache across restarts.

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 (false default)
  • LORCANA_SKIP_IF_DB_EXISTS (true default)

MCP client setup examples

Local process (Claude Desktop-style)

{
  "mcpServers": {
    "lorcana": {
      "command": "uv",
      "args": ["run", "python", "/absolute/path/to/lorcana-mcp/main.py"]
    }
  }
}

Published image — GHCR (Claude Desktop-style, no clone required)

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

Docker process (Claude Desktop-style, locally built)

{
  "mcpServers": {
    "lorcana": {
      "command": "docker",
      "args": [
        "run",
        "--rm",
        "-i",
        "lorcana-mcp:latest"
      ]
    }
  }
}

Docker Compose process (Claude Desktop-style)

{
  "mcpServers": {
    "lorcana": {
      "command": "docker",
      "args": ["compose", "run", "--rm", "-T", "lorcana-mcp"]
    }
  }
}

Via the Claude CLI — published image (global, no clone required)

claude mcp add --scope user \
  -- lorcana docker run --rm -i \
  ghcr.io/danielenricocahall/lorcana-mcp:latest

Via the Claude CLI — locally built

claude mcp add --scope user \
  -- lorcana docker run --rm -i lorcana-mcp:latest

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 tools

  • search_cards — filter and retrieve card objects (supports response_format="toon" for ~10% fewer tokens)
  • count_cards — count cards matching a filter without returning full objects
  • aggregate_cards — card counts grouped by cost (ink curve), rarity, color, set_code, or type
  • resolve_card — fuzzy-match an informal/partial/misspelled card name to the closest cards (returns full card data)
  • top_traits — most common traits across all cards
  • export_deck — render a deck as a Dreamborn/Pixelborn-compatible text deck list
  • import_deck — parse a Dreamborn/Pixelborn-style deck list, returning resolved cards plus any unresolved lines with fuzzy candidates
  • validate_deck — check a deck against the format rules (≥60 cards, max 4 copies, ≤2 inks); returns {legal, total_cards, inks, violations}
  • deck_stats — compute ink curve, color split, inkable count, type breakdown, keyword counts (Evasive, Ward, Shift, ...), subtype counts, and per-card keyword tags for a deck
  • server_status — startup metadata (card count, config)

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.

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,270-card dataset (post-consolidation), tokenizing with tiktoken cl100k_base (used as a proxy for Claude's tokenizer):

queryrowsJSON tokensTOON tokensΔ
color="amber", limit=20020043,67239,282−10.1%
color="ruby", limit=505010,4469,464−9.4%
card_type="action", limit=50 (sparse cols)5010,1509,265−8.7%
body_text="when", limit=50 (long full_text)5011,57410,380−10.3%
name="elsa", limit=20143,4562,925−15.4%
total79,29871,316−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. I worked only with publicly available/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.

Rendered live from danielenricocahall/lorcana-mcp's GitHub README — not stored, always reflects the source repo.

1 Install Method

NameDescriptionCategorySource
oci packageInstall via oci (stdio transport)mcp-serverghcr.io/danielenricocahall/lorcana-mcp:2.3.0

0 Comments

Login required
Log in to post a comment or update on this repo.

No comments yet — be the first to share an update.