Model Context Protocol server for Magic: The Gathering Commander/EDH deckbuilding.
Strong foundation with 18 well-named tools following verb_noun convention (analyze_*, card_*, deck_*, collection_*, etc.). All tools have detailed descriptions (100-400 chars, well above 20-char minimum). Input schemas present with type definitions and enums for constrained parameters (e.g., role, color, order). However, output schemas are documented only in descriptions, not as formal JSON Schema. Error handling is implicit rather than explicit, no structured error guidance for LLMs on recovery paths. Tool composition is excellent (single responsibility, clear chaining via IDs). Missing: formal output schema definitions, explicit error classification, and per-parameter validation rules in descriptions.
Count deck cards by card type and functional role, quantity-weighted. USE: type/role breakdowns. NOT: gaps vs target bands (analyze_role_coverage). FLOW: deck_status -> analyze_composition -> card_search. ARGS: deck_id. RETURNS: by_type, by_role, total.
Compute the deck's quantity-weighted mana-value histogram. USE: curve-shape questions, incl. filtered views by role/color. NOT: the one-call overview (deck_status includes the curve). FLOW: deck_status -> analyze_curve -> deck_remove. ARGS: deck_id; exclude_lands; role (functional role); color W|U|B|R|G. RETURNS: buckets {0..6, 7+}, total.
Compute exact deck stats: counts, average mana value, color pips, prices. USE: precise numbers for tuning (LLMs miscount — this doesn't). NOT: the one-call overview (deck_status). FLOW: deck_status -> analyze_stats -> budget_plan. ARGS: deck_id. RETURNS: total_cards, nonland_cards, avg_mv, avg_mv_nonland, color_pips, total_price_usd and min_buy_usd (library compatibility); budget.full_deck, command_zone, companion and coverage. USD estimates exclude fees, tax and shipping.
Explain supported engines and dependencies in the actual deck. USE: game plans, fragile support and deliberate niche builds. NOT: legality (validate_deck) or provider combo evidence (meta_combos). FLOW: deck_get_intent -> analyze_strategy -> card_discover. ARGS: deck_id; expected_version optionally checks the read. RETURNS: nodes, edges with Oracle evidence, dependencies, bottlenecks, redundancy, recovery_options, conflicts, win_condition_requirements, game_plan, declared_intent, coverage, deck_version. Conditional advisory catalog; unknown interactions stay explicit; no win rates.
Output schemas documented only in prose descriptions, not as formal JSON Schema. LLMs cannot parse unstructured text to infer response structure for downstream tool chaining.
Error handling is implicit. No structured error responses with recovery guidance (e.g., 'AMBIGUOUS_NAME carries candidates[]' is mentioned in card_resolve_name but not formalized across all tools).
Parameter validation rules not explicitly stated in descriptions. E.g., card_search 'limit' accepts max 175 but no description states this constraint; LLMs may pass invalid values.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | C | 63 | 2025-06-18+ | v2 |
Fetch full Card objects by name or oracle_id, singular or array. USE: reading oracle text/roles/legality/prices for known cards. NOT: browsing (card_search). FLOW: card_search/deck_get -> card_get -> deck_add. ARGS: cards: "Sol Ring" | [names or ids] (legacy alias: oracle_ids); include_printings:true adds printings[]; compact:true trims detail, keeping gameplay face evidence, oracle text, cost, types, ci, commander legality, roles, usd. RETURNS: cards[] (full or compact Card + pricing), missing[] (unresolvable inputs). gameplay preserves source characteristics and ordered faces; playability is not evaluated. Flat fields remain search/analysis projections.
List every printing of a card (set, collector number, prices), newest first. USE: picking a printing or price-shopping one card. NOT: batch pricing (card_get's default_usd/cheapest_usd). FLOW: card_get -> card_printings -> (choose printing). ARGS: card (name or oracle_id; oracle_id is a legacy alias). RETURNS: oracle_id, printings[] {set, collector_number, released, prices}.
Resolve one card name to its canonical oracle_id. USE: explicit disambiguation when a name may be ambiguous. NOT: routine adds — deck_add and card_get accept names directly. FLOW: (name in hand) -> card_resolve_name -> deck_add. ARGS: name; exact:true disables the fuzzy fallback. RETURNS: oracle_id + lean card. Errors: AMBIGUOUS_NAME carries candidates[]; UNKNOWN_CARD carries did-you-mean suggestions[].
Search cards with Scryfall query grammar against the local index. USE: finding candidates by type/color/cost/text/set, e.g. "t:instant ci<=wu mv<=2 o:draw" or "set:hob t:legendary". NOT: one known card (card_get); resolving a name (card_resolve_name). FLOW: deck_status/analyze_role_coverage -> card_search -> deck_add. ARGS: query (Scryfall grammar incl. set:/e:, rarity:/r:, year, is:commander, is:gamechanger); order name|mv|price|released; limit (max 175); cursor (from next_cursor); owned_only:true restricts to the collection (no-op until collection_set). RETURNS: results[] lean CardRefs (oracle_id, name, mv, ci, type), total, returned, next_cursor.
Add cards to the owned-card collection. USE: growing the owned set incrementally. NOT: replacing it wholesale (collection_set). FLOW: collection_get -> collection_add -> card_search (owned_only). ARGS: cards: name-or-id, single string or array. RETURNS: owned_count, total, owned[]/cards[] (echo capped at 200), unresolved[].
Empty the owned-card collection for this session. USE: starting ownership tracking over. NOT: swapping in a new list (collection_set replaces in one call). FLOW: collection_get -> collection_clear -> collection_set. ARGS: none. RETURNS: owned_count 0.
Read the owned-card collection, paginated. USE: reviewing what's loaded. NOT: ownership-filtered search (card_search owned_only). FLOW: collection_set -> collection_get -> (page with cursor). ARGS: limit (default 200); cursor (from next_cursor). RETURNS: owned[]/cards[] page, total (exact), next_cursor.
Replace the owned-card collection. USE: loading what the user owns before ownership-aware search or budgeting. NOT: adding a few cards (collection_add). FLOW: (owned list) -> collection_set -> card_search (owned_only) / budget_plan (use_collection). ARGS: cards: name-or-id, single string or array. RETURNS: owned_count, total, owned[]/cards[] (echo capped at 200), unresolved[]. Session-scoped; persisted by the server.
Normalize empty, commander, theme or partial-deck build goals. USE: resolve constraints before search. NOT: saving cards (deck_add). FLOW: deck_get_intent -> construction_spec -> card_discover. ARGS: optional request; deck_id reads saved cards/intent; expected_version checks that source. Omitted choices remain unresolved. RETURNS: status, specification, diagnostics, choices, unresolved_requirements, source version; hard constraints never relax silently. Read-only; no search or deck creation.
Download Scryfall bulk data (~700MB) and rebuild the local card index atomically. USE: first-run setup; refreshing a stale snapshot. NOT: quick checks (data_status). FLOW: data_status -> data_ingest -> data_status (poll). ARGS: force:true rebuilds even when upstream is unchanged. RETURNS: started, already_running — returns immediately; poll data_status for phase. One run at a time; a failed run never corrupts the served index.
Report card-index readiness, bulk-data staleness, and any in-flight ingest. USE: checking data exists before searching; polling during data_ingest. NOT: deck state (deck_status). FLOW: ping -> data_status -> card_search. ARGS: none. RETURNS: has_index; bulk_age_hours + stale (age of the served bulk data vs the refresh interval); ingest {running, phase download|build|done|error, snapshot, cards, error}. After ingest completes, refresh tools/list to discover the card tools; the running server activates the index without a restart.
Construct a complete Commander deck proposal without saving. USE: building from a commander, theme or partial deck. NOT: saving (deck_plan_apply). FLOW: construction_spec -> deck_construct -> deck_plan_apply. ARGS: request goals, optional name and bounded search limits; saved deck_id requires expected_version. RETURNS: status found/proven_conflict/search_exhausted, normalization choices, search evidence, explanations; found adds desired, diff, validation and a reviewed plan. Review all constraints and pass plan unchanged to apply. Search exhaustion is not proof of impossibility; hard constraints never relax silently.
Read authored build intent and effective advisory defaults. USE: planning any empty or existing deck. NOT: checking legality (validate_deck). FLOW: deck_create -> deck_get_intent -> deck_set_intent. ARGS: deck_id; expected_version optionally checks the read. RETURNS: intent (null means no preferences), version, defaults, effective, evaluation. Hard constraints are stored for planning; legality is independent.
Set, patch or clear build intent without changing cards. USE: goals, locked quantities, exclusions, commander constraints and preferences. NOT: card edits (deck_add). FLOW: deck_get_intent -> deck_set_intent -> analyze_role_coverage. ARGS: deck_id; expected_version required; action set|patch|clear; intent for set, patch for patch. Patch omits to retain, null removes, arrays replace. Card refs require exact names or IDs. RETURNS: intent, version, changed, defaults, effective, evaluation; conflicts include diagnostics. Hard constraints are not legality rules; soft and unsupported requirements are not guarantees.
Pagination cursors returned but no explicit guidance on when to use them. card_search and collection_get support pagination but descriptions don't explain cursor lifecycle or when results are truncated.
Tool composition assumes agent knows workflow order (e.g., 'FLOW: deck_status -> analyze_curve -> deck_remove'). No explicit dependency documentation or tool discovery hints.