feat: Add missing routing/component tools and fix SWIG/UUID bugs

New MCP tools added (TypeScript layer):
- Routing: delete_trace, query_traces, get_nets_list, modify_trace,
  create_netclass, route_differential_pair, refill_zones (with SWIG warning)
- Component: get_component_pads, get_component_list, get_pad_position,
  place_component_array, align_components, duplicate_component

Bug fixes:
- routing.py: Fix SwigPyObject UUID comparison (str() -> m_Uuid.AsString())
- routing.py: Fix SWIG iterator invalidation after board.Remove()
  by converting board.Tracks() to list() before iteration
- routing.py: Add board.SetModified() and clear Python refs after Remove()
  to prevent dangling SWIG pointers crashing subsequent calls
- routing.py: Wrap each track access in try/except in query_traces()
  to gracefully skip invalid track objects after bulk delete
- routing.py: Add missing return statement (mypy fix)
- library.py: Fix search_footprints param mapping search_term->pattern
- library.py: Fix fp.name -> fp.full_name field access
- library.py: Accept both 'pattern' and 'search_term' parameter names
- library.py: Add library filter support in search_footprints
- library.py: Fix loop variable shadowing Path object (mypy fix)
- design_rules.py: Add type annotation for violation_counts (mypy fix)

Contribution guidelines followed (CONTRIBUTING.md):
- black python/ applied (3 touched files pass without changes)
- mypy python/ passes with 0 errors on all changed files
- npx prettier --write applied to changed TypeScript files
- npm run build passes (tsc --noEmit: 0 errors)
- Commit messages follow feat:/fix: convention

Note on diff size: The large insertion/deletion count in the changed files
is due to black and prettier reformatting previously unformatted code
(missing trailing commas, quote style, line length). The actual logic
changes are limited to the 6 files listed above.

Note: refill_zones has known SWIG segfault risk (see KNOWN_ISSUES.md).
Prefer IPC backend (KiCAD open) or zone fill via KiCAD UI.
This commit is contained in:
Tom
2026-02-27 17:49:04 +01:00
parent b5a1483fc2
commit 2945b52eae
7 changed files with 1624 additions and 903 deletions

View File

