search_cards
Search for Magic: The Gathering cards using Scryfall syntax. Filter by color, type, set, price, and more. Supports pagination, sorting, and options to include tokens or variants. Retrieve results in JSON or text format.
Instructions
Search for Magic: The Gathering cards using Scryfall search syntax. Supports complex queries with operators like color:, type:, set:, etc.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| arena_only | No | Only return cards available in Arena | |
| direction | No | Sort direction | auto |
| format | No | Response format - text for human-readable, json for structured data | text |
| include_extras | No | Include tokens, emblems, and other extras in search results | |
| include_multilingual | No | Include cards in all languages | |
| include_variations | No | Include rare card variants | |
| limit | No | Number of cards to return (1-175) | |
| order | No | Sort order for results | |
| page | No | Page number for pagination (starts at 1) | |
| price_range | No | Price filtering constraints | |
| query | Yes | Scryfall search query using their syntax (e.g., "lightning bolt", "c:red type:instant", "set:dom") | |
| unique | No | Strategy for omitting similar cards | cards |
Implementation Reference
- src/tools/search-cards.ts:15-267 (handler)The SearchCardsTool class and its execute() method provide the core handler logic for the 'search_cards' tool. It validates inputs using Zod schema, sanitizes and validates the Scryfall query, executes the search via ScryfallClient, formats results as text or JSON, and handles various errors comprehensively.export class SearchCardsTool { readonly name = "search_cards"; readonly description = "Search for Magic: The Gathering cards using Scryfall search syntax. Supports complex queries with operators like color:, type:, set:, etc."; readonly inputSchema = { type: "object" as const, properties: { query: { type: "string", description: 'Scryfall search query using their syntax (e.g., "lightning bolt", "c:red type:instant", "set:dom")', }, limit: { type: "number", description: "Number of cards to return (1-175)", minimum: 1, maximum: 175, default: 20, }, page: { type: "number", description: "Page number for pagination (starts at 1)", minimum: 1, default: 1, }, format: { type: "string", enum: ["json", "text"], description: "Response format - text for human-readable, json for structured data", default: "text", }, include_extras: { type: "boolean", description: "Include tokens, emblems, and other extras in search results", default: false, }, order: { type: "string", enum: [ "name", "released", "cmc", "power", "toughness", "artist", "set", "rarity", "color", "usd", "eur", "tix", "edhrec", "penny", "review", ], description: "Sort order for results", }, unique: { type: "string", enum: ["cards", "art", "prints"], description: "Strategy for omitting similar cards", default: "cards", }, direction: { type: "string", enum: ["asc", "desc", "auto"], description: "Sort direction", default: "auto", }, include_multilingual: { type: "boolean", description: "Include cards in all languages", default: false, }, include_variations: { type: "boolean", description: "Include rare card variants", default: false, }, price_range: { type: "object", properties: { min: { type: "number", minimum: 0, description: "Minimum price", }, max: { type: "number", minimum: 0, description: "Maximum price", }, currency: { type: "string", enum: ["usd", "eur", "tix"], default: "usd", description: "Currency for price filtering", }, }, description: "Price filtering constraints", }, arena_only: { type: "boolean", description: "Only return cards available in Arena", default: false, }, }, required: ["query"], }; constructor(private scryfallClient: ScryfallClient) {} async execute(args: unknown) { try { // Validate parameters const params = validateSearchCardsParams(args); // Sanitize the query input const sanitizedQuery = sanitizeQuery(params.query); // Enhanced Scryfall query validation const validationResult = await validateScryfallQuery(sanitizedQuery); // If validation fails, return detailed error information if (!validationResult.isValid) { return { content: [ { type: "text", text: this.formatValidationErrors(validationResult), }, ], isError: true, }; } // If there are warnings, we can still proceed but inform the user if (validationResult.warnings.length > 0) { // Could log warnings or include them in response } // Build final query with Arena filtering if requested let finalQuery = sanitizedQuery; if (params.arena_only) { const arenaModification = sanitizeQueryModification(" game:arena"); finalQuery = `${finalQuery} ${arenaModification}`; } // Execute search (request ID will be generated by the service if not provided) const results = await this.scryfallClient.searchCards({ query: finalQuery, limit: params.limit, page: params.page, include_extras: params.include_extras, order: params.order, unique: params.unique, direction: params.direction, include_multilingual: params.include_multilingual, include_variations: params.include_variations, price_range: params.price_range, }); // Handle no results if (results.total_cards === 0 || results.data.length === 0) { return { content: [ { type: "text", text: `No cards found matching "${params.query}". Try adjusting your search terms or check the Scryfall syntax.`, }, ], }; } // Format response based on requested format let responseText: string; if (params.format === "json") { const formattedResults = formatSearchResultsAsJson(results, params.page, params.limit); responseText = JSON.stringify(formattedResults, null, 2); } else { responseText = formatSearchResultsAsText(results, params.page, params.limit); } return { content: [ { type: "text", text: responseText, }, ], }; } catch (error) { // Handle different error types if (error instanceof ValidationError) { return { content: [ { type: "text", text: `Validation error: ${error.message}`, }, ], isError: true, }; } if (error instanceof RateLimitError) { const retry = error.retryAfter ? ` Retry after ${error.retryAfter}s.` : ""; return { content: [ { type: "text", text: `Rate limit exceeded.${retry} Please wait and try again.` }, ], isError: true, }; } if (error instanceof ScryfallAPIError) { let errorMessage = `Scryfall API error: ${error.message}`; if (error.status === 404) { errorMessage = `No cards found matching "${ (args as SearchCardsParams)?.query || "your query" }". The search query may be invalid or too specific.`; } else if (error.status === 422) { errorMessage = `Invalid search query syntax. Please check your Scryfall search syntax and try again.`; } else if (error.status === 429) { errorMessage = "Rate limit exceeded. Please wait a moment and try again."; } return { content: [ { type: "text", text: errorMessage, }, ], isError: true, }; } // Generic error handling with enhanced context const errorDetails = this.formatGenericError(error, args as SearchCardsParams); return { content: [ { type: "text", text: errorDetails, }, ], isError: true, }; } }
- src/types/mcp-types.ts:5-42 (schema)Zod schema (SearchCardsParamsSchema) defining the input parameters and validation rules for the search_cards tool, imported and used for parameter validation.export const SearchCardsParamsSchema = z.object({ query: z.string().min(1, "Query cannot be empty"), limit: z.number().int().min(1).max(175).optional().default(20), page: z.number().int().min(1).optional().default(1), format: z.enum(["json", "text"]).optional().default("text"), include_extras: z.boolean().optional().default(false), order: z .enum([ "name", "released", "cmc", "power", "toughness", "artist", "set", "rarity", "color", "usd", "eur", "tix", "edhrec", "penny", "review", ]) .optional(), unique: z.enum(["cards", "art", "prints"]).optional().default("cards"), direction: z.enum(["asc", "desc", "auto"]).optional().default("auto"), include_multilingual: z.boolean().optional().default(false), include_variations: z.boolean().optional().default(false), price_range: z .object({ min: z.number().min(0).optional(), max: z.number().min(0).optional(), currency: z.enum(["usd", "eur", "tix"]).optional().default("usd"), }) .optional(), arena_only: z.boolean().optional().default(false), });
- src/server.ts:24-66 (registration)Import and registration of SearchCardsTool in the MCP server's tools Map under the key 'search_cards' in the ScryfallMCPServer constructor.import { SearchCardsTool } from "./tools/search-cards.js"; import { GetCardTool } from "./tools/get-card.js"; import { GetCardPricesTool } from "./tools/get-card-prices.js"; import { RandomCardTool } from "./tools/random-card.js"; import { SearchSetsTool } from "./tools/search-sets.js"; import { QueryRulesTool } from "./tools/query-rules.js"; import { SearchFormatStaplesTool } from "./tools/search-format-staples.js"; import { SearchAlternativesTool } from "./tools/search-alternatives.js"; import { FindSynergisticCardsTool } from "./tools/find-synergistic-cards.js"; import { BatchCardAnalysisTool } from "./tools/batch-card-analysis.js"; import { ValidateBrawlCommanderTool } from "./tools/validate-brawl-commander.js"; import { BuildScryfallQueryTool } from "./tools/build-scryfall-query.js"; import { AnalyzeDeckCompositionTool } from "./tools/analyze-deck-composition.js"; import { SuggestManaBaseTool } from "./tools/suggest-mana-base.js"; // Resources import { CardDatabaseResource } from "./resources/card-database.js"; import { SetDatabaseResource } from "./resources/set-database.js"; // Prompts import { AnalyzeCardPrompt } from "./prompts/analyze-card.js"; import { BuildDeckPrompt } from "./prompts/build-deck.js"; /** * Main MCP Server for Scryfall integration */ export class ScryfallMCPServer { private readonly scryfallClient: ScryfallClient; private readonly rateLimiter: RateLimiter; private readonly cache: CacheService; private readonly tools: Map<string, any>; private readonly resources: Map<string, any>; private readonly prompts: Map<string, any>; constructor() { // Initialize core services this.rateLimiter = new RateLimiter(); this.cache = new CacheService(); this.scryfallClient = new ScryfallClient(this.rateLimiter, this.cache); // Initialize tools this.tools = new Map(); this.tools.set("search_cards", new SearchCardsTool(this.scryfallClient));