analyze_deck
Analyze any Hearthstone deck code to classify its archetype, explain its gameplan, and identify strengths, weaknesses, and matchup dynamics for strategic coaching.
Instructions
Analyze a Hearthstone deck code to classify its archetype, explain its gameplan, identify strengths and weaknesses, and predict matchup dynamics. Use this for strategic deck coaching — it combines card data with strategy knowledge.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| deck_code | Yes | Hearthstone deck code (base64 deckstring) |
Implementation Reference
- src/tools/analyze-deck.ts:96-239 (handler)Main handler function `analyzeDeck` that decodes a Hearthstone deck code, builds card data, classifies the archetype, and looks up strategy information (archetype info, matchups). Returns the full analysis result.
export function analyzeDeck( db: Database.Database, input: AnalyzeDeckInputType, ): AnalyzeDeckResult { // 1. Decode the deck let decoded: { cards: Array<[number, number]>; heroes: number[]; format: number }; try { decoded = decode(input.deck_code); } catch (err) { return { success: false, message: `Invalid deck code: ${err instanceof Error ? err.message : String(err)}`, }; } // 2. Format const format = decoded.format === 1 ? 'Wild' : 'Standard'; // 3. Hero class let heroClass = 'UNKNOWN'; if (decoded.heroes.length > 0) { const heroDbfId = decoded.heroes[0]; const heroRow = db .prepare('SELECT player_class FROM cards WHERE id = ?') .get(String(heroDbfId)) as { player_class: string | null } | undefined; if (heroRow?.player_class) { heroClass = heroRow.player_class; } else if (HERO_CLASS_MAP[heroDbfId]) { heroClass = HERO_CLASS_MAP[heroDbfId]; } } // 4. Build card list and profile data const deckCards: Array<{ name: string; mana_cost: number | null; count: number }> = []; const profileCards: DeckProfile['cards'] = []; const manaCurve: Record<string, number> = {}; const typeDistribution: Record<string, number> = {}; let totalCards = 0; let totalCost = 0; let cardsWithCost = 0; for (const [dbfId, count] of decoded.cards) { const row = db .prepare('SELECT * FROM cards WHERE id = ?') .get(String(dbfId)) as CardRow | undefined; const name = row ? row.name : `Unknown Card (${dbfId})`; const manaCost = row?.mana_cost ?? null; const type = row?.type ?? null; const text = row?.text ?? null; const keywords = row?.keywords ?? null; deckCards.push({ name, mana_cost: manaCost, count }); // Add profile cards (expand by count for classification) for (let i = 0; i < count; i++) { profileCards.push({ mana_cost: manaCost, type, text, keywords }); } totalCards += count; // Mana curve const bucket = manaCurveBucket(manaCost); manaCurve[bucket] = (manaCurve[bucket] ?? 0) + count; // Type distribution const cardType = type ?? 'UNKNOWN'; typeDistribution[cardType] = (typeDistribution[cardType] ?? 0) + count; // Average cost tracking if (manaCost != null) { totalCost += manaCost * count; cardsWithCost += count; } } const avgCost = cardsWithCost > 0 ? totalCost / cardsWithCost : 0; // 5. Build DeckProfile and classify const profile: DeckProfile = { avg_cost: avgCost, cards: profileCards, total_cards: totalCards, mana_curve: manaCurve, type_distribution: typeDistribution, }; const classification = classifyArchetype(profile); // 6. Look up archetype info from strategy tables let archetypeInfo: AnalyzeDeckSuccess['archetype_info'] | undefined; const archetypeRow = db .prepare('SELECT * FROM archetypes WHERE LOWER(name) = LOWER(?)') .get(classification.archetype) as ArchetypeRow | undefined; if (archetypeRow) { archetypeInfo = { description: archetypeRow.description, gameplan: archetypeRow.gameplan, strengths: parseJson(archetypeRow.strengths), weaknesses: parseJson(archetypeRow.weaknesses), }; } // 7. Look up matchup expectations let matchups: AnalyzeDeckSuccess['matchups'] | undefined; const matchupRows = db .prepare( `SELECT archetype_a, archetype_b, favoured, key_tension FROM matchup_framework WHERE LOWER(archetype_a) = LOWER(?) OR LOWER(archetype_b) = LOWER(?)`, ) .all(classification.archetype, classification.archetype) as MatchupRow[]; if (matchupRows.length > 0) { matchups = matchupRows .filter((m) => m.archetype_a.toLowerCase() !== m.archetype_b.toLowerCase() || m.archetype_a.toLowerCase() !== classification.archetype.toLowerCase()) .map((m) => { const isA = m.archetype_a.toLowerCase() === classification.archetype.toLowerCase(); return { vs_archetype: isA ? m.archetype_b : m.archetype_a, favoured: m.favoured, key_tension: m.key_tension, }; }); } return { success: true, deck: { format, hero_class: heroClass, cards: deckCards, total_cards: totalCards, mana_curve: manaCurve, type_distribution: typeDistribution, }, classification, archetype_info: archetypeInfo, matchups: matchups && matchups.length > 0 ? matchups : undefined, }; } - src/tools/analyze-deck.ts:9-46 (schema)Input schema (`AnalyzeDeckInput`) and result types (`AnalyzeDeckSuccess`, `AnalyzeDeckError`, `AnalyzeDeckResult`). Input requires a `deck_code` string.
export const AnalyzeDeckInput = z.object({ deck_code: z.string().describe('Hearthstone deck code (base64 deckstring)'), }); export type AnalyzeDeckInputType = z.infer<typeof AnalyzeDeckInput>; // --- Result Types --- export interface AnalyzeDeckSuccess { success: true; deck: { format: string; hero_class: string; cards: Array<{ name: string; mana_cost: number | null; count: number }>; total_cards: number; mana_curve: Record<string, number>; type_distribution: Record<string, number>; }; classification: ClassificationResult; archetype_info?: { description: string; gameplan: string; strengths: string[]; weaknesses: string[]; }; matchups?: Array<{ vs_archetype: string; favoured: string; key_tension: string; }>; } export interface AnalyzeDeckError { success: false; message: string; } export type AnalyzeDeckResult = AnalyzeDeckSuccess | AnalyzeDeckError; - src/server.ts:164-183 (registration)Registration of the 'analyze_deck' tool on the MCP server using `server.tool()`, linking the input schema, handler, and formatter.
// 5. analyze_deck server.tool( 'analyze_deck', 'Analyze a Hearthstone deck code to classify its archetype, explain its gameplan, identify strengths and weaknesses, and predict matchup dynamics. Use this for strategic deck coaching — it combines card data with strategy knowledge.', AnalyzeDeckInput.shape, async (params) => { try { const result = analyzeDeck(db, params); return { content: [ { type: 'text' as const, text: formatAnalyzeDeck(result) }, ], }; } catch (err) { return { content: [ { type: 'text' as const, text: `Error: ${err instanceof Error ? err.message : String(err)}`, }, - src/format.ts:375-435 (helper)Formatter function `formatAnalyzeDeck` that renders the analysis result as a human-readable markdown string (class, archetype, gameplan, strengths/weaknesses, mana curve, matchups).
export function formatAnalyzeDeck(result: AnalyzeDeckResult): string { if (!result.success) { return result.message; } const lines: string[] = []; // Header lines.push('# Deck Analysis'); lines.push(''); lines.push(`**Class:** ${result.deck.hero_class} | **Format:** ${result.deck.format} | **Cards:** ${result.deck.total_cards}`); lines.push(''); // Archetype classification const confidencePct = Math.round(result.classification.confidence * 100); lines.push(`## Archetype: ${result.classification.archetype.charAt(0).toUpperCase() + result.classification.archetype.slice(1)} (${confidencePct}% confidence)`); lines.push(result.classification.reasoning); lines.push(''); // Gameplan (from archetype_info) if (result.archetype_info) { lines.push('## Gameplan'); lines.push(result.archetype_info.gameplan); lines.push(''); lines.push('## Strengths'); for (const s of result.archetype_info.strengths) { lines.push(`- ${s}`); } lines.push(''); lines.push('## Weaknesses'); for (const w of result.archetype_info.weaknesses) { lines.push(`- ${w}`); } lines.push(''); } // Mana Curve lines.push('## Mana Curve'); const buckets = ['0', '1', '2', '3', '4', '5', '6', '7+']; for (const bucket of buckets) { const count = result.deck.mana_curve[bucket] ?? 0; if (count > 0) { const bar = '\u2588'.repeat(count); lines.push(`${bucket}: ${bar} ${count}`); } } lines.push(''); // Matchup Expectations if (result.matchups && result.matchups.length > 0) { lines.push('## Matchup Expectations'); for (const m of result.matchups) { lines.push(`- vs ${m.vs_archetype}: ${m.favoured} \u2014 ${m.key_tension}`); } lines.push(''); } return lines.join('\n'); } - src/tools/analyze-deck.ts:64-77 (helper)Helper functions for analyzing decks: `manaCurveBucket` (buckets mana costs) and `parseJson` (parses JSON arrays from DB fields like strengths/weaknesses).
function manaCurveBucket(cost: number | null): string { if (cost == null) return '0'; if (cost >= 7) return '7+'; return String(cost); } function parseJson(raw: string | null): string[] { if (!raw) return []; try { return JSON.parse(raw) as string[]; } catch { return []; } }