piltover-archive
Click on "Deploy 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., "@piltover-archive what does Vi, Destructive cost in Riftbound?"
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.
PiltoverArchives-mcp
An MCP server for the Piltover Archive API — a community card database, deck builder and proxy generator for the Riftbound TCG.
It exposes card search, card lookup, set listing and decklists as MCP tools, so an assistant can answer questions about Riftbound cards it otherwise knows nothing about.
Why this exists
Riftbound is recent enough that its card data is not in any language model's
training data. Ask an assistant what Vi, Destructive costs and you get a guess.
This server closes that gap by giving it the real database.
Related MCP server: Scryfall MCP Server
Install
git clone https://github.com/Liaxum/PiltoverArchives-mcp.git
cd PiltoverArchives-mcp
npm install
npm run buildClaude Code
claude mcp add piltover-archive -- node /absolute/path/to/PiltoverArchives-mcp/dist/index.jsClaude Desktop
Add to claude_desktop_config.json:
{
"mcpServers": {
"piltover-archive": {
"command": "node",
"args": ["/absolute/path/to/PiltoverArchives-mcp/dist/index.js"]
}
}
}No credentials are needed. See Authentication — supplying an API key currently makes things worse, not better.
Tools
Tool | What it does |
| Search cards by text, colour, type, set, rarity, tag, and energy/power/might ranges |
| Resolve known cards in one request by collector number, exact name, or UUID |
| List every set with its prefix and release date |
| Search public decklists by name, description or legend |
| Fetch one decklist, resolving card UUIDs into real card names |
All tools are read-only and take a response_format of markdown (default) or
json.
piltover_get_deck is the one that earns its keep: the API stores deck contents
as bare UUIDs with no names attached, so the tool batch-resolves every one of
them and renders a readable list.
## Main deck (39)
- 3x Charm [OGN-043] — Spell, 1 energy, Calm
- 3x B.F. Sword [SFD-161] — Gear, 4 energy, OrderConfiguration
Variable | Default | Purpose |
|
| Override the API base |
| (unset) | Optional bearer token. Leave unset — see below |
Development
npm run build # compile TypeScript
npm run inspect # run against the MCP InspectorAPI reference
The rest of this document records what was learned about the upstream API. It is kept because none of it is officially documented anywhere.
How this was determined
Piltover Archive publishes no API documentation. There is no docs. or
developers. host, no OpenAPI/Swagger document, no developer section on the
site, and nothing indexed by search engines.
Everything below was reverse-engineered from the production Next.js client
bundles (https://piltoverarchive.com/_next/static/chunks/*.js, notably the
API client in module 491560) and then verified with live requests. Where
something was inferred but not confirmed against the running API, it is marked
as such.
Because none of this is a published contract, it may change without notice. Treat it as observed behaviour, not a stable interface.
Base URL
The API is not on a separate host. It is a route inside the main Next.js app:
https://piltoverarchive.com/api/external/v1The client bundle reads a server-side EXTERNAL_API_URL environment variable
and falls back to the relative path /api/external, which suggests the backend
may also be reachable directly on another origin. That origin is not public;
api.piltoverarchive.com does not resolve.
Infrastructure observed from response headers: Cloudflare in front of Railway
(x-railway-edge: iad1), with Clerk for user
authentication (x-clerk-auth-status).
Authentication
Bearer tokens only:
Authorization: Bearer <token>An x-api-key header is not read — sending the key that way is treated as
no credential at all.
Two kinds of bearer token exist. The web app sends a Clerk session token for
signed-in users. Programmatic clients are expected to send an API key with
an ak_ prefix.
Important: the read endpoints are public
/v1/cards, /v1/decks and /v1/sets return 200 with no credentials at
all. A card-data MCP server needs no authentication to be useful.
The ak_ API key and partner status
An ak_-prefixed API key was tested against this API. The results are worth
recording, because the failure mode is misleading:
Request | Response |
Deliberately invalid |
|
The real |
|
The real key on |
|
The key is valid and recognised — it produces a different error than a
forged one, and it passes the authentication gate on /v1/partners only to
fail the authorization gate. What it lacks is an association with a partner
record on the server side.
The practical consequence is that sending the key is currently worse than sending nothing:
Endpoint | No auth | With the |
| 200 | 401 |
| 200 | 401 |
| 200 | 401 |
| 401 | 401 |
| 401 | 401 |
Authenticating downgrades otherwise-public endpoints to 401 and unlocks
nothing.
This is not a missing request header. X-Partner-Id, X-Partner and
X-Partner-Key were each tried alongside the bearer token and all returned the
identical PARTNER_REQUIRED. Partner association has to be granted server-side.
The app has an internal /admin/partners route, a checkIsPartner server
action and a usePartnerStatus hook, and exposes "Partner" as an account tier
alongside admin and supporter — so this is an account flag the Piltover Archive
maintainers set, reachable through their
help channels (email or Discord).
Recommendation for this server: build against the public endpoints and do
not send a key by default. Put the Authorization header behind an opt-in
setting so it can be switched on if and when partner access is granted.
Response envelope
List endpoints return:
{
"data": [ ... ],
"pagination": {
"total": 1238, "page": 1, "limit": 1,
"totalPages": 1238, "hasNext": true, "hasPrevious": false
},
"meta": { "filters": { "colors": [ { "id": "…", "name": "Body", "count": 223 } ] } }
}meta.filters carries facet counts for the current result set, which is handy
for exposing filter options without a second request.
Errors are JSON: {"error": "CODE", "message": "…"}. Some responses also carry
a requestId. Validation failures use a different shape:
{"type":"validation","on":"params","found":{…}} with status 422.
Observed error codes: INVALID_API_KEY, AUTH_REQUIRED, PARTNER_REQUIRED,
ADMIN_REQUIRED, INVALID_REQUEST, PREMIUM_REQUIRED, NOT_FOUND.
Endpoints
Cards
GET /v1/cards # search — public
POST /v1/cards/batch # bulk lookup — public/v1/cards/batch accepts at least one of variantNumbers, names or ids:
curl -X POST https://piltoverarchive.com/api/external/v1/cards/batch \
-H 'Content-Type: application/json' \
-d '{"variantNumbers":["ARC-001"]}'Search parameters, taken from the client's own parameter normaliser
(urlToCardSearchParams) and confirmed working:
Kind | Parameters | Notes |
Free text |
| trimmed, truncated to 200 chars |
Identity |
| truncated to 64 chars |
Multi-value |
| comma-separated, truncated to 500 chars |
Ranges |
| non-negative integers |
Sorting |
|
|
Paging |
| |
Convenience |
| client-side sugar that sets |
Example — ?q=Vi&limit=2&sort=name&dir=asc returns 56 matches; ?energyMin=5
returns 313.
A card variant looks like:
{
"id": "a60d2063-…", "variantNumber": "ARC-001",
"rarity": "Showcase", "variantType": "Promo", "foilMode": "foil_only",
"imageUrl": "https://piltoverarchive.b-cdn.net/…webp",
"artist": "Fortiche Production", "releaseDate": "2025-10-14",
"variantLabel": "Arcane Box Promo",
"showInLibrary": true, "isCollectible": true,
"cardmarketId": 858987, "tcgplayerId": 678053,
"cardmarketPrice": 94.35, "tcgplayerPrice": 97.16,
"set": { "id": "…", "name": "Arcane Box Set", "prefix": "ARC", "releaseDate": "2025-12-31" },
"card": {
"id": "…", "name": "Vi, Destructive",
"types": ["Unit"], "type": "Unit", "super": "Champion",
"description": "[GANKING] …", "energy": 2, "might": 3, "power": 1,
"tags": ["Vi", "Piltover"], "maxCopies": null, "banEffectiveDate": null,
"colors": [ { "id": "…", "name": "Fury", "hexCode": "#CB222D", "imageUrl": "…" } ]
}
}Note the card/variant split: a card is the game object, and the top level is a
printing of it. Market prices come from Cardmarket and TCGplayer.
Sets
GET /v1/sets # publicReturns { id, name, prefix, releaseDate, imageUrl }, e.g.
Origins | Nexus Night (OGN-NN).
Decks
GET /v1/decks # list — public
GET /v1/decks/{uuid} # detail
PATCH /v1/decks/{uuid}
DELETE /v1/decks/{uuid}
GET /v1/decks/{uuid}/likes
POST /v1/decks/{uuid}/like
DELETE /v1/decks/{uuid}/like{uuid} must be a UUID — /v1/decks/me returns 422.
Deck list parameters differ from card search in two ways worth knowing:
The request parameter is
limit, but the response reports it back aspageSize. SendingpageSizeis ignored and you silently get the default 20.qsearches deck name, description and legend name, so a champion name finds decks led by that champion even when the title never mentions it.sort+dirorder the results;sortByis ignored here (unlike card search).totalappears to cap at 10000.
Deck contents are stored as bare identifiers — {cardId, variantId, quantity} —
with no card names. Sections are champions, battlefields, runes,
maindeck, sideboard and bench. To render a decklist you must collect the
ids and resolve them through POST /v1/cards/batch with ids.
Deck export
All POST, all taking a deckCode plus an options object. image and
proxies return a binary blob rather than JSON; text returns { "text": … }.
POST /v1/decks/export/image # deck image (PNG)
POST /v1/decks/export/proxies # printable proxy PDF
POST /v1/decks/export/registration # tournament registration sheet
POST /v1/decks/export/tts # Tabletop Simulator
POST /v1/decks/export/text # plain text listExport options seen in the client include expandCards, showQR,
showWatermark, sortBy (as {field, order} pairs) and per-section toggles
(legend, battlefields, runes). Some option combinations return
PREMIUM_REQUIRED. Only the image export passes a bearer token.
Authenticated / restricted
Reachable but gated; not usable without partner or admin status:
GET /v1/notifications # AUTH_REQUIRED
GET /v1/collection/stats # AUTH_REQUIRED
GET /v1/collection/export
GET /v1/collection/sets/{id}/details
GET /v1/collection/users/{id}/ownership
PATCH /v1/collection/{id}
PATCH /v1/collection/binders/{id}
DELETE /v1/collection/{id}
DELETE /v1/collection/binders/{id}
GET /v1/partners/{id}
GET /v1/partners # ADMIN_REQUIRED
PATCH /v1/admin/decks/{id}/featured # admin only/v1/users and /v1/news return a bare 403 {"error":"Forbidden"}.
/v1/tournaments returns NOT_FOUND, despite tournaments existing in the
product — that data is presumably served another way.
Implementation status
The tools listed at the top of this document are implemented and verified against the live API. All of them use unauthenticated endpoints.
Collection and notification tools are not implemented: they require partner access that the current key does not have. They can be added once Piltover Archive grants it, without changing anything already built.
Caveats
None of this is a published API. It can change at any time, without notice.
Be a good citizen: the endpoints are public but not advertised as such. Cache responses, respect the
limitcap of 100, and do not hammer the service.Card data and images are Riot Games intellectual property, served by a community project. This repository is unaffiliated with both.
Available Tools
5 toolspiltover_get_cardsLook up Riftbound cards by identifierARead-onlyIdempotent
Resolve known Riftbound cards in one request, by collector number, exact name, or UUID.
Provide at least one of variantNumbers, names or ids; they can be combined.
This is the right tool for turning the cardId/variantId values inside a deck
into real card names, and for looking up a list of cards without one search per card.
Args:
variantNumbers: collector numbers, e.g. ["OGN-007"]
names: exact card names, e.g. ["Vi, Destructive"]
ids: card or variant UUIDs
response_format: 'markdown' (default) or 'json'
Examples:
"What is OGN-007?" -> { variantNumbers: ["OGN-007"] }
Resolving a decklist -> { ids: ["0283d70f-…", "f40b0e74-…"] }
Don't use when: you are searching by criteria rather than identity — use piltover_search_cards.
Inputs: ids, names, variantNumbers, response_format.
piltover_get_deckGet a Riftbound decklistARead-onlyIdempotent
Fetch one deck by ID and return its full contents.
The API stores deck contents as bare card UUIDs, so by default this tool resolves every one of them into card names, types, costs and colours — that resolution is the point of the tool, and it costs a few extra requests. Set resolve_cards=false for the raw id form.
Sections returned: champions, battlefields, runes, main deck, sideboard, bench.
Args:
deck_id: the deck UUID, as returned by piltover_search_decks
resolve_cards: resolve card UUIDs to names (default true)
response_format: 'markdown' (default) or 'json'
Examples:
"What's in deck ae198cdf-…?" -> { deck_id: "ae198cdf-…" }
Don't use when: you only need deck metadata for many decks — piltover_search_decks already returns that.
Inputs: deck_id (required), resolve_cards, response_format.
piltover_list_setsList Riftbound setsARead-onlyIdempotent
List every Riftbound card set, with its prefix and release date.
Set prefixes (e.g. OGN, ARC) are what the sets filter of piltover_search_cards
expects, so call this first when a question names a set in words ("the Arcane box")
rather than by prefix.
Args:
response_format: 'markdown' (default) or 'json'
Examples:
"What Riftbound sets exist?" -> {}
"What's the prefix for Origins?" -> {} then read the table
Inputs: response_format.
piltover_search_cardsSearch Riftbound cardsARead-onlyIdempotent
Search the Piltover Archive database of Riftbound trading card game cards.
Riftbound is a Riot Games TCG whose card data is not part of any model's training data, so use this tool rather than answering from memory whenever a specific card, cost, rules text or price is involved.
Filters combine with AND. Every filter is optional; with none, it lists all cards.
Results are card printings (variants), each carrying the game card under card.
Args:
q: free-text across name, rules text, flavour and artist
name / description / artist / flavor: search one field only
colors / sets / types / supertypes / rarities / tags: multi-value filters
energyMin|Max, powerMin|Max, mightMin|Max: numeric ranges
releaseDateAfter: ISO date lower bound
sort + dir: ordering; page + limit: pagination (limit max 100)
response_format: 'markdown' (default) or 'json'
Examples:
"What does Vi, Destructive do?" -> { name: "Vi, Destructive" }
"Cheap Fury units" -> { colors: ["Fury"], types: ["Unit"], energyMax: 2 }
"Most expensive Showcase cards" -> { rarities: ["Showcase"], sort: "cardmarketPrice", dir: "desc" }
Don't use when: you already have exact collector numbers or IDs — use piltover_get_cards, which is one request instead of many.
Inputs: q, dir, name, page, +21 more.
piltover_search_decksSearch Riftbound decksARead-onlyIdempotent
Search public decklists published on Piltover Archive.
Useful for seeing how a champion or archetype is actually built, and what the community is playing right now.
Args:
q: free-text across deck name, description and legend name, so a champion name finds decks led by that champion even when the title never says it
sort: field to order by, e.g. likes, views, createdAt (pair with dir)
dir: 'asc' or 'desc'
page, limit: pagination (limit max 100)
response_format: 'markdown' (default) or 'json'
Examples:
"Popular Akali decks" -> { q: "Akali", sort: "likes", dir: "desc" }
"Newest decks" -> { sort: "createdAt", dir: "desc" }
Returns deck summaries including a deck ID; pass that to piltover_get_deck for the full card list.
Inputs: q, dir, page, sort, +2 more.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 5 tools
Each tool has a clearly distinct purpose: search vs. identity lookup for cards, list sets, search vs. fetch for decks. The descriptions even include explicit 'Don't use when' guidance steering between the card-search and card-get pair, which is the only plausible overlap.
All five tools follow a strict piltover_<verb>_<noun> snake_case pattern (search_cards, get_cards, list_sets, search_decks, get_deck). Search/get/list verbs are used consistently and distinguish the operations predictably.
Five tools is well-scoped for a read-only card and deck archive: two for cards, one for sets, two for decks. No bloat, and no single tool is doing unrelated work.
The surface covers the core lifecycle for a read-only archive: card search, identity resolution, set enumeration, deck search, and full deck retrieval, with resolution of deck UUIDs built in. Minor gaps remain (e.g. no explicit rulings/price-history or set-contents browsing beyond the search filter), but these are workable via piltover_search_cards.
Maintenance
Related MCP Connectors
Search Stack Exchange questions, fetch Q&A threads as markdown, look up tag FAQs and user profiles.
Search 16,000+ API operations and 95 built tools by need, then call any of them by name
Read-only search of 2,189 AI agent tools (MCP servers, x402 APIs) with daily automated trust scores.
Pay-per-use tool marketplace for AI agents. Search, price-check, and call APIs via MCP.
Related MCP Servers
- AlicenseBqualityDmaintenanceEnables AI assistants to search and retrieve Magic: The Gathering card data through the Scryfall API. Supports card searches, random card generation, autocomplete, set listings, and rulings lookup.2272 npm1MIT
- AlicenseAqualityDmaintenanceEnables Claude to search and retrieve Magic: The Gathering card details, prices, set information, and random cards from Scryfall's database through natural language.42MIT
- AlicenseNot gradedqualityDmaintenanceProvides AI assistants with access to Magic: The Gathering card data via Scryfall API, enabling card search, image downloads, and database management.60 npm2Apache 2.0
- FlicenseAqualityDmaintenanceProvides AI assistants with real-time access to Magic: The Gathering card data, prices, rulings, search, and synergy discovery via the Scryfall API.5-