Skip to main content
Glama
index.ts16.1 kB
import { z } from 'zod'; import { getTank, getTankByName, searchTanks, getAllTanks } from '../parsers/tanks.js'; import { getEquipment, getConsumables, getProvisions } from '../parsers/equipment.js'; import { getMaps, getMapByName, getMapWithTerrain } from '../parsers/maps.js'; import { getCrewSkills, getSkillsByClass } from '../parsers/crew.js'; import { getCamouflages, getCamouflageByName, searchCamouflages, getCamoBonusTable, getCamouflageBonus } from '../parsers/camouflages.js'; import { getTopTurret, getTankTopGun, getTopTracks, formatGunSummary } from '../utils/tools.js'; import { NATIONS, type Nation, type TankClass, type TankType } from '../data/types.js'; import { loadMapImage, hasMapImage } from '../images/maps.js'; export interface ImageResult { __type: 'image'; data: string; mimeType: string; } const NATION_VALUES = ['ussr', 'germany', 'usa', 'china', 'france', 'uk', 'japan', 'european', 'other'] as const; export const tools = { get_tank: { description: 'Get detailed stats for a specific tank by name or ID', inputSchema: z.object({ name: z.string().optional().describe('Tank name (fuzzy match)'), id: z.number().optional().describe('Tank ID'), }), handler: async (input: { name?: string; id?: number }) => { if (input.id) { const tank = getTank(input.id); if (!tank) return { error: `Tank with ID ${input.id} not found` }; return tank; } if (input.name) { const tank = getTankByName(input.name); if (!tank) { const similar = searchTanks({ query: input.name }).slice(0, 5); return { error: `Tank "${input.name}" not found`, suggestions: similar.map(t => t.name), }; } return tank; } return { error: 'Provide either name or id' }; }, }, search_tanks: { description: 'Search for tanks by nation, tier, type, or name query', inputSchema: z.object({ nation: z.enum(NATION_VALUES).optional().describe('Filter by nation'), tier: z.number().min(1).max(10).optional().describe('Filter by tier (1-10)'), class: z.enum(['lightTank', 'mediumTank', 'heavyTank', 'AT-SPG']).optional().describe('Filter by tank class'), type: z.enum(['researchable', 'premium', 'collector']).optional().describe('Filter by tank type'), query: z.string().optional().describe('Search query for tank name'), }), handler: async (input: { nation?: Nation; tier?: number; class?: TankClass; type?: TankType; query?: string }) => { const results = searchTanks(input); return { count: results.length, tanks: results.slice(0, 50), }; }, }, compare_tanks: { description: 'Compare two tanks side by side', inputSchema: z.object({ tankA: z.string().describe('First tank name'), tankB: z.string().describe('Second tank name'), }), handler: async (input: { tankA: string; tankB: string }) => { const a = getTankByName(input.tankA); const b = getTankByName(input.tankB); if (!a) return { error: `Tank "${input.tankA}" not found` }; if (!b) return { error: `Tank "${input.tankB}" not found` }; const topGunA = getTankTopGun(a); const topGunB = getTankTopGun(b); const topTurretA = getTopTurret(a); const topTurretB = getTopTurret(b); return { tankA: { name: a.name, tier: a.tier, class: a.class, nation: a.nation, health: a.health, armor: a.hullArmor, speed: { forward: a.speedForward, backward: a.speedBackward }, gun: formatGunSummary(topGunA), viewRange: topTurretA?.viewRange, camouflage: { still: a.camouflageStill, moving: a.camouflageMoving }, }, tankB: { name: b.name, tier: b.tier, class: b.class, nation: b.nation, health: b.health, armor: b.hullArmor, speed: { forward: b.speedForward, backward: b.speedBackward }, gun: formatGunSummary(topGunB), viewRange: topTurretB?.viewRange, camouflage: { still: b.camouflageStill, moving: b.camouflageMoving }, }, }; }, }, list_nations: { description: 'List all available nations in the game', inputSchema: z.object({}), handler: async () => { const nationNames: Record<Nation, string> = { ussr: 'USSR', germany: 'Germany', usa: 'USA', china: 'China', france: 'France', uk: 'United Kingdom', japan: 'Japan', european: 'European', other: 'Other', }; return NATIONS.map(code => ({ code, name: nationNames[code], })); }, }, list_maps: { description: 'List all maps with spawn points and tactical data', inputSchema: z.object({}), handler: async () => { const maps = getMaps(); return maps.map(m => ({ id: m.id, name: m.name, tag: m.tag, modes: m.modes, spawns: { allies: m.alliesSpawns, enemies: m.enemiesSpawns, }, })); }, }, get_map: { description: 'Get detailed map data including terrain objects (bushes, buildings, rocks) for tactical analysis', inputSchema: z.object({ name: z.string().describe('Map name'), includeTerrain: z.boolean().optional().default(true).describe('Include terrain objects (bushes, trees, buildings, rocks)'), }), handler: async (input: { name: string; includeTerrain?: boolean }) => { const includeTerrain = input.includeTerrain !== false; if (includeTerrain) { const map = await getMapWithTerrain(input.name); if (!map) { const available = getMaps().map(m => m.name); return { error: `Map "${input.name}" not found`, available }; } return { id: map.id, name: map.name, tag: map.tag, modes: map.modes, bounds: map.bounds, terrainSummary: map.terrainSummary, spawns: { allies: map.alliesSpawns, enemies: map.enemiesSpawns, }, terrain: { bushes: map.terrain?.filter(e => e.type === 'bush') || [], trees: map.terrain?.filter(e => e.type === 'tree') || [], buildings: map.terrain?.filter(e => e.type === 'building') || [], rocks: map.terrain?.filter(e => e.type === 'rock') || [], walls: map.terrain?.filter(e => e.type === 'wall') || [], capturePoints: map.terrain?.filter(e => e.type === 'capture_point') || [], }, }; } const map = getMapByName(input.name); if (!map) { const available = getMaps().map(m => m.name); return { error: `Map "${input.name}" not found`, available }; } return map; }, }, list_equipment: { description: 'List all available equipment modules', inputSchema: z.object({}), handler: async () => { return getEquipment(); }, }, list_consumables: { description: 'List all available consumables', inputSchema: z.object({}), handler: async () => { return getConsumables(); }, }, list_provisions: { description: 'List all available provisions', inputSchema: z.object({}), handler: async () => { return getProvisions(); }, }, list_crew_skills: { description: 'List all crew skills with effects and bonuses', inputSchema: z.object({ tankClass: z.enum(['lightTank', 'mediumTank', 'heavyTank', 'AT-SPG']).optional().describe('Filter by tank class'), }), handler: async (input: { tankClass?: TankClass }) => { if (input.tankClass) { return getSkillsByClass(input.tankClass); } return getCrewSkills(); }, }, analyze_matchup: { description: 'Analyze a 1v1 matchup between two tanks', inputSchema: z.object({ tankA: z.string().describe('First tank name'), tankB: z.string().describe('Second tank name'), }), handler: async (input: { tankA: string; tankB: string }) => { const a = getTankByName(input.tankA); const b = getTankByName(input.tankB); if (!a) return { error: `Tank "${input.tankA}" not found` }; if (!b) return { error: `Tank "${input.tankB}" not found` }; const topTurretA = getTopTurret(a); const topTurretB = getTopTurret(b); const topGunA = getTankTopGun(a); const topGunB = getTankTopGun(b); const topTracksA = getTopTracks(a); const topTracksB = getTopTracks(b); return { [a.name]: { tier: a.tier, class: a.class, health: a.health, armor: a.hullArmor, mobility: { speedForward: a.speedForward, speedBackward: a.speedBackward, hullTraverse: topTracksA?.traverseSpeed, turretTraverse: topTurretA?.traverseSpeed, }, firepower: topGunA ? { damage: topGunA.damage, penetration: topGunA.penetration, dpm: topGunA.dpm, reload: topGunA.reloadTime, accuracy: topGunA.accuracy, aimTime: topGunA.aimTime, depression: topGunA.depression, gunType: topGunA.gunType, clipSize: topGunA.clipSize, } : null, vision: { viewRange: topTurretA?.viewRange, camoStill: a.camouflageStill, camoMoving: a.camouflageMoving, camoFiring: a.camouflageFiring, }, shells: topGunA?.shells, }, [b.name]: { tier: b.tier, class: b.class, health: b.health, armor: b.hullArmor, mobility: { speedForward: b.speedForward, speedBackward: b.speedBackward, hullTraverse: topTracksB?.traverseSpeed, turretTraverse: topTurretB?.traverseSpeed, }, firepower: topGunB ? { damage: topGunB.damage, penetration: topGunB.penetration, dpm: topGunB.dpm, reload: topGunB.reloadTime, accuracy: topGunB.accuracy, aimTime: topGunB.aimTime, depression: topGunB.depression, gunType: topGunB.gunType, clipSize: topGunB.clipSize, } : null, vision: { viewRange: topTurretB?.viewRange, camoStill: b.camouflageStill, camoMoving: b.camouflageMoving, camoFiring: b.camouflageFiring, }, shells: topGunB?.shells, }, }; }, }, analyze_match: { description: 'Analyze a full match with team compositions on a specific map', inputSchema: z.object({ map: z.string().describe('Map name'), myTank: z.string().describe('Your tank name'), friendlyTeam: z.array(z.string()).describe('Friendly team tank names'), enemyTeam: z.array(z.string()).describe('Enemy team tank names'), }), handler: async (input: { map: string; myTank: string; friendlyTeam: string[]; enemyTeam: string[] }) => { const map = getMapByName(input.map); const myTank = getTankByName(input.myTank); if (!map) return { error: `Map "${input.map}" not found` }; if (!myTank) return { error: `Tank "${input.myTank}" not found` }; const resolveTanks = (names: string[]) => names.map(name => { const tank = getTankByName(name); if (!tank) return { name, error: 'not found' }; const topTurret = getTopTurret(tank); const topGun = getTankTopGun(tank); return { name: tank.name, tier: tank.tier, class: tank.class, health: tank.health, dpm: topGun?.dpm, penetration: topGun?.penetration, viewRange: topTurret?.viewRange, }; }); return { map: { name: map.name, spawns: { allies: map.alliesSpawns, enemies: map.enemiesSpawns, }, }, myTank: { name: myTank.name, tier: myTank.tier, class: myTank.class, health: myTank.health, armor: myTank.hullArmor, speed: myTank.speedForward, }, friendlyTeam: resolveTanks(input.friendlyTeam), enemyTeam: resolveTanks(input.enemyTeam), }; }, }, list_camouflages: { description: 'List all camouflages with their invisibility bonus categories', inputSchema: z.object({ nation: z.string().optional().describe('Filter by nation'), kind: z.enum(['summer', 'winter', 'desert', 'universal']).optional().describe('Filter by camo type'), query: z.string().optional().describe('Search query for camouflage name'), }), handler: async (input: { nation?: string; kind?: 'summer' | 'winter' | 'desert' | 'universal'; query?: string }) => { const results = searchCamouflages(input); return { count: results.length, camouflages: results.slice(0, 100), }; }, }, get_camo_bonus: { description: 'Get camouflage invisibility bonus values per tank class for each category', inputSchema: z.object({ tankClass: z.enum(['lightTank', 'mediumTank', 'heavyTank', 'AT-SPG']).optional().describe('Filter by tank class'), }), handler: async (input: { tankClass?: TankClass }) => { const bonusTable = getCamoBonusTable(); if (input.tankClass) { const result: Record<string, number> = {}; for (const [category, bonuses] of Object.entries(bonusTable)) { result[category] = bonuses[input.tankClass]; } return { tankClass: input.tankClass, bonusByCategory: result, }; } return { categories: bonusTable, note: 'Values are additional invisibility percentage added to tank base camo', }; }, }, get_camouflage: { description: 'Get details for a specific camouflage including its bonus values', inputSchema: z.object({ name: z.string().describe('Camouflage name to search for'), }), handler: async (input: { name: string }) => { const camo = getCamouflageByName(input.name); if (!camo) { const all = getCamouflages(); const suggestions = all.filter(c => c.name.toLowerCase().includes(input.name.toLowerCase())).slice(0, 5); return { error: `Camouflage "${input.name}" not found`, suggestions: suggestions.map(c => c.name), }; } const bonusTable = getCamoBonusTable(); const categoryBonuses = bonusTable[camo.category]; return { ...camo, bonuses: categoryBonuses ? { lightTank: `+${(categoryBonuses.lightTank * 100).toFixed(0)}%`, mediumTank: `+${(categoryBonuses.mediumTank * 100).toFixed(0)}%`, heavyTank: `+${(categoryBonuses.heavyTank * 100).toFixed(0)}%`, 'AT-SPG': `+${(categoryBonuses['AT-SPG'] * 100).toFixed(0)}%`, } : null, }; }, }, get_map_image: { description: 'Get top-down tactical map image for visual analysis', inputSchema: z.object({ name: z.string().describe('Map name'), }), handler: async (input: { name: string }): Promise<ImageResult | { error: string; available?: string[] }> => { const map = getMapByName(input.name); if (!map) { const available = getMaps().map(m => m.name); return { error: `Map "${input.name}" not found`, available }; } if (!hasMapImage(map.tag)) { return { error: `No image available for map "${map.name}"` }; } const image = await loadMapImage(map.tag); if (!image) { return { error: `Failed to load image for map "${map.name}"` }; } return { __type: 'image', data: image.data, mimeType: image.mimeType, }; }, }, }; export type ToolName = keyof typeof tools;

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/Revenant30102000/wotblitz-mcp'

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