Files
kicad-mcp-server/src/tools/routing.ts
Tom 3e329cbf6b fix: route_pad_to_pad auto-inserts via when pads are on different copper layers
- Detects F.Cu<->B.Cu layer mismatch in route_pad_to_pad
- Splits route into: trace on start layer + via at midpoint + trace on end layer
- route_trace description warns: use route_pad_to_pad for cross-layer routing
- route_pad_to_pad description highlights automatic via insertion
2026-03-07 15:46:54 +01:00

387 lines
11 KiB
TypeScript

/**
* 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),
},
],
};
},
);
}