/** * Symbol creator tools for KiCAD MCP server * * create_symbol – add a new symbol to a .kicad_sym library * delete_symbol – remove a symbol from a library * list_symbols_in_library – list all symbols in a .kicad_sym file * register_symbol_library – add library to sym-lib-table */ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { z } from "zod"; const PinSchema = z.object({ name: z.string().describe("Pin name, e.g. 'VCC', 'GND', 'IN+', '~' for unnamed"), number: z.union([z.string(), z.number()]).describe("Pin number, e.g. '1', '2', 'A1'"), type: z .enum([ "input", "output", "bidirectional", "tri_state", "passive", "free", "unspecified", "power_in", "power_out", "open_collector", "open_emitter", "no_connect", ]) .describe("Electrical pin type"), at: z .object({ x: z.number().describe("X position in mm"), y: z.number().describe("Y position in mm"), angle: z .number() .describe( "Direction the pin wire extends FROM the symbol body: 0=right, 90=up, 180=left, 270=down", ), }) .describe("Pin endpoint position (where the wire connects)"), length: z.number().optional().describe("Pin length in mm (default 2.54)"), shape: z .enum([ "line", "inverted", "clock", "inverted_clock", "input_low", "clock_low", "output_low", "falling_edge_clock", "non_logic", ]) .optional() .describe("Pin graphic shape (default: line)"), }); const RectSchema = z.object({ x1: z.number(), y1: z.number(), x2: z.number(), y2: z.number(), width: z.number().optional().describe("Stroke width in mm (default 0.254)"), fill: z .enum(["none", "outline", "background"]) .optional() .describe("Fill type (default: background)"), }); const PolylineSchema = z.object({ points: z.array(z.object({ x: z.number(), y: z.number() })).describe("List of XY points in mm"), width: z.number().optional().describe("Stroke width in mm (default 0.254)"), fill: z.enum(["none", "outline", "background"]).optional(), }); export function registerSymbolCreatorTools(server: McpServer, callKicadScript: Function) { // ── create_symbol ────────────────────────────────────────────────────── // server.tool( "create_symbol", "Create a new schematic symbol in a .kicad_sym library file (created if missing). " + "After creation, use register_symbol_library so KiCAD finds it. " + "Pin positions are where the wire connects; the symbol body is drawn between them.\n\n" + "Coordinate tips:\n" + "- Body rectangle typically spans ±2.54 to ±5.08 mm\n" + "- Pins on left side: at.x = body_left - length, angle=0 (wire goes right)\n" + "- Pins on right side: at.x = body_right + length, angle=180 (wire goes left)\n" + "- Pins on top: at.y = body_top + length, angle=270 (wire goes down)\n" + "- Pins on bottom: at.y = body_bottom - length, angle=90 (wire goes up)\n" + "- Standard pin length: 2.54 mm, standard grid: 2.54 mm", { libraryPath: z.string().describe("Path to the .kicad_sym file (created if missing)"), name: z.string().describe("Symbol name, e.g. 'TMC2209', 'MyOpAmp'"), referencePrefix: z .string() .optional() .describe( "Schematic reference prefix: 'U' (IC), 'R' (resistor), 'J' (connector), etc. Default: 'U'", ), description: z.string().optional().describe("Human-readable description"), keywords: z.string().optional().describe("Space-separated search keywords"), datasheet: z.string().optional().describe("Datasheet URL or '~'"), footprint: z .string() .optional() .describe("Default footprint, e.g. 'Package_SO:SOIC-8_3.9x4.9mm_P1.27mm'"), inBom: z.boolean().optional().describe("Include in BOM (default true)"), onBoard: z.boolean().optional().describe("Include in netlist for PCB (default true)"), pins: z .array(PinSchema) .optional() .describe("List of pins (can be empty for graphical-only symbols)"), rectangles: z .array(RectSchema) .optional() .describe("Body rectangle(s). Typically one rectangle defining the IC body."), polylines: z .array(PolylineSchema) .optional() .describe("Polyline graphics for custom body shapes (op-amp triangles, etc.)"), overwrite: z .boolean() .optional() .describe("Replace existing symbol with same name (default false)"), }, async (args: { libraryPath: string; name: string; referencePrefix?: string; description?: string; keywords?: string; datasheet?: string; footprint?: string; inBom?: boolean; onBoard?: boolean; pins?: z.infer[]; rectangles?: z.infer[]; polylines?: z.infer[]; overwrite?: boolean; }) => { const result = await callKicadScript("create_symbol", args); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], }; }, ); // ── delete_symbol ────────────────────────────────────────────────────── // server.tool( "delete_symbol", "Remove a symbol from a .kicad_sym library file.", { libraryPath: z.string().describe("Path to the .kicad_sym file"), name: z.string().describe("Symbol name to delete"), }, async (args: { libraryPath: string; name: string }) => { const result = await callKicadScript("delete_symbol", args); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], }; }, ); // ── list_symbols_in_library ──────────────────────────────────────────── // server.tool( "list_symbols_in_library", "List all symbol names in a .kicad_sym library file.", { libraryPath: z.string().describe("Path to the .kicad_sym file"), }, async (args: { libraryPath: string }) => { const result = await callKicadScript("list_symbols_in_library", args); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], }; }, ); // ── register_symbol_library ──────────────────────────────────────────── // server.tool( "register_symbol_library", "Register a .kicad_sym library in KiCAD's sym-lib-table so symbols can be used in schematics. " + "Run this after create_symbol when KiCAD shows 'library not found'.", { libraryPath: z.string().describe("Full path to the .kicad_sym file"), libraryName: z .string() .optional() .describe("Nickname (default: file name without extension)"), description: z.string().optional(), scope: z .enum(["project", "global"]) .optional() .describe("project = writes sym-lib-table next to .kicad_pro; global = user config"), projectPath: z .string() .optional() .describe("Path to .kicad_pro or its directory (for scope=project)"), }, async (args: { libraryPath: string; libraryName?: string; description?: string; scope?: "project" | "global"; projectPath?: string; }) => { const result = await callKicadScript("register_symbol_library", args); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], }; }, ); }