Skip to main content
Glama

search_alternatives

Identify budget-friendly, upgraded, or functionally similar Magic: The Gathering cards based on format legality, price constraints, and desired direction. Ideal for optimizing deck building.

Instructions

Find budget alternatives, upgrades, or functionally similar cards

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
directionYesType of alternative to find
formatNoFormat legality requirement
limitNoNumber of alternatives to return
max_priceNoMaximum price constraint
min_priceNoMinimum price constraint
preserve_functionNoMaintain similar functionality
target_cardYesCard to find alternatives for

Implementation Reference

  • The `execute` method that implements the core logic of the `search_alternatives` tool: validates input parameters, retrieves the target card, builds a Scryfall search query for alternatives, executes the search, filters results, formats output as text, and handles errors.
    async execute(args: unknown) { try { // Validate parameters const params = this.validateParams(args); // First, get the target card to analyze its properties let targetCard; try { targetCard = await this.scryfallClient.getCard({ identifier: params.target_card }); } catch (error) { if (error instanceof ScryfallAPIError && error.status === 404) { return { content: [ { type: 'text', text: `Target card not found: "${params.target_card}". Please check the card name.` } ], isError: true }; } throw error; } // Build search query for alternatives const query = this.buildAlternativesQuery(targetCard, params); // Execute search const results = await this.scryfallClient.searchCards({ query, limit: params.limit, order: this.getOrderForDirection(params.direction) }); // Filter out the original card from results const filteredResults = { ...results, data: results.data.filter(card => card.id !== targetCard.id) }; // Format results let responseText = `**Alternatives for ${targetCard.name}** (${params.direction}):\n\n`; if (filteredResults.data.length === 0) { responseText += `No ${params.direction} alternatives found for "${targetCard.name}"`; if (params.format) { responseText += ` in ${params.format}`; } responseText += '. Try adjusting your search criteria.'; } else { responseText += formatSearchResultsAsText(filteredResults); } return { content: [ { type: 'text', text: responseText } ] }; } catch (error) { // Handle validation errors if (error instanceof ValidationError) { return { content: [ { type: 'text', text: `Validation error: ${error.message}` } ], isError: true }; } // Generic error handling return { content: [ { type: 'text', text: `Unexpected error: ${error instanceof Error ? error.message : 'Unknown error occurred'}` } ], isError: true }; } }
  • Input schema defining the parameters for the tool, including required fields `target_card` and `direction`, optional format, price constraints, and result limit.
    readonly inputSchema = { type: 'object' as const, properties: { target_card: { type: 'string', description: 'Card to find alternatives for' }, direction: { type: 'string', enum: ['cheaper', 'upgrade', 'similar'], description: 'Type of alternative to find' }, format: { type: 'string', enum: ['standard', 'modern', 'legacy', 'vintage', 'commander', 'pioneer'], description: 'Format legality requirement' }, max_price: { type: 'number', minimum: 0, description: 'Maximum price constraint' }, min_price: { type: 'number', minimum: 0, description: 'Minimum price constraint' }, preserve_function: { type: 'boolean', default: true, description: 'Maintain similar functionality' }, limit: { type: 'number', default: 10, minimum: 1, maximum: 50, description: 'Number of alternatives to return' } }, required: ['target_card', 'direction'] };
  • src/server.ts:73-73 (registration)
    Registers the `SearchAlternativesTool` instance in the server's tools Map with the name 'search_alternatives'.
    this.tools.set("search_alternatives", new SearchAlternativesTool(this.scryfallClient));
  • Helper method to validate and parse input arguments, enforcing types, required fields, enums, and constraints before execution.
    private validateParams(args: unknown): { target_card: string; direction: string; format?: string; max_price?: number; min_price?: number; preserve_function: boolean; limit: number; } { if (!args || typeof args !== 'object') { throw new ValidationError('Invalid parameters'); } const params = args as any; if (!params.target_card || typeof params.target_card !== 'string') { throw new ValidationError('Target card is required and must be a string'); } if (!params.direction || typeof params.direction !== 'string') { throw new ValidationError('Direction is required and must be a string'); } const validDirections = ['cheaper', 'upgrade', 'similar']; if (!validDirections.includes(params.direction)) { throw new ValidationError(`Direction must be one of: ${validDirections.join(', ')}`); } if (params.format) { const validFormats = ['standard', 'modern', 'legacy', 'vintage', 'commander', 'pioneer']; if (!validFormats.includes(params.format)) { throw new ValidationError(`Format must be one of: ${validFormats.join(', ')}`); } } if (params.max_price !== undefined) { if (typeof params.max_price !== 'number' || params.max_price < 0) { throw new ValidationError('Max price must be a non-negative number'); } } if (params.min_price !== undefined) { if (typeof params.min_price !== 'number' || params.min_price < 0) { throw new ValidationError('Min price must be a non-negative number'); } } const preserveFunction = params.preserve_function ?? true; if (typeof preserveFunction !== 'boolean') { throw new ValidationError('Preserve function must be a boolean'); } const limit = params.limit || 10; if (typeof limit !== 'number' || limit < 1 || limit > 50) { throw new ValidationError('Limit must be a number between 1 and 50'); } return { target_card: params.target_card.trim(), direction: params.direction, format: params.format, max_price: params.max_price, min_price: params.min_price, preserve_function: preserveFunction, limit }; }
  • Helper method that constructs the Scryfall search query string based on target card properties, direction, format, function preservation, and price constraints.
    private buildAlternativesQuery(targetCard: any, params: { direction: string; format?: string; max_price?: number; min_price?: number; preserve_function: boolean; }): string { let query = ''; // Add format constraint if (params.format) { query += `f:${params.format} `; } // Add functional similarity constraints if requested if (params.preserve_function) { // Match primary type const primaryType = targetCard.type_line.split(' ')[0]; query += `t:${primaryType} `; // Match mana value (within 1) const cmc = targetCard.cmc || 0; if (cmc > 0) { query += `(cmc:${cmc} OR cmc:${Math.max(0, cmc - 1)} OR cmc:${cmc + 1}) `; } } // Add price constraints based on direction const targetPrice = parseFloat((targetCard.prices as any).usd || '0'); if (params.direction === 'cheaper' && targetPrice > 0) { const maxPrice = params.max_price || targetPrice * 0.8; // 20% cheaper by default query += `usd<=${maxPrice} `; } else if (params.direction === 'upgrade' && targetPrice > 0) { const minPrice = params.min_price || targetPrice * 1.2; // 20% more expensive by default query += `usd>=${minPrice} `; } // Add explicit price constraints if (params.max_price !== undefined) { query += `usd<=${params.max_price} `; } if (params.min_price !== undefined) { query += `usd>=${params.min_price} `; } return query.trim(); }

Latest Blog Posts

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/bmurdock/scryfall-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server