search_cards
Find Disney Lorcana cards by name, text, or filters like ink color, type, rarity, set, and cost. Get paginated results for deck building and gameplay.
Instructions
Search Disney Lorcana cards by name, rules text, or filters (ink color, type, rarity, set, cost range). Returns paginated results.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | No | Search by card name or rules text | |
| ink | No | Filter by ink color (e.g. Amber, Amethyst, Emerald, Ruby, Sapphire, Steel) | |
| type | No | Filter by card type (e.g. Character, Song, Item, Action, Location) | |
| rarity | No | Filter by rarity (e.g. Common, Uncommon, Rare, Super Rare, Legendary, Enchanted) | |
| set | No | Filter by set code | |
| cost_min | No | Minimum ink cost (inclusive) | |
| cost_max | No | Maximum ink cost (inclusive) | |
| limit | No | Max results to return (default 20) | |
| cursor | No | Offset for pagination |
Implementation Reference
- src/tools/search-cards.ts:47-81 (handler)The actual handler function for 'search_cards' which maps MCP tool arguments to internal DB filters and executes the database search.
async (args) => { const filters: SearchFilters = { query: args.query, color: args.ink, type: args.type, rarity: args.rarity, setCode: args.set, costMin: args.cost_min, costMax: args.cost_max, limit: args.limit, offset: args.cursor, }; const { rows, total } = searchCards(db, filters); if (rows.length === 0) { return { content: [{ type: 'text' as const, text: 'No cards found matching your search criteria.' }], }; } const offset = args.cursor ?? 0; const parts = rows.map(formatCard); const footer: string[] = []; footer.push(`\n---\nShowing ${offset + 1}–${offset + rows.length} of ${total} results.`); if (offset + rows.length < total) { footer.push(`Use cursor: ${offset + rows.length} to see more.`); } return { content: [ { type: 'text' as const, text: parts.join('\n\n') + footer.join(' ') }, ], }; }, - src/tools/search-cards.ts:29-46 (registration)Registration of the 'search_cards' tool within the MCP server, including the input schema definition.
server.registerTool( 'search_cards', { title: 'Search Cards', description: 'Search Disney Lorcana cards by name, rules text, or filters (ink color, type, rarity, set, cost range). Returns paginated results.', inputSchema: { query: z.string().optional().describe('Search by card name or rules text'), ink: z.string().optional().describe('Filter by ink color (e.g. Amber, Amethyst, Emerald, Ruby, Sapphire, Steel)'), type: z.string().optional().describe('Filter by card type (e.g. Character, Song, Item, Action, Location)'), rarity: z.string().optional().describe('Filter by rarity (e.g. Common, Uncommon, Rare, Super Rare, Legendary, Enchanted)'), set: z.string().optional().describe('Filter by set code'), cost_min: z.number().optional().describe('Minimum ink cost (inclusive)'), cost_max: z.number().optional().describe('Maximum ink cost (inclusive)'), limit: z.number().optional().default(20).describe('Max results to return (default 20)'), cursor: z.number().optional().describe('Offset for pagination'), }, }, - src/data/db.ts:51-136 (helper)The core database function that performs the SQL queries for searching cards based on provided filters.
export function searchCards( db: Database.Database, filters: SearchFilters, ): SearchResult { const conditions: string[] = []; const params: (string | number)[] = []; if (filters.query) { const ftsQuery = sanitizeFtsQuery(filters.query); if (ftsQuery.length > 0) { conditions.push( 'c.id IN (SELECT rowid FROM cards_fts WHERE cards_fts MATCH ?)', ); params.push(ftsQuery); } } if (filters.color) { conditions.push('LOWER(c.color) = LOWER(?)'); params.push(filters.color); } if (filters.type) { conditions.push('LOWER(c.type) = LOWER(?)'); params.push(filters.type); } if (filters.cost !== undefined) { const op = filters.costOp ?? 'eq'; const sqlOp = op === 'lte' ? '<=' : op === 'gte' ? '>=' : '='; conditions.push(`c.cost ${sqlOp} ?`); params.push(filters.cost); } if (filters.costMin !== undefined) { conditions.push('c.cost >= ?'); params.push(filters.costMin); } if (filters.costMax !== undefined) { conditions.push('c.cost <= ?'); params.push(filters.costMax); } if (filters.rarity) { conditions.push('LOWER(c.rarity) = LOWER(?)'); params.push(filters.rarity); } if (filters.setCode) { conditions.push('c.set_code = ?'); params.push(filters.setCode); } if (filters.story) { conditions.push('LOWER(c.story) = LOWER(?)'); params.push(filters.story); } if (filters.inkwell !== undefined) { conditions.push('c.inkwell = ?'); params.push(filters.inkwell ? 1 : 0); } if (filters.hasKeyword) { conditions.push('c.keyword_abilities LIKE ?'); params.push(`%${filters.hasKeyword}%`); } 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 cards c ${where}`) .get(...params) as { count: number }; const rows = db .prepare( `SELECT c.* FROM cards c ${where} ORDER BY c.cost ASC, c.name ASC LIMIT ? OFFSET ?`, ) .all(...params, limit, offset) as CardRow[]; return { rows, total: countRow.count }; }