analyze_deck_composition
Evaluate deck composition, mana curve, and card types for Magic: The Gathering decks. Receive balance recommendations tailored to specific formats and archetypes.
Instructions
Analyze deck composition, mana curve, card types, and provide balance recommendations
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| commander | No | Commander card name (for Commander/Brawl formats) | |
| deck_list | Yes | List of card names in the deck, one per line or comma-separated | |
| format | No | Format to analyze for (affects recommendations) | |
| strategy | No | Deck strategy archetype | unknown |
Implementation Reference
- The execute method implements the core tool logic: parameter validation, deck parsing, card data fetching via Scryfall, composition analysis, recommendation generation, and formatted response.async execute(args: unknown) { try { const params = this.validateParams(args); // Parse deck list const cardNames = this.parseDeckList(params.deck_list); if (cardNames.length === 0) { return { content: [{ type: 'text', text: 'No valid card names found in deck list. Please provide a list of card names separated by commas or newlines.' }], isError: true }; } // Fetch card data const cardData = await this.fetchCardData(cardNames); if (cardData.length === 0) { return { content: [{ type: 'text', text: 'Unable to fetch data for any cards in the deck list. Please check card names and try again.' }], isError: true }; } // Analyze composition const analysis = this.analyzeComposition(cardData); // Generate recommendations const recommendations = this.generateRecommendations(analysis, params); // Format response const responseText = this.formatAnalysisResponse(analysis, recommendations, params); return { content: [{ type: 'text', text: responseText }] }; } catch (error) { if (error instanceof ValidationError) { return { content: [{ type: 'text', text: `Validation error: ${error.message}` }], isError: true }; } return { content: [{ type: 'text', text: `Unexpected error: ${error instanceof Error ? error.message : 'Unknown error occurred'}` }], isError: true }; } }
- Input schema defining parameters for deck_list (required), format, strategy, and commander.readonly inputSchema = { type: 'object' as const, properties: { deck_list: { type: 'string', description: 'List of card names in the deck, one per line or comma-separated' }, format: { type: 'string', enum: ['standard', 'modern', 'legacy', 'vintage', 'commander', 'pioneer', 'brawl', 'standardbrawl'], description: 'Format to analyze for (affects recommendations)' }, strategy: { type: 'string', enum: ['aggro', 'midrange', 'control', 'combo', 'ramp', 'tribal', 'unknown'], default: 'unknown', description: 'Deck strategy archetype' }, commander: { type: 'string', description: 'Commander card name (for Commander/Brawl formats)' } }, required: ['deck_list'] };
- src/server.ts:78-78 (registration)Registration of the AnalyzeDeckCompositionTool instance in the MCP server's tools Map during constructor initialization.this.tools.set("analyze_deck_composition", new AnalyzeDeckCompositionTool(this.scryfallClient));
- Core analysis helper that computes mana curve, type/color/rarity breakdowns, average CMC, expensive cards, and key cards from fetched card data.private analyzeComposition(cardData: any[]) { const analysis: any = { totalCards: cardData.length, manaCurve: {}, typeBreakdown: {}, colorBreakdown: {}, rarityBreakdown: {}, averageCMC: 0, expensiveCards: [], keyCards: [], problems: [] }; let totalCMC = 0; // Analyze each card for (const card of cardData) { const cmc = card.cmc || 0; const types = card.type_line?.toLowerCase() || ''; const colors = card.color_identity || []; const rarity = card.rarity || 'common'; const price = parseFloat(card.prices?.usd || '0'); // Mana curve analysis.manaCurve[cmc] = (analysis.manaCurve[cmc] || 0) + 1; totalCMC += cmc; // Type breakdown if (types.includes('creature')) { analysis.typeBreakdown.creatures = (analysis.typeBreakdown.creatures || 0) + 1; } else if (types.includes('instant')) { analysis.typeBreakdown.instants = (analysis.typeBreakdown.instants || 0) + 1; } else if (types.includes('sorcery')) { analysis.typeBreakdown.sorceries = (analysis.typeBreakdown.sorceries || 0) + 1; } else if (types.includes('artifact')) { analysis.typeBreakdown.artifacts = (analysis.typeBreakdown.artifacts || 0) + 1; } else if (types.includes('enchantment')) { analysis.typeBreakdown.enchantments = (analysis.typeBreakdown.enchantments || 0) + 1; } else if (types.includes('planeswalker')) { analysis.typeBreakdown.planeswalkers = (analysis.typeBreakdown.planeswalkers || 0) + 1; } else if (types.includes('land')) { analysis.typeBreakdown.lands = (analysis.typeBreakdown.lands || 0) + 1; } // Color breakdown for (const color of colors) { analysis.colorBreakdown[color] = (analysis.colorBreakdown[color] || 0) + 1; } // Rarity breakdown analysis.rarityBreakdown[rarity] = (analysis.rarityBreakdown[rarity] || 0) + 1; // Expensive cards (>$5) if (price > 5) { analysis.expensiveCards.push({ name: card.name, price }); } // Key cards (mythic/rare with relevant abilities) if ((rarity === 'mythic' || rarity === 'rare') && card.oracle_text) { analysis.keyCards.push(card.name); } } analysis.averageCMC = totalCMC / cardData.length; // Sort expensive cards by price analysis.expensiveCards.sort((a: any, b: any) => b.price - a.price); return analysis; }
- Parameter validation helper ensuring required deck_list and valid enums for format/strategy.private validateParams(args: unknown): { deck_list: string; format?: string; strategy: string; commander?: string; } { if (!args || typeof args !== 'object') { throw new ValidationError('Invalid parameters'); } const params = args as any; if (!params.deck_list || typeof params.deck_list !== 'string') { throw new ValidationError('Deck list is required and must be a string'); } if (params.format) { const validFormats = ['standard', 'modern', 'legacy', 'vintage', 'commander', 'pioneer', 'brawl', 'standardbrawl']; if (!validFormats.includes(params.format)) { throw new ValidationError(`Format must be one of: ${validFormats.join(', ')}`); } } const strategy = params.strategy || 'unknown'; const validStrategies = ['aggro', 'midrange', 'control', 'combo', 'ramp', 'tribal', 'unknown']; if (!validStrategies.includes(strategy)) { throw new ValidationError(`Strategy must be one of: ${validStrategies.join(', ')}`); } return { deck_list: params.deck_list.trim(), format: params.format, strategy, commander: params.commander }; } async execute(args: unknown) { try { const params = this.validateParams(args); // Parse deck list const cardNames = this.parseDeckList(params.deck_list); if (cardNames.length === 0) { return { content: [{ type: 'text', text: 'No valid card names found in deck list. Please provide a list of card names separated by commas or newlines.' }], isError: true }; } // Fetch card data const cardData = await this.fetchCardData(cardNames); if (cardData.length === 0) { return { content: [{ type: 'text', text: 'Unable to fetch data for any cards in the deck list. Please check card names and try again.' }], isError: true }; } // Analyze composition const analysis = this.analyzeComposition(cardData); // Generate recommendations const recommendations = this.generateRecommendations(analysis, params); // Format response const responseText = this.formatAnalysisResponse(analysis, recommendations, params); return { content: [{ type: 'text', text: responseText }] }; } catch (error) { if (error instanceof ValidationError) { return { content: [{ type: 'text', text: `Validation error: ${error.message}` }], isError: true }; } return { content: [{ type: 'text', text: `Unexpected error: ${error instanceof Error ? error.message : 'Unknown error occurred'}` }], isError: true }; } }