feat: Week 1 complete - Linux support + IPC API prep
🎉 Major v2.0 rebuild kickoff - Week 1 accomplished! ## Highlights ### Cross-Platform Support 🌍 - ✅ Linux primary platform (Ubuntu/Debian tested) - ✅ Windows fully supported - ✅ macOS experimental support - ✅ Platform-agnostic path handling (XDG spec) - ✅ Auto-detection of KiCAD installation ### Infrastructure 🏗️ - ✅ GitHub Actions CI/CD pipeline - ✅ Pytest framework with 20+ tests - ✅ Pre-commit hooks (Black, MyPy, ESLint) - ✅ Automated Linux installation script - ✅ Enhanced npm scripts ### IPC API Migration Prep 🚀 - ✅ Comprehensive migration plan (30 pages) - ✅ Backend abstraction layer (800+ lines) - ✅ Factory pattern with auto-detection - ✅ SWIG backward compatibility wrapper - ✅ IPC backend skeleton ready ### Documentation 📚 - ✅ Updated README (Linux installation) - ✅ CONTRIBUTING.md guide - ✅ Linux compatibility audit - ✅ IPC API migration plan - ✅ Session summaries - ✅ Platform-specific config templates ## Files Changed - 27 files created - ~3,000 lines of code/docs - 8 comprehensive documentation pages - 20+ unit tests - 5 abstraction layer modules ## Next Steps - Week 2: IPC API migration (project.py → component.py → routing.py) - Migrate from deprecated SWIG to official IPC API - JLCPCB/Digikey integration prep 🤖 Generated with Claude Code https://claude.com/claude-code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
345
src/tools/board.ts
Normal file
345
src/tools/board.ts
Normal file
@@ -0,0 +1,345 @@
|
||||
/**
|
||||
* Board management tools for KiCAD MCP server
|
||||
*
|
||||
* These tools handle board setup, layer management, and board properties
|
||||
*/
|
||||
|
||||
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 board management tools with the MCP server
|
||||
*
|
||||
* @param server MCP server instance
|
||||
* @param callKicadScript Function to call KiCAD script commands
|
||||
*/
|
||||
export function registerBoardTools(server: McpServer, callKicadScript: CommandFunction): void {
|
||||
logger.info('Registering board management tools');
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Set Board Size Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"set_board_size",
|
||||
{
|
||||
width: z.number().describe("Board width"),
|
||||
height: z.number().describe("Board height"),
|
||||
unit: z.enum(["mm", "inch"]).describe("Unit of measurement")
|
||||
},
|
||||
async ({ width, height, unit }) => {
|
||||
logger.debug(`Setting board size to ${width}x${height} ${unit}`);
|
||||
const result = await callKicadScript("set_board_size", {
|
||||
width,
|
||||
height,
|
||||
unit
|
||||
});
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Add Layer Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"add_layer",
|
||||
{
|
||||
name: z.string().describe("Layer name"),
|
||||
type: z.enum([
|
||||
"copper", "technical", "user", "signal"
|
||||
]).describe("Layer type"),
|
||||
position: z.enum([
|
||||
"top", "bottom", "inner"
|
||||
]).describe("Layer position"),
|
||||
number: z.number().optional().describe("Layer number (for inner layers)")
|
||||
},
|
||||
async ({ name, type, position, number }) => {
|
||||
logger.debug(`Adding ${type} layer: ${name}`);
|
||||
const result = await callKicadScript("add_layer", {
|
||||
name,
|
||||
type,
|
||||
position,
|
||||
number
|
||||
});
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Set Active Layer Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"set_active_layer",
|
||||
{
|
||||
layer: z.string().describe("Layer name to set as active")
|
||||
},
|
||||
async ({ layer }) => {
|
||||
logger.debug(`Setting active layer to: ${layer}`);
|
||||
const result = await callKicadScript("set_active_layer", { layer });
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Get Board Info Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"get_board_info",
|
||||
{},
|
||||
async () => {
|
||||
logger.debug('Getting board information');
|
||||
const result = await callKicadScript("get_board_info", {});
|
||||
|
||||
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", {});
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Add Board Outline Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"add_board_outline",
|
||||
{
|
||||
shape: z.enum(["rectangle", "circle", "polygon"]).describe("Shape of the outline"),
|
||||
params: z.object({
|
||||
// For rectangle
|
||||
width: z.number().optional().describe("Width of rectangle"),
|
||||
height: z.number().optional().describe("Height of rectangle"),
|
||||
// For circle
|
||||
radius: z.number().optional().describe("Radius of circle"),
|
||||
// For polygon
|
||||
points: z.array(
|
||||
z.object({
|
||||
x: z.number().describe("X coordinate"),
|
||||
y: z.number().describe("Y coordinate")
|
||||
})
|
||||
).optional().describe("Points of polygon"),
|
||||
// Common parameters
|
||||
x: z.number().describe("X coordinate of center/origin"),
|
||||
y: z.number().describe("Y coordinate of center/origin"),
|
||||
unit: z.enum(["mm", "inch"]).describe("Unit of measurement")
|
||||
}).describe("Parameters for the outline shape")
|
||||
},
|
||||
async ({ shape, params }) => {
|
||||
logger.debug(`Adding ${shape} board outline`);
|
||||
const result = await callKicadScript("add_board_outline", {
|
||||
shape,
|
||||
params
|
||||
});
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Add Mounting Hole Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"add_mounting_hole",
|
||||
{
|
||||
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 of the mounting hole"),
|
||||
diameter: z.number().describe("Diameter of the hole"),
|
||||
padDiameter: z.number().optional().describe("Optional diameter of the pad around the hole")
|
||||
},
|
||||
async ({ position, diameter, padDiameter }) => {
|
||||
logger.debug(`Adding mounting hole at (${position.x},${position.y}) ${position.unit}`);
|
||||
const result = await callKicadScript("add_mounting_hole", {
|
||||
position,
|
||||
diameter,
|
||||
padDiameter
|
||||
});
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Add Text Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"add_board_text",
|
||||
{
|
||||
text: z.string().describe("Text content"),
|
||||
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 of the text"),
|
||||
layer: z.string().describe("Layer to place the text on"),
|
||||
size: z.number().describe("Text size"),
|
||||
thickness: z.number().optional().describe("Line thickness"),
|
||||
rotation: z.number().optional().describe("Rotation angle in degrees"),
|
||||
style: z.enum(["normal", "italic", "bold"]).optional().describe("Text style")
|
||||
},
|
||||
async ({ text, position, layer, size, thickness, rotation, style }) => {
|
||||
logger.debug(`Adding text "${text}" at (${position.x},${position.y}) ${position.unit}`);
|
||||
const result = await callKicadScript("add_board_text", {
|
||||
text,
|
||||
position,
|
||||
layer,
|
||||
size,
|
||||
thickness,
|
||||
rotation,
|
||||
style
|
||||
});
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Add Zone Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"add_zone",
|
||||
{
|
||||
layer: z.string().describe("Layer for the zone"),
|
||||
net: z.string().describe("Net name for the zone"),
|
||||
points: z.array(
|
||||
z.object({
|
||||
x: z.number().describe("X coordinate"),
|
||||
y: z.number().describe("Y coordinate")
|
||||
})
|
||||
).describe("Points defining the zone outline"),
|
||||
unit: z.enum(["mm", "inch"]).describe("Unit of measurement"),
|
||||
clearance: z.number().optional().describe("Clearance value"),
|
||||
minWidth: z.number().optional().describe("Minimum width"),
|
||||
padConnection: z.enum(["thermal", "solid", "none"]).optional().describe("Pad connection type")
|
||||
},
|
||||
async ({ layer, net, points, unit, clearance, minWidth, padConnection }) => {
|
||||
logger.debug(`Adding zone on layer ${layer} for net ${net}`);
|
||||
const result = await callKicadScript("add_zone", {
|
||||
layer,
|
||||
net,
|
||||
points,
|
||||
unit,
|
||||
clearance,
|
||||
minWidth,
|
||||
padConnection
|
||||
});
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Get Board Extents Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"get_board_extents",
|
||||
{
|
||||
unit: z.enum(["mm", "inch"]).optional().describe("Unit of measurement for the result")
|
||||
},
|
||||
async ({ unit }) => {
|
||||
logger.debug('Getting board extents');
|
||||
const result = await callKicadScript("get_board_extents", { unit });
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Get Board 2D View Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"get_board_2d_view",
|
||||
{
|
||||
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"),
|
||||
height: z.number().optional().describe("Optional height of the image in pixels"),
|
||||
format: z.enum(["png", "jpg", "svg"]).optional().describe("Image format")
|
||||
},
|
||||
async ({ layers, width, height, format }) => {
|
||||
logger.debug('Getting 2D board view');
|
||||
const result = await callKicadScript("get_board_2d_view", {
|
||||
layers,
|
||||
width,
|
||||
height,
|
||||
format
|
||||
});
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
logger.info('Board management tools registered');
|
||||
}
|
||||
291
src/tools/component.ts
Normal file
291
src/tools/component.ts
Normal file
@@ -0,0 +1,291 @@
|
||||
/**
|
||||
* 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');
|
||||
}
|
||||
26
src/tools/component.txt
Normal file
26
src/tools/component.txt
Normal file
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* 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: any) => 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.registerTool({
|
||||
name: "place_component",
|
||||
description: "Places a component on the PCB at the specified location",
|
||||
261
src/tools/design-rules.ts
Normal file
261
src/tools/design-rules.ts
Normal file
@@ -0,0 +1,261 @@
|
||||
/**
|
||||
* Design rules tools for KiCAD MCP server
|
||||
*
|
||||
* These tools handle design rule checking and configuration
|
||||
*/
|
||||
|
||||
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 design rule tools with the MCP server
|
||||
*
|
||||
* @param server MCP server instance
|
||||
* @param callKicadScript Function to call KiCAD script commands
|
||||
*/
|
||||
export function registerDesignRuleTools(server: McpServer, callKicadScript: CommandFunction): void {
|
||||
logger.info('Registering design rule tools');
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Set Design Rules Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"set_design_rules",
|
||||
{
|
||||
clearance: z.number().optional().describe("Minimum clearance between copper items (mm)"),
|
||||
trackWidth: z.number().optional().describe("Default track width (mm)"),
|
||||
viaDiameter: z.number().optional().describe("Default via diameter (mm)"),
|
||||
viaDrill: z.number().optional().describe("Default via drill size (mm)"),
|
||||
microViaDiameter: z.number().optional().describe("Default micro via diameter (mm)"),
|
||||
microViaDrill: z.number().optional().describe("Default micro via drill size (mm)"),
|
||||
minTrackWidth: z.number().optional().describe("Minimum track width (mm)"),
|
||||
minViaDiameter: z.number().optional().describe("Minimum via diameter (mm)"),
|
||||
minViaDrill: z.number().optional().describe("Minimum via drill size (mm)"),
|
||||
minMicroViaDiameter: z.number().optional().describe("Minimum micro via diameter (mm)"),
|
||||
minMicroViaDrill: z.number().optional().describe("Minimum micro via drill size (mm)"),
|
||||
minHoleDiameter: z.number().optional().describe("Minimum hole diameter (mm)"),
|
||||
requireCourtyard: z.boolean().optional().describe("Whether to require courtyards for all footprints"),
|
||||
courtyardClearance: z.number().optional().describe("Minimum clearance between courtyards (mm)")
|
||||
},
|
||||
async (params) => {
|
||||
logger.debug('Setting design rules');
|
||||
const result = await callKicadScript("set_design_rules", params);
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Get Design Rules Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"get_design_rules",
|
||||
{},
|
||||
async () => {
|
||||
logger.debug('Getting design rules');
|
||||
const result = await callKicadScript("get_design_rules", {});
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Run DRC Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"run_drc",
|
||||
{
|
||||
reportPath: z.string().optional().describe("Optional path to save the DRC report")
|
||||
},
|
||||
async ({ reportPath }) => {
|
||||
logger.debug('Running DRC check');
|
||||
const result = await callKicadScript("run_drc", { reportPath });
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Add Net Class Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"add_net_class",
|
||||
{
|
||||
name: z.string().describe("Name of the net class"),
|
||||
description: z.string().optional().describe("Optional description of the net class"),
|
||||
clearance: z.number().describe("Clearance for this net class (mm)"),
|
||||
trackWidth: z.number().describe("Track width for this net class (mm)"),
|
||||
viaDiameter: z.number().describe("Via diameter for this net class (mm)"),
|
||||
viaDrill: z.number().describe("Via drill size for this net class (mm)"),
|
||||
uvia_diameter: z.number().optional().describe("Micro via diameter for this net class (mm)"),
|
||||
uvia_drill: z.number().optional().describe("Micro via drill size for this net class (mm)"),
|
||||
diff_pair_width: z.number().optional().describe("Differential pair width for this net class (mm)"),
|
||||
diff_pair_gap: z.number().optional().describe("Differential pair gap for this net class (mm)"),
|
||||
nets: z.array(z.string()).optional().describe("Array of net names to assign to this class")
|
||||
},
|
||||
async ({ name, description, clearance, trackWidth, viaDiameter, viaDrill, uvia_diameter, uvia_drill, diff_pair_width, diff_pair_gap, nets }) => {
|
||||
logger.debug(`Adding net class: ${name}`);
|
||||
const result = await callKicadScript("add_net_class", {
|
||||
name,
|
||||
description,
|
||||
clearance,
|
||||
trackWidth,
|
||||
viaDiameter,
|
||||
viaDrill,
|
||||
uvia_diameter,
|
||||
uvia_drill,
|
||||
diff_pair_width,
|
||||
diff_pair_gap,
|
||||
nets
|
||||
});
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Assign Net to Class Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"assign_net_to_class",
|
||||
{
|
||||
net: z.string().describe("Name of the net"),
|
||||
netClass: z.string().describe("Name of the net class")
|
||||
},
|
||||
async ({ net, netClass }) => {
|
||||
logger.debug(`Assigning net ${net} to class ${netClass}`);
|
||||
const result = await callKicadScript("assign_net_to_class", {
|
||||
net,
|
||||
netClass
|
||||
});
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Set Layer Constraints Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"set_layer_constraints",
|
||||
{
|
||||
layer: z.string().describe("Layer name (e.g., 'F.Cu')"),
|
||||
minTrackWidth: z.number().optional().describe("Minimum track width for this layer (mm)"),
|
||||
minClearance: z.number().optional().describe("Minimum clearance for this layer (mm)"),
|
||||
minViaDiameter: z.number().optional().describe("Minimum via diameter for this layer (mm)"),
|
||||
minViaDrill: z.number().optional().describe("Minimum via drill size for this layer (mm)")
|
||||
},
|
||||
async ({ layer, minTrackWidth, minClearance, minViaDiameter, minViaDrill }) => {
|
||||
logger.debug(`Setting constraints for layer: ${layer}`);
|
||||
const result = await callKicadScript("set_layer_constraints", {
|
||||
layer,
|
||||
minTrackWidth,
|
||||
minClearance,
|
||||
minViaDiameter,
|
||||
minViaDrill
|
||||
});
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Check Clearance Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"check_clearance",
|
||||
{
|
||||
item1: z.object({
|
||||
type: z.enum(["track", "via", "pad", "zone", "component"]).describe("Type of the first item"),
|
||||
id: z.string().optional().describe("ID of the first item (if applicable)"),
|
||||
reference: z.string().optional().describe("Reference designator (for component)"),
|
||||
position: z.object({
|
||||
x: z.number().optional(),
|
||||
y: z.number().optional(),
|
||||
unit: z.enum(["mm", "inch"]).optional()
|
||||
}).optional().describe("Position to check (if ID not provided)")
|
||||
}).describe("First item to check"),
|
||||
item2: z.object({
|
||||
type: z.enum(["track", "via", "pad", "zone", "component"]).describe("Type of the second item"),
|
||||
id: z.string().optional().describe("ID of the second item (if applicable)"),
|
||||
reference: z.string().optional().describe("Reference designator (for component)"),
|
||||
position: z.object({
|
||||
x: z.number().optional(),
|
||||
y: z.number().optional(),
|
||||
unit: z.enum(["mm", "inch"]).optional()
|
||||
}).optional().describe("Position to check (if ID not provided)")
|
||||
}).describe("Second item to check")
|
||||
},
|
||||
async ({ item1, item2 }) => {
|
||||
logger.debug(`Checking clearance between ${item1.type} and ${item2.type}`);
|
||||
const result = await callKicadScript("check_clearance", {
|
||||
item1,
|
||||
item2
|
||||
});
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Get DRC Violations Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"get_drc_violations",
|
||||
{
|
||||
severity: z.enum(["error", "warning", "all"]).optional().describe("Filter violations by severity")
|
||||
},
|
||||
async ({ severity }) => {
|
||||
logger.debug('Getting DRC violations');
|
||||
const result = await callKicadScript("get_drc_violations", { severity });
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
logger.info('Design rule tools registered');
|
||||
}
|
||||
260
src/tools/export.ts
Normal file
260
src/tools/export.ts
Normal file
@@ -0,0 +1,260 @@
|
||||
/**
|
||||
* Export tools for KiCAD MCP server
|
||||
*
|
||||
* These tools handle exporting PCB data to various formats
|
||||
*/
|
||||
|
||||
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 export tools with the MCP server
|
||||
*
|
||||
* @param server MCP server instance
|
||||
* @param callKicadScript Function to call KiCAD script commands
|
||||
*/
|
||||
export function registerExportTools(server: McpServer, callKicadScript: CommandFunction): void {
|
||||
logger.info('Registering export tools');
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Export Gerber Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"export_gerber",
|
||||
{
|
||||
outputDir: z.string().describe("Directory to save Gerber files"),
|
||||
layers: z.array(z.string()).optional().describe("Optional array of layer names to export (default: all)"),
|
||||
useProtelExtensions: z.boolean().optional().describe("Whether to use Protel filename extensions"),
|
||||
generateDrillFiles: z.boolean().optional().describe("Whether to generate drill files"),
|
||||
generateMapFile: z.boolean().optional().describe("Whether to generate a map file"),
|
||||
useAuxOrigin: z.boolean().optional().describe("Whether to use auxiliary axis as origin")
|
||||
},
|
||||
async ({ outputDir, layers, useProtelExtensions, generateDrillFiles, generateMapFile, useAuxOrigin }) => {
|
||||
logger.debug(`Exporting Gerber files to: ${outputDir}`);
|
||||
const result = await callKicadScript("export_gerber", {
|
||||
outputDir,
|
||||
layers,
|
||||
useProtelExtensions,
|
||||
generateDrillFiles,
|
||||
generateMapFile,
|
||||
useAuxOrigin
|
||||
});
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Export PDF Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"export_pdf",
|
||||
{
|
||||
outputPath: z.string().describe("Path to save the PDF file"),
|
||||
layers: z.array(z.string()).optional().describe("Optional array of layer names to include (default: all)"),
|
||||
blackAndWhite: z.boolean().optional().describe("Whether to export in black and white"),
|
||||
frameReference: z.boolean().optional().describe("Whether to include frame reference"),
|
||||
pageSize: z.enum(["A4", "A3", "A2", "A1", "A0", "Letter", "Legal", "Tabloid"]).optional().describe("Page size")
|
||||
},
|
||||
async ({ outputPath, layers, blackAndWhite, frameReference, pageSize }) => {
|
||||
logger.debug(`Exporting PDF to: ${outputPath}`);
|
||||
const result = await callKicadScript("export_pdf", {
|
||||
outputPath,
|
||||
layers,
|
||||
blackAndWhite,
|
||||
frameReference,
|
||||
pageSize
|
||||
});
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Export SVG Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"export_svg",
|
||||
{
|
||||
outputPath: z.string().describe("Path to save the SVG file"),
|
||||
layers: z.array(z.string()).optional().describe("Optional array of layer names to include (default: all)"),
|
||||
blackAndWhite: z.boolean().optional().describe("Whether to export in black and white"),
|
||||
includeComponents: z.boolean().optional().describe("Whether to include component outlines")
|
||||
},
|
||||
async ({ outputPath, layers, blackAndWhite, includeComponents }) => {
|
||||
logger.debug(`Exporting SVG to: ${outputPath}`);
|
||||
const result = await callKicadScript("export_svg", {
|
||||
outputPath,
|
||||
layers,
|
||||
blackAndWhite,
|
||||
includeComponents
|
||||
});
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Export 3D Model Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"export_3d",
|
||||
{
|
||||
outputPath: z.string().describe("Path to save the 3D model file"),
|
||||
format: z.enum(["STEP", "STL", "VRML", "OBJ"]).describe("3D model format"),
|
||||
includeComponents: z.boolean().optional().describe("Whether to include 3D component models"),
|
||||
includeCopper: z.boolean().optional().describe("Whether to include copper layers"),
|
||||
includeSolderMask: z.boolean().optional().describe("Whether to include solder mask"),
|
||||
includeSilkscreen: z.boolean().optional().describe("Whether to include silkscreen")
|
||||
},
|
||||
async ({ outputPath, format, includeComponents, includeCopper, includeSolderMask, includeSilkscreen }) => {
|
||||
logger.debug(`Exporting 3D model to: ${outputPath}`);
|
||||
const result = await callKicadScript("export_3d", {
|
||||
outputPath,
|
||||
format,
|
||||
includeComponents,
|
||||
includeCopper,
|
||||
includeSolderMask,
|
||||
includeSilkscreen
|
||||
});
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Export BOM Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"export_bom",
|
||||
{
|
||||
outputPath: z.string().describe("Path to save the BOM file"),
|
||||
format: z.enum(["CSV", "XML", "HTML", "JSON"]).describe("BOM file format"),
|
||||
groupByValue: z.boolean().optional().describe("Whether to group components by value"),
|
||||
includeAttributes: z.array(z.string()).optional().describe("Optional array of additional attributes to include")
|
||||
},
|
||||
async ({ outputPath, format, groupByValue, includeAttributes }) => {
|
||||
logger.debug(`Exporting BOM to: ${outputPath}`);
|
||||
const result = await callKicadScript("export_bom", {
|
||||
outputPath,
|
||||
format,
|
||||
groupByValue,
|
||||
includeAttributes
|
||||
});
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Export Netlist Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"export_netlist",
|
||||
{
|
||||
outputPath: z.string().describe("Path to save the netlist file"),
|
||||
format: z.enum(["KiCad", "Spice", "Cadstar", "OrcadPCB2"]).optional().describe("Netlist format (default: KiCad)")
|
||||
},
|
||||
async ({ outputPath, format }) => {
|
||||
logger.debug(`Exporting netlist to: ${outputPath}`);
|
||||
const result = await callKicadScript("export_netlist", {
|
||||
outputPath,
|
||||
format
|
||||
});
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Export Position File Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"export_position_file",
|
||||
{
|
||||
outputPath: z.string().describe("Path to save the position file"),
|
||||
format: z.enum(["CSV", "ASCII"]).optional().describe("File format (default: CSV)"),
|
||||
units: z.enum(["mm", "inch"]).optional().describe("Units to use (default: mm)"),
|
||||
side: z.enum(["top", "bottom", "both"]).optional().describe("Which board side to include (default: both)")
|
||||
},
|
||||
async ({ outputPath, format, units, side }) => {
|
||||
logger.debug(`Exporting position file to: ${outputPath}`);
|
||||
const result = await callKicadScript("export_position_file", {
|
||||
outputPath,
|
||||
format,
|
||||
units,
|
||||
side
|
||||
});
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Export VRML Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"export_vrml",
|
||||
{
|
||||
outputPath: z.string().describe("Path to save the VRML file"),
|
||||
includeComponents: z.boolean().optional().describe("Whether to include 3D component models"),
|
||||
useRelativePaths: z.boolean().optional().describe("Whether to use relative paths for 3D models")
|
||||
},
|
||||
async ({ outputPath, includeComponents, useRelativePaths }) => {
|
||||
logger.debug(`Exporting VRML to: ${outputPath}`);
|
||||
const result = await callKicadScript("export_vrml", {
|
||||
outputPath,
|
||||
includeComponents,
|
||||
useRelativePaths
|
||||
});
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
logger.info('Export tools registered');
|
||||
}
|
||||
13
src/tools/index.ts
Normal file
13
src/tools/index.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Tools index for KiCAD MCP server
|
||||
*
|
||||
* Exports all tool registration functions
|
||||
*/
|
||||
|
||||
export { registerProjectTools } from './project.js';
|
||||
export { registerBoardTools } from './board.js';
|
||||
export { registerComponentTools } from './component.js';
|
||||
export { registerRoutingTools } from './routing.js';
|
||||
export { registerDesignRuleTools } from './design-rules.js';
|
||||
export { registerExportTools } from './export.js';
|
||||
export { registerSchematicTools } from './schematic.js';
|
||||
Reference in New Issue
Block a user