compare_monsters
Compare D&D 5e monsters side by side to analyze AC, HP, abilities, resistances, immunities, and special actions for encounter planning.
Instructions
Compare 2-3 D&D 5e monsters side by side. Shows AC, HP, ability scores, speeds, resistances, immunities, actions, and special abilities.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| monster_names | Yes | Names of 2-3 monsters to compare |
Implementation Reference
- src/tools/compare-monsters.ts:83-175 (handler)The async handler function for the 'compare_monsters' tool, which processes input monster names, retrieves data from the database, formats the comparison table in Markdown, and returns it.
async ({ monster_names }) => { const monsters: MonsterRow[] = []; const notFound: string[] = []; for (const name of monster_names) { const monster = getMonsterByName(db, name); if (monster) { monsters.push(monster); } else { notFound.push(name); } } if (notFound.length > 0) { return { content: [ { type: 'text' as const, text: `Monster(s) not found: ${notFound.join(', ')}. Use the search_monsters tool first to find the exact name.`, }, ], isError: true, }; } const lines: string[] = []; lines.push('# Monster Comparison'); lines.push(''); // Separator helper for table const sep = monsters.map(() => '---').join(' | '); const header = monsters.map((m) => `**${m.name}**`).join(' | '); lines.push(`| Stat | ${header} |`); lines.push(`|------|${sep}|`); // Basic info lines.push(`| Size | ${monsters.map((m) => m.size).join(' | ')} |`); lines.push(`| Type | ${monsters.map((m) => { const sub = m.subtype ? ` (${m.subtype})` : ''; return `${m.type}${sub}`; }).join(' | ')} |`); lines.push(`| Alignment | ${monsters.map((m) => m.alignment).join(' | ')} |`); // Combat stats lines.push(`| AC | ${monsters.map((m) => { return m.ac_type ? `${m.ac} (${m.ac_type})` : `${m.ac}`; }).join(' | ')} |`); lines.push(`| HP | ${monsters.map((m) => `${m.hp} (${m.hit_dice})`).join(' | ')} |`); lines.push(`| Speed | ${monsters.map((m) => formatSpeed(m.speed)).join(' | ')} |`); lines.push(`| CR | ${monsters.map((m) => { const xp = m.xp || getXpForCr(m.cr); return `${m.cr} (${xp.toLocaleString()} XP)`; }).join(' | ')} |`); lines.push(`| Prof. Bonus | ${monsters.map((m) => m.proficiency_bonus ? `+${m.proficiency_bonus}` : '—').join(' | ')} |`); // Ability scores lines.push(`| STR | ${monsters.map((m) => formatScore(m.str)).join(' | ')} |`); lines.push(`| DEX | ${monsters.map((m) => formatScore(m.dex)).join(' | ')} |`); lines.push(`| CON | ${monsters.map((m) => formatScore(m.con)).join(' | ')} |`); lines.push(`| INT | ${monsters.map((m) => formatScore(m.int)).join(' | ')} |`); lines.push(`| WIS | ${monsters.map((m) => formatScore(m.wis)).join(' | ')} |`); lines.push(`| CHA | ${monsters.map((m) => formatScore(m.cha)).join(' | ')} |`); // Defenses lines.push(`| Resistances | ${monsters.map((m) => m.resistances ?? '—').join(' | ')} |`); lines.push(`| Immunities | ${monsters.map((m) => m.immunities ?? '—').join(' | ')} |`); lines.push(`| Vulnerabilities | ${monsters.map((m) => m.vulnerabilities ?? '—').join(' | ')} |`); lines.push(`| Condition Immunities | ${monsters.map((m) => m.condition_immunities ?? '—').join(' | ')} |`); // Senses & languages lines.push(`| Senses | ${monsters.map((m) => m.senses ?? '—').join(' | ')} |`); lines.push(`| Languages | ${monsters.map((m) => m.languages ?? '—').join(' | ')} |`); lines.push(''); // Detailed sections const traitSection = formatAbilitiesSection('Traits', monsters, 'traits'); if (traitSection) lines.push(traitSection); const actionSection = formatAbilitiesSection('Actions', monsters, 'actions'); if (actionSection) lines.push(actionSection); const reactionSection = formatAbilitiesSection('Reactions', monsters, 'reactions'); if (reactionSection) lines.push(reactionSection); const legendarySection = formatAbilitiesSection('Legendary Actions', monsters, 'legendary_actions'); if (legendarySection) lines.push(legendarySection); return { content: [{ type: 'text' as const, text: lines.join('\n') }], }; }, ); - src/tools/compare-monsters.ts:70-82 (schema)The definition and input schema (zod validation) for the 'compare_monsters' tool.
server.registerTool( 'compare_monsters', { description: 'Compare 2-3 D&D 5e monsters side by side. Shows AC, HP, ability scores, speeds, resistances, immunities, actions, and special abilities.', inputSchema: { monster_names: z .array(z.string()) .min(2) .max(3) .describe('Names of 2-3 monsters to compare'), }, }, - src/tools/compare-monsters.ts:66-176 (registration)Function used to register the 'compare_monsters' tool with the McpServer.
export function registerCompareMonsters( server: McpServer, db: Database.Database, ): void { server.registerTool( 'compare_monsters', { description: 'Compare 2-3 D&D 5e monsters side by side. Shows AC, HP, ability scores, speeds, resistances, immunities, actions, and special abilities.', inputSchema: { monster_names: z .array(z.string()) .min(2) .max(3) .describe('Names of 2-3 monsters to compare'), }, }, async ({ monster_names }) => { const monsters: MonsterRow[] = []; const notFound: string[] = []; for (const name of monster_names) { const monster = getMonsterByName(db, name); if (monster) { monsters.push(monster); } else { notFound.push(name); } } if (notFound.length > 0) { return { content: [ { type: 'text' as const, text: `Monster(s) not found: ${notFound.join(', ')}. Use the search_monsters tool first to find the exact name.`, }, ], isError: true, }; } const lines: string[] = []; lines.push('# Monster Comparison'); lines.push(''); // Separator helper for table const sep = monsters.map(() => '---').join(' | '); const header = monsters.map((m) => `**${m.name}**`).join(' | '); lines.push(`| Stat | ${header} |`); lines.push(`|------|${sep}|`); // Basic info lines.push(`| Size | ${monsters.map((m) => m.size).join(' | ')} |`); lines.push(`| Type | ${monsters.map((m) => { const sub = m.subtype ? ` (${m.subtype})` : ''; return `${m.type}${sub}`; }).join(' | ')} |`); lines.push(`| Alignment | ${monsters.map((m) => m.alignment).join(' | ')} |`); // Combat stats lines.push(`| AC | ${monsters.map((m) => { return m.ac_type ? `${m.ac} (${m.ac_type})` : `${m.ac}`; }).join(' | ')} |`); lines.push(`| HP | ${monsters.map((m) => `${m.hp} (${m.hit_dice})`).join(' | ')} |`); lines.push(`| Speed | ${monsters.map((m) => formatSpeed(m.speed)).join(' | ')} |`); lines.push(`| CR | ${monsters.map((m) => { const xp = m.xp || getXpForCr(m.cr); return `${m.cr} (${xp.toLocaleString()} XP)`; }).join(' | ')} |`); lines.push(`| Prof. Bonus | ${monsters.map((m) => m.proficiency_bonus ? `+${m.proficiency_bonus}` : '—').join(' | ')} |`); // Ability scores lines.push(`| STR | ${monsters.map((m) => formatScore(m.str)).join(' | ')} |`); lines.push(`| DEX | ${monsters.map((m) => formatScore(m.dex)).join(' | ')} |`); lines.push(`| CON | ${monsters.map((m) => formatScore(m.con)).join(' | ')} |`); lines.push(`| INT | ${monsters.map((m) => formatScore(m.int)).join(' | ')} |`); lines.push(`| WIS | ${monsters.map((m) => formatScore(m.wis)).join(' | ')} |`); lines.push(`| CHA | ${monsters.map((m) => formatScore(m.cha)).join(' | ')} |`); // Defenses lines.push(`| Resistances | ${monsters.map((m) => m.resistances ?? '—').join(' | ')} |`); lines.push(`| Immunities | ${monsters.map((m) => m.immunities ?? '—').join(' | ')} |`); lines.push(`| Vulnerabilities | ${monsters.map((m) => m.vulnerabilities ?? '—').join(' | ')} |`); lines.push(`| Condition Immunities | ${monsters.map((m) => m.condition_immunities ?? '—').join(' | ')} |`); // Senses & languages lines.push(`| Senses | ${monsters.map((m) => m.senses ?? '—').join(' | ')} |`); lines.push(`| Languages | ${monsters.map((m) => m.languages ?? '—').join(' | ')} |`); lines.push(''); // Detailed sections const traitSection = formatAbilitiesSection('Traits', monsters, 'traits'); if (traitSection) lines.push(traitSection); const actionSection = formatAbilitiesSection('Actions', monsters, 'actions'); if (actionSection) lines.push(actionSection); const reactionSection = formatAbilitiesSection('Reactions', monsters, 'reactions'); if (reactionSection) lines.push(reactionSection); const legendarySection = formatAbilitiesSection('Legendary Actions', monsters, 'legendary_actions'); if (legendarySection) lines.push(legendarySection); return { content: [{ type: 'text' as const, text: lines.join('\n') }], }; }, ); }