/** * Routing tools for KiCAD MCP server */ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { z } from "zod"; export function registerRoutingTools( server: McpServer, callKicadScript: Function, ) { // Add net tool server.tool( "add_net", "Create a new net on the PCB", { name: z.string().describe("Net name"), netClass: z.string().optional().describe("Net class name"), }, async (args: { name: string; netClass?: string }) => { const result = await callKicadScript("add_net", args); return { content: [ { type: "text", text: JSON.stringify(result, null, 2), }, ], }; }, ); // Route trace tool server.tool( "route_trace", "Route a trace segment between two XY points on a fixed layer. WARNING: Does NOT handle layer changes — if start and end are on different copper layers, use route_pad_to_pad instead, which automatically inserts a via.", { start: z .object({ x: z.number(), y: z.number(), unit: z.string().optional(), }) .describe("Start position"), end: z .object({ x: z.number(), y: z.number(), unit: z.string().optional(), }) .describe("End position"), layer: z.string().describe("PCB layer"), width: z.number().describe("Trace width in mm"), net: z.string().describe("Net name"), }, async (args: any) => { const result = await callKicadScript("route_trace", args); return { content: [ { type: "text", text: JSON.stringify(result, null, 2), }, ], }; }, ); // Add via tool server.tool( "add_via", "Add a via to the PCB", { position: z .object({ x: z.number(), y: z.number(), unit: z.string().optional(), }) .describe("Via position"), net: z.string().describe("Net name"), viaType: z .string() .optional() .describe("Via type (through, blind, buried)"), }, async (args: any) => { const result = await callKicadScript("add_via", args); return { content: [ { type: "text", text: JSON.stringify(result, null, 2), }, ], }; }, ); // Add copper pour tool server.tool( "add_copper_pour", "Add a copper pour (ground/power plane) to the PCB", { layer: z.string().describe("PCB layer"), net: z.string().describe("Net name"), clearance: z.number().optional().describe("Clearance in mm"), outline: z .array(z.object({ x: z.number(), y: z.number() })) .optional() .describe( "Array of {x, y} points defining the pour boundary. If omitted, the board outline is used.", ), }, async (args: any) => { const result = await callKicadScript("add_copper_pour", args); return { content: [ { type: "text", text: JSON.stringify(result, null, 2), }, ], }; }, ); // Delete trace tool server.tool( "delete_trace", "Delete traces from the PCB. Can delete by UUID, position, or bulk-delete all traces on a net.", { traceUuid: z .string() .optional() .describe("UUID of a specific trace to delete"), position: z .object({ x: z.number(), y: z.number(), unit: z.enum(["mm", "inch"]).optional(), }) .optional() .describe("Delete trace nearest to this position"), net: z .string() .optional() .describe("Delete all traces on this net (bulk delete)"), layer: z .string() .optional() .describe("Filter by layer when using net-based deletion"), includeVias: z .boolean() .optional() .describe("Include vias in net-based deletion"), }, async (args: any) => { const result = await callKicadScript("delete_trace", args); return { content: [ { type: "text", text: JSON.stringify(result, null, 2), }, ], }; }, ); // Query traces tool server.tool( "query_traces", "Query traces on the board with optional filters by net, layer, or bounding box.", { net: z.string().optional().describe("Filter by net name"), layer: z.string().optional().describe("Filter by layer name"), boundingBox: z .object({ x1: z.number(), y1: z.number(), x2: z.number(), y2: z.number(), unit: z.enum(["mm", "inch"]).optional(), }) .optional() .describe("Filter by bounding box region"), unit: z.enum(["mm", "inch"]).optional().describe("Unit for coordinates"), }, async (args: any) => { const result = await callKicadScript("query_traces", args); return { content: [ { type: "text", text: JSON.stringify(result, null, 2), }, ], }; }, ); // Get nets list tool server.tool( "get_nets_list", "Get a list of all nets in the PCB with optional statistics.", { includeStats: z .boolean() .optional() .describe("Include statistics (track count, total length, etc.)"), unit: z .enum(["mm", "inch"]) .optional() .describe("Unit for length measurements"), }, async (args: any) => { const result = await callKicadScript("get_nets_list", args); return { content: [ { type: "text", text: JSON.stringify(result, null, 2), }, ], }; }, ); // Modify trace tool server.tool( "modify_trace", "Modify an existing trace (change width, layer, or net).", { traceUuid: z.string().describe("UUID of the trace to modify"), width: z.number().optional().describe("New trace width in mm"), layer: z.string().optional().describe("New layer name"), net: z.string().optional().describe("New net name"), }, async (args: any) => { const result = await callKicadScript("modify_trace", args); return { content: [ { type: "text", text: JSON.stringify(result, null, 2), }, ], }; }, ); // Create netclass tool server.tool( "create_netclass", "Create a new net class with custom design rules.", { name: z.string().describe("Net class name"), traceWidth: z.number().optional().describe("Default trace width in mm"), clearance: z.number().optional().describe("Clearance in mm"), viaDiameter: z.number().optional().describe("Via diameter in mm"), viaDrill: z.number().optional().describe("Via drill size in mm"), }, async (args: any) => { const result = await callKicadScript("create_netclass", args); return { content: [ { type: "text", text: JSON.stringify(result, null, 2), }, ], }; }, ); // Route differential pair tool server.tool( "route_differential_pair", "Route a differential pair between two sets of points.", { positivePad: z .object({ reference: z.string(), pad: z.string(), }) .describe("Positive pad (component and pad number)"), negativePad: z .object({ reference: z.string(), pad: z.string(), }) .describe("Negative pad (component and pad number)"), layer: z.string().describe("PCB layer"), width: z.number().describe("Trace width in mm"), gap: z.number().describe("Gap between traces in mm"), positiveNet: z.string().describe("Positive net name"), negativeNet: z.string().describe("Negative net name"), }, async (args: any) => { const result = await callKicadScript("route_differential_pair", args); return { content: [ { type: "text", text: JSON.stringify(result, null, 2), }, ], }; }, ); // Refill zones tool server.tool( "refill_zones", "Refill all copper zones on the board. WARNING: SWIG path has known segfault risk (see KNOWN_ISSUES.md). Prefer using IPC backend (KiCAD open) or triggering zone fill via KiCAD UI instead.", {}, async (args: any) => { const result = await callKicadScript("refill_zones", args); return { content: [ { type: "text", text: JSON.stringify(result, null, 2), }, ], }; }, ); // Route pad to pad tool server.tool( "route_pad_to_pad", "PREFERRED tool for pad-to-pad routing. Looks up pad positions automatically, detects the net from the pad, and — critically — if the two pads are on different copper layers (e.g. J1 on F.Cu and J2 on B.Cu) automatically inserts a via at the midpoint so the connection is complete. Always use this instead of route_trace when routing between named component pads.", { fromRef: z.string().describe("Reference of the source component (e.g. 'U2')"), fromPad: z.union([z.string(), z.number()]).describe("Pad number on the source component (e.g. '6' or 6)"), toRef: z.string().describe("Reference of the target component (e.g. 'U1')"), toPad: z.union([z.string(), z.number()]).describe("Pad number on the target component (e.g. '15' or 15)"), layer: z.string().optional().describe("PCB layer (default: F.Cu)"), width: z.number().optional().describe("Trace width in mm (default: board default)"), net: z.string().optional().describe("Net name override (default: auto-detected from pad)"), }, async (args: any) => { const result = await callKicadScript("route_pad_to_pad", args); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], }; }, ); // Copy routing pattern tool server.tool( "copy_routing_pattern", "Copy routing pattern (traces and vias) from a group of source components to a matching group of target components. The offset is calculated automatically from the position difference between the first source and first target component. Useful for replicating routing between identical circuit blocks.", { sourceRefs: z .array(z.string()) .describe("References of the source components (e.g. ['U1', 'R1', 'C1'])"), targetRefs: z .array(z.string()) .describe( "References of the target components in same order as sourceRefs (e.g. ['U2', 'R2', 'C2'])", ), includeVias: z .boolean() .optional() .describe("Also copy vias (default: true)"), traceWidth: z .number() .optional() .describe("Override trace width in mm (default: keep original width)"), }, async (args: any) => { const result = await callKicadScript("copy_routing_pattern", args); return { content: [ { type: "text", text: JSON.stringify(result, null, 2), }, ], }; }, ); }