pokemon-tcg-mcp 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,46 @@
1
+ # pokemon tcg mcp
2
+
3
+ The Pokémon TCG via [TCGdex](https://www.tcgdex.dev/), open database, no key.
4
+
5
+ ## Tools
6
+
7
+ - `search_cards`, search by name, filter by rarity
8
+ - `get_card`, full card: HP, types, abilities, attacks, weakness, market prices
9
+ - `list_sets`, all sets, newest first
10
+ - `get_set`, a set with its full card list
11
+
12
+ ## Run
13
+
14
+ ```bash
15
+ npm install && npm run build && node dist/index.js
16
+ ```
17
+
18
+ ## Example
19
+
20
+ > "Show me Charizard's Vivid Voltage card"
21
+ > `search_cards("charizard")` → `get_card("swsh4-25")`
22
+
23
+ ## Quick start
24
+
25
+ ```bash
26
+ npm install
27
+ npm run build
28
+ node dist/index.js
29
+ ```
30
+
31
+ The server uses stdio, so it can be connected to Claude Desktop, Cursor, VS Code, MCP Inspector, or another compatible MCP client.
32
+
33
+ ## Tools at a glance
34
+
35
+ - `search_cards`: Search Pokémon TCG cards by name, optionally filtered by rarity.
36
+ - `get_card`: Get a single card
37
+ - `list_sets`: List Pokémon TCG sets (newest first).
38
+ - `get_set`: Get a Pokémon TCG set and its card list.
39
+
40
+ ## Limits and privacy
41
+
42
+ This project is intentionally narrow. It should be treated as a practical helper, not a complete certification or security audit. Check the implementation and the returned data before using it with sensitive material. No credentials are required unless the project explicitly says otherwise.
43
+
44
+ ## Try it
45
+
46
+ After building, connect the server through your MCP client. The repository root also contains `smoke-test.mjs` for projects covered by the shared harness. A typical tool call starts with `search_cards`.
package/dist/api.d.ts ADDED
@@ -0,0 +1,73 @@
1
+ /**
2
+ * TCGdex API v2 client — the open Pokémon TCG database.
3
+ * Docs: https://www.tcgdex.dev/ (free, no key)
4
+ */
5
+ export declare class TcgError extends Error {
6
+ }
7
+ export interface CardSummary {
8
+ id: string;
9
+ localId: string;
10
+ name: string;
11
+ image?: string;
12
+ category?: string;
13
+ rarity?: string;
14
+ set?: {
15
+ id?: string;
16
+ name?: string;
17
+ };
18
+ }
19
+ export interface CardDetail {
20
+ id: string;
21
+ name: string;
22
+ category?: string;
23
+ hp?: string;
24
+ types?: string[];
25
+ rarity?: string;
26
+ illustrator?: string;
27
+ abilities?: {
28
+ name?: string;
29
+ effect?: string;
30
+ }[];
31
+ attacks?: {
32
+ name?: string;
33
+ cost?: string[];
34
+ damage?: string;
35
+ effect?: string;
36
+ }[];
37
+ weakness?: {
38
+ type?: string;
39
+ value?: string;
40
+ }[];
41
+ resistances?: {
42
+ type?: string;
43
+ value?: string;
44
+ }[];
45
+ retreat?: number;
46
+ image?: string;
47
+ set?: {
48
+ id?: string;
49
+ name?: string;
50
+ };
51
+ variants?: {
52
+ firstEdition?: boolean;
53
+ holo?: boolean;
54
+ reverse?: boolean;
55
+ };
56
+ prices?: Record<string, Record<string, number>>;
57
+ }
58
+ export interface SetSummary {
59
+ id: string;
60
+ name: string;
61
+ releaseDate?: string;
62
+ cardCount?: number;
63
+ logo?: string;
64
+ }
65
+ export declare function searchCards(name: string, rarity?: string, limit?: number): Promise<CardSummary[]>;
66
+ export declare function getCard(id: string): Promise<CardDetail | null>;
67
+ export declare function listSets(): Promise<SetSummary[]>;
68
+ export declare function getSet(id: string): Promise<SetSummary & {
69
+ cards: CardSummary[];
70
+ } | null>;
71
+ export declare function formatCardSummary(c: CardSummary): string;
72
+ export declare function formatCardDetail(c: CardDetail): string;
73
+ export declare function formatSet(s: SetSummary): string;
package/dist/api.js ADDED
@@ -0,0 +1,104 @@
1
+ /**
2
+ * TCGdex API v2 client — the open Pokémon TCG database.
3
+ * Docs: https://www.tcgdex.dev/ (free, no key)
4
+ */
5
+ const BASE = "https://api.tcgdex.net/v2/en";
6
+ export class TcgError extends Error {
7
+ }
8
+ async function getJson(path) {
9
+ const res = await fetch(`${BASE}${path}`, {
10
+ headers: { Accept: "application/json", "User-Agent": "pokemon-tcg-mcp/1.0" },
11
+ });
12
+ if (!res.ok)
13
+ throw new TcgError(`TCGdex API error ${res.status}: ${res.statusText}`);
14
+ return (await res.json());
15
+ }
16
+ // ---------------------------------------------------------------------------
17
+ // Endpoints
18
+ // ---------------------------------------------------------------------------
19
+ export async function searchCards(name, rarity, limit = 12) {
20
+ const rarityParam = rarity ? `&rarity=${encodeURIComponent(rarity)}` : "";
21
+ const data = await getJson(`/cards?name=${encodeURIComponent(name)}${rarityParam}`);
22
+ return (data ?? []).slice(0, limit);
23
+ }
24
+ export async function getCard(id) {
25
+ const data = await getJson(`/cards/${id}`);
26
+ return data && data.id ? data : null;
27
+ }
28
+ export async function listSets() {
29
+ const data = await getJson("/sets");
30
+ return (data ?? []).sort((a, b) => (b.releaseDate ?? "").localeCompare(a.releaseDate ?? ""));
31
+ }
32
+ export async function getSet(id) {
33
+ const data = await getJson(`/sets/${id}`);
34
+ if (!data || !data.id)
35
+ return null;
36
+ return {
37
+ id: data.id,
38
+ name: data.name ?? "?",
39
+ releaseDate: data.releaseDate,
40
+ cardCount: data.cardCount?.total ?? data.cards?.length,
41
+ logo: data.logo,
42
+ cards: (data.cards ?? []).map((c) => ({
43
+ id: c.id,
44
+ localId: c.localId,
45
+ name: c.name,
46
+ category: c.category,
47
+ rarity: c.rarity,
48
+ image: c.image,
49
+ })),
50
+ };
51
+ }
52
+ // ---------------------------------------------------------------------------
53
+ // Formatting
54
+ // ---------------------------------------------------------------------------
55
+ export function formatCardSummary(c) {
56
+ return (`[${c.id}] ${c.name}` +
57
+ (c.rarity ? ` (${c.rarity})` : "") +
58
+ (c.category ? ` — ${c.category}` : "") +
59
+ (c.set?.name ? `\n Set: ${c.set.name}` : "") +
60
+ (c.image ? `\n ${c.image}` : ""));
61
+ }
62
+ export function formatCardDetail(c) {
63
+ const lines = [
64
+ `[${c.id}] ${c.name}${c.rarity ? ` (${c.rarity})` : ""}`,
65
+ `${c.category ?? "?"}${c.hp ? ` | HP ${c.hp}` : ""}` +
66
+ (c.types?.length ? ` | Types: ${c.types.join("/")}` : "") +
67
+ (c.retreat ? ` | Retreat: ${c.retreat}` : ""),
68
+ ];
69
+ for (const a of c.abilities ?? []) {
70
+ lines.push(`\nAbility: ${a.name}\n${a.effect ?? ""}`);
71
+ }
72
+ for (const atk of c.attacks ?? []) {
73
+ lines.push(`\nAttack: ${atk.name}${atk.damage ? ` (${atk.damage})` : ""}` +
74
+ (atk.cost?.length ? ` [${atk.cost.join(" ")}]` : "") +
75
+ (atk.effect ? `\n${atk.effect}` : ""));
76
+ }
77
+ if (c.weakness?.length) {
78
+ lines.push(`Weakness: ${c.weakness.map((w) => `${w.type} ${w.value ?? ""}`.trim()).join(", ")}`);
79
+ }
80
+ if (c.prices) {
81
+ const entries = Object.entries(c.prices)
82
+ .map(([variant, p]) => {
83
+ const low = p.low ? `low $${p.low}` : "";
84
+ const mid = p.average ? `avg $${p.average}` : "";
85
+ const high = p.high ? `high $${p.high}` : "";
86
+ return `${variant}: ${[low, mid, high].filter(Boolean).join(" / ")}`;
87
+ })
88
+ .join(", ");
89
+ if (entries)
90
+ lines.push(`Market prices: ${entries}`);
91
+ }
92
+ if (c.illustrator)
93
+ lines.push(`Illustrator: ${c.illustrator}`);
94
+ if (c.set?.name)
95
+ lines.push(`Set: ${c.set.name}`);
96
+ if (c.image)
97
+ lines.push(`\n${c.image}`);
98
+ return lines.join("\n");
99
+ }
100
+ export function formatSet(s) {
101
+ return (`[${s.id}] ${s.name}` +
102
+ (s.releaseDate ? ` — ${s.releaseDate}` : "") +
103
+ (s.cardCount ? ` (${s.cardCount} cards)` : ""));
104
+ }
@@ -0,0 +1 @@
1
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
2
+ import { createServer } from "./server.js";
3
+ async function main() {
4
+ const server = createServer();
5
+ const transport = new StdioServerTransport();
6
+ await server.connect(transport);
7
+ console.error("Pokemon TCG MCP server running on stdio");
8
+ }
9
+ main().catch((err) => {
10
+ console.error("Fatal error:", err);
11
+ process.exit(1);
12
+ });
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function createServer(): McpServer;
package/dist/server.js ADDED
@@ -0,0 +1,98 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import { TcgError, formatCardDetail, formatCardSummary, formatSet, getCard, getSet, listSets, searchCards, } from "./api.js";
4
+ const text = (t) => ({ content: [{ type: "text", text: t }] });
5
+ const textError = (t) => ({ content: [{ type: "text", text: t }], isError: true });
6
+ const READ_ONLY = { readOnlyHint: true, openWorldHint: true };
7
+ export function createServer() {
8
+ const server = new McpServer({
9
+ name: "pokemon-tcg-mcp",
10
+ version: "1.0.0",
11
+ });
12
+ server.registerTool("search_cards", {
13
+ title: "Search cards",
14
+ description: "Search Pokémon TCG cards by name, optionally filtered by rarity.",
15
+ inputSchema: z.object({
16
+ name: z.string().describe("Card name, e.g. 'Charizard' or 'Pikachu'"),
17
+ rarity: z.string().optional().describe("e.g. 'Rare Holo', 'Common', 'Rare Holo EX'"),
18
+ limit: z.number().int().min(1).max(25).default(12).describe("Max results"),
19
+ }),
20
+ annotations: READ_ONLY,
21
+ }, async ({ name, rarity, limit }) => {
22
+ try {
23
+ const cards = await searchCards(name, rarity, limit);
24
+ if (cards.length === 0) {
25
+ return text(`No cards found for "${name}"${rarity ? ` (${rarity})` : ""}.`);
26
+ }
27
+ return text(`Cards matching "${name}":\n` +
28
+ cards.map((c, i) => `${i + 1}. ${formatCardSummary(c)}`).join("\n\n"));
29
+ }
30
+ catch (e) {
31
+ return textError(errorMessage(e));
32
+ }
33
+ });
34
+ server.registerTool("get_card", {
35
+ title: "Get card",
36
+ description: "Get a single card's full detail: attacks, abilities, weakness, " +
37
+ "variants, and market prices when available.",
38
+ inputSchema: z.object({ id: z.string().describe("Card id from search_cards, e.g. 'swsh4-25'") }),
39
+ annotations: READ_ONLY,
40
+ }, async ({ id }) => {
41
+ try {
42
+ const card = await getCard(id);
43
+ if (!card)
44
+ return text(`No card with id "${id}".`);
45
+ return text(formatCardDetail(card));
46
+ }
47
+ catch (e) {
48
+ return textError(errorMessage(e));
49
+ }
50
+ });
51
+ server.registerTool("list_sets", {
52
+ title: "List sets",
53
+ description: "List Pokémon TCG sets (newest first).",
54
+ inputSchema: z.object({}),
55
+ annotations: READ_ONLY,
56
+ }, async () => {
57
+ try {
58
+ const sets = await listSets();
59
+ if (sets.length === 0)
60
+ return text("No sets found.");
61
+ return text(`Pokémon TCG sets (${sets.length}):\n` +
62
+ sets.slice(0, 20).map((s, i) => `${i + 1}. ${formatSet(s)}`).join("\n"));
63
+ }
64
+ catch (e) {
65
+ return textError(errorMessage(e));
66
+ }
67
+ });
68
+ server.registerTool("get_set", {
69
+ title: "Get set",
70
+ description: "Get a Pokémon TCG set and its card list.",
71
+ inputSchema: z.object({ id: z.string().describe("Set id from list_sets, e.g. 'swsh4' or 'base1'") }),
72
+ annotations: READ_ONLY,
73
+ }, async ({ id }) => {
74
+ try {
75
+ const set = await getSet(id);
76
+ if (!set)
77
+ return text(`No set with id "${id}".`);
78
+ const head = `${formatSet(set)} (${set.cards.length} cards)\n`;
79
+ const body = set.cards
80
+ .slice(0, 25)
81
+ .map((c) => `• #${c.localId} ${c.name}${c.rarity ? ` (${c.rarity})` : ""}`)
82
+ .join("\n");
83
+ const tail = set.cards.length > 25 ? `\n…and ${set.cards.length - 25} more` : "";
84
+ return text(head + body + tail);
85
+ }
86
+ catch (e) {
87
+ return textError(errorMessage(e));
88
+ }
89
+ });
90
+ return server;
91
+ }
92
+ function errorMessage(e) {
93
+ if (e instanceof TcgError)
94
+ return `Error: ${e.message}`;
95
+ if (e instanceof Error)
96
+ return `Error: ${e.message}`;
97
+ return `Error: ${String(e)}`;
98
+ }
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "pokemon-tcg-mcp",
3
+ "version": "1.0.0",
4
+ "description": "Use this MCP server to the Pok\u00e9mon TCG, search cards, sets, rarities via TCGdex. Tools include search cards, get card, list sets",
5
+ "type": "module",
6
+ "mcpName": "io.github.mrfentmen/pokemon-tcg-mcp",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/mrfentmen/pokemon-tcg-mcp.git"
10
+ },
11
+ "bin": {
12
+ "pokemon-tcg-mcp": "./dist/index.js"
13
+ },
14
+ "main": "./dist/index.js",
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsc -p tsconfig.json",
20
+ "start": "node dist/index.js",
21
+ "dev": "rm -rf dist && tsc -p tsconfig.json && node dist/index.js",
22
+ "inspect": "npx @modelcontextprotocol/inspector node dist/index.js"
23
+ },
24
+ "keywords": [
25
+ "mcp",
26
+ "pokemon",
27
+ "tcg",
28
+ "cards",
29
+ "trading-cards",
30
+ "tcgdex"
31
+ ],
32
+ "license": "MIT",
33
+ "dependencies": {
34
+ "@modelcontextprotocol/sdk": "^1.0.4",
35
+ "zod": "^3.23.8"
36
+ },
37
+ "devDependencies": {
38
+ "@types/node": "^22.0.0",
39
+ "typescript": "^5.6.0"
40
+ },
41
+ "engines": {
42
+ "node": ">=20"
43
+ }
44
+ }