feat: add no_connect handler, server icon, split READMEs by language, VS Code Copilot guide, tool inventory update

This commit is contained in:
Tom
2026-05-03 11:40:51 +02:00
parent d3c01e20bd
commit 963a39c463
18 changed files with 680 additions and 433 deletions

View File

@@ -214,9 +214,7 @@ export class KiCADMcpServer {
logger.info("Registering KiCAD tools, resources, and prompts...");
// Register router tools FIRST (for tool discovery and execution)
// NOTE: Router disabled — causes Claude to hallucinate tool schemas via search_tools/execute_tool.
// All tools are registered directly below and are immediately visible to Claude.
// registerRouterTools(this.server, this.callKicadScript.bind(this));
registerRouterTools(this.server, this.callKicadScript.bind(this));
// Register all tools
registerProjectTools(this.server, this.callKicadScript.bind(this));
@@ -248,7 +246,6 @@ export class KiCADMcpServer {
registerFootprintPrompts(this.server);
logger.info("All KiCAD tools, resources, and prompts registered");
logger.info("Router pattern enabled: 4 router tools + direct tools for discovery");
}
/**

View File

@@ -25,6 +25,7 @@ export function registerBoardTools(server: McpServer, callKicadScript: CommandFu
// ------------------------------------------------------
server.tool(
"set_board_size",
"Set the PCB board dimensions (width and height) in the specified unit.",
{
width: z.number().describe("Board width"),
height: z.number().describe("Board height"),
@@ -54,6 +55,7 @@ export function registerBoardTools(server: McpServer, callKicadScript: CommandFu
// ------------------------------------------------------
server.tool(
"add_layer",
"Add a new copper or technical layer to the PCB stackup.",
{
name: z.string().describe("Layer name"),
type: z.enum(["copper", "technical", "user", "signal"]).describe("Layer type"),
@@ -85,6 +87,7 @@ export function registerBoardTools(server: McpServer, callKicadScript: CommandFu
// ------------------------------------------------------
server.tool(
"set_active_layer",
"Set the currently active PCB layer by name (e.g. F.Cu, B.Cu).",
{
layer: z.string().describe("Layer name to set as active"),
},
@@ -106,42 +109,53 @@ export function registerBoardTools(server: McpServer, callKicadScript: CommandFu
// ------------------------------------------------------
// Get Board Info Tool
// ------------------------------------------------------
server.tool("get_board_info", {}, async () => {
logger.debug("Getting board information");
const result = await callKicadScript("get_board_info", {});
server.tool(
"get_board_info",
"Retrieve general information about the current PCB board (dimensions, layer count, DRC status).",
{},
async () => {
logger.debug("Getting board information");
const result = await callKicadScript("get_board_info", {});
return {
content: [
{
type: "text",
text: JSON.stringify(result),
},
],
};
});
return {
content: [
{
type: "text",
text: JSON.stringify(result),
},
],
};
},
);
// ------------------------------------------------------
// Get Layer List Tool
// ------------------------------------------------------
server.tool("get_layer_list", {}, async () => {
logger.debug("Getting layer list");
const result = await callKicadScript("get_layer_list", {});
server.tool(
"get_layer_list",
"Return the list of all layers defined in the current PCB board.",
{},
async () => {
logger.debug("Getting layer list");
const result = await callKicadScript("get_layer_list", {});
return {
content: [
{
type: "text",
text: JSON.stringify(result),
},
],
};
});
return {
content: [
{
type: "text",
text: JSON.stringify(result),
},
],
};
},
);
// ------------------------------------------------------
// Add Board Outline Tool
// ------------------------------------------------------
server.tool(
"add_board_outline",
"Draw the PCB board outline (Edge.Cuts layer) as a rectangle, rounded rectangle, circle or polygon.",
{
shape: z
.enum(["rectangle", "circle", "polygon", "rounded_rectangle"])
@@ -196,6 +210,7 @@ export function registerBoardTools(server: McpServer, callKicadScript: CommandFu
// ------------------------------------------------------
server.tool(
"add_mounting_hole",
"Place a mounting hole (NPTH or PTH) at the specified position on the PCB.",
{
position: z
.object({
@@ -231,6 +246,7 @@ export function registerBoardTools(server: McpServer, callKicadScript: CommandFu
// ------------------------------------------------------
server.tool(
"add_board_text",
"Add a text label to a PCB layer (e.g. silkscreen, fab, courtyard).",
{
text: z.string().describe("Text content"),
position: z
@@ -274,6 +290,7 @@ export function registerBoardTools(server: McpServer, callKicadScript: CommandFu
// ------------------------------------------------------
server.tool(
"add_zone",
"Create a copper fill zone (pour) on a PCB layer for a specified net.",
{
layer: z.string().describe("Layer for the zone"),
net: z.string().describe("Net name for the zone"),
@@ -321,6 +338,7 @@ export function registerBoardTools(server: McpServer, callKicadScript: CommandFu
// ------------------------------------------------------
server.tool(
"get_board_extents",
"Return the bounding box (min/max X and Y) of all objects on the current PCB board.",
{
unit: z.enum(["mm", "inch"]).optional().describe("Unit of measurement for the result"),
},
@@ -344,6 +362,7 @@ export function registerBoardTools(server: McpServer, callKicadScript: CommandFu
// ------------------------------------------------------
server.tool(
"get_board_2d_view",
"Render a 2D image of the current PCB board and return it as PNG, JPG or SVG.",
{
layers: z.array(z.string()).optional().describe("Optional array of layer names to include"),
width: z.number().optional().describe("Optional width of the image in pixels"),

View File

@@ -23,6 +23,7 @@ export function registerComponentTools(server: McpServer, callKicadScript: Comma
// ------------------------------------------------------
server.tool(
"place_component",
"Place a footprint component onto the PCB at the specified position. Optionally set reference, value, footprint, rotation and layer.",
{
componentId: z
.string()
@@ -77,6 +78,7 @@ export function registerComponentTools(server: McpServer, callKicadScript: Comma
// ------------------------------------------------------
server.tool(
"move_component",
"Move a PCB component to a new position. Optionally update rotation or flip to a different copper layer.",
{
reference: z.string().describe("Reference designator of the component (e.g., 'R5')"),
position: z
@@ -119,6 +121,7 @@ export function registerComponentTools(server: McpServer, callKicadScript: Comma
// ------------------------------------------------------
server.tool(
"rotate_component",
"Rotate a PCB component to an absolute angle in degrees.",
{
reference: z.string().describe("Reference designator of the component (e.g., 'R5')"),
angle: z.number().describe("Rotation angle in degrees (absolute, not relative)"),
@@ -146,6 +149,7 @@ export function registerComponentTools(server: McpServer, callKicadScript: Comma
// ------------------------------------------------------
server.tool(
"delete_component",
"Remove a component from the PCB by its reference designator.",
{
reference: z
.string()
@@ -171,6 +175,7 @@ export function registerComponentTools(server: McpServer, callKicadScript: Comma
// ------------------------------------------------------
server.tool(
"edit_component",
"Edit properties of an existing PCB component (reference, value, footprint).",
{
reference: z.string().describe("Reference designator of the component (e.g., 'R5')"),
newReference: z.string().optional().describe("Optional new reference designator"),
@@ -202,6 +207,7 @@ export function registerComponentTools(server: McpServer, callKicadScript: Comma
// ------------------------------------------------------
server.tool(
"find_component",
"Search for a PCB component by reference designator or value and return its position and properties.",
{
reference: z.string().optional().describe("Reference designator to search for"),
value: z.string().optional().describe("Component value to search for"),
@@ -231,6 +237,7 @@ export function registerComponentTools(server: McpServer, callKicadScript: Comma
// ------------------------------------------------------
server.tool(
"get_component_properties",
"Return all properties of a PCB component (position, rotation, layer, value, footprint).",
{
reference: z.string().describe("Reference designator of the component (e.g., 'R5')"),
},
@@ -256,6 +263,7 @@ export function registerComponentTools(server: McpServer, callKicadScript: Comma
// ------------------------------------------------------
server.tool(
"add_component_annotation",
"Add a text annotation or comment to a PCB component.",
{
reference: z.string().describe("Reference designator of the component (e.g., 'R5')"),
annotation: z.string().describe("Annotation or comment text to add"),
@@ -288,6 +296,7 @@ export function registerComponentTools(server: McpServer, callKicadScript: Comma
// ------------------------------------------------------
server.tool(
"group_components",
"Group multiple PCB components together by name for easier selection and manipulation.",
{
references: z.array(z.string()).describe("Reference designators of components to group"),
groupName: z.string().describe("Name for the component group"),
@@ -315,6 +324,7 @@ export function registerComponentTools(server: McpServer, callKicadScript: Comma
// ------------------------------------------------------
server.tool(
"replace_component",
"Replace an existing PCB component with a different component type, optionally changing footprint and value.",
{
reference: z.string().describe("Reference designator of the component to replace"),
newComponentId: z.string().describe("ID of the new component to use"),
@@ -346,6 +356,7 @@ export function registerComponentTools(server: McpServer, callKicadScript: Comma
// ------------------------------------------------------
server.tool(
"get_component_pads",
"Return all pads of a PCB component with their positions, net assignments and sizes.",
{
reference: z.string().describe("Reference designator of the component (e.g., 'U1')"),
unit: z.enum(["mm", "inch"]).optional().describe("Unit for coordinates (default: mm)"),
@@ -373,6 +384,7 @@ export function registerComponentTools(server: McpServer, callKicadScript: Comma
// ------------------------------------------------------
server.tool(
"get_component_list",
"Return a list of all components on the PCB, optionally filtered by layer or bounding box region.",
{
layer: z.string().optional().describe("Filter by layer (e.g., 'F.Cu', 'B.Cu')"),
boundingBox: z
@@ -411,6 +423,7 @@ export function registerComponentTools(server: McpServer, callKicadScript: Comma
// ------------------------------------------------------
server.tool(
"get_pad_position",
"Return the exact XY position of a specific pad on a PCB component. Use this before routing to get accurate start/end coordinates.",
{
reference: z.string().describe("Component reference designator (e.g., 'U1')"),
pad: z.string().describe("Pad number or name (e.g., '1', 'A1')"),
@@ -440,6 +453,7 @@ export function registerComponentTools(server: McpServer, callKicadScript: Comma
// ------------------------------------------------------
server.tool(
"place_component_array",
"Place a rectangular grid array of identical components on the PCB with configurable row/column spacing.",
{
componentId: z.string().describe("Component identifier"),
startPosition: z
@@ -500,6 +514,7 @@ export function registerComponentTools(server: McpServer, callKicadScript: Comma
// ------------------------------------------------------
server.tool(
"align_components",
"Align multiple PCB components horizontally, vertically or on a grid with optional spacing.",
{
references: z.array(z.string()).describe("Array of component references to align"),
alignmentType: z.enum(["horizontal", "vertical", "grid"]).describe("Type of alignment"),
@@ -531,6 +546,7 @@ export function registerComponentTools(server: McpServer, callKicadScript: Comma
// ------------------------------------------------------
server.tool(
"duplicate_component",
"Duplicate an existing PCB component at an offset position, optionally with a new reference designator.",
{
reference: z.string().describe("Reference of component to duplicate"),
offset: z

View File

@@ -25,6 +25,7 @@ export function registerDesignRuleTools(server: McpServer, callKicadScript: Comm
// ------------------------------------------------------
server.tool(
"set_design_rules",
"Configure PCB design rules: clearance, track width, via dimensions and courtyard requirements.",
{
clearance: z.number().optional().describe("Minimum clearance between copper items (mm)"),
trackWidth: z.number().optional().describe("Default track width (mm)"),
@@ -65,25 +66,31 @@ export function registerDesignRuleTools(server: McpServer, callKicadScript: Comm
// ------------------------------------------------------
// Get Design Rules Tool
// ------------------------------------------------------
server.tool("get_design_rules", {}, async () => {
logger.debug("Getting design rules");
const result = await callKicadScript("get_design_rules", {});
server.tool(
"get_design_rules",
"Return the current PCB design rules (clearance, track width, via sizes, courtyard settings).",
{},
async () => {
logger.debug("Getting design rules");
const result = await callKicadScript("get_design_rules", {});
return {
content: [
{
type: "text",
text: JSON.stringify(result),
},
],
};
});
return {
content: [
{
type: "text",
text: JSON.stringify(result),
},
],
};
},
);
// ------------------------------------------------------
// Run DRC Tool
// ------------------------------------------------------
server.tool(
"run_drc",
"Run the KiCAD Design Rule Check (DRC) on the current PCB and return violations. Optionally save the report to a file.",
{
reportPath: z.string().optional().describe("Optional path to save the DRC report"),
},
@@ -107,6 +114,7 @@ export function registerDesignRuleTools(server: McpServer, callKicadScript: Comm
// ------------------------------------------------------
server.tool(
"add_net_class",
"Create a named net class with specific design rules (clearance, track width, via size) and assign nets to it.",
{
name: z.string().describe("Name of the net class"),
description: z.string().optional().describe("Optional description of the net class"),
@@ -170,6 +178,7 @@ export function registerDesignRuleTools(server: McpServer, callKicadScript: Comm
// ------------------------------------------------------
server.tool(
"assign_net_to_class",
"Assign a net to an existing net class to apply its specific design rules.",
{
net: z.string().describe("Name of the net"),
netClass: z.string().describe("Name of the net class"),
@@ -197,6 +206,7 @@ export function registerDesignRuleTools(server: McpServer, callKicadScript: Comm
// ------------------------------------------------------
server.tool(
"set_layer_constraints",
"Set per-layer design rule constraints (minimum track width, clearance and via dimensions).",
{
layer: z.string().describe("Layer name (e.g., 'F.Cu')"),
minTrackWidth: z.number().optional().describe("Minimum track width for this layer (mm)"),
@@ -230,6 +240,7 @@ export function registerDesignRuleTools(server: McpServer, callKicadScript: Comm
// ------------------------------------------------------
server.tool(
"check_clearance",
"Check the actual clearance between two PCB items (track, via, pad, zone or component) and report whether it meets the design rules.",
{
item1: z
.object({
@@ -289,6 +300,7 @@ export function registerDesignRuleTools(server: McpServer, callKicadScript: Comm
// ------------------------------------------------------
server.tool(
"get_drc_violations",
"Return the list of current DRC violations on the PCB, optionally filtered by severity (error, warning).",
{
severity: z
.enum(["error", "warning", "all"])

View File

@@ -25,6 +25,7 @@ export function registerExportTools(server: McpServer, callKicadScript: CommandF
// ------------------------------------------------------
server.tool(
"export_gerber",
"Export PCB Gerber manufacturing files to a directory. Optionally include drill files, map files and choose layer subset.",
{
outputDir: z.string().describe("Directory to save Gerber files"),
layers: z
@@ -73,6 +74,7 @@ export function registerExportTools(server: McpServer, callKicadScript: CommandF
// ------------------------------------------------------
server.tool(
"export_pdf",
"Export the PCB layout as a PDF document, optionally selecting layers, page size and colour mode.",
{
outputPath: z.string().describe("Path to save the PDF file"),
layers: z
@@ -112,6 +114,7 @@ export function registerExportTools(server: McpServer, callKicadScript: CommandF
// ------------------------------------------------------
server.tool(
"export_svg",
"Export the PCB layout as an SVG vector image, optionally selecting layers and colour mode.",
{
outputPath: z.string().describe("Path to save the SVG file"),
layers: z
@@ -146,6 +149,7 @@ export function registerExportTools(server: McpServer, callKicadScript: CommandF
// ------------------------------------------------------
server.tool(
"export_3d",
"Export the PCB as a 3D model (STEP, STL, VRML or OBJ) including optional copper, solder mask, silkscreen and component 3D models.",
{
outputPath: z.string().describe("Path to save the 3D model file"),
format: z.enum(["STEP", "STL", "VRML", "OBJ"]).describe("3D model format"),
@@ -188,6 +192,7 @@ export function registerExportTools(server: McpServer, callKicadScript: CommandF
// ------------------------------------------------------
server.tool(
"export_bom",
"Export a Bill of Materials (BOM) from the PCB in CSV, XML, HTML or JSON format.",
{
outputPath: z.string().describe("Path to save the BOM file"),
format: z.enum(["CSV", "XML", "HTML", "JSON"]).describe("BOM file format"),
@@ -255,6 +260,7 @@ export function registerExportTools(server: McpServer, callKicadScript: CommandF
// ------------------------------------------------------
server.tool(
"export_position_file",
"Export a component placement/position file (pick-and-place) for PCB assembly in CSV or ASCII format.",
{
outputPath: z.string().describe("Path to save the position file"),
format: z.enum(["CSV", "ASCII"]).optional().describe("File format (default: CSV)"),
@@ -289,6 +295,7 @@ export function registerExportTools(server: McpServer, callKicadScript: CommandF
// ------------------------------------------------------
server.tool(
"export_vrml",
"Export the PCB as a VRML 3D model for use in web viewers or simulation tools.",
{
outputPath: z.string().describe("Path to save the VRML file"),
includeComponents: z.boolean().optional().describe("Whether to include 3D component models"),

View File

@@ -96,6 +96,7 @@ export const toolCategories: ToolCategory[] = [
"delete_schematic_wire",
"add_schematic_net_label",
"delete_schematic_net_label",
"add_no_connect",
"connect_to_net",
"connect_passthrough",
"get_net_connections",

View File

@@ -10,7 +10,6 @@ import { logger } from "../logger.js";
import {
getAllCategories,
getCategory,
getToolCategory,
searchTools as registrySearchTools,
getRegistryStats,
} from "./registry.js";
@@ -18,26 +17,10 @@ import {
// Command function type for KiCAD script calls
type CommandFunction = (command: string, params: Record<string, unknown>) => Promise<any>;
// Map to store tool execution handlers
// This will be populated by registerToolHandler()
const toolHandlers = new Map<string, (params: any) => Promise<any>>();
/**
* Register a tool handler for execution via execute_tool
* This should be called by each tool registration function
*/
export function registerToolHandler(
toolName: string,
handler: (params: any) => Promise<any>,
): void {
toolHandlers.set(toolName, handler);
logger.debug(`Registered handler for routed tool: ${toolName}`);
}
/**
* Register all router tools with the MCP server
*/
export function registerRouterTools(server: McpServer, callKicadScript: CommandFunction): void {
export function registerRouterTools(server: McpServer, _callKicadScript: CommandFunction): void {
logger.info("Registering router tools");
// ============================================================================
@@ -45,6 +28,7 @@ export function registerRouterTools(server: McpServer, callKicadScript: CommandF
// ============================================================================
server.tool(
"list_tool_categories",
"List all available KiCAD tool categories with their descriptions and tool counts. Use this to discover which tools are available via the router.",
{
// No parameters
},
@@ -82,6 +66,7 @@ export function registerRouterTools(server: McpServer, callKicadScript: CommandF
// ============================================================================
server.tool(
"get_category_tools",
"Return all tools available in a specific category. Use list_tool_categories first to find valid category names.",
{
category: z.string().describe("Category name from list_tool_categories"),
},
@@ -133,136 +118,12 @@ export function registerRouterTools(server: McpServer, callKicadScript: CommandF
},
);
// ============================================================================
// execute_tool
// ============================================================================
server.tool(
"execute_tool",
{
tool_name: z.string().describe("Tool name from get_category_tools"),
params: z.record(z.unknown()).optional().describe("Tool parameters (optional)"),
},
async ({ tool_name, params }) => {
logger.info(`Executing routed tool: ${tool_name}`);
// Check if tool exists in registry
const category = getToolCategory(tool_name);
if (!category) {
return {
content: [
{
type: "text",
text: JSON.stringify(
{
error: `Unknown tool: ${tool_name}`,
hint: "Use list_tool_categories and get_category_tools to find available tools",
},
null,
2,
),
},
],
};
}
// Get the handler
const handler = toolHandlers.get(tool_name);
if (!handler) {
// Tool is in registry but handler not registered yet
// This means the tool exists but hasn't been migrated to router pattern yet
// Fall back to calling KiCAD script directly
logger.warn(
`Tool ${tool_name} in registry but no handler registered, falling back to direct call`,
);
try {
const result = await callKicadScript(tool_name, params || {});
return {
content: [
{
type: "text",
text: JSON.stringify(
{
tool: tool_name,
category: category,
result: result,
},
null,
2,
),
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: JSON.stringify(
{
error: `Tool execution failed: ${(error as Error).message}`,
tool: tool_name,
category: category,
},
null,
2,
),
},
],
};
}
}
// Execute the tool via its handler
try {
const result = await handler(params || {});
// The handler already returns MCP-formatted response
// Just add metadata
return {
content: [
{
type: "text",
text: JSON.stringify(
{
tool: tool_name,
category: category,
...result,
},
null,
2,
),
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: JSON.stringify(
{
error: `Tool execution failed: ${(error as Error).message}`,
tool: tool_name,
category: category,
},
null,
2,
),
},
],
};
}
},
);
// ============================================================================
// search_tools
// ============================================================================
server.tool(
"search_tools",
"Search all available KiCAD tools by keyword. Returns matching tool names and their categories.",
{
query: z.string().describe("Search term (e.g., 'gerber', 'zone', 'export', 'drc')"),
},

View File

@@ -578,6 +578,53 @@ edit_schematic_component and set its value to an empty string.`,
},
);
// Add no-connect flag
server.tool(
"add_no_connect",
"Add a no-connect flag (X marker) to a pin that is intentionally left unconnected. " +
"This suppresses ERC 'Pin not connected' errors for unused pins. " +
"PREFERRED: supply componentRef + pinNumber to snap to the exact pin endpoint. " +
"Alternatively supply position [x, y] in mm matching the pin endpoint exactly.",
{
schematicPath: z.string().describe("Path to the schematic file"),
position: z
.array(z.number())
.length(2)
.optional()
.describe("Position [x, y] in mm. Required when componentRef/pinNumber are not given."),
componentRef: z
.string()
.optional()
.describe("Component reference to snap to (e.g. U1, R1). Use with pinNumber."),
pinNumber: z
.union([z.string(), z.number()])
.optional()
.describe("Pin number or name on componentRef (e.g. '1', 'GND'). Use with componentRef."),
},
async (args: {
schematicPath: string;
position?: number[];
componentRef?: string;
pinNumber?: string | number;
}) => {
const result = await callKicadScript("add_no_connect", args);
if (result.success) {
return {
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
};
} else {
return {
content: [
{
type: "text",
text: `Failed to add no-connect: ${result.message || "Unknown error"}`,
},
],
};
}
},
);
// Connect pin to net
server.tool(
"connect_to_net",