lookup_rule
Retrieve official Magic: The Gathering rules by section number or search text. Find exact rule text and interactions.
Instructions
Look up a specific section of the Magic: The Gathering Comprehensive Rules by section number (e.g., "702", "702.1") or search rules text. Use this when a user asks about specific game rules, rule interactions, or needs the official rule text. Returns the rule and its subsections.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| section | No | Rule section number to look up (e.g. "702" or "702.1"). Returns exact match plus all subsections. | |
| query | No | Text to search for across all rule titles and text (case-insensitive). |
Implementation Reference
- src/tools/lookup-rule.ts:36-121 (handler)Main handler function and helper functions (lookupBySection, searchByText, toEntry) implementing the lookup_rule tool logic. Dispatches to lookupBySection or searchByText based on params.
export function handler(db: Database.Database, params: LookupRuleParams): LookupRuleResult { if (params.section) { return lookupBySection(db, params.section); } return searchByText(db, params.query!); } function lookupBySection(db: Database.Database, section: string): LookupRuleResult { // Exact match const exact = db.prepare( 'SELECT * FROM rules WHERE section = ?' ).get(section) as RuleRow | undefined; // Subsections: match anything starting with "section." (e.g. "702" matches "702.1", "702.1a") const subsections = db.prepare( 'SELECT * FROM rules WHERE section LIKE ? AND section != ? ORDER BY section' ).all(`${section}.%`, section) as RuleRow[]; // Also get sub-subsections for sections like "702" → "702.1a" const subSubsections = db.prepare( 'SELECT * FROM rules WHERE section LIKE ? AND section NOT LIKE ? AND section != ? ORDER BY section' ).all(`${section}%`, `${section}.%`, section) as RuleRow[]; // Filter subSubsections to only those not already in subsections const subsectionSections = new Set(subsections.map(r => r.section)); const additional = subSubsections.filter(r => !subsectionSections.has(r.section) && r.section !== section); const allRules: RuleEntry[] = []; if (exact) { allRules.push(toEntry(exact)); } for (const r of [...subsections, ...additional]) { allRules.push(toEntry(r)); } if (allRules.length === 0) { return { found: false, message: `No rule found for section "${section}"`, }; } // Include parent context if the exact rule has a parent const result: LookupRuleResult = { found: true, rules: allRules }; if (exact?.parent_section) { const parent = db.prepare( 'SELECT * FROM rules WHERE section = ?' ).get(exact.parent_section) as RuleRow | undefined; if (parent) { result.parent = toEntry(parent); } } return result; } function searchByText(db: Database.Database, query: string): LookupRuleResult { const pattern = `%${query}%`; const rows = db.prepare( 'SELECT * FROM rules WHERE LOWER(title) LIKE LOWER(?) OR LOWER(text) LIKE LOWER(?) ORDER BY section LIMIT 20' ).all(pattern, pattern) as RuleRow[]; if (rows.length === 0) { return { found: false, message: `No rules found matching "${query}"`, }; } return { found: true, rules: rows.map(toEntry), }; } function toEntry(row: RuleRow): RuleEntry { return { section: row.section, title: row.title, text: row.text, parent_section: row.parent_section, }; } - src/tools/lookup-rule.ts:7-32 (schema)Input schema (LookupRuleInput) with optional 'section' and 'query' fields, and output types (RuleEntry, LookupRuleResult) for the lookup_rule tool.
export const LookupRuleInput = z.object({ section: z.string().optional().describe('Rule section number to look up (e.g. "702" or "702.1"). Returns exact match plus all subsections.'), query: z.string().optional().describe('Text to search for across all rule titles and text (case-insensitive).'), }).refine(data => data.section || data.query, { message: 'Either section or query must be provided', }); export type LookupRuleParams = z.infer<typeof LookupRuleInput>; // --- Output types --- export interface RuleEntry { section: string; title: string | null; text: string; parent_section: string | null; } export type LookupRuleResult = { found: true; rules: RuleEntry[]; parent?: RuleEntry; } | { found: false; message: string; }; - src/server.ts:151-163 (registration)Registration of the 'lookup_rule' tool on the MCP server, binding the schema, handler, and formatter.
server.tool( 'lookup_rule', 'Look up a specific section of the Magic: The Gathering Comprehensive Rules by section number (e.g., "702", "702.1") or search rules text. Use this when a user asks about specific game rules, rule interactions, or needs the official rule text. Returns the rule and its subsections.', LookupRuleInput.innerType().shape, async (params) => { try { const result = lookupRuleHandler(db, params); return { content: [{ type: 'text' as const, text: formatLookupRule(result) }] }; } catch (err) { return { content: [{ type: 'text' as const, text: `Error looking up rule: ${err instanceof Error ? err.message : String(err)}` }], isError: true }; } }, ); - src/format.ts:231-249 (helper)Formatter for LookupRuleResult, converting the result into human-readable markdown text.
export function formatLookupRule(result: LookupRuleResult): string { if (!result.found) { return result.message; } const lines: string[] = []; if (result.parent) { lines.push(`(Parent: ${result.parent.section} — ${result.parent.title ?? result.parent.text})\n`); } for (const rule of result.rules) { const titlePart = rule.title ? ` — ${rule.title}` : ''; lines.push(`**${rule.section}${titlePart}**`); lines.push(rule.text); lines.push(''); } return lines.join('\n').trimEnd(); - src/data/db.ts:62-67 (helper)RuleRow interface defining the database row shape used by the lookup_rule handler.
export interface RuleRow { section: string; title: string | null; text: string; parent_section: string | null; }