search_equipment
Search D&D 5e SRD equipment with filters for category, cost, weight, properties, armor type, and rarity. Find weapons, armor, and magic items quickly.
Instructions
Search D&D 5e SRD equipment, weapons, armor, and magic items. Filter by category, cost, weight, weapon properties, armor type, or rarity.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | No | Search term for item name or description | |
| category | No | Equipment category (e.g. "Weapon", "Armor", "Adventuring Gear", "Tools") | |
| cost_min | No | Minimum cost in gold pieces | |
| cost_max | No | Maximum cost in gold pieces | |
| weight_max | No | Maximum weight in pounds | |
| weapon_property | No | Weapon property (e.g. "finesse", "heavy", "two-handed", "versatile") | |
| armor_category | No | Armor category (e.g. "Light", "Medium", "Heavy", "Shield") | |
| rarity | No | Magic item rarity (e.g. "common", "uncommon", "rare", "very rare", "legendary") | |
| limit | No | Results per page (max 50) | |
| offset | No | Offset for pagination |
Implementation Reference
- src/tools/search-equipment.ts:115-196 (handler)The registerSearchEquipment function that implements the search_equipment tool handler. It registers the tool on the MCP server with Zod schema for input validation (query, category, cost_min, cost_max, weight_max, weapon_property, armor_category, rarity, limit, offset), calls searchEquipment from db.ts, and formats results using formatEquipmentItem or formatMagicItem.
export function registerSearchEquipment( server: McpServer, db: Database.Database, ): void { server.registerTool( 'search_equipment', { description: 'Search D&D 5e SRD equipment, weapons, armor, and magic items. Filter by category, cost, weight, weapon properties, armor type, or rarity.', inputSchema: { query: z.string().optional().describe('Search term for item name or description'), category: z .string() .optional() .describe('Equipment category (e.g. "Weapon", "Armor", "Adventuring Gear", "Tools")'), cost_min: z.number().optional().describe('Minimum cost in gold pieces'), cost_max: z.number().optional().describe('Maximum cost in gold pieces'), weight_max: z.number().optional().describe('Maximum weight in pounds'), weapon_property: z .string() .optional() .describe('Weapon property (e.g. "finesse", "heavy", "two-handed", "versatile")'), armor_category: z .string() .optional() .describe('Armor category (e.g. "Light", "Medium", "Heavy", "Shield")'), rarity: z .string() .optional() .describe('Magic item rarity (e.g. "common", "uncommon", "rare", "very rare", "legendary")'), limit: z.number().min(1).max(50).default(10).describe('Results per page (max 50)'), offset: z.number().min(0).default(0).describe('Offset for pagination'), }, }, async ({ query, category, cost_min, cost_max, weight_max, weapon_property, armor_category, rarity, limit, offset, }) => { const result = searchEquipment(db, { query, category, cost_min, cost_max, weight_max, weapon_property, armor_category, rarity, limit, offset, }); if (result.rows.length === 0) { return { content: [ { type: 'text' as const, text: 'No equipment found matching your criteria. Try a broader search — for example, remove some filters or use a partial name.', }, ], isError: true, }; } const start = (offset ?? 0) + 1; const end = (offset ?? 0) + result.rows.length; const header = `Found ${result.total} item${result.total === 1 ? '' : 's'} (showing ${start}-${end})\n`; const items = result.rows.map(formatEquipmentItem).join('\n\n---\n\n'); return { content: [{ type: 'text' as const, text: header + '\n' + items }], }; }, ); - src/types.ts:154-165 (schema)EquipmentFilters interface defining all filter parameters accepted by the search_equipment tool: query, category, cost_min, cost_max, weight_max, weapon_property, armor_category, rarity, limit, offset.
export interface EquipmentFilters { query?: string; category?: string; cost_min?: number; cost_max?: number; weight_max?: number; weapon_property?: string; armor_category?: string; rarity?: string; limit?: number; offset?: number; } - src/server.ts:48-48 (registration)Registration call: registerSearchEquipment(server, db) invoked in the createServer function to register the search_equipment tool.
registerSearchEquipment(server, db); - src/data/db.ts:49-53 (helper)sanitizeFtsQuery helper used to clean user query strings for FTS5 full-text search queries on equipment and magic_items tables.
export function sanitizeFtsQuery(query: string): string { const cleaned = query.replace(/[*":()^~{}<>]/g, ''); const tokens = cleaned.split(/\s+/).filter((t) => t.length > 0); return tokens.map((t) => `"${t}"`).join(' '); } - src/data/db.ts:275-344 (helper)The searchEquipment database function that builds and executes SQL queries against the equipment table (or delegates to searchMagicItems if rarity filter is set). Returns SearchResult with rows and total count.
export function searchEquipment( db: Database.Database, filters: EquipmentFilters, ): SearchResult<EquipmentRow | MagicItemRow> { // Search both equipment and magic_items tables if (filters.rarity) { // Rarity filter only applies to magic items return searchMagicItems(db, filters); } const conditions: string[] = []; const params: (string | number)[] = []; if (filters.query) { const ftsQuery = sanitizeFtsQuery(filters.query); if (ftsQuery.length > 0) { conditions.push( 'e.id IN (SELECT rowid FROM equipment_fts WHERE equipment_fts MATCH ?)', ); params.push(ftsQuery); } } if (filters.category) { conditions.push('LOWER(e.category) = LOWER(?)'); params.push(filters.category); } if (filters.cost_min !== undefined) { conditions.push('e.cost_gp >= ?'); params.push(filters.cost_min); } if (filters.cost_max !== undefined) { conditions.push('e.cost_gp <= ?'); params.push(filters.cost_max); } if (filters.weight_max !== undefined) { conditions.push('e.weight <= ?'); params.push(filters.weight_max); } if (filters.weapon_property) { conditions.push('LOWER(e.weapon_properties) LIKE LOWER(?)'); params.push(`%${filters.weapon_property}%`); } if (filters.armor_category) { conditions.push('LOWER(e.armor_category) = LOWER(?)'); params.push(filters.armor_category); } const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''; const limit = filters.limit ?? 20; const offset = filters.offset ?? 0; const countRow = db .prepare(`SELECT COUNT(*) as count FROM equipment e ${where}`) .get(...params) as { count: number }; const rows = db .prepare( `SELECT e.* FROM equipment e ${where} ORDER BY e.name ASC LIMIT ? OFFSET ?`, ) .all(...params, limit, offset) as EquipmentRow[]; return { rows, total: countRow.count }; }