cz-mtg-compare-mcp
This MCP server lets you search, compare, and optimize Magic: The Gathering card prices across Czech and European shops, manage shop accounts/carts, and look up card details via Scryfall.
Search for Cards (search_card): Find prices across 15+ shops (e.g., tolarie.cz, najada.cz, cernyrytir, blacklotus, rishada, untap, cardmarket). Results are sorted by price in CZK and include shop, edition, condition, language, foil status, stock, and a direct offer link. Filter by edition, stock availability, specific shops, or optionally include non-playable cards (Art Series, oversized, etc.).
Optimize Decklists (optimize_decklist): Paste an Arena/MTGO-format decklist (up to 100 cards) and get a full shopping plan with two strategies:
cheapest: Picks the lowest-priced offer per card across all shops.fewest_shops: Consolidates purchases into the fewest shops within a configurable price tolerance (default 10% above cheapest). Returns per-card picks, grouped shopping plans with subtotals, missing cards, and overall totals.
Look Up Cards via Scryfall (lookup_card): Resolve canonical card names, set info, oracle text, mana cost, image URLs, multilingual printed names, rarity, and Scryfall URI. Supports fuzzy or exact matching.
List Shop Statuses (list_shops): View all configured shops with their operational status, last error, and last offer count.
Account & Cart Management: For supported shops (najada, blacklotus, tolarie, cernyrytir, rishada, untap):
Login using credentials stored as plain text or 1Password
op://references.Add to cart using a
shop_reffrom search results.View or clear cart contents.
Check shop capabilities to see which shops support login, cart, or watchlist features.
Note: The server does not place orders — cart items must still be checked out manually on each shop's website.
Allows storing shop login credentials in 1Password and retrieving them securely via the op CLI for authentication to supported Magic: The Gathering shops.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@cz-mtg-compare-mcpfind the cheapest in-stock Lightning Bolt across Czech shops"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
cz-mtg-compare-mcp
A Model Context Protocol server that lets Claude (or any other MCP client) compare Magic: The Gathering single-card prices across six Czech shops and nine additional European shop ids, optionally falling back to Cardmarket (16 shop ids total). Ask Claude what something costs — it queries every enabled shop in parallel and returns one normalized, CZK-converted, price-sorted list.
You: Find me the cheapest in-stock Lightning Bolt across the Czech shops.
Claude: (calls search_card)
→ 31 offers found:
• tolarie: 35 Kč NM Battle for Baldur's Gate Extras
• najada: 49 Kč NM Commander Legends: BfBG Extras (Showcase)
• cernyrytir: 59 Kč LP 4th Edition (4ED)
...
Cheapest copy is on tolarie.cz at 35 Kč.Table of contents
Setup —
uvx,pipx, plainpip, or local clone
Related MCP server: Market Price Finder
What this is
This is an MCP server. MCP is the protocol Claude Desktop (and other clients) use to call external tools. Once configured, Claude can:
Search a single card across six Czech shops plus shops in the UK, Poland, France, Germany, the Netherlands, and Italy.
Optimize a Commander/Standard/Modern decklist — paste the list in chat, get back either the cheapest cross-shop split (default) or a plan that consolidates into the fewest distinct shops (
strategy="fewest_shops", within a 10% price tolerance), plus each shop's solo total.Resolve card names through Scryfall (canonical name, set/collector#, oracle text, multilingual printed names).
Fall back to Cardmarket for European pricing when CZ shops don't carry a card (optional, requires API credentials).
(Optional) Log into a shop and manage your cart for shops where account features are implemented. Currently:
najada/blacklotus/tolarie/rishada/cernyrytir(full — login + add/view/clear cart) anduntap(login only). See Account features. Credentials may be plain strings orop://...1Password references.
It does not place orders or send notifications — cart contents must still be checked out manually on each shop's website.
Supported shops
Shop | Mechanism | Covered fields |
| HTML scrape (server-rendered table) | name, edition, condition, foil, stock, price |
| JSON API ( | name, edition, set code, condition, language, foil, stock count, price |
| HTML scrape (Shoptet) + detail-page enrichment | name, edition, condition, foil, stock, price |
| HTML scrape (windows-1250, POST search) | name, edition, set code, condition, foil, stock, price |
| HTML scrape (custom-PHP, tabular) | name, edition, condition, foil (incl. judge / etched), stock, price |
| HTML scrape (Prestashop) | name, edition, set code, condition + foil from product reference, stock, price |
| Shopify JSON ( | name, edition, set code, condition, foil, binary stock, price (GBP→CZK) |
| JSON API ( | name, edition, condition, language, foil, stock count, price (PLN→CZK) |
| HTML scrape (Laravel variant forms) | name, edition, NM condition, language (VO/VF), foil, stock count, price (EUR→CZK) |
| HTML scrape (Shopware GA4 dataLayer + aligned product DOM) | name, edition, condition, language, foil, stock count, price (EUR→CZK); default variant per listing |
| HTML scrape (Bazaar Games singles tiles) + optional JSON-LD detail | name, edition, NM condition, foil, binary stock, price (EUR→CZK) |
| Shared Bazaar Games HTML adapter | singles fields as above; currently no MTG singles catalog, so searches legitimately return no offers |
| HTML scrape (OXID single-card tiles) | name, edition, set code, NM-default condition, language, foil, binary stock, price (EUR→CZK); buylist rows excluded |
| BigCommerce BODL JSON island + optional variant enrichment | name, edition, set code, condition when enriched, language, foil, stock, price (GBP→CZK). ⚠️ currently blocked live — see Limitations |
| HTML scrape (custom-PHP result rows) | Italian name and edition, NM-default condition, foil, stock count, price (EUR→CZK) |
| OAuth1 API (opt-in, untested live) | aggregate priceGuide (TREND/AVG/LOW + foil), EUR→CZK — see Cardmarket section |
What you can ask Claude
Once installed, you can talk to Claude in plain Czech or English. Some examples that work well:
"Najdi mi nejlevnější Lightning Bolt skladem napříč českými obchody."
"How much would this Commander deck cost from each shop separately, and what's the cheapest if I buy across all of them?" (then paste the decklist)
"Show me all foil printings of Sol Ring available right now and where they are."
"Lookup Atraxa, Praetors' Voice on Scryfall and tell me which sets it's printed in."
"Compare prices for the cards in this Pioneer deck — but only from najada and tolarie."
"Compare Lightning Bolt across the Czech shops, Axion Now, MTGSpot, and the other European stores."
"I'd rather place fewer separate orders — optimize this decklist to use the fewest shops possible, even if it costs a bit more."
"Log into najada and add 4× Lightning Bolt at the cheapest price into my cart." (requires
CZ_MTG_NAJADA_USER/_PASSenv vars; see Account features)"Show me what's currently in my najada cart."
Claude picks the right tool, calls it, and summarises the result.
Setup
You don't need to clone the repo. Pick one of the install methods below, paste the matching JSON into Claude Desktop's config, restart Claude — done. Method D is for users who do want to clone (development, running unreleased commits).
Prerequisites
Requirement | How to check |
Claude Desktop | |
Python 3.11+ |
|
One of: | see below |
If your python3 is 3.10 or older, install a newer one: brew install python@3.12 on macOS, or python.org on any platform.
Install method A — uvx (recommended)
uv is a fast Python package manager. uvx runs Python apps in isolated environments and caches them. No clone, no venv, no manual install — uvx fetches the package and caches it on first run.
1. Install uv (one-liner):
# macOS / Linux
curl -LsSf https://astral.sh/uv/install.sh | sh
# Windows PowerShell
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"2. Find uvx's absolute path — Claude Desktop doesn't always inherit your shell's PATH, so you must give it the full path:
# macOS / Linux
which uvx
# → /Users/you/.local/bin/uvx (or /opt/homebrew/bin/uvx)
# Windows PowerShell
(Get-Command uvx).Source3. Add this to Claude Desktop's config (see the config-file location table below):
{
"mcpServers": {
"cz-mtg-compare": {
"command": "/ABSOLUTE/PATH/TO/uvx",
"args": ["cz-mtg-compare-mcp"]
}
}
}The first time Claude Desktop starts the server, uvx fetches the package from PyPI and installs it (a few seconds). Subsequent starts are instant.
To upgrade later, run uvx --refresh-package cz-mtg-compare-mcp cz-mtg-compare-mcp once.
Install method B — pipx
pipx installs Python apps in isolated venvs and exposes their console scripts on your PATH.
1. Install pipx (https://pipx.pypa.io/stable/installation/):
# macOS
brew install pipx && pipx ensurepath
# Linux
python3 -m pip install --user pipx && python3 -m pipx ensurepath
# Windows
python -m pip install --user pipx
python -m pipx ensurepathReopen your terminal so PATH updates take effect.
2. Install the server:
pipx install cz-mtg-compare-mcp3. Find the absolute path of the installed binary:
# macOS / Linux
which cz-mtg-compare-mcp
# → /Users/you/.local/bin/cz-mtg-compare-mcp
# Windows PowerShell
(Get-Command cz-mtg-compare-mcp).Source4. Add this to Claude Desktop's config:
{
"mcpServers": {
"cz-mtg-compare": {
"command": "/ABSOLUTE/PATH/TO/cz-mtg-compare-mcp",
"args": []
}
}
}To upgrade later: pipx upgrade cz-mtg-compare-mcp.
Install method C — plain pip
If you don't want extra tooling, install with system pip and point Claude at the script directly.
python3 -m pip install --user cz-mtg-compare-mcp
# Find the script
python3 -c "import sysconfig; print(sysconfig.get_path('scripts'))"
# → /Users/you/Library/Python/3.12/bin (macOS)
# → /home/you/.local/bin (Linux)
# → C:\Users\you\AppData\Roaming\Python\Python312\Scripts (Windows)The full path to the script is <that-directory>/cz-mtg-compare-mcp. Then add to Claude Desktop's config:
{
"mcpServers": {
"cz-mtg-compare": {
"command": "/ABSOLUTE/PATH/TO/cz-mtg-compare-mcp",
"args": []
}
}
}To upgrade later: python3 -m pip install --user --upgrade cz-mtg-compare-mcp.
Install method D — from a local clone (development / unreleased changes)
Use this when you've cloned the repo and want to run the server from your working copy — useful for developing on the server, testing PRs, or running an unreleased commit.
git clone https://github.com/xvyslo05/czech-mtg-price-comparator.git
cd czech-mtg-price-comparator
python3 -m venv .venv
source .venv/bin/activate # macOS / Linux
# .venv\Scripts\activate # Windows PowerShell
pip install -e .Then point Claude Desktop at the in-venv interpreter:
{
"mcpServers": {
"cz-mtg-compare": {
"command": "/absolute/path/to/repo/.venv/bin/python",
"args": ["-m", "cz_mtg_compare"]
}
}
}Edits to the source take effect on the next Claude Desktop restart — no rebuild needed thanks to -e (editable install).
Step: add to Claude Desktop's config file
The config file lives at:
OS | Path |
macOS |
|
Windows |
|
Linux |
|
If the file doesn't exist yet, create it. If you already have other MCP servers configured, don't replace the whole file — add the "cz-mtg-compare" entry alongside the existing ones inside the same "mcpServers" object.
A complete macOS example using uvx:
{
"mcpServers": {
"cz-mtg-compare": {
"command": "/opt/homebrew/bin/uvx",
"args": ["cz-mtg-compare-mcp"]
}
}
}Step: restart Claude Desktop
Fully quit Claude Desktop (don't just close the window — use Cmd+Q on macOS, or right-click the tray icon → Quit on Windows) and reopen it.
Verify it's working
Open a new chat in Claude Desktop and ask:
"What MCP tools do you have available?"
You should see at least these tools listed:
search_cardoptimize_decklistlookup_cardlist_shopsshop_account_capabilities,shop_login,add_to_cart,view_cart,clear_cart,add_to_watchlist(account features — only useful for shops where they're supported; see the matrix below)
Then try a real query:
"Find Lightning Bolt across all Czech card shops, show me the five cheapest in-stock copies."
Claude will call search_card, the server will fan out to every enabled shop in parallel, and Claude will summarise the results.
If something doesn't work, jump to Troubleshooting.
Optional: enable Cardmarket
Cardmarket would give you EU-wide pricing as a fallback for cards Czech shops don't carry. The adapter is implemented and ships with the server, but it's off by default and only activates if you provide the four MKM_* OAuth1 credentials as environment variables.
⚠️ Untested in production. As of the last update to this repo, Cardmarket is not accepting new API access requests — they've paused signups for the Dedicated App / Personal tier. The adapter was built against their published API spec (request signing verified against the OAuth1 reference, response shape verified against their
/products/findschema), and unit tests cover the full request/response cycle, but it has not been live-tested end-to-end. If you have an existing Cardmarket API key from before signups were paused, the steps below should work — please open an issue if anything misbehaves.
1. Get a Cardmarket dedicated app token (if signups have reopened)
Apply for a Dedicated App (Personal/Free tier is enough for read-only price aggregates).
After approval, you'll see four values:
App Token,App Secret,Access Token,Access Token Secret.
2. Add them to your Claude Desktop config
Add an "env" block alongside "command" and "args" in your existing entry. With uvx, that looks like:
{
"mcpServers": {
"cz-mtg-compare": {
"command": "/opt/homebrew/bin/uvx",
"args": ["cz-mtg-compare-mcp"],
"env": {
"MKM_APP_TOKEN": "...",
"MKM_APP_SECRET": "...",
"MKM_ACCESS_TOKEN": "...",
"MKM_ACCESS_TOKEN_SECRET": "...",
"MKM_EUR_TO_CZK": "24.7"
}
}
}
}MKM_EUR_TO_CZK is an optional manual override. When omitted, Cardmarket uses the same cached daily CNB EUR rate as the other European adapters (with the static 24.5 fallback). Restart Claude Desktop and Cardmarket offers should start showing up alongside the Czech shops; verify with list_shops.
What the adapter does and doesn't cover
Covered:
/products/find?search=<name>&idGame=1with OAuth1 HMAC-SHA1 signing,priceGuideparsing (TREND / AVG / LOW with LOW fallback), foil variants surfaced as separate offers, EUR→CZK conversion,set_code/editionfiltering, max-results truncation, missing-key tolerance.Not covered: per-seller article listings (
/articles/{idProduct}— requires a paid Trader-tier API key, ~€20/year). The Free/Personal tier only exposes aggregate priceGuide data, so cardmarket offers come back without specific condition / language / seller info —conditionisUNKNOWNandstock_qtydefaults to 1 (priceGuide implies sellers exist but doesn't quantify them).Behaviour without credentials: if any of the four
MKM_*env vars is missing or empty, the adapter is silently dropped from the default adapter list — no startup errors, no failed auth requests, just nocardmarketentries inlist_shops.
Optional: account features (login + cart)
In addition to anonymous price comparison, the server can log into individual shops on your behalf and manage the contents of your cart there. Useful when Claude has already picked the cheapest split — instead of clicking through every offer URL manually, you can ask Claude to add them straight into the shop's cart, then check out yourself on the shop's website.
This is opt-in per shop. The server has no credentials by default and never tries to log in unless you explicitly configure them.
⚠️ Read this before enabling. You are storing shop passwords in your local Claude Desktop config (or your 1Password vault). The MCP server will use them to make authenticated requests on your behalf. Use a unique password per shop; consider whether each shop's Terms of Service permit automated cart manipulation; and remember that any
search_cardcall made after a successful login goes out with your session cookie attached, so the shop can correlate those searches with your account.
Per-shop capability matrix
Shop | Login | Cart (add / view / clear) | Watchlist |
| ✅ | ✅ | ❌ (planned) |
| ✅ | ✅ | ❌ |
| ✅ | ✅ | ❌ (planned) |
| ✅ | ⛔ disabled (each login starts a fresh checkout — items don't persist) | ❌ |
| ✅ | ✅ | ❌ |
| ✅ | ✅ | ❌ |
| ❌ | ❌ | ❌ |
| ❌ | ❌ | ❌ |
| ❌ | ❌ | ❌ |
| ❌ | ❌ | ❌ |
| ❌ | ❌ | ❌ |
| ❌ | ❌ | ❌ |
| ❌ | ❌ | ❌ |
| ❌ | ❌ | ❌ |
| ❌ | ❌ | ❌ |
| ❌ | ❌ | ❌ |
Each shop's account flow has to be reverse-engineered separately, so the supported set grows shop-by-shop. The shop_account_capabilities MCP tool reports this matrix at runtime including whether credentials are currently configured — ask Claude "which shops can I log into?".
Configuring credentials
Each shop reads its credentials from two env vars, set in your Claude Desktop config under the server's "env" block:
CZ_MTG_<SHOP>_USER # username or email (depending on shop)
CZ_MTG_<SHOP>_PASS # passwordShop ids match those used everywhere else (najada, tolarie, …). Example using the uvx setup:
{
"mcpServers": {
"cz-mtg-compare": {
"command": "/opt/homebrew/bin/uvx",
"args": ["cz-mtg-compare-mcp"],
"env": {
"CZ_MTG_NAJADA_USER": "alice@example.com",
"CZ_MTG_NAJADA_PASS": "your-najada-password",
"CZ_MTG_BLACKLOTUS_USER": "alice@example.com",
"CZ_MTG_BLACKLOTUS_PASS": "your-blacklotus-password",
"CZ_MTG_UNTAP_USER": "alice@example.com",
"CZ_MTG_UNTAP_PASS": "your-untap-password",
"CZ_MTG_TOLARIE_USER": "alice",
"CZ_MTG_TOLARIE_PASS": "your-tolarie-password",
"CZ_MTG_CERNYRYTIR_USER": "alice",
"CZ_MTG_CERNYRYTIR_PASS": "your-cernyrytir-password",
"CZ_MTG_RISHADA_USER": "alice",
"CZ_MTG_RISHADA_PASS": "your-rishada-password"
}
}
}
}If one var of a pair is set and the other isn't, you'll get an explicit CredentialError on the first login attempt for that shop — silent partial-config is rejected. Both vars missing means the shop is just treated as "no credentials" (the read-only search adapter keeps working).
Using 1Password instead of putting passwords in config
Either env var value may be a 1Password secret reference of the form op://Vault/Item/Field. On first use, the server shells out to the 1Password CLI (op) to resolve it and caches the result in-process — so each op read happens at most once per server run.
{
"env": {
"CZ_MTG_NAJADA_USER": "op://Personal/Najada/username",
"CZ_MTG_NAJADA_PASS": "op://Personal/Najada/password"
}
}Requirements:
The
opCLI is installed and on the PATH visible to Claude Desktop. (SamePATHcaveat asuvx— Claude Desktop may not inherit your shell PATH; ifop readresolution fails, the error message will say so.)You're signed into 1Password (
op signin) or have biometric / system-auth integration enabled so non-interactiveop readcalls succeed.Each
op://reference resolves to a non-empty value (an empty value is rejected with a clear error).
You can mix the two forms — e.g. literal _USER and op:// _PASS — freely.
What Claude can actually do
Once configured, ask Claude things like:
"Add four copies of Lightning Bolt from najada into my cart — pick the cheapest in-stock copies."
"What's in my najada cart right now?"
"Empty my najada cart, I'm starting over." (asks for confirmation;
clear_cartis destructive)
Under the hood Claude calls:
shop_account_capabilities()— discover what's supported and what's configuredshop_login(shop="najada")— eager-login if needed; otherwise the cart tools log in lazilyadd_to_cart(shop, shop_ref, count=N)—shop_refis the per-shop product/article id that appears on everyOfferreturned bysearch_card/optimize_decklist. Pass it through verbatim (it's a UUID for najada, a numeric id for tolarie / rishada / cernyrytir). If you don't have ashop_refyet, runsearch_cardfirst. For rishada and cernyrytir,countis clamped to the row's available stock by the server, so a request for more than is available silently adds the maximum the shop has.view_cart(shop)— return the shop's current cart contentsclear_cart(shop)— delete every item from the shop's cart (returns{"removed_items": N})add_to_watchlist(shop, shop_ref)— only on shops that support it (none today; placeholder for follow-up PRs)
Sessions live in-process for the lifetime of the MCP server. If the cached token ever expires server-side, the next cart call transparently re-logs in and retries once before surfacing an error — you don't need to call shop_login manually.
Known limitations
najada watchlist (
own-wantlist-items/own-shopping-list-items) is not wired up yet — the endpoints exist but require an authenticated session to inspect the schema, and the nested-product shape is non-trivial. Planned for a follow-up PR.untap cart is disabled even though it works. untap's Prestashop install starts a brand-new checkout on every login. Items added by this MCP server during a Claude Desktop session disappear the moment the user logs in again (whether through this server or in a browser). The cart code is kept around so a future PR can flip
supports_cartback on if untap migrates to a session-spanning cart, but exposing it today would just confuse users — the cart-add API call returns success but the cards never materialise on the user's account.No checkout. The server stops at "items are in your cart"; finalizing the order, choosing shipping, paying — all still happens manually on the shop's website. By design.
Configuration reference
Environment variable | Purpose | Default |
| Cardmarket OAuth1 app token | unset (Cardmarket disabled) |
| Cardmarket OAuth1 app secret | unset |
| Cardmarket OAuth1 access token | unset |
| Cardmarket OAuth1 access token secret | unset |
| Override Cardmarket API base URL |
|
| Cardmarket-only manual EUR → CZK override — when set, disables the live CNB rate for Cardmarket | unset (CNB; static fallback |
| Manual EUR → CZK override — when set, disables the live CNB rate for that currency | unset (CNB; static fallback |
| Manual GBP → CZK override — when set, disables the live CNB rate for that currency | unset (CNB; static fallback |
| Manual PLN → CZK override — when set, disables the live CNB rate for that currency | unset (CNB; static fallback |
| Override daily CNB-rate on-disk cache directory |
|
| Override Scryfall on-disk cache directory |
|
| Comma-separated, case-insensitive list of shop IDs to drop at startup (e.g. | unset |
| Hard cap on unique cards per |
|
| How much extra (in %) the |
|
| Najada ( | unset (login disabled for najada) |
| Blacklotus account credentials for login + cart | unset (login disabled for blacklotus) |
| Untap (Prestashop) account credentials for login + cart | unset (login disabled for untap) |
| Tolarie account credentials for login + cart | unset (login disabled for tolarie) |
| Černý rytíř account credentials for login + cart | unset (login disabled for cernyrytir) |
| Rishada account credentials for login + cart | unset (login disabled for rishada) |
| SQLAlchemy URL for the web app's database. Required for the upcoming auth / vault work (issue #9 → B/C). Example: |
|
| Log all SQL emitted by the web app's engine (debug) |
|
| Cookie name carrying the opaque server-side session id |
|
| Cookie name carrying the CSRF token (readable by JS by design) |
|
| Session lifetime in seconds; invalid / non-positive values fall back to the default |
|
| Whether session/CSRF cookies require HTTPS. Set to |
|
| SameSite flag for both cookies. One of |
|
| Cookie Domain attribute. Leave unset for host-only cookies | unset |
| Email-verification token lifetime in seconds. Invalid / non-positive values fall back to the default |
|
| Origin used when building links in outbound mail and Google's OAuth redirect URI |
|
| OAuth 2.0 client id from Google Cloud Console. When unset, | unset (Google sign-in disabled) |
| OAuth 2.0 client secret matching the client id above | unset |
| Override the callback URL (must match a redirect URI registered in the console) |
|
Disabling individual shops
Three ways, in increasing scope:
Per-call allow-list: tell Claude which shops you DO want.
"Only check tolarie and najada for this card."
Claude passes
shops=["tolarie", "najada"]tosearch_card/optimize_decklist.Per-call deny-list (opt-out): tell Claude which shops you DON'T want.
"Search Lightning Bolt everywhere except blacklotus."
Claude passes
exclude_shops=["blacklotus"]. Combines withshops: the deny-list is applied AFTER the allow-list, so an explicit deny always wins. Excluded shops also disappear fromper_shop_bundlesin the optimizer's output.Server-wide opt-out via env var. Add
CZ_MTG_DISABLED_SHOPSto your Claude Desktop config — comma-separated, case-insensitive — and those shops are dropped from the default adapter list at startup. Useful if you have a standing reason to never query a particular shop (bad past experience, slow responses, etc.):{ "mcpServers": { "cz-mtg-compare": { "command": "/opt/homebrew/bin/uvx", "args": ["cz-mtg-compare-mcp"], "env": { "CZ_MTG_DISABLED_SHOPS": "blacklotus,untap" } } } }Unknown shop names in the env var are silently ignored, so a typo can't brick the server.
Troubleshooting
Tools don't appear in Claude Desktop after restart.
Make sure the path in
commandis absolute. Claude Desktop usually doesn't inherit your shell'sPATH, so a bareuvxorcz-mtg-compare-mcpwill fail to launch.Open Claude Desktop's developer tools (macOS:
Cmd+Option+Iwhile focused on the chat) and check the console for MCP server errors.Try running the server manually:
uvx:
uvx cz-mtg-compare-mcppipx / pip:
cz-mtg-compare-mcplocal clone:
.venv/bin/python -m cz_mtg_compare
It should hang waiting for stdin input — that's correct behaviour. Press
Ctrl+Cto exit.
ModuleNotFoundError: No module named 'cz_mtg_compare'.
The install didn't complete. Re-run your install command (
pipx install ...orpython3 -m pip install --user ...) and confirm it finishes without errors.
uvx hangs or times out the first time Claude Desktop starts the server.
First-time installs take a few seconds while uvx fetches the package + its dependencies from PyPI. Subsequent starts are instant. If it consistently fails, run the manual command above from a terminal to see the underlying error.
Event loop is closed errors during testing.
Already handled by
tests/conftest.py. If you see it elsewhere, the sharedhttpx.AsyncClientwas bound to a now-closed loop — callcz_mtg_compare.http_client.close_client()between event-loop boundaries.
One shop's results are missing or stale.
Each shop's last-call status is exposed via the
list_shopstool. Ask Claude:"Run list_shops and tell me if any shop is failing."
Results are cached for 10 minutes; results older than that get auto-refreshed.
Cardmarket returns nothing.
Run
list_shopsand check ifcardmarketis included. If it isn't, the credentials weren't loaded — verify theenvblock in your Claude Desktop config and that you fully restarted Claude Desktop (Cmd+Q, not just close window).
Search returns offers that don't match the card I asked about.
Some Czech shops' search engines are loose with substring matching. If you're querying a card with a common word in its name (e.g. "Lightning"), narrow down with
edition=.... Ask Claude:"Search for Lightning Bolt, but only from the Strixhaven set."
How it works under the hood
┌────────────────────┐
Claude Desktop ◄────► │ MCP server (stdio)│
│ cz_mtg_compare │
└─────────┬──────────┘
│ fans out in parallel
│
┌────────────────────────┴────────────────────────┐
│ │
▼ ▼
Czech adapters European adapters
tolarie · najada · blacklotus axionnow · mtgspot
cernyrytir · rishada · untap magiccorporation · jkentertainment
bazaarofmagic · spellenwinkel
traderonline · magicmadhouse · magicstore
│ │
└────────────────────────┬────────────────────────┘
│
cardmarket (opt-in)
│
▼
normalized Offer[] sorted by price_czkA single
search_cardcall dispatches to every adapter concurrently, with per-host concurrency capped at 3 and a 20-second timeout per shop.Axion Now uses a bounded two-stage Shopify lookup: predictive search finds at most 10 product handles, then the adapter fetches each handle's
.jsvariants under the same per-host concurrency cap.Magic Madhouse defaults to one BigCommerce BODL search-page request.
MagicMadhouseAdapter(enrich_variants=True)additionally follows at most five matching products and resolves their condition variants through the BigCommerce product-attributes endpoint.Foreign-currency adapters resolve EUR, GBP, and PLN through a 24-hour cached CNB daily rate. Constructor overrides win first, followed by the matching environment override and a fresh in-memory/on-disk CNB rate. When a stale disk rate cannot be refreshed it still beats the static offline fallback. Cardmarket's
MKM_EUR_TO_CZKremains its highest-priority environment override.Each adapter returns normalized
Offerobjects.price_czkremains the sortable comparison price; foreign offers additionally expose their original amount and ISO currency inprice_nativeandcurrency.Per-shop results are cached in-memory for 10 minutes (LRU eviction not yet, just TTL).
One shop failing or timing out never kills the query — partial results come back, and the failed shop's error is surfaced through
list_shops.
The decklist optimizer is a thin layer on top: it parses the deck, fans out one search_card per unique card (still capped per-host, so 100 cards → 100 sequential-per-host searches but parallel across shops), then computes one of two shopping plans:
cheapest(default) — per-card greedy split: pick the cheapest in-stock copy of each card across all shops. Minimizes total CZK; may fragment the order across many shops.fewest_shops— consolidate the order into the smallest number of distinct shops, while staying within 10% of the cheapest-split total (overridable viaCZ_MTG_CONSOLIDATE_TOLERANCE_PCT). Internally it enumerates every non-empty subset of contributing shops, picks the cheapest in-subset offer per card (falling back to the global cheapest for cards the subset doesn't sell), filters to candidates within budget, and returns the plan with the fewest effective shops, ties broken by total.
Both modes return a shopping_plan grouped by shop plus a cheapest_split_total_czk baseline; fewest_shops also populates consolidated_total_czk so callers can show the consolidation premium.
Per-shop bundles: for each shop on its own, sum the cheapest offer per card it has and count cards it's missing. Strategy-independent — always returned.
Each Offer includes a url you can click through to the shop. Offers from shops that support account features also carry an opaque shop_ref (e.g. najada's UUID article id, tolarie's numeric product id) that the cart tools need — but only when produced by a search in the same server session.
Account features pipeline
The login + cart tools live in adapters/base.py (capability flags + AccountFeatureNotSupported default impls), credentials.py (env-var and 1Password resolution), and each adapter that opts in. The pipeline for an add_to_cart call:
server.pyforwards the call toservice.CardCompareService, which looks up the right adapter byshop_id.The adapter checks for an existing auth token / session. If absent, it pulls credentials via
credentials_for(shop_id)(resolving anyop://references through the 1Password CLI on first use), hits the shop's login endpoint, and stores the resulting bearer token or session cookie on the adapter instance.It POSTs the cart-add request using the offer's
shop_ref. On 401 it clears the cached session so the next call triggers a fresh login.The raw shop response (or an error with a clear message) is returned to Claude.
Sessions are per-process; they live as long as the MCP server does and are torn down on restart.
Non-playable cards
Display-only products (Art Series, oversized cards, helper / tip / checklist cards, spindowns) are excluded by default because they aren't legal in any constructed Magic format. The filter looks at both card name and edition for any of these markers.
If you specifically want them — e.g. you're price-checking an art print or a collector item — pass include_non_playable=True to either search_card or optimize_decklist. Or just say so in chat:
"Find me Art Series Lightning Bolt — include non-playable cards."
Claude will pass the flag through automatically.
HTTP API (experimental)
A FastAPI surface ships alongside the MCP server for the upcoming web-app work tracked in issue #9. Read-only and unauthenticated for now — cart and login endpoints will land with the credential vault (workstream C); per-user API keys + rate limiting land with G1/G2. Run anywhere you'd run the MCP server.
pip install "cz-mtg-compare-mcp[web]"
cz-mtg-compare-web --host 0.0.0.0 --port 8080
# OpenAPI docs at http://localhost:8080/docsAvailable endpoints (v1):
Method | Path | Notes |
GET |
| Liveness probe |
GET |
| Configured shops + last-call status (mirrors |
GET |
| Per-shop login/cart/watchlist flags + credential presence |
GET |
|
|
GET |
|
|
POST |
| JSON body — |
GET |
| Mint or refresh the CSRF token; sets the session + CSRF cookies |
GET |
| Returns |
POST |
|
|
POST |
|
|
POST |
| Delete the current session, clear cookies (204 always) |
POST |
| Re-send verification email for the logged-in user ( |
POST |
|
|
GET |
| Redirect to Google's consent screen ( |
GET |
| Google → us. Exchanges code, creates/links user, redirects back |
The MCP server (cz-mtg-compare-mcp) and the HTTP server (cz-mtg-compare-web) share the same in-process service layer (cz_mtg_compare.service.CardCompareService); behaviour is identical across surfaces.
Database
The web app owns a database — required once auth (B1) and the credential vault (C) start landing. Today's read-only surface doesn't touch it, so a fresh checkout still runs without setup, but you should configure one before stacking later PRs:
# 1. Point the app at your database
export CZ_MTG_DATABASE_URL="postgresql+asyncpg://user:pass@host/dbname"
# 2. Apply migrations (creates the users table)
alembic upgrade head
# 3. Start the server
cz-mtg-compare-web --host 0.0.0.0 --port 8080Without CZ_MTG_DATABASE_URL the engine falls back to in-memory SQLite — fine for poking around, not for anything persistent. The MCP server does not use this DB at all; only the HTTP / web surface does.
CSRF
State-changing requests (POST / PUT / PATCH / DELETE) are gated by a double-submit CSRF check when the request carries a session cookie. Unsessioned anonymous requests aren't gated — there's no escalation to defend against. Once login (B1 PR3) is wired up, every authenticated request must:
Have called
GET /v1/auth/csrfat least once (sets thecz_sessionandcz_csrfcookies).Mirror the CSRF cookie value into the
X-CSRF-Tokenrequest header.
Server-side the middleware also checks the header against the session row's stored csrf_token, so revoking a session immediately kills its CSRF token — not just the cookie pair on whichever browser still has it cached.
Email / password auth
Passwords are hashed with argon2id (library defaults — time_cost=2, memory_cost=64 MiB, parallelism=1). Plaintext never touches the DB or logs. Hashes are re-upgraded on successful login when argon2's recommended parameters move forward.
Emails are normalised to lowercase before storage AND lookup, so
Alice@Example.comandalice@example.comcan't both register.The login endpoint returns a single generic 401 for both "unknown email" and "wrong password" — it is not a user-enumeration oracle.
Sessions are rotated on every successful login (the old session row is left untouched; explicit
POST /v1/auth/logoutrevokes it).A verification email is sent on signup; the user can re-trigger it via
POST /v1/auth/verify/request. Tokens are single-use, short-lived (24h default — override withCZ_MTG_EMAIL_VERIFY_TTL_SECONDS), and stored as SHA-256 hashes only.Email verification is not a hard gate yet — accounts are usable right after signup, and
email_verifiedjust flips when the user clicks the link. Routes that should require verification will get explicit guards in a follow-up PR.
Mail delivery
The web app sends verification emails through a pluggable Mailer (cz_mtg_compare.web.mailer). The default is a LoggingMailer that writes the URL to the logger at INFO — good for dev, no SMTP setup needed. Production deployments swap in a real implementation by passing it to create_app(mailer=...):
from cz_mtg_compare.web.app import create_app
app = create_app(mailer=MyResendMailer())Set CZ_MTG_PUBLIC_BASE_URL to your deployed origin (e.g. https://card-compare.cz) so the link in the email points at the right host. Defaults to http://localhost:8080.
Google OAuth
Users can sign in with Google in addition to email + password. The flow is the standard server-side OAuth 2.0 / OpenID Connect dance:
Browser →
GET /v1/auth/oauth/google/start→ 302 to Google's consent screen.Google →
GET /v1/auth/oauth/google/callback?code=...&state=....Server exchanges the code, validates Google's ID token signature against Google's JWKS, then:
Already linked (matching
oauth_identities(provider="google", provider_user_id=sub)row) → log the same user back in.No link, but
User.email == google.emailAND Google saysemail_verified: true→ attach a newoauth_identitiesrow; flipuser.email_verifiedif it wasn't already.No link, email matches but Google says
email_verified: false→ reject with a specific 400 (the user must log in with password first and link Google from settings). This is the only non-generic auth error in the codebase, on purpose — the alternative (silent reject) is worse UX, and the one bit of information leaked ("an account with this email exists") is the same bitPOST /v1/auth/signupalready returns via 409.No link, no email match → create a fresh user (
password_hash=null,email_verifiedinherited from Google's claim) + identity row.
Server rotates the session (drops the anonymous pre-flow row, mints an authenticated one), sets cookies, redirects back to
CZ_MTG_PUBLIC_BASE_URL.
The state parameter is single-use: the server clears it the moment the callback fires, regardless of outcome. A replay (correct state or not) lands on a fresh 400.
Google Cloud Console — one-time setup
Open Google Cloud Console, pick or create a project.
APIs & Services → OAuth consent screen: User type
External, app name, your support / developer emails, scopesopenid email profile(built-in, no review). Add yourself as a test user while the app is inTestingmode.APIs & Services → Credentials → Create credentials → OAuth client ID: type
Web application. Under Authorized redirect URIs, register every environment you'll run from:http://localhost:8080/v1/auth/oauth/google/callback(local dev)https://<staging-host>/v1/auth/oauth/google/callbackhttps://<prod-host>/v1/auth/oauth/google/callback
Copy the Client ID and Client secret into your deployment's env (
CZ_MTG_OAUTH_GOOGLE_CLIENT_ID,CZ_MTG_OAUTH_GOOGLE_CLIENT_SECRET). SetCZ_MTG_PUBLIC_BASE_URLto the matching origin.
Caveats:
/v1/decklists/optimizeruns inline in the handler today. A 100-card list can fan out to roughly 1,500 top-level requests across the 15 default shop ids, plus Axion Now's bounded product-variant lookups, and take several seconds. Moving to a background job queue is tracked as A4 in issue #9.No rate limiting on the auth endpoints yet — that lands with G2.
GitHub OAuth is not wired yet — that's B1 PR6, and most of the schema (
oauth_identitieskeyed by(provider, provider_user_id)) is reused verbatim.No "unlink Google" endpoint yet — that needs the settings page (B2). Workaround for now: delete the row from
oauth_identitiesdirectly.
Limitations
Shipping cost isn't modelled explicitly. The
cheapeststrategy minimizes card prices only and ignores per-shop shipping fees. Usestrategy="fewest_shops"to consolidate into fewer orders (within 10% of the cheapest-split total by default, configurable viaCZ_MTG_CONSOLIDATE_TOLERANCE_PCT). Per-shop totals still let you eyeball trade-offs manually.blacklotus condition can occasionally still be
?if the product page lacks the gtag variant marker — best-effort only.Cardmarket per-seller offers require a paid Trader-tier API key, not yet wired up. Free tier surfaces priceGuide aggregates only.
Decklist size capped at 100 cards total AND 100 unique cards. Commander format is the largest legal format. The unique-cards limit is what actually drives the request count (one top-level search per unique card per enabled shop, plus Axion Now's bounded per-product variant lookups) and exists to keep a single tool call from spawning runaway traffic. Override via
CZ_MTG_MAX_UNIQUE_CARDSif you genuinely need a bigger list.No price history. Each query is a fresh snapshot. Track prices yourself if you need it (or open an issue requesting it).
Foreign-currency prices use daily CNB rates, with environment overrides and an offline fallback. EUR, GBP, and PLN rates are cached for 24 hours in memory and on disk. A stale real CNB rate is reused when refresh fails; only installations with no usable cache fall back to the static defaults. Setting
CZ_MTG_EUR_TO_CZK,CZ_MTG_GBP_TO_CZK, orCZ_MTG_PLN_TO_CZKdisables live resolution for that currency; Cardmarket'sMKM_EUR_TO_CZKtakes precedence for Cardmarket only.Axion Now stock is binary. Shopify exposes each variant only as available/unavailable, so
stock_qtyis1or0; no exact quantity or language field is available.MagicCorporation and Bazaar of Magic expose a single NM/default-new grade. MagicCorporation search results surface only in-stock new VO/VF variants; used “exemplaires uniques” and their per-copy grades would require detail-page fan-out and are not included. Bazaar of Magic's Bazaar Games catalog likewise exposes one new/NM grade and binary stock.
Bazaar of Magic searches only the first page of singles results (24 tiles). Pagination is deliberately not crawled so the shared Bazaar Games host request budget remains bounded.
JK Entertainment exposes only the default variant per listing. Other conditions/languages can exist behind its JavaScript-only sibling-variant selector, but the server-rendered GA4 dataLayer and aligned product box carry one purchasable variant and its current stock count.
Spellenwinkel currently has no MTG singles catalog. Its shared Bazaar Games adapter deliberately ignores sealed/accessory
div.productstiles and therefore returns zero offers for the checked catalog; it will begin parsing automatically ifdiv.singleslistings appear later.MTGSpot uses a reverse-engineered public gateway contract. The anonymous SPA key and
filter[...]request shape can change, and the API exposes edition names but no set codes.Trader-Online is results-page-only. Condition is absent from tiles and defaults to NM; stock is binary. The mixed search also contains
/card-purchase/buylist prices, which the adapter explicitly rejects before parsing offers.Magic Madhouse is currently blocked in production by Cloudflare TLS fingerprinting. Live requests from this server's HTTP stack (httpx) receive
403 Forbiddenon every search regardless of headers — Cloudflare appears to fingerprint the TLS handshake itself, which no header spoofing can address. The adapter ships and its offline fixture tests pass, but live searches return no offers; the aggregator isolates the failure (other shops are unaffected) andlist_shopssurfaces the error. AddmagicmadhousetoCZ_MTG_DISABLED_SHOPSto silence it, or wait — if the shop's protection changes, the adapter starts working again without a code change.Magic Madhouse defaults to BODL “from” prices (when reachable). The one-request default reports
condition=?and usually binary-like stock derived from the listing/dispatch text. Construct the adapter withenrich_variants=Trueto resolve exact per-condition price and stock for at most five name-matched option products; failures gracefully retain the BODL offer. Cross-game search crowding can still make ambiguous names incomplete.Magic Store returns Italian-localized names and fuzzy server matches. The adapter intentionally skips the English client-side name filter, so raw comparison can include fuzzy results and English decklist matching remains weak. Edition filters match the Italian storefront label, all offers default to NM, and only the single server-returned page is parsed (the captured single-card pages contained 30–78 rows).
Account features cover six of sixteen shop ids, with five of them fully cartable. Full login + cart:
najada(Djoser/DRF API),blacklotus(Shoptet),tolarie(Django + jQuerygetJSONper-product URLs),rishada(custom-PHP form POST —act=20005against/),cernyrytir(custom-PHP form POST —nakupzbozi=Pridat+carovy_kod). Login only:untap— cart works against Prestashop but doesn't persist between logins (see Known limitations). The nine additional European shop ids and Cardmarket are read-only.No automated checkout. The cart tools stop at "items are in the cart" — finalizing the order, shipping, and payment still happen manually on the shop's website. This is intentional.
Development
The local-clone setup is documented as Install method D above — that gets the working copy hooked up to Claude Desktop. To run the test suite, install with the dev extra and use pytest:
# from inside the cloned repo with the venv activated
pip install -e ".[dev]"
# Fast deterministic tests (offline fixtures; no live shop requests)
pytest
# Live smoke tests against real shops + Scryfall (~40s, 10 tests)
pytest -m live --override-ini="addopts="
# Manual MCP smoke test (server speaks stdio; Ctrl+C to exit)
python -m cz_mtg_compareThe shop adapters are tested against checked-in HTML/JSON fixtures in tests/fixtures/, so the bulk of the test suite is offline and deterministic. Live smoke tests under tests/test_live_smoke.py are opt-in via -m live.
Releasing to PyPI
The project ships a Publish to PyPI GitHub Actions workflow at .github/workflows/publish.yml. It builds an sdist + wheel, smoke-tests the wheel install + entry point in a fresh venv, then publishes via PyPI Trusted Publishing (OIDC — no API tokens stored anywhere).
One-time setup (maintainer only):
Create the project on PyPI by going to https://pypi.org/manage/account/publishing/ and adding a new "pending" Trusted Publisher with:
PyPI Project Name:
cz-mtg-compare-mcpOwner:
xvyslo05Repository name:
czech-mtg-price-comparatorWorkflow filename:
publish.ymlEnvironment name:
pypi
In the GitHub repo, go to Settings → Environments → New environment and create one named
pypi. (Optional: add required reviewers as a release-gate.)
Cutting a release:
# Bump the version in pyproject.toml, commit it
sed -i '' 's/version = "0.1.0"/version = "0.1.1"/' pyproject.toml
git add pyproject.toml && git commit -m "Release v0.1.1"
# Tag and push — this triggers the workflow
git tag v0.1.1
git push origin main v0.1.1The workflow runs on the tag push: build → smoke test → publish. Verify on https://pypi.org/project/cz-mtg-compare-mcp/. Users can then uvx cz-mtg-compare-mcp immediately.
Manual publish from a local machine (fallback if Trusted Publishing isn't set up yet):
pip install -e ".[dev]"
rm -rf dist && python -m build
python -m twine upload dist/* # prompts for PyPI API tokenRepo layout
src/cz_mtg_compare/
server.py MCP entrypoint; thin FastMCP wrappers around the
service layer. Registers search_card / optimize_decklist /
lookup_card / list_shops + account-feature tools
(shop_account_capabilities / shop_login /
add_to_cart / view_cart / clear_cart / add_to_watchlist)
service.py Transport-agnostic core (CardCompareService). All tool
logic lives here — MCP, FastAPI, future hosted MCP,
and tests all forward to this.
web/ FastAPI delivery surface (optional `[web]` extra).
app.py Route definitions, exception handlers, lifespan, middleware.
schemas.py Request bodies (responses reuse core pydantic models).
main.py `cz-mtg-compare-web` console-script entry point.
auth_config.py Cookie names, session TTL, Secure / SameSite knobs.
auth_schemas.py Pydantic request bodies for signup / login / verify.
email_verification.py Token issue/consume + verification URL builder.
mailer.py Mailer protocol + LoggingMailer default.
middleware.py SessionLoader + CSRF middlewares.
oauth_config.py GoogleOAuthSettings (client id/secret/redirect URI).
oauth_google.py GoogleOAuthClient protocol + authlib-backed default.
passwords.py argon2id hashing wrapper.
sessions.py Session create / load / cookie-attach helpers.
db/ Database layer (optional `[web]` extra).
config.py DatabaseSettings (reads CZ_MTG_DATABASE_URL).
engine.py Async engine + session factory + get_session dep.
models.py ORM models (Base, User, ...).
models.py Offer / Condition / SearchQuery / ShopId
aggregator.py async fan-out + per-shop timeouts + cache + get_adapter()
optimizer.py decklist optimization (multi-shop split + per-shop bundles)
decklist.py Arena/MTGO text parser; ≤100 cards
scryfall.py Scryfall lookup with throttle + disk cache
credentials.py env-var resolver with 1Password (`op://...`) support
http_client.py shared httpx.AsyncClient
cache.py TTL cache
normalize.py price / stock / condition / foil helpers
fx.py CZK conversion: override → env var → live CNB daily
rate (24h memory+disk cache) → static default
adapters/
base.py ShopAdapter ABC + account capability flags
tolarie.py (Django; login implemented, cart pending)
najada.py (JSON API on wizardshop.cz; login + cart implemented)
blacklotus.py (Shoptet HTML + detail-page enrichment)
cernyrytir.py (windows-1250 HTML)
rishada.py (custom-PHP tabular HTML)
untap.py (Prestashop HTML; condition+foil in product reference)
axionnow.py (Shopify predictive JSON + per-product variant JSON)
mtgspot.py (Polish gateway JSON API)
magiccorporation.py (Laravel HTML; per-row language/foil variants)
jkentertainment.py (Shopware GA4 dataLayer + DOM detail links)
bazaargames.py (shared Bazaar of Magic / Spellenwinkel HTML adapter)
traderonline.py (OXID HTML tiles; buylist rows excluded)
magicmadhouse.py (BigCommerce BODL JSON + optional variant enrichment)
magicstore.py (custom-PHP rows; Italian-localized names)
cardmarket.py (OAuth1 API; opt-in)
tests/
fixtures/ saved real-world responses
test_*_adapter.py deterministic adapter tests
test_fx.py FX resolution chain, CNB parsing, cache hermeticity
test_aggregator.py
test_optimizer.py
test_decklist_parser.py
test_scryfall.py
test_live_smoke.py opt-in live tests
docs/
adapter-plans/ per-shop integration plans from the EU-shop survey
(implemented + candidate + dropped shops, with recon notes)Etiquette: this server reads only public listing pages, identifies itself with a clear User-Agent, caps per-host concurrency at 3, and caches results for 10 minutes to avoid hammering shops. It's intended for personal price-comparison use. If a shop owner asks you to stop, please respect that.
Available Tools
10 toolsadd_to_cartA
Add count of a specific offer to the shop's online cart.
shop_ref is the per-shop product/article identifier that appears on
every Offer returned by search_card / optimize_decklist. For
najada it's the article UUID (e.g. d762c438-8915-4131-be7e-e301d91d8935);
for tolarie it's the numeric product id. Pass it through verbatim —
don't construct it yourself. If you don't have one for the offer the user
wants, call search_card first.
Logs in automatically on first call and transparently re-logs in if the cached token expires. Returns the shop's raw response (e.g. the updated cart-item record).
| Name | Required | Description | Default |
|---|---|---|---|
| shop | Yes | ||
| count | No | ||
| shop_ref | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Despite no annotations, the description discloses automatic login and re-login behavior and the raw return format. It does not fully cover error handling or potential side effects like duplicate handling, but the disclosed behaviors are helpful.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is concise at 5 sentences, front-loaded with the core purpose, and each sentence adds value without redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the presence of an output schema, the description adequately covers return value, parameter usage, and authentication. It could be improved by mentioning edge cases like failed additions, but overall it's sufficient.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
With 0% schema description coverage, the description compensates well by detailing shop_ref with examples and warnings. However, it lacks explanation for the 'shop' and 'count' parameters beyond what the schema provides.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool adds a count of a specific offer to the shop's online cart, which is a precise verb+resource pair. It distinguishes itself from siblings like add_to_watchlist by focusing on cart operations.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly instructs to call search_card first if the shop_ref is missing, providing clear guidance on when to use this tool. However, it does not explicitly state when not to use it (e.g., vs. add_to_watchlist).
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
add_to_watchlistA
Add an offer to the shop's wishlist / watchlist / wantlist if the shop
supports it. shop_ref is the same per-shop product/article identifier
used by add_to_cart. Raises AccountFeatureNotSupported if the shop
has no watchlist concept — check shop_account_capabilities first.
| Name | Required | Description | Default |
|---|---|---|---|
| shop | Yes | ||
| shop_ref | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries full burden. It discloses that the tool raises AccountFeatureNotSupported if the shop lacks a watchlist, but does not cover other behavioral aspects like authentication requirements, idempotency, or side effects.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is three sentences, front-loaded with the main purpose. Each sentence adds distinct information. It is concise without being terse.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's simplicity (2 params, no annotations, but presence of output schema), the description covers the essential aspects: action, parameter semantics, preconditions, and error handling. No critical gaps are apparent.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 0%, so description must add value. It clarifies that shop_ref is the same identifier used by add_to_cart, which is helpful. However, it does not explain the shop parameter beyond what the enum provides.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the action ('Add an offer to the shop's wishlist') and resource. It distinguishes from sibling tools like add_to_cart by specifying the target (watchlist vs cart).
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description tells when to use the tool (when shop supports watchlist) and references shop_account_capabilities as a prerequisite. It also explains the shop_ref parameter's relationship to add_to_cart. However, it does not explicitly state when not to use or list alternatives.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
clear_cartA
Remove every item from the shop's online cart for the authenticated user.
Returns {"removed_items": <int>}. Destructive — confirm with the user
before calling.
| Name | Required | Description | Default |
|---|---|---|---|
| shop | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Given no annotations, the description fully discloses the destructive nature (requires confirmation) and the return format (\`{"removed_items": <int>}\`). This adds behavioral context beyond the schema. It could be improved by specifying that the entire cart is cleared, not partial, but the current detail is sufficient.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is extremely concise: two sentences and a return type snippet. Every part adds value (purpose, destructive warning, return format). No filler or redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple tool with one parameter and a clear return, the description is nearly complete. It covers purpose, destructive behavior, and return format. The only gap is the lack of parameter explanation, but the output schema is present and the sibling tools (e.g., list_shops) might provide context. Overall, it adequately supports an agent in selecting and invoking the tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The input schema has one parameter 'shop' with an enum but no description. The tool description does not explain what 'shop' represents or list the valid values. With 0% schema description coverage, the description should compensate but fails to add meaningful parameter semantics. The agent must infer from the tool name and siblings like list_shops.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the verb ('Remove'), resource ('every item from the shop's online cart'), and scope ('for the authenticated user'). This distinguishes it from siblings like view_cart (viewing) and add_to_cart (adding). The purpose is specific and unambiguous.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly mentions the tool is destructive and advises confirming with the user before calling, which provides clear usage guidance. However, it does not specify when to use alternatives like add_to_cart (for adding items) or view_cart (for inspection), though the destructive hint strongly implies caution.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_shopsA
List configured shops with their last-call status.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description must carry the full burden. It indicates a read operation but does not disclose any potential side effects, authentication requirements, or rate limits. The meaning of 'last-call status' is not explained, leaving some ambiguity about the data returned.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single, front-loaded sentence with no unnecessary words. It efficiently conveys the tool's purpose.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given zero parameters and the existence of an output schema (which likely describes the return structure), the description is adequate. It could be improved by clarifying 'last-call status', but it is complete enough for a simple list tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
There are no parameters (schema coverage 100%), so the description does not need to add parameter details. The baseline is 4, and the description is sufficient as it does not repeat schema information.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the verb 'List', the resource 'configured shops', and includes 'with their last-call status' to specify the information returned. This distinguishes it from sibling tools which focus on cart operations, card searching, and account management.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description does not explicitly state when to use this tool versus alternatives. While the sibling tools have distinct purposes (e.g., add_to_cart, search_card), no direct guidance is provided for when this tool is preferred or not.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
lookup_cardA
Resolve a Magic: The Gathering card name via Scryfall.
Returns canonical name, set code/name, oracle text, mana cost, image URL, Scryfall URI. Useful for disambiguating reprints, validating spelling, or fetching multilingual names.
Set exact=True to require an exact name match; default is fuzzy.
Returns null if no card matches.
| Name | Required | Description | Default |
|---|---|---|---|
| name | Yes | ||
| exact | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries full burden. It discloses return fields, null return on no match, and the fuzzy vs. exact search behavior. Could mention rate limits or Scryfall API details, but for a read-only lookup it is adequately transparent.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences plus a list of return fields, all front-loaded. Every sentence adds value: purpose, use cases, and parameter clarification. No wasted words.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's simplicity (2 params, output schema exists), the description covers all essential aspects: purpose, return values, behavior on no match, and parameter options. The output schema covers return format, so no additional explanation needed.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 0%, so description must compensate. It explains the 'exact' parameter (set to true for exact match, default fuzzy) and the 'name' parameter implicitly as card name. Adds meaning beyond schema names, though 'name' could be more explicitly described.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Description starts with 'Resolve a Magic: The Gathering card name via Scryfall,' clearly stating the verb-resource pair. It distinguishes from sibling 'search_card' by focusing on resolving a single name with fuzzy/exact matching, and lists key return fields (canonical name, set, oracle text, mana cost, image URL, Scryfall URI).
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly states usefulness for 'disambiguating reprints, validating spelling, or fetching multilingual names,' providing clear context for when to use. Does not explicitly mention when not to use or compare to siblings, but the context is sufficient for the agent.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
optimize_decklistA
Resolve a Magic decklist (Arena/MTGO text) against all shops in parallel
and return a shopping_plan built under one of two strategies.
Strategies (strategy param):
"cheapest"(default): per-card greedy lowest price across all shops. Minimises total CZK; may fragment the order across many shops."fewest_shops": minimises the number of distinct shops in the final plan so the user places fewer separate orders / pays less shipping. Stays within 10% of the cheapest-split total by default; override the tolerance via theCZ_MTG_CONSOLIDATE_TOLERANCE_PCTenv var (integer percent). When no single shop can cover everything, cards missing from the chosen set fall back to the globally-cheapest offer (which may add one or more shops to the plan).
Response shape:
strategy: echoes the strategy that produced the picks/plan.picks: chosen offer per card under the active strategy.shopping_plan: the picks regrouped per shop — render as a summary table. Each group has the shop name, alineslist (quantity, card name, edition, condition, foil, unit price, subtotal, url) and asubtotal_czk. Groups sorted by descending shop subtotal.cheapest_split_total_czk: total of the per-card cheapest split, always populated as a reference even in fewest_shops mode.consolidated_total_czk: total of the consolidated plan in fewest_shops mode (equals the sum ofshopping_plansubtotals).nullin cheapest mode.per_shop_bundles: how each individual shop covers the decklist on its own (cards covered/missing, single-shop total CZK), sorted best-to-worst.cheapest_split_missing: cards no shop has in stock.
When presenting results to the user, render the shopping_plan as a
per-shop chart: one section per shop with a table of cards to buy from
it, plus the shop subtotal. Then show the headline total (use
consolidated_total_czk for fewest_shops, otherwise
cheapest_split_total_czk) and any cheapest_split_missing. In
fewest_shops mode it's useful to also surface the delta vs. the cheapest
split so the user can see the consolidation premium.
The decklist must contain at most 100 cards in total (Commander deck size)
AND at most 100 unique cards (one HTTP request per unique card per shop —
the unique-cards cap exists to keep a single tool call from spawning a
runaway number of requests). Override the unique cap via the
CZ_MTG_MAX_UNIQUE_CARDS env var if you need a higher limit. Format
example:
4 Lightning Bolt
4 Counterspell
2 Sol Ring (CMR) 263
Sideboard
1 NegateDisplay-only products (Art Series, oversized, helper cards) are excluded by
default. Pass include_non_playable=True to keep them in the picks.
Shop selection mirrors search_card:
shops: allow-list (None = all configured shops).exclude_shops: deny-list (applied after the allow-list). Excluded shops also disappear fromper_shop_bundlesso the chart only shows the shops the user actually wants to see.
| Name | Required | Description | Default |
|---|---|---|---|
| shops | No | ||
| decklist | Yes | ||
| strategy | No | cheapest | |
| exclude_shops | No | ||
| in_stock_only | No | ||
| include_non_playable | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| picks | Yes | |
| strategy | No | |
| total_cards | Yes | |
| unique_cards | Yes | |
| parser_errors | No | |
| shopping_plan | No | |
| per_shop_bundles | Yes | |
| cheapest_split_missing | Yes | |
| consolidated_total_czk | No | |
| cheapest_split_total_czk | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description fully bears the burden of behavior disclosure. It details parallel requests, strategy logic, limits (100 cards/unique cards), default exclusions, shop filtering, and response shape, leaving no major behavioral gaps.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is well-organized in sections but overly verbose, running multiple paragraphs. While thorough, it could be more concise without losing essential information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's complexity (two strategies, multiple parameters, output schema), the description is highly complete. It covers response fields, conditional behavior, user presentation guidance, and edge cases like missing cards, exceeding the schema alone.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 0%, so the description must compensate. It does so thoroughly for all 6 parameters, explaining strategy options, shop filtering, include_non_playable, and in_stock_only, far beyond just listing them.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly specifies the tool's purpose: 'Resolve a Magic decklist against all shops in parallel and return a shopping_plan.' It differentiates from siblings like search_card (single card) and add_to_cart (cart operations) by focusing on batch optimization.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explains when to use the tool and details two strategies with trade-offs, constraints, and override mechanisms. However, it does not explicitly state alternatives for single-card lookups or when not to use it, though the context is clear.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_cardA
Search a Magic: The Gathering single card across Czech shops.
Returns a flat list of offers sorted by price_czk ascending. Each offer has shop, edition, condition, language, foil, price_czk, stock_qty, and a deep-link url.
Shop selection:
shops: optional allow-list. If given, only these shops are queried. None = all.exclude_shops: optional deny-list. Any shop here is dropped aftershopsis applied. Use this when the user wants results "from everywhere except shop X".
Display-only products (Art Series, oversized, helper / tip / checklist cards,
spindowns) are excluded by default because they aren't legal in constructed
Magic formats. Pass include_non_playable=True if you specifically want them
(e.g. for a collector / art print query).
| Name | Required | Description | Default |
|---|---|---|---|
| name | Yes | ||
| shops | No | ||
| edition | No | ||
| exclude_shops | No | ||
| in_stock_only | No | ||
| include_non_playable | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Discloses default sorting (price_czk ascending), exclusion of non-playable cards, and shop filtering logic. No annotations exist, so description carries full burden; it is thorough but omits rate limits or auth requirements.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with purpose and output details. Reasonably concise, though the shop logic explanation could be more terse. No wasted sentences.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With 6 parameters and no schema descriptions, the description covers most critical aspects but omits 'edition' and 'in_stock_only' semantics. Output schema exists but isn't shown; still, the return field list is provided.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 0%, so description must explain all parameters. It explains shops, exclude_shops, and include_non_playable, but does not mention the 'edition' parameter or the 'in_stock_only' default behavior. This leaves gaps.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool searches Magic cards across Czech shops and returns sorted offers. It distinguishes from sibling tools like lookup_card (card details) and list_shops (shop listing).
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides clear context for shop filtering (allow/deny lists) and when to include non-playable cards. Does not explicitly state when not to use this tool versus alternatives, but the sibling context helps.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
shop_account_capabilitiesA
Per-shop account-feature support and credential status.
Returns one entry per enabled shop with:
shop: shop idsupports_login/supports_cart/supports_watchlist: capability flagscredentials_configured: whetherCZ_MTG_<SHOP>_USERandCZ_MTG_<SHOP>_PASSenv vars are both set (does NOT resolveop://...1Password references — just a presence check).
Use this to tell the user which shops they can actually log into and which account actions are available before attempting them.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations provided, so description carries full behavioral burden. It explains that credential check is a presence check of env vars and does NOT resolve 1Password references, which is a critical behavioral detail. Also notes it returns one entry per enabled shop.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Description is well-structured with bullet points and code formatting. It is somewhat lengthy but every sentence adds value. Could be slightly more concise, but overall efficient.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given zero parameters and no output schema (but description provides output structure), the description is fully complete. It covers purpose, behavior, usage guidance, and output details, leaving no gaps for an agent to misinterpret.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
No parameters in schema, so baseline is 4. Description adds meaning by detailing output fields and their significance, compensating for the lack of parameters.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states it returns 'Per-shop account-feature support and credential status.' It lists specific output fields and distinguishes from sibling tools like shop_login by focusing on capability checking rather than performing actions.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly says 'Use this to tell the user which shops they can actually log into and which account actions are available before attempting them.' This provides clear when-to-use guidance and implies alternatives (e.g., shop_login for actual login).
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
shop_loginA
Authenticate against a shop using its CZ_MTG__USER / _PASS credentials.
The credentials may be literal strings or 1Password secret references of the
form op://Vault/Item/Field — the latter is resolved via the op CLI
on first use. Login sessions are kept in-process for as long as the MCP
server runs and are reused automatically by add_to_cart / view_cart.
Returns {"shop": ..., "ok": true} on success. Raises with a clear message
if the shop doesn't support login, credentials are missing/invalid, or the
1Password reference can't be resolved.
| Name | Required | Description | Default |
|---|---|---|---|
| shop | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations provided, but description fully discloses session reuse, credential handling (including 1Password), error cases, and return format.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Multi-sentence but well-structured and front-loaded. Each sentence adds value. Slightly longer than necessary but not excessive.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Complete given single param and implied output schema. Covers inputs, behavior, session lifecycle, and error messages.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Only one parameter with enum; description adds value by explaining credential naming convention (CZ_MTG_<SHOP>_USER / _PASS), not in schema. Schema coverage 0%, description compensates well.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Clearly states authentication against a shop using credentials. Distinguishes from siblings like add_to_cart and view_cart.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explains when to use (before add_to_cart/view_cart) and reuses sessions. No explicit when-not-to-use, but context from sibling tools implies it.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
view_cartA
Return the current contents of the shop's online cart for the authenticated user. Logs in automatically if needed.
| Name | Required | Description | Default |
|---|---|---|---|
| shop | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Discloses automatic login behavior. No annotations provided, so description carries full burden; is reasonably transparent for a read operation.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences, no fluff. Perfectly sized for the tool's simplicity.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Covers purpose and key behavior (auto-login). With output schema present, return values need no description. Minor gap: no mention of empty cart handling.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema has enum for 'shop' but no description; parameter meaning is clear from context. No extra detail added beyond schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Clearly states 'Return the current contents of the shop's online cart', using a specific verb and resource. Distinguishes from siblings like add_to_cart and clear_cart.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Implicitly indicates when to use (viewing cart) and notes automatic login, but lacks explicit exclusions or alternatives for viewing.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
TDQS
Each tool targets a distinct operation: listing shops, searching cards, optimizing decklists, managing cart/watchlist, and account capabilities. No two tools have overlapping purposes; even similar actions like add_to_cart and view_cart are clearly separated by verb.
All tool names follow a consistent verb_noun pattern with underscores (e.g., list_shops, search_card, add_to_cart). No mixing of camelCase or other conventions.
10 tools is well within the ideal 3-15 range for a focused server. Each tool earns its place for a comprehensive MTG shopping experience.
The toolset covers the core workflow: search, optimize, cart management, login, and watchlist. Minor gaps exist, such as no ability to remove a single item from cart (only clear all) or update cart quantities, but these are not critical.
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
Live TCG card and decklist prices for AI assistants. 22+ games, no account, ready-to-buy links.
Daily EU card prices, deal detection and EU-vs-US arbitrage for 19 trading card games.
Search Rakuten Ichiba products and compare prices via Claude. Zero setup, no API key needed.
Search ~8.5M products from 2,500+ Central European e-shops. Semantic, keyword, GTIN lookup.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceEnables Magic: The Gathering players to manage decks and access card information through Claude, supporting gameplay actions like drawing cards and mulligans while providing Scryfall API integration for card lookups.15
- FlicenseNot gradedqualityDmaintenanceEnables querying and comparing product prices across different marketplaces with real-time updates. Deployable on Cloudflare Workers with tools for searching products, comparing prices, and retrieving price history.
- AlicenseAqualityCmaintenanceEnables Claude to search and retrieve Magic: The Gathering card details, prices, set information, and random cards from Scryfall's database through natural language.42MIT
- AlicenseAqualityAmaintenanceMCP server for Magic: The Gathering card prices, deck analysis, sealed product EV calculations, and investment insights, powered by live data from 5 vendors covering 99K+ cards.21MIT
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/xvyslo05/czech-mtg-price-comparator-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server