@@ -1,291 +1,644 @@
/**
* Component management tools for KiCAD MCP server
*/
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
import { logger } from '../logger.js';
// Command function type for KiCAD script calls
type CommandFunction = (command: string, params: Record<string, unknown>) => Promise<any>;
/**
* Register component management tools with the MCP server
*
* @param server MCP server instance
* @param callKicadScript Function to call KiCAD script commands
*/
export function registerComponentTools(server: McpServer, callKicadScript: CommandFunction): void {
logger.info('Registering component management tools');
// ------------------------------------------------------
// Place Component Tool
// ------------------------------------------------------
server.tool(
"place_component",
{
componentId: z.string().describe("Identifier for the component to place (e.g., 'R_0603_10k')"),
position: z.object({
x: z.number().describe("X coordinate"),
y: z.number().describe("Y coordinate"),
unit: z.enum(["mm", "inch"]).describe("Unit of measurement")
}).describe("Position coordinates and unit"),
reference: z.string().optional().describe("Optional desired reference (e.g., 'R5')"),
value: z.string().optional().describe("Optional component value (e.g., '10k')"),
footprint: z.string().optional().describe("Optional specific footprint name"),
rotation: z.number().optional().describe("Optional rotation in degrees"),
layer: z.string().optional().describe("Optional layer (e.g., 'F.Cu', 'B.SilkS')")
},
async ({ componentId, position, reference, value, footprint, rotation, layer }) => {
logger.debug(`Placing component: ${componentId} at ${position.x},${position.y} ${position.unit}`);
const result = await callKicadScript("place_component", {
componentId,
position,
reference,
value,
footprint,
rotation,
layer
});
return {
content: [{
type: "text",
text: JSON.stringify(result)
}]
};
}
);
// ------------------------------------------------------
// Move Component Tool
// ------------------------------------------------------
server.tool(
"move_component",
{
reference: z.string().describe("Reference designator of the component (e.g., 'R5')"),
position: z.object({
x: z.number().describe("X coordinate"),
y: z.number().describe("Y coordinate"),
unit: z.enum(["mm", "inch"]).describe("Unit of measurement")
}).describe("New position coordinates and unit"),
rotation: z.number().optional().describe("Optional new rotation in degrees")
},
async ({ reference, position, rotation }) => {
logger.debug(`Moving component: ${reference} to ${position.x},${position.y} ${position.unit}`);
const result = await callKicadScript("move_component", {
reference,
position,
rotation
});
return {
content: [{
type: "text",
text: JSON.stringify(result)
}]
};
}
);
// ------------------------------------------------------
// Rotate Component Tool
// ------------------------------------------------------
server.tool(
"rotate_component",
{
reference: z.string().describe("Reference designator of the component (e.g., 'R5')"),
angle: z.number().describe("Rotation angle in degrees (absolute, not relative)")
},
async ({ reference, angle }) => {
logger.debug(`Rotating component: ${reference} to ${angle} degrees`);
const result = await callKicadScript("rotate_component", {
reference,
angle
});
return {
content: [{
type: "text",
text: JSON.stringify(result)
}]
};
}
);
// ------------------------------------------------------
// Delete Component Tool
// ------------------------------------------------------
server.tool(
"delete_component",
{
reference: z.string().describe("Reference designator of the component to delete (e.g., 'R5')")
},
async ({ reference }) => {
logger.debug(`Deleting component: ${reference}`);
const result = await callKicadScript("delete_component", { reference });
return {
content: [{
type: "text",
text: JSON.stringify(result)
}]
};
}
);
// ------------------------------------------------------
// Edit Component Properties Tool
// ------------------------------------------------------
server.tool(
"edit_component",
{
reference: z.string().describe("Reference designator of the component (e.g., 'R5')"),
newReference: z.string().optional().describe("Optional new reference designator"),
value: z.string().optional().describe("Optional new component value"),
footprint: z.string().optional().describe("Optional new footprint")
},
async ({ reference, newReference, value, footprint }) => {
logger.debug(`Editing component: ${reference}`);
const result = await callKicadScript("edit_component", {
reference,
newReference,
value,
footprint
});
return {
content: [{
type: "text",
text: JSON.stringify(result)
}]
};
}
);
// ------------------------------------------------------
// Find Component Tool
// ------------------------------------------------------
server.tool(
"find_component",
{
reference: z.string().optional().describe("Reference designator to search for"),
value: z.string().optional().describe("Component value to search for")
},
async ({ reference, value }) => {
logger.debug(`Finding component with ${reference ? `reference: ${reference}` : `value: ${value}`}`);
const result = await callKicadScript("find_component", { reference, value });
return {
content: [{
type: "text",
text: JSON.stringify(result)
}]
};
}
);
// ------------------------------------------------------
// Get Component Properties Tool
// ------------------------------------------------------
server.tool(
"get_component_properties",
{
reference: z.string().describe("Reference designator of the component (e.g., 'R5')")
},
async ({ reference }) => {
logger.debug(`Getting properties for component: ${reference}`);
const result = await callKicadScript("get_component_properties", { reference });
return {
content: [{
type: "text",
text: JSON.stringify(result)
}]
};
}
);
// ------------------------------------------------------
// Add Component Annotation Tool
// ------------------------------------------------------
server.tool(
"add_component_annotation",
{
reference: z.string().describe("Reference designator of the component (e.g., 'R5')"),
annotation: z.string().describe("Annotation or comment text to add"),
visible: z.boolean().optional().describe("Whether the annotation should be visible on the PCB")
},
async ({ reference, annotation, visible }) => {
logger.debug(`Adding annotation to component: ${reference}`);
const result = await callKicadScript("add_component_annotation", {
reference,
annotation,
visible
});
return {
content: [{
type: "text",
text: JSON.stringify(result)
}]
};
}
);
// ------------------------------------------------------
// Group Components Tool
// ------------------------------------------------------
server.tool(
"group_components",
{
references: z.array(z.string()).describe("Reference designators of components to group"),
groupName: z.string().describe("Name for the component group")
},
async ({ references, groupName }) => {
logger.debug(`Grouping components: ${references.join(', ')} as ${groupName}`);
const result = await callKicadScript("group_components", {
references,
groupName
});
return {
content: [{
type: "text",
text: JSON.stringify(result)
}]
};
}
);
// ------------------------------------------------------
// Replace Component Tool
// ------------------------------------------------------
server.tool(
"replace_component",
{
reference: z.string().describe("Reference designator of the component to replace"),
newComponentId: z.string().describe("ID of the new component to use"),
newFootprint: z.string().optional().describe("Optional new footprint"),
newValue: z.string().optional().describe("Optional new component value")
},
async ({ reference, newComponentId, newFootprint, newValue }) => {
logger.debug(`Replacing component: ${reference} with ${newComponentId}`);
const result = await callKicadScript("replace_component", {
reference,
newComponentId,
newFootprint,
newValue
});
return {
content: [{
type: "text",
text: JSON.stringify(result)
}]
};
}
);
logger.info('Component management tools registered');
}
/**
* Component management tools for KiCAD MCP server
*/
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import { logger } from "../logger.js";
// Command function type for KiCAD script calls
type CommandFunction = (
command: string,
params: Record<string, unknown>,
) => Promise<any>;
/**
* Register component management tools with the MCP server
*
* @param server MCP server instance
* @param callKicadScript Function to call KiCAD script commands
*/
export function registerComponentTools(
server: McpServer,
callKicadScript: CommandFunction,
): void {
logger.info("Registering component management tools");
// ------------------------------------------------------
// Place Component Tool
// ------------------------------------------------------
server.tool(
"place_component",
{
componentId: z
.string()
.describe("Identifier for the component to place (e.g., 'R_0603_10k')"),
position: z
.object({
x: z.number().describe("X coordinate"),
y: z.number().describe("Y coordinate"),
unit: z.enum(["mm", "inch"]).describe("Unit of measurement"),
})
.describe("Position coordinates and unit"),
reference: z
.string()
.optional()
.describe("Optional desired reference (e.g., 'R5')"),
value: z
.string()
.optional()
.describe("Optional component value (e.g., '10k')"),
footprint: z
.string()
.optional()
.describe("Optional specific footprint name"),
rotation: z.number().optional().describe("Optional rotation in degrees"),
layer: z
.string()
.optional()
.describe("Optional layer (e.g., 'F.Cu', 'B.SilkS')"),
},
async ({
componentId,
position,
reference,
value,
footprint,
rotation,
layer,
}) => {
logger.debug(
`Placing component: ${componentId} at ${position.x},${position.y} ${position.unit}`,
);
const result = await callKicadScript("place_component", {
componentId,
position,
reference,
value,
footprint,
rotation,
layer,
});
return {
content: [
{
type: "text",
text: JSON.stringify(result),
},
],
};
},
);
// ------------------------------------------------------
// Move Component Tool
// ------------------------------------------------------
server.tool(
"move_component",
{
reference: z
.string()
.describe("Reference designator of the component (e.g., 'R5')"),
position: z
.object({
x: z.number().describe("X coordinate"),
y: z.number().describe("Y coordinate"),
unit: z.enum(["mm", "inch"]).describe("Unit of measurement"),
})
.describe("New position coordinates and unit"),
rotation: z
.number()
.optional()
.describe("Optional new rotation in degrees"),
},
async ({ reference, position, rotation }) => {
logger.debug(
`Moving component: ${reference} to ${position.x},${position.y} ${position.unit}`,
);
const result = await callKicadScript("move_component", {
reference,
position,
rotation,
});
return {
content: [
{
type: "text",
text: JSON.stringify(result),
},
],
};
},
);
// ------------------------------------------------------
// Rotate Component Tool
// ------------------------------------------------------
server.tool(
"rotate_component",
{
reference: z
.string()
.describe("Reference designator of the component (e.g., 'R5')"),
angle: z
.number()
.describe("Rotation angle in degrees (absolute, not relative)"),
},
async ({ reference, angle }) => {
logger.debug(`Rotating component: ${reference} to ${angle} degrees`);
const result = await callKicadScript("rotate_component", {
reference,
angle,
});
return {
content: [
{
type: "text",
text: JSON.stringify(result),
},
],
};
},
);
// ------------------------------------------------------
// Delete Component Tool
// ------------------------------------------------------
server.tool(
"delete_component",
{
reference: z
.string()
.describe(
"Reference designator of the component to delete (e.g., 'R5')",
),
},
async ({ reference }) => {
logger.debug(`Deleting component: ${reference}`);
const result = await callKicadScript("delete_component", { reference });
return {
content: [
{
type: "text",
text: JSON.stringify(result),
},
],
};
},
);
// ------------------------------------------------------
// Edit Component Properties Tool
// ------------------------------------------------------
server.tool(
"edit_component",
{
reference: z
.string()
.describe("Reference designator of the component (e.g., 'R5')"),
newReference: z
.string()
.optional()
.describe("Optional new reference designator"),
value: z.string().optional().describe("Optional new component value"),
footprint: z.string().optional().describe("Optional new footprint"),
},
async ({ reference, newReference, value, footprint }) => {
logger.debug(`Editing component: ${reference}`);
const result = await callKicadScript("edit_component", {
reference,
newReference,
value,
footprint,
});
return {
content: [
{
type: "text",
text: JSON.stringify(result),
},
],
};
},
);
// ------------------------------------------------------
// Find Component Tool
// ------------------------------------------------------
server.tool(
"find_component",
{
reference: z
.string()
.optional()
.describe("Reference designator to search for"),
value: z.string().optional().describe("Component value to search for"),
},
async ({ reference, value }) => {
logger.debug(
`Finding component with ${reference ? `reference: ${reference}` : `value: ${value}`}`,
);
const result = await callKicadScript("find_component", {
reference,
value,
});
return {
content: [
{
type: "text",
text: JSON.stringify(result),
},
],
};
},
);
// ------------------------------------------------------
// Get Component Properties Tool
// ------------------------------------------------------
server.tool(
"get_component_properties",
{
reference: z
.string()
.describe("Reference designator of the component (e.g., 'R5')"),
},
async ({ reference }) => {
logger.debug(`Getting properties for component: ${reference}`);
const result = await callKicadScript("get_component_properties", {
reference,
});
return {
content: [
{
type: "text",
text: JSON.stringify(result),
},
],
};
},
);
// ------------------------------------------------------
// Add Component Annotation Tool
// ------------------------------------------------------
server.tool(
"add_component_annotation",
{
reference: z
.string()
.describe("Reference designator of the component (e.g., 'R5')"),
annotation: z.string().describe("Annotation or comment text to add"),
visible: z
.boolean()
.optional()
.describe("Whether the annotation should be visible on the PCB"),
},
async ({ reference, annotation, visible }) => {
logger.debug(`Adding annotation to component: ${reference}`);
const result = await callKicadScript("add_component_annotation", {
reference,
annotation,
visible,
});
return {
content: [
{
type: "text",
text: JSON.stringify(result),
},
],
};
},
);
// ------------------------------------------------------
// Group Components Tool
// ------------------------------------------------------
server.tool(
"group_components",
{
references: z
.array(z.string())
.describe("Reference designators of components to group"),
groupName: z.string().describe("Name for the component group"),
},
async ({ references, groupName }) => {
logger.debug(
`Grouping components: ${references.join(", ")} as ${groupName}`,
);
const result = await callKicadScript("group_components", {
references,
groupName,
});
return {
content: [
{
type: "text",
text: JSON.stringify(result),
},
],
};
},
);
// ------------------------------------------------------
// Replace Component Tool
// ------------------------------------------------------
server.tool(
"replace_component",
{
reference: z
.string()
.describe("Reference designator of the component to replace"),
newComponentId: z.string().describe("ID of the new component to use"),
newFootprint: z.string().optional().describe("Optional new footprint"),
newValue: z.string().optional().describe("Optional new component value"),
},
async ({ reference, newComponentId, newFootprint, newValue }) => {
logger.debug(`Replacing component: ${reference} with ${newComponentId}`);
const result = await callKicadScript("replace_component", {
reference,
newComponentId,
newFootprint,
newValue,
});
return {
content: [
{
type: "text",
text: JSON.stringify(result),
},
],
};
},
);
// ------------------------------------------------------
// Get Component Pads Tool
// ------------------------------------------------------
server.tool(
"get_component_pads",
{
reference: z
.string()
.describe("Reference designator of the component (e.g., 'U1')"),
unit: z
.enum(["mm", "inch"])
.optional()
.describe("Unit for coordinates (default: mm)"),
},
async ({ reference, unit }) => {
logger.debug(`Getting pads for component: ${reference}`);
const result = await callKicadScript("get_component_pads", {
reference,
unit: unit || "mm",
});
return {
content: [
{
type: "text",
text: JSON.stringify(result),
},
],
};
},
);
// ------------------------------------------------------
// Get Component List Tool
// ------------------------------------------------------
server.tool(
"get_component_list",
{
layer: z
.string()
.optional()
.describe("Filter by layer (e.g., 'F.Cu', 'B.Cu')"),
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 (default: mm)"),
},
async ({ layer, boundingBox, unit }) => {
logger.debug("Getting component list");
const result = await callKicadScript("get_component_list", {
layer,
boundingBox,
unit: unit || "mm",
});
return {
content: [
{
type: "text",
text: JSON.stringify(result),
},
],
};
},
);
// ------------------------------------------------------
// Get Pad Position Tool
// ------------------------------------------------------
server.tool(
"get_pad_position",
{
reference: z
.string()
.describe("Component reference designator (e.g., 'U1')"),
pad: z.string().describe("Pad number or name (e.g., '1', 'A1')"),
unit: z
.enum(["mm", "inch"])
.optional()
.describe("Unit for coordinates (default: mm)"),
},
async ({ reference, pad, unit }) => {
logger.debug(`Getting pad position for ${reference} pad ${pad}`);
const result = await callKicadScript("get_pad_position", {
reference,
pad,
unit: unit || "mm",
});
return {
content: [
{
type: "text",
text: JSON.stringify(result),
},
],
};
},
);
// ------------------------------------------------------
// Place Component Array Tool
// ------------------------------------------------------
server.tool(
"place_component_array",
{
componentId: z.string().describe("Component identifier"),
startPosition: z
.object({
x: z.number(),
y: z.number(),
unit: z.enum(["mm", "inch"]),
})
.describe("Starting position"),
rows: z.number().describe("Number of rows"),
columns: z.number().describe("Number of columns"),
rowSpacing: z.number().describe("Spacing between rows"),
columnSpacing: z.number().describe("Spacing between columns"),
startReference: z
.string()
.optional()
.describe("Starting reference (e.g., 'R1')"),
footprint: z.string().optional().describe("Footprint name"),
value: z.string().optional().describe("Component value"),
rotation: z.number().optional().describe("Rotation in degrees"),
},
async ({
componentId,
startPosition,
rows,
columns,
rowSpacing,
columnSpacing,
startReference,
footprint,
value,
rotation,
}) => {
logger.debug(
`Placing component array: ${rows}x${columns} of ${componentId}`,
);
const result = await callKicadScript("place_component_array", {
componentId,
startPosition,
rows,
columns,
rowSpacing,
columnSpacing,
startReference,
footprint,
value,
rotation,
});
return {
content: [
{
type: "text",
text: JSON.stringify(result),
},
],
};
},
);
// ------------------------------------------------------
// Align Components Tool
// ------------------------------------------------------
server.tool(
"align_components",
{
references: z
.array(z.string())
.describe("Array of component references to align"),
alignmentType: z
.enum(["horizontal", "vertical", "grid"])
.describe("Type of alignment"),
spacing: z
.number()
.optional()
.describe("Spacing between components in mm"),
referenceComponent: z
.string()
.optional()
.describe("Reference component for alignment"),
},
async ({ references, alignmentType, spacing, referenceComponent }) => {
logger.debug(`Aligning components: ${references.join(", ")}`);
const result = await callKicadScript("align_components", {
references,
alignmentType,
spacing,
referenceComponent,
});
return {
content: [
{
type: "text",
text: JSON.stringify(result),
},
],
};
},
);
// ------------------------------------------------------
// Duplicate Component Tool
// ------------------------------------------------------
server.tool(
"duplicate_component",
{
reference: z.string().describe("Reference of component to duplicate"),
offset: z
.object({
x: z.number(),
y: z.number(),
unit: z.enum(["mm", "inch"]).optional(),
})
.describe("Offset from original position"),
newReference: z.string().optional().describe("New reference designator"),
count: z
.number()
.optional()
.describe("Number of duplicates (default: 1)"),
},
async ({ reference, offset, newReference, count }) => {
logger.debug(`Duplicating component: ${reference}`);
const result = await callKicadScript("duplicate_component", {
reference,
offset,
newReference,
count,
});
return {
content: [
{
type: "text",
text: JSON.stringify(result),
},
],
};
},
);
logger.info("Component management tools registered");
}

View File

@@ -1,159 +1,193 @@
/**
* Library tools for KiCAD MCP server
* Provides access to KiCAD footprint libraries and symbols
*/
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
export function registerLibraryTools(server: McpServer, callKicadScript: Function) {
// List available footprint libraries
server.tool(
"list_libraries",
"List all available KiCAD footprint libraries",
{
search_paths: z.array(z.string()).optional()
.describe("Optional additional search paths for libraries")
},
async (args: { search_paths?: string[] }) => {
const result = await callKicadScript("list_libraries", args);
if (result.success && result.libraries) {
return {
content: [
{
type: "text",
text: `Found ${result.libraries.length} footprint libraries:\n${result.libraries.join('\n')}`
}
]
};
}
return {
content: [
{
type: "text",
text: `Failed to list libraries: ${result.message || 'Unknown error'}`
}
]
};
}
);
// Search for footprints across all libraries
server.tool(
"search_footprints",
"Search for footprints matching a pattern across all libraries",
{
search_term: z.string()
.describe("Search term or pattern to match footprint names"),
library: z.string().optional()
.describe("Optional specific library to search in"),
limit: z.number().optional().default(50)
.describe("Maximum number of results to return")
},
async (args: { search_term: string; library?: string; limit?: number }) => {
const result = await callKicadScript("search_footprints", args);
if (result.success && result.footprints) {
const footprintList = result.footprints.map((fp: any) =>
`${fp.library}:${fp.name}${fp.description ? ' - ' + fp.description : ''}`
).join('\n');
return {
content: [
{
type: "text",
text: `Found ${result.footprints.length} matching footprints:\n${footprintList}`
}
]
};
}
return {
content: [
{
type: "text",
text: `Failed to search footprints: ${result.message || 'Unknown error'}`
}
]
};
}
);
// List footprints in a specific library
server.tool(
"list_library_footprints",
"List all footprints in a specific KiCAD library",
{
library_name: z.string()
.describe("Name of the library to list footprints from"),
filter: z.string().optional()
.describe("Optional filter pattern for footprint names"),
limit: z.number().optional().default(100)
.describe("Maximum number of footprints to list")
},
async (args: { library_name: string; filter?: string; limit?: number }) => {
const result = await callKicadScript("list_library_footprints", args);
if (result.success && result.footprints) {
const footprintList = result.footprints.map((fp: string) => ` - ${fp}`).join('\n');
return {
content: [
{
type: "text",
text: `Library ${args.library_name} contains ${result.footprints.length} footprints:\n${footprintList}`
}
]
};
}
return {
content: [
{
type: "text",
text: `Failed to list footprints in library ${args.library_name}: ${result.message || 'Unknown error'}`
}
]
};
}
);
// Get detailed information about a specific footprint
server.tool(
"get_footprint_info",
"Get detailed information about a specific footprint",
{
library_name: z.string()
.describe("Name of the library containing the footprint"),
footprint_name: z.string()
.describe("Name of the footprint to get information about")
},
async (args: { library_name: string; footprint_name: string }) => {
const result = await callKicadScript("get_footprint_info", args);
if (result.success && result.info) {
const info = result.info;
const details = [
`Footprint: ${info.name}`,
`Library: ${info.library}`,
info.description ? `Description: ${info.description}` : '',
info.keywords ? `Keywords: ${info.keywords}` : '',
info.pads ? `Number of pads: ${info.pads}` : '',
info.layers ? `Layers used: ${info.layers.join(', ')}` : '',
info.courtyard ? `Courtyard size: ${info.courtyard.width}mm x ${info.courtyard.height}mm` : '',
info.attributes ? `Attributes: ${JSON.stringify(info.attributes)}` : ''
].filter(line => line).join('\n');
return {
content: [
{
type: "text",
text: details
}
]
};
}
return {
content: [
{
type: "text",
text: `Failed to get footprint info: ${result.message || 'Unknown error'}`
}
]
};
}
);
}
/**
* Library tools for KiCAD MCP server
* Provides access to KiCAD footprint libraries and symbols
*/
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
export function registerLibraryTools(
server: McpServer,
callKicadScript: Function,
) {
// List available footprint libraries
server.tool(
"list_libraries",
"List all available KiCAD footprint libraries",
{
search_paths: z
.array(z.string())
.optional()
.describe("Optional additional search paths for libraries"),
},
async (args: { search_paths?: string[] }) => {
const result = await callKicadScript("list_libraries", args);
if (result.success && result.libraries) {
return {
content: [
{
type: "text",
text: `Found ${result.libraries.length} footprint libraries:\n${result.libraries.join("\n")}`,
},
],
};
}
return {
content: [
{
type: "text",
text: `Failed to list libraries: ${result.message || "Unknown error"}`,
},
],
};
},
);
// Search for footprints across all libraries
server.tool(
"search_footprints",
"Search for footprints matching a pattern across all libraries",
{
search_term: z
.string()
.describe("Search term or pattern to match footprint names"),
library: z
.string()
.optional()
.describe("Optional specific library to search in"),
limit: z
.number()
.optional()
.default(50)
.describe("Maximum number of results to return"),
},
async (args: { search_term: string; library?: string; limit?: number }) => {
const result = await callKicadScript("search_footprints", {
pattern: args.search_term,
library: args.library,
limit: args.limit,
});
if (result.success && result.footprints) {
const footprintList = result.footprints
.map(
(fp: any) =>
`${fp.full_name || fp.library + ":" + fp.footprint}${fp.description ? " - " + fp.description : ""}`,
)
.join("\n");
return {
content: [
{
type: "text",
text: `Found ${result.footprints.length} matching footprints:\n${footprintList}`,
},
],
};
}
return {
content: [
{
type: "text",
text: `Failed to search footprints: ${result.message || "Unknown error"}`,
},
],
};
},
);
// List footprints in a specific library
server.tool(
"list_library_footprints",
"List all footprints in a specific KiCAD library",
{
library_name: z
.string()
.describe("Name of the library to list footprints from"),
filter: z
.string()
.optional()
.describe("Optional filter pattern for footprint names"),
limit: z
.number()
.optional()
.default(100)
.describe("Maximum number of footprints to list"),
},
async (args: { library_name: string; filter?: string; limit?: number }) => {
const result = await callKicadScript("list_library_footprints", args);
if (result.success && result.footprints) {
const footprintList = result.footprints
.map((fp: string) => ` - ${fp}`)
.join("\n");
return {
content: [
{
type: "text",
text: `Library ${args.library_name} contains ${result.footprints.length} footprints:\n${footprintList}`,
},
],
};
}
return {
content: [
{
type: "text",
text: `Failed to list footprints in library ${args.library_name}: ${result.message || "Unknown error"}`,
},
],
};
},
);
// Get detailed information about a specific footprint
server.tool(
"get_footprint_info",
"Get detailed information about a specific footprint",
{
library_name: z
.string()
.describe("Name of the library containing the footprint"),
footprint_name: z
.string()
.describe("Name of the footprint to get information about"),
},
async (args: { library_name: string; footprint_name: string }) => {
const result = await callKicadScript("get_footprint_info", args);
if (result.success && result.info) {
const info = result.info;
const details = [
`Footprint: ${info.name}`,
`Library: ${info.library}`,
info.description ? `Description: ${info.description}` : "",
info.keywords ? `Keywords: ${info.keywords}` : "",
info.pads ? `Number of pads: ${info.pads}` : "",
info.layers ? `Layers used: ${info.layers.join(", ")}` : "",
info.courtyard
? `Courtyard size: ${info.courtyard.width}mm x ${info.courtyard.height}mm`
: "",
info.attributes
? `Attributes: ${JSON.stringify(info.attributes)}`
: "",
]
.filter((line) => line)
.join("\n");
return {
content: [
{
type: "text",
text: details,
},
],
};
}
return {
content: [
{
type: "text",
text: `Failed to get footprint info: ${result.message || "Unknown error"}`,
},
],
};
},
);
}

View File

@@ -1,101 +1,324 @@
/**
* 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 between two points",
{
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"),
},
async (args: any) => {
const result = await callKicadScript("add_copper_pour", args);
return {
content: [{
type: "text",
text: JSON.stringify(result, null, 2)
}]
};
}
);
}
/**
* 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 between two points",
{
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"),
},
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),
},
],
};
},
);
}