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:
KiCAD MCP Bot
2025-10-25 20:48:00 -04:00
commit e4c7119c51
81 changed files with 16003 additions and 0 deletions

66
src/config.ts Normal file
View File

@@ -0,0 +1,66 @@
/**
* Configuration handling for KiCAD MCP server
*/
import { readFile } from 'fs/promises';
import { existsSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import { z } from 'zod';
import { logger } from './logger.js';
// Get the current directory
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Default config location
const DEFAULT_CONFIG_PATH = join(dirname(__dirname), 'config', 'default-config.json');
/**
* Server configuration schema
*/
const ConfigSchema = z.object({
name: z.string().default('kicad-mcp-server'),
version: z.string().default('1.0.0'),
description: z.string().default('MCP server for KiCAD PCB design operations'),
pythonPath: z.string().optional(),
kicadPath: z.string().optional(),
logLevel: z.enum(['error', 'warn', 'info', 'debug']).default('info'),
logDir: z.string().optional()
});
/**
* Server configuration type
*/
export type Config = z.infer<typeof ConfigSchema>;
/**
* Load configuration from file
*
* @param configPath Path to the configuration file (optional)
* @returns Loaded and validated configuration
*/
export async function loadConfig(configPath?: string): Promise<Config> {
try {
// Determine which config file to load
const filePath = configPath || DEFAULT_CONFIG_PATH;
// Check if file exists
if (!existsSync(filePath)) {
logger.warn(`Configuration file not found: ${filePath}, using defaults`);
return ConfigSchema.parse({});
}
// Read and parse configuration
const configData = await readFile(filePath, 'utf-8');
const config = JSON.parse(configData);
// Validate configuration
return ConfigSchema.parse(config);
} catch (error) {
logger.error(`Error loading configuration: ${error}`);
// Return default configuration
return ConfigSchema.parse({});
}
}

119
src/index.ts Normal file
View File

@@ -0,0 +1,119 @@
/**
* KiCAD Model Context Protocol Server
* Main entry point
*/
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import { KiCADMcpServer } from './server.js';
import { loadConfig } from './config.js';
import { logger } from './logger.js';
// Get the current directory
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
/**
* Main function to start the KiCAD MCP server
*/
async function main() {
try {
// Parse command line arguments
const args = process.argv.slice(2);
const options = parseCommandLineArgs(args);
// Load configuration
const config = await loadConfig(options.configPath);
// Path to the Python script that interfaces with KiCAD
const kicadScriptPath = join(dirname(__dirname), 'python', 'kicad_interface.py');
// Create the server
const server = new KiCADMcpServer(
kicadScriptPath,
config.logLevel
);
// Start the server
await server.start();
// Setup graceful shutdown
setupGracefulShutdown(server);
logger.info('KiCAD MCP server started with STDIO transport');
} catch (error) {
logger.error(`Failed to start KiCAD MCP server: ${error}`);
process.exit(1);
}
}
/**
* Parse command line arguments
*/
function parseCommandLineArgs(args: string[]) {
let configPath = undefined;
for (let i = 0; i < args.length; i++) {
if (args[i] === '--config' && i + 1 < args.length) {
configPath = args[i + 1];
i++;
}
}
return { configPath };
}
/**
* Setup graceful shutdown handlers
*/
function setupGracefulShutdown(server: KiCADMcpServer) {
// Handle termination signals
process.on('SIGINT', async () => {
logger.info('Received SIGINT signal. Shutting down...');
await shutdownServer(server);
});
process.on('SIGTERM', async () => {
logger.info('Received SIGTERM signal. Shutting down...');
await shutdownServer(server);
});
// Handle uncaught exceptions
process.on('uncaughtException', async (error) => {
logger.error(`Uncaught exception: ${error}`);
await shutdownServer(server);
});
// Handle unhandled promise rejections
process.on('unhandledRejection', async (reason) => {
logger.error(`Unhandled promise rejection: ${reason}`);
await shutdownServer(server);
});
}
/**
* Shut down the server and exit
*/
async function shutdownServer(server: KiCADMcpServer) {
try {
logger.info('Shutting down KiCAD MCP server...');
await server.stop();
logger.info('Server shutdown complete. Exiting...');
process.exit(0);
} catch (error) {
logger.error(`Error during shutdown: ${error}`);
process.exit(1);
}
}
// Run the main function if this file is executed directly
if (import.meta.url === `file://${process.argv[1]}`) {
main().catch((error) => {
logger.error(`Unhandled error in main: ${error}`);
process.exit(1);
});
}
// For testing and programmatic usage
export { KiCADMcpServer };

500
src/kicad-server.ts Normal file
View File

@@ -0,0 +1,500 @@
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
import { spawn, ChildProcess } from 'child_process';
import { existsSync } from 'fs';
import path from 'path';
// Import all tool definitions for reference
// import { registerBoardTools } from './tools/board.js';
// import { registerComponentTools } from './tools/component.js';
// import { registerRoutingTools } from './tools/routing.js';
// import { registerDesignRuleTools } from './tools/design-rules.js';
// import { registerExportTools } from './tools/export.js';
// import { registerProjectTools } from './tools/project.js';
// import { registerSchematicTools } from './tools/schematic.js';
class KiCADServer {
private server: Server;
private pythonProcess: ChildProcess | null = null;
private kicadScriptPath: string;
private requestQueue: Array<{ request: any, resolve: Function, reject: Function }> = [];
private processingRequest = false;
constructor() {
// Set absolute path to the Python KiCAD interface script
// Using a hardcoded path to avoid cwd() issues when running from Cline
this.kicadScriptPath = 'c:/repo/KiCAD-MCP/python/kicad_interface.py';
// Check if script exists
if (!existsSync(this.kicadScriptPath)) {
throw new Error(`KiCAD interface script not found: ${this.kicadScriptPath}`);
}
// Initialize the server
this.server = new Server(
{
name: 'kicad-mcp-server',
version: '1.0.0'
},
{
capabilities: {
tools: {
// Empty object here, tools will be registered dynamically
}
}
}
);
// Initialize handler with direct pass-through to Python KiCAD interface
// We don't register TypeScript tools since we'll handle everything in Python
// Register tool list handler
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
// Project tools
{
name: 'create_project',
description: 'Create a new KiCAD project',
inputSchema: {
type: 'object',
properties: {
projectName: { type: 'string', description: 'Name of the new project' },
path: { type: 'string', description: 'Path where to create the project' },
template: { type: 'string', description: 'Optional template to use' }
},
required: ['projectName']
}
},
{
name: 'open_project',
description: 'Open an existing KiCAD project',
inputSchema: {
type: 'object',
properties: {
filename: { type: 'string', description: 'Path to the project file' }
},
required: ['filename']
}
},
{
name: 'save_project',
description: 'Save the current KiCAD project',
inputSchema: {
type: 'object',
properties: {
filename: { type: 'string', description: 'Optional path to save to' }
}
}
},
{
name: 'get_project_info',
description: 'Get information about the current project',
inputSchema: {
type: 'object',
properties: {}
}
},
// Board tools
{
name: 'set_board_size',
description: 'Set the size of the PCB board',
inputSchema: {
type: 'object',
properties: {
width: { type: 'number', description: 'Board width' },
height: { type: 'number', description: 'Board height' },
unit: { type: 'string', description: 'Unit of measurement (mm or inch)' }
},
required: ['width', 'height']
}
},
{
name: 'add_board_outline',
description: 'Add a board outline to the PCB',
inputSchema: {
type: 'object',
properties: {
shape: { type: 'string', description: 'Shape of outline (rectangle, circle, polygon, rounded_rectangle)' },
width: { type: 'number', description: 'Width for rectangle shapes' },
height: { type: 'number', description: 'Height for rectangle shapes' },
radius: { type: 'number', description: 'Radius for circle shapes' },
cornerRadius: { type: 'number', description: 'Corner radius for rounded rectangles' },
points: { type: 'array', description: 'Array of points for polygon shapes' },
centerX: { type: 'number', description: 'X coordinate of center' },
centerY: { type: 'number', description: 'Y coordinate of center' },
unit: { type: 'string', description: 'Unit of measurement (mm or inch)' }
}
}
},
// Component tools
{
name: 'place_component',
description: 'Place a component on the PCB',
inputSchema: {
type: 'object',
properties: {
componentId: { type: 'string', description: 'Component ID/footprint to place' },
position: { type: 'object', description: 'Position coordinates' },
reference: { type: 'string', description: 'Component reference designator' },
value: { type: 'string', description: 'Component value' },
rotation: { type: 'number', description: 'Rotation angle in degrees' },
layer: { type: 'string', description: 'Layer to place component on' }
},
required: ['componentId', 'position']
}
},
// Routing tools
{
name: 'add_net',
description: 'Add a new net to the PCB',
inputSchema: {
type: 'object',
properties: {
name: { type: 'string', description: 'Net name' },
class: { type: 'string', description: 'Net class' }
},
required: ['name']
}
},
{
name: 'route_trace',
description: 'Route a trace between two points or pads',
inputSchema: {
type: 'object',
properties: {
start: { type: 'object', description: 'Start point or pad' },
end: { type: 'object', description: 'End point or pad' },
layer: { type: 'string', description: 'Layer to route on' },
width: { type: 'number', description: 'Track width' },
net: { type: 'string', description: 'Net name' }
},
required: ['start', 'end']
}
},
// Schematic tools
{
name: 'create_schematic',
description: 'Create a new KiCAD schematic',
inputSchema: {
type: 'object',
properties: {
projectName: { type: 'string', description: 'Name of the schematic project' },
path: { type: 'string', description: 'Path where to create the schematic file' },
metadata: { type: 'object', description: 'Optional metadata for the schematic' }
},
required: ['projectName']
}
},
{
name: 'load_schematic',
description: 'Load an existing KiCAD schematic',
inputSchema: {
type: 'object',
properties: {
filename: { type: 'string', description: 'Path to the schematic file to load' }
},
required: ['filename']
}
},
{
name: 'add_schematic_component',
description: 'Add a component to a KiCAD schematic',
inputSchema: {
type: 'object',
properties: {
schematicPath: { type: 'string', description: 'Path to the schematic file' },
component: {
type: 'object',
description: 'Component definition',
properties: {
type: { type: 'string', description: 'Component type (e.g., R, C, LED)' },
reference: { type: 'string', description: 'Reference designator (e.g., R1, C2)' },
value: { type: 'string', description: 'Component value (e.g., 10k, 0.1uF)' },
library: { type: 'string', description: 'Symbol library name' },
x: { type: 'number', description: 'X position in schematic' },
y: { type: 'number', description: 'Y position in schematic' },
rotation: { type: 'number', description: 'Rotation angle in degrees' },
properties: { type: 'object', description: 'Additional properties' }
},
required: ['type', 'reference']
}
},
required: ['schematicPath', 'component']
}
},
{
name: 'add_schematic_wire',
description: 'Add a wire connection to a KiCAD schematic',
inputSchema: {
type: 'object',
properties: {
schematicPath: { type: 'string', description: 'Path to the schematic file' },
startPoint: {
type: 'array',
description: 'Starting point coordinates [x, y]',
items: { type: 'number' },
minItems: 2,
maxItems: 2
},
endPoint: {
type: 'array',
description: 'Ending point coordinates [x, y]',
items: { type: 'number' },
minItems: 2,
maxItems: 2
}
},
required: ['schematicPath', 'startPoint', 'endPoint']
}
},
{
name: 'list_schematic_libraries',
description: 'List available KiCAD symbol libraries',
inputSchema: {
type: 'object',
properties: {
searchPaths: {
type: 'array',
description: 'Optional search paths for libraries',
items: { type: 'string' }
}
}
}
},
{
name: 'export_schematic_pdf',
description: 'Export a KiCAD schematic to PDF',
inputSchema: {
type: 'object',
properties: {
schematicPath: { type: 'string', description: 'Path to the schematic file' },
outputPath: { type: 'string', description: 'Path for the output PDF file' }
},
required: ['schematicPath', 'outputPath']
}
}
]
}));
// Register tool call handler
this.server.setRequestHandler(CallToolRequestSchema, async (request: any) => {
const toolName = request.params.name;
const args = request.params.arguments || {};
// Pass all commands directly to KiCAD Python interface
try {
return await this.callKicadScript(toolName, args);
} catch (error) {
console.error(`Error executing tool ${toolName}:`, error);
throw new Error(`Unknown tool: ${toolName}`);
}
});
}
async start() {
try {
console.error('Starting KiCAD MCP server...');
// Start the Python process for KiCAD scripting
console.error(`Starting Python process with script: ${this.kicadScriptPath}`);
const pythonExe = 'C:\\Program Files\\KiCad\\9.0\\bin\\python.exe';
console.error(`Using Python executable: ${pythonExe}`);
this.pythonProcess = spawn(pythonExe, [this.kicadScriptPath], {
stdio: ['pipe', 'pipe', 'pipe'],
env: {
...process.env,
PYTHONPATH: 'C:/Program Files/KiCad/9.0/lib/python3/dist-packages'
}
});
// Listen for process exit
this.pythonProcess.on('exit', (code, signal) => {
console.error(`Python process exited with code ${code} and signal ${signal}`);
this.pythonProcess = null;
});
// Listen for process errors
this.pythonProcess.on('error', (err) => {
console.error(`Python process error: ${err.message}`);
});
// Set up error logging for stderr
if (this.pythonProcess.stderr) {
this.pythonProcess.stderr.on('data', (data: Buffer) => {
console.error(`Python stderr: ${data.toString()}`);
});
}
// Connect to transport
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('KiCAD MCP server running');
// Keep the process running
process.on('SIGINT', () => {
if (this.pythonProcess) {
this.pythonProcess.kill();
}
this.server.close().catch(console.error);
process.exit(0);
});
} catch (error: unknown) {
if (error instanceof Error) {
console.error('Failed to start MCP server:', error.message);
} else {
console.error('Failed to start MCP server: Unknown error');
}
process.exit(1);
}
}
private async callKicadScript(command: string, params: any): Promise<any> {
return new Promise((resolve, reject) => {
// Check if Python process is running
if (!this.pythonProcess) {
console.error('Python process is not running');
reject(new Error("Python process for KiCAD scripting is not running"));
return;
}
// Add request to queue
this.requestQueue.push({
request: { command, params },
resolve,
reject
});
// Process the queue if not already processing
if (!this.processingRequest) {
this.processNextRequest();
}
});
}
private processNextRequest(): void {
// If no more requests or already processing, return
if (this.requestQueue.length === 0 || this.processingRequest) {
return;
}
// Set processing flag
this.processingRequest = true;
// Get the next request
const { request, resolve, reject } = this.requestQueue.shift()!;
try {
console.error(`Processing KiCAD command: ${request.command}`);
// Format the command and parameters as JSON
const requestStr = JSON.stringify(request);
// Set up response handling
let responseData = '';
// Clear any previous listeners
if (this.pythonProcess?.stdout) {
this.pythonProcess.stdout.removeAllListeners('data');
}
// Set up new listeners
if (this.pythonProcess?.stdout) {
this.pythonProcess.stdout.on('data', (data: Buffer) => {
const chunk = data.toString();
console.error(`Received data chunk: ${chunk.length} bytes`);
responseData += chunk;
// Check if we have a complete response
try {
// Try to parse the response as JSON
const result = JSON.parse(responseData);
// If we get here, we have a valid JSON response
console.error(`Completed KiCAD command: ${request.command} with result: ${JSON.stringify(result)}`);
// Reset processing flag
this.processingRequest = false;
// Process next request if any
setTimeout(() => this.processNextRequest(), 0);
// Clear listeners
if (this.pythonProcess?.stdout) {
this.pythonProcess.stdout.removeAllListeners('data');
}
// Resolve with the expected MCP tool response format
if (result.success) {
resolve({
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2)
}
]
});
} else {
resolve({
content: [
{
type: 'text',
text: result.errorDetails || result.message || 'Unknown error'
}
],
isError: true
});
}
} catch (e) {
// Not a complete JSON yet, keep collecting data
}
});
}
// Set a timeout
const timeout = setTimeout(() => {
console.error(`Command timeout: ${request.command}`);
// Clear listeners
if (this.pythonProcess?.stdout) {
this.pythonProcess.stdout.removeAllListeners('data');
}
// Reset processing flag
this.processingRequest = false;
// Process next request
setTimeout(() => this.processNextRequest(), 0);
// Reject the promise
reject(new Error(`Command timeout: ${request.command}`));
}, 30000); // 30 seconds timeout
// Write the request to the Python process
console.error(`Sending request: ${requestStr}`);
this.pythonProcess?.stdin?.write(requestStr + '\n');
} catch (error) {
console.error(`Error processing request: ${error}`);
// Reset processing flag
this.processingRequest = false;
// Process next request
setTimeout(() => this.processNextRequest(), 0);
// Reject the promise
reject(error);
}
}
}
// Start the server
const server = new KiCADServer();
server.start().catch(console.error);

121
src/logger.ts Normal file
View File

@@ -0,0 +1,121 @@
/**
* Logger for KiCAD MCP server
*/
import { existsSync, mkdirSync, appendFileSync } from 'fs';
import { join } from 'path';
import * as os from 'os';
// Log levels
type LogLevel = 'error' | 'warn' | 'info' | 'debug';
// Default log directory
const DEFAULT_LOG_DIR = join(os.homedir(), '.kicad-mcp', 'logs');
/**
* Logger class for KiCAD MCP server
*/
class Logger {
private logLevel: LogLevel = 'info';
private logDir: string = DEFAULT_LOG_DIR;
/**
* Set the log level
* @param level Log level to set
*/
setLogLevel(level: LogLevel): void {
this.logLevel = level;
}
/**
* Set the log directory
* @param dir Directory to store log files
*/
setLogDir(dir: string): void {
this.logDir = dir;
// Ensure log directory exists
if (!existsSync(this.logDir)) {
mkdirSync(this.logDir, { recursive: true });
}
}
/**
* Log an error message
* @param message Message to log
*/
error(message: string): void {
this.log('error', message);
}
/**
* Log a warning message
* @param message Message to log
*/
warn(message: string): void {
if (['error', 'warn', 'info', 'debug'].includes(this.logLevel)) {
this.log('warn', message);
}
}
/**
* Log an info message
* @param message Message to log
*/
info(message: string): void {
if (['info', 'debug'].includes(this.logLevel)) {
this.log('info', message);
}
}
/**
* Log a debug message
* @param message Message to log
*/
debug(message: string): void {
if (this.logLevel === 'debug') {
this.log('debug', message);
}
}
/**
* Log a message with the specified level
* @param level Log level
* @param message Message to log
*/
private log(level: LogLevel, message: string): void {
const timestamp = new Date().toISOString();
const formattedMessage = `[${timestamp}] [${level.toUpperCase()}] ${message}`;
// Log to console
switch (level) {
case 'error':
console.error(formattedMessage);
break;
case 'warn':
console.warn(formattedMessage);
break;
case 'info':
case 'debug':
default:
console.log(formattedMessage);
break;
}
// Log to file
try {
// Ensure log directory exists
if (!existsSync(this.logDir)) {
mkdirSync(this.logDir, { recursive: true });
}
const logFile = join(this.logDir, `kicad-mcp-${new Date().toISOString().split('T')[0]}.log`);
appendFileSync(logFile, formattedMessage + '\n');
} catch (error) {
console.error(`Failed to write to log file: ${error}`);
}
}
}
// Create and export logger instance
export const logger = new Logger();

231
src/prompts/component.ts Normal file
View File

@@ -0,0 +1,231 @@
/**
* Component prompts for KiCAD MCP server
*
* These prompts guide the LLM in providing assistance with component-related tasks
* in KiCAD PCB design.
*/
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
import { logger } from '../logger.js';
/**
* Register component prompts with the MCP server
*
* @param server MCP server instance
*/
export function registerComponentPrompts(server: McpServer): void {
logger.info('Registering component prompts');
// ------------------------------------------------------
// Component Selection Prompt
// ------------------------------------------------------
server.prompt(
"component_selection",
{
requirements: z.string().describe("Description of the circuit requirements and constraints")
},
() => ({
messages: [
{
role: "user",
content: {
type: "text",
text: `You're helping to select components for a circuit design. Given the following requirements:
{{requirements}}
Suggest appropriate components with their values, ratings, and footprints. Consider factors like:
- Power and voltage ratings
- Current handling capabilities
- Tolerance requirements
- Physical size constraints and package types
- Availability and cost considerations
- Thermal characteristics
- Performance specifications
For each component type, recommend specific values and provide a brief explanation of your recommendation. If appropriate, suggest alternatives with different trade-offs.`
}
}
]
})
);
// ------------------------------------------------------
// Component Placement Strategy Prompt
// ------------------------------------------------------
server.prompt(
"component_placement_strategy",
{
components: z.string().describe("List of components to be placed on the PCB")
},
() => ({
messages: [
{
role: "user",
content: {
type: "text",
text: `You're helping with component placement for a PCB layout. Here are the components to place:
{{components}}
Provide a strategy for optimal placement considering:
1. Signal Integrity:
- Group related components to minimize signal path length
- Keep sensitive signals away from noisy components
- Consider appropriate placement for bypass/decoupling capacitors
2. Thermal Management:
- Distribute heat-generating components
- Ensure adequate spacing for cooling
- Placement near heat sinks or vias for thermal dissipation
3. EMI/EMC Concerns:
- Separate digital and analog sections
- Consider ground plane partitioning
- Shield sensitive components
4. Manufacturing and Assembly:
- Component orientation for automated assembly
- Adequate spacing for rework
- Consider component height distribution
Group components functionally and suggest a logical arrangement. If possible, provide a rough sketch or description of component zones.`
}
}
]
})
);
// ------------------------------------------------------
// Component Replacement Analysis Prompt
// ------------------------------------------------------
server.prompt(
"component_replacement_analysis",
{
component_info: z.string().describe("Information about the component that needs to be replaced")
},
() => ({
messages: [
{
role: "user",
content: {
type: "text",
text: `You're helping to find a replacement for a component that is unavailable or needs to be updated. Here's the original component information:
{{component_info}}
Consider these factors when suggesting replacements:
1. Electrical Compatibility:
- Match or exceed key electrical specifications
- Ensure voltage/current/power ratings are compatible
- Consider parametric equivalents
2. Physical Compatibility:
- Footprint compatibility or adaptation requirements
- Package differences and mounting considerations
- Size and clearance requirements
3. Performance Impact:
- How the replacement might affect circuit performance
- Potential need for circuit adjustments
4. Availability and Cost:
- Current market availability
- Cost comparison with original part
- Lead time considerations
Suggest suitable replacement options and explain the advantages and disadvantages of each. Include any circuit modifications that might be necessary.`
}
}
]
})
);
// ------------------------------------------------------
// Component Troubleshooting Prompt
// ------------------------------------------------------
server.prompt(
"component_troubleshooting",
{
issue_description: z.string().describe("Description of the component or circuit issue being troubleshooted")
},
() => ({
messages: [
{
role: "user",
content: {
type: "text",
text: `You're helping to troubleshoot an issue with a component or circuit section in a PCB design. Here's the issue description:
{{issue_description}}
Use the following systematic approach to diagnose the problem:
1. Component Verification:
- Check component values, footprints, and orientation
- Verify correct part numbers and specifications
- Examine for potential manufacturing defects
2. Circuit Analysis:
- Review the schematic for design errors
- Check for proper connections and signal paths
- Verify power and ground connections
3. Layout Review:
- Examine component placement and orientation
- Check for adequate clearances
- Review trace routing and potential interference
4. Environmental Factors:
- Consider temperature, humidity, and other environmental impacts
- Check for potential EMI/RFI issues
- Review mechanical stress or vibration effects
Based on the available information, suggest likely causes of the issue and recommend specific steps to diagnose and resolve the problem.`
}
}
]
})
);
// ------------------------------------------------------
// Component Value Calculation Prompt
// ------------------------------------------------------
server.prompt(
"component_value_calculation",
{
circuit_requirements: z.string().describe("Description of the circuit function and performance requirements")
},
() => ({
messages: [
{
role: "user",
content: {
type: "text",
text: `You're helping to calculate appropriate component values for a specific circuit function. Here's the circuit description and requirements:
{{circuit_requirements}}
Follow these steps to determine the optimal component values:
1. Identify the relevant circuit equations and design formulas
2. Consider the design constraints and performance requirements
3. Calculate initial component values based on ideal behavior
4. Adjust for real-world factors:
- Component tolerances
- Temperature coefficients
- Parasitic effects
- Available standard values
Present your calculations step-by-step, showing your work and explaining your reasoning. Recommend specific component values, explaining why they're appropriate for this application. If there are multiple valid approaches, discuss the trade-offs between them.`
}
}
]
})
);
logger.info('Component prompts registered');
}

321
src/prompts/design.ts Normal file
View File

@@ -0,0 +1,321 @@
/**
* Design prompts for KiCAD MCP server
*
* These prompts guide the LLM in providing assistance with general PCB design tasks
* in KiCAD.
*/
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
import { logger } from '../logger.js';
/**
* Register design prompts with the MCP server
*
* @param server MCP server instance
*/
export function registerDesignPrompts(server: McpServer): void {
logger.info('Registering design prompts');
// ------------------------------------------------------
// PCB Layout Review Prompt
// ------------------------------------------------------
server.prompt(
"pcb_layout_review",
{
pcb_design_info: z.string().describe("Information about the current PCB design, including board dimensions, layer stack-up, component placement, and routing details")
},
() => ({
messages: [
{
role: "user",
content: {
type: "text",
text: `You're helping to review a PCB layout for potential issues and improvements. Here's information about the current PCB design:
{{pcb_design_info}}
When reviewing the PCB layout, consider these key areas:
1. Component Placement:
- Logical grouping of related components
- Orientation for efficient routing
- Thermal considerations for heat-generating components
- Mechanical constraints (mounting holes, connectors at edges)
- Accessibility for testing and rework
2. Signal Integrity:
- Trace lengths for critical signals
- Differential pair routing quality
- Potential crosstalk issues
- Return path continuity
- Decoupling capacitor placement
3. Power Distribution:
- Adequate copper for power rails
- Power plane design and continuity
- Decoupling strategy effectiveness
- Voltage regulator thermal management
4. EMI/EMC Considerations:
- Ground plane integrity
- Potential antenna effects
- Shielding requirements
- Loop area minimization
- Edge radiation control
5. Manufacturing and Assembly:
- DFM (Design for Manufacturing) issues
- DFA (Design for Assembly) considerations
- Testability features
- Silkscreen clarity and usefulness
- Solder mask considerations
Based on the provided information, identify potential issues and suggest specific improvements to enhance the PCB design.`
}
}
]
})
);
// ------------------------------------------------------
// Layer Stack-up Planning Prompt
// ------------------------------------------------------
server.prompt(
"layer_stackup_planning",
{
design_requirements: z.string().describe("Information about the PCB design requirements, including signal types, speed/frequency, power requirements, and any special considerations")
},
() => ({
messages: [
{
role: "user",
content: {
type: "text",
text: `You're helping to plan an appropriate layer stack-up for a PCB design. Here's information about the design requirements:
{{design_requirements}}
When planning a PCB layer stack-up, consider these important factors:
1. Signal Integrity Requirements:
- Controlled impedance needs
- High-speed signal routing
- EMI/EMC considerations
- Crosstalk mitigation
2. Power Distribution Needs:
- Current requirements for power rails
- Power integrity considerations
- Decoupling effectiveness
- Thermal management
3. Manufacturing Constraints:
- Fabrication capabilities and limitations
- Cost considerations
- Available materials and their properties
- Standard vs. specialized processes
4. Layer Types and Arrangement:
- Signal layers
- Power and ground planes
- Mixed signal/plane layers
- Microstrip vs. stripline configurations
5. Material Selection:
- Dielectric constant (Er) requirements
- Loss tangent considerations for high-speed
- Thermal properties
- Mechanical stability
Based on the provided requirements, recommend an appropriate layer stack-up, including the number of layers, their arrangement, material specifications, and thickness parameters. Explain the rationale behind your recommendations.`
}
}
]
})
);
// ------------------------------------------------------
// Design Rule Development Prompt
// ------------------------------------------------------
server.prompt(
"design_rule_development",
{
project_requirements: z.string().describe("Information about the PCB project requirements, including technology, speed/frequency, manufacturing capabilities, and any special considerations")
},
() => ({
messages: [
{
role: "user",
content: {
type: "text",
text: `You're helping to develop appropriate design rules for a PCB project. Here's information about the project requirements:
{{project_requirements}}
When developing PCB design rules, consider these key areas:
1. Clearance Rules:
- Minimum spacing between copper features
- Different clearance requirements for different net classes
- High-voltage clearance requirements
- Polygon pour clearances
2. Width Rules:
- Minimum trace widths for signal nets
- Power trace width requirements based on current
- Differential pair width and spacing
- Net class-specific width rules
3. Via Rules:
- Minimum via size and drill diameter
- Via annular ring requirements
- Microvias and buried/blind via specifications
- Via-in-pad rules
4. Manufacturing Constraints:
- Minimum hole size
- Aspect ratio limitations
- Soldermask and silkscreen constraints
- Edge clearances
5. Special Requirements:
- Impedance control specifications
- High-speed routing constraints
- Thermal relief parameters
- Teardrop specifications
Based on the provided project requirements, recommend a comprehensive set of design rules that will ensure signal integrity, manufacturability, and reliability of the PCB. Provide specific values where appropriate and explain the rationale behind critical rules.`
}
}
]
})
);
// ------------------------------------------------------
// Component Selection Guidance Prompt
// ------------------------------------------------------
server.prompt(
"component_selection_guidance",
{
circuit_requirements: z.string().describe("Information about the circuit requirements, including functionality, performance needs, operating environment, and any special considerations")
},
() => ({
messages: [
{
role: "user",
content: {
type: "text",
text: `You're helping with component selection for a PCB design. Here's information about the circuit requirements:
{{circuit_requirements}}
When selecting components for a PCB design, consider these important factors:
1. Electrical Specifications:
- Voltage and current ratings
- Power handling capabilities
- Speed/frequency requirements
- Noise and precision considerations
- Operating temperature range
2. Package and Footprint:
- Space constraints on the PCB
- Thermal dissipation requirements
- Manual vs. automated assembly
- Inspection and rework considerations
- Available footprint libraries
3. Availability and Sourcing:
- Multiple source options
- Lead time considerations
- Lifecycle status (new, mature, end-of-life)
- Cost considerations
- Minimum order quantities
4. Reliability and Quality:
- Industrial vs. commercial vs. automotive grade
- Expected lifetime of the product
- Environmental conditions
- Compliance with relevant standards
5. Special Considerations:
- EMI/EMC performance
- Thermal characteristics
- Moisture sensitivity
- RoHS/REACH compliance
- Special handling requirements
Based on the provided circuit requirements, recommend appropriate component types, packages, and specific considerations for this design. Provide guidance on critical component selections and explain the rationale behind your recommendations.`
}
}
]
})
);
// ------------------------------------------------------
// PCB Design Optimization Prompt
// ------------------------------------------------------
server.prompt(
"pcb_design_optimization",
{
design_info: z.string().describe("Information about the current PCB design, including board dimensions, layer stack-up, component placement, and routing details"),
optimization_goals: z.string().describe("Specific goals for optimization, such as performance improvement, cost reduction, size reduction, or manufacturability enhancement")
},
() => ({
messages: [
{
role: "user",
content: {
type: "text",
text: `You're helping to optimize a PCB design. Here's information about the current design and optimization goals:
{{design_info}}
{{optimization_goals}}
When optimizing a PCB design, consider these key areas based on the stated goals:
1. Performance Optimization:
- Critical signal path length reduction
- Impedance control improvement
- Decoupling strategy enhancement
- Thermal management improvement
- EMI/EMC reduction techniques
2. Manufacturability Optimization:
- DFM rule compliance
- Testability improvements
- Assembly process simplification
- Yield improvement opportunities
- Tolerance and variation management
3. Cost Optimization:
- Board size reduction opportunities
- Layer count optimization
- Component consolidation
- Alternative component options
- Panelization efficiency
4. Reliability Optimization:
- Stress point identification and mitigation
- Environmental robustness improvements
- Failure mode mitigation
- Margin analysis and improvement
- Redundancy considerations
5. Space/Size Optimization:
- Component placement density
- 3D space utilization
- Flex and rigid-flex opportunities
- Alternative packaging approaches
- Connector and interface optimization
Based on the provided information and optimization goals, suggest specific, actionable improvements to the PCB design. Prioritize your recommendations based on their potential impact and implementation feasibility.`
}
}
]
})
);
logger.info('Design prompts registered');
}

9
src/prompts/index.ts Normal file
View File

@@ -0,0 +1,9 @@
/**
* Prompts index for KiCAD MCP server
*
* Exports all prompt registration functions
*/
export { registerComponentPrompts } from './component.js';
export { registerRoutingPrompts } from './routing.js';
export { registerDesignPrompts } from './design.js';

288
src/prompts/routing.ts Normal file
View File

@@ -0,0 +1,288 @@
/**
* Routing prompts for KiCAD MCP server
*
* These prompts guide the LLM in providing assistance with routing-related tasks
* in KiCAD PCB design.
*/
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
import { logger } from '../logger.js';
/**
* Register routing prompts with the MCP server
*
* @param server MCP server instance
*/
export function registerRoutingPrompts(server: McpServer): void {
logger.info('Registering routing prompts');
// ------------------------------------------------------
// Routing Strategy Prompt
// ------------------------------------------------------
server.prompt(
"routing_strategy",
{
board_info: z.string().describe("Information about the PCB board, including dimensions, layer stack-up, and components")
},
() => ({
messages: [
{
role: "user",
content: {
type: "text",
text: `You're helping to develop a routing strategy for a PCB design. Here's information about the board:
{{board_info}}
Consider the following aspects when developing your routing strategy:
1. Signal Integrity:
- Group related signals and keep them close
- Minimize trace length for high-speed signals
- Consider differential pair routing for appropriate signals
- Avoid right-angle bends in traces
2. Power Distribution:
- Use appropriate trace widths for power and ground
- Consider using power planes for better distribution
- Place decoupling capacitors close to ICs
3. EMI/EMC Considerations:
- Keep digital and analog sections separated
- Consider ground plane partitioning
- Minimize loop areas for sensitive signals
4. Manufacturing Constraints:
- Adhere to minimum trace width and spacing requirements
- Consider via size and placement restrictions
- Account for soldermask and silkscreen limitations
5. Layer Stack-up Utilization:
- Determine which signals go on which layers
- Plan for layer transitions (vias)
- Consider impedance control requirements
Provide a comprehensive routing strategy that addresses these aspects, with specific recommendations for this particular board design.`
}
}
]
})
);
// ------------------------------------------------------
// Differential Pair Routing Prompt
// ------------------------------------------------------
server.prompt(
"differential_pair_routing",
{
differential_pairs: z.string().describe("Information about the differential pairs to be routed, including signal names, source and destination components, and speed/frequency requirements")
},
() => ({
messages: [
{
role: "user",
content: {
type: "text",
text: `You're helping with routing differential pairs on a PCB. Here's information about the differential pairs:
{{differential_pairs}}
When routing differential pairs, follow these best practices:
1. Length Matching:
- Keep both traces in each pair the same length
- Maintain consistent spacing between the traces
- Use serpentine routing (meanders) for length matching when necessary
2. Impedance Control:
- Maintain consistent trace width and spacing to control impedance
- Consider the layer stack-up and dielectric properties
- Avoid changing layers if possible; when necessary, use symmetrical via pairs
3. Coupling and Crosstalk:
- Keep differential pairs tightly coupled to each other
- Maintain adequate spacing between different differential pairs
- Route away from single-ended signals that could cause interference
4. Reference Planes:
- Route over continuous reference planes
- Avoid splits in reference planes under differential pairs
- Consider the return path for the signals
5. Termination:
- Plan for proper termination at the ends of the pairs
- Consider the need for series or parallel termination resistors
- Place termination components close to the endpoints
Based on the provided information, suggest specific routing approaches for these differential pairs, including recommended trace width, spacing, and any special considerations for this particular design.`
}
}
]
})
);
// ------------------------------------------------------
// High-Speed Routing Prompt
// ------------------------------------------------------
server.prompt(
"high_speed_routing",
{
high_speed_signals: z.string().describe("Information about the high-speed signals to be routed, including signal names, source and destination components, and speed/frequency requirements")
},
() => ({
messages: [
{
role: "user",
content: {
type: "text",
text: `You're helping with routing high-speed signals on a PCB. Here's information about the high-speed signals:
{{high_speed_signals}}
When routing high-speed signals, consider these critical factors:
1. Impedance Control:
- Maintain consistent trace width to control impedance
- Use controlled impedance calculations based on layer stack-up
- Consider microstrip vs. stripline routing depending on signal requirements
2. Signal Integrity:
- Minimize trace length to reduce propagation delay
- Avoid sharp corners (use 45° angles or curves)
- Minimize vias to reduce discontinuities
- Consider using teardrops at pad connections
3. Crosstalk Mitigation:
- Maintain adequate spacing between high-speed traces
- Use ground traces or planes for isolation
- Cross traces at 90° when traces must cross on adjacent layers
4. Return Path Management:
- Ensure continuous return path under the signal
- Avoid reference plane splits under high-speed signals
- Use ground vias near signal vias for return path continuity
5. Termination and Loading:
- Plan for proper termination (series, parallel, AC, etc.)
- Consider transmission line effects
- Account for capacitive loading from components and vias
Based on the provided information, suggest specific routing approaches for these high-speed signals, including recommended trace width, layer assignment, and any special considerations for this particular design.`
}
}
]
})
);
// ------------------------------------------------------
// Power Distribution Prompt
// ------------------------------------------------------
server.prompt(
"power_distribution",
{
power_requirements: z.string().describe("Information about the power requirements, including voltage rails, current needs, and components requiring power")
},
() => ({
messages: [
{
role: "user",
content: {
type: "text",
text: `You're helping with designing the power distribution network for a PCB. Here's information about the power requirements:
{{power_requirements}}
Consider these key aspects of power distribution network design:
1. Power Planes vs. Traces:
- Determine when to use power planes versus wide traces
- Consider current requirements and voltage drop
- Plan the layer stack-up to accommodate power distribution
2. Decoupling Strategy:
- Place decoupling capacitors close to ICs
- Use appropriate capacitor values and types
- Consider high-frequency and bulk decoupling needs
- Plan for power entry filtering
3. Current Capacity:
- Calculate trace widths based on current requirements
- Consider thermal issues and heat dissipation
- Plan for current return paths
4. Voltage Regulation:
- Place regulators strategically
- Consider thermal management for regulators
- Plan feedback paths for regulators
5. EMI/EMC Considerations:
- Minimize loop areas
- Keep power and ground planes closely coupled
- Consider filtering for noise-sensitive circuits
Based on the provided information, suggest a comprehensive power distribution strategy, including specific recommendations for plane usage, trace widths, decoupling, and any special considerations for this particular design.`
}
}
]
})
);
// ------------------------------------------------------
// Via Usage Prompt
// ------------------------------------------------------
server.prompt(
"via_usage",
{
board_info: z.string().describe("Information about the PCB board, including layer count, thickness, and design requirements")
},
() => ({
messages: [
{
role: "user",
content: {
type: "text",
text: `You're helping with planning via usage in a PCB design. Here's information about the board:
{{board_info}}
Consider these important aspects of via usage:
1. Via Types:
- Through-hole vias (span all layers)
- Blind vias (connect outer layer to inner layer)
- Buried vias (connect inner layers only)
- Microvias (small diameter vias for HDI designs)
2. Manufacturing Constraints:
- Minimum via diameter and drill size
- Aspect ratio limitations (board thickness to hole diameter)
- Annular ring requirements
- Via-in-pad considerations and special processing
3. Signal Integrity Impact:
- Capacitive loading effects of vias
- Impedance discontinuities
- Stub effects in through-hole vias
- Strategies to minimize via impact on high-speed signals
4. Thermal Considerations:
- Using vias for thermal relief
- Via patterns for heat dissipation
- Thermal via sizing and spacing
5. Design Optimization:
- Via fanout strategies
- Sharing vias between signals vs. dedicated vias
- Via placement to minimize trace length
- Tenting and plugging options
Based on the provided information, recommend appropriate via strategies for this PCB design, including specific via types, sizes, and placement guidelines.`
}
}
]
})
);
logger.info('Routing prompts registered');
}

354
src/resources/board.ts Normal file
View File

@@ -0,0 +1,354 @@
/**
* Board resources for KiCAD MCP server
*
* These resources provide information about the PCB board
* to the LLM, enabling better context-aware assistance.
*/
import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
import { logger } from '../logger.js';
import { createJsonResponse, createBinaryResponse } from '../utils/resource-helpers.js';
// Command function type for KiCAD script calls
type CommandFunction = (command: string, params: Record<string, unknown>) => Promise<any>;
/**
* Register board resources with the MCP server
*
* @param server MCP server instance
* @param callKicadScript Function to call KiCAD script commands
*/
export function registerBoardResources(server: McpServer, callKicadScript: CommandFunction): void {
logger.info('Registering board resources');
// ------------------------------------------------------
// Board Information Resource
// ------------------------------------------------------
server.resource(
"board_info",
"kicad://board/info",
async (uri) => {
logger.debug('Retrieving board information');
const result = await callKicadScript("get_board_info", {});
if (!result.success) {
logger.error(`Failed to retrieve board information: ${result.errorDetails}`);
return {
contents: [{
uri: uri.href,
text: JSON.stringify({
error: "Failed to retrieve board information",
details: result.errorDetails
}),
mimeType: "application/json"
}]
};
}
logger.debug('Successfully retrieved board information');
return {
contents: [{
uri: uri.href,
text: JSON.stringify(result),
mimeType: "application/json"
}]
};
}
);
// ------------------------------------------------------
// Layer List Resource
// ------------------------------------------------------
server.resource(
"layer_list",
"kicad://board/layers",
async (uri) => {
logger.debug('Retrieving layer list');
const result = await callKicadScript("get_layer_list", {});
if (!result.success) {
logger.error(`Failed to retrieve layer list: ${result.errorDetails}`);
return {
contents: [{
uri: uri.href,
text: JSON.stringify({
error: "Failed to retrieve layer list",
details: result.errorDetails
}),
mimeType: "application/json"
}]
};
}
logger.debug(`Successfully retrieved ${result.layers?.length || 0} layers`);
return {
contents: [{
uri: uri.href,
text: JSON.stringify(result),
mimeType: "application/json"
}]
};
}
);
// ------------------------------------------------------
// Board Extents Resource
// ------------------------------------------------------
server.resource(
"board_extents",
new ResourceTemplate("kicad://board/extents/{unit?}", {
list: async () => ({
resources: [
{ uri: "kicad://board/extents/mm", name: "Millimeters" },
{ uri: "kicad://board/extents/inch", name: "Inches" }
]
})
}),
async (uri, params) => {
const unit = params.unit || 'mm';
logger.debug(`Retrieving board extents in ${unit}`);
const result = await callKicadScript("get_board_extents", { unit });
if (!result.success) {
logger.error(`Failed to retrieve board extents: ${result.errorDetails}`);
return {
contents: [{
uri: uri.href,
text: JSON.stringify({
error: "Failed to retrieve board extents",
details: result.errorDetails
}),
mimeType: "application/json"
}]
};
}
logger.debug('Successfully retrieved board extents');
return {
contents: [{
uri: uri.href,
text: JSON.stringify(result),
mimeType: "application/json"
}]
};
}
);
// ------------------------------------------------------
// Board 2D View Resource
// ------------------------------------------------------
server.resource(
"board_2d_view",
new ResourceTemplate("kicad://board/2d-view/{format?}", {
list: async () => ({
resources: [
{ uri: "kicad://board/2d-view/png", name: "PNG Format" },
{ uri: "kicad://board/2d-view/jpg", name: "JPEG Format" },
{ uri: "kicad://board/2d-view/svg", name: "SVG Format" }
]
})
}),
async (uri, params) => {
const format = (params.format || 'png') as 'png' | 'jpg' | 'svg';
const width = params.width ? parseInt(params.width as string) : undefined;
const height = params.height ? parseInt(params.height as string) : undefined;
// Handle layers parameter - could be string or array
const layers = typeof params.layers === 'string' ? params.layers.split(',') : params.layers;
logger.debug('Retrieving 2D board view');
const result = await callKicadScript("get_board_2d_view", {
layers,
width,
height,
format
});
if (!result.success) {
logger.error(`Failed to retrieve 2D board view: ${result.errorDetails}`);
return {
contents: [{
uri: uri.href,
text: JSON.stringify({
error: "Failed to retrieve 2D board view",
details: result.errorDetails
}),
mimeType: "application/json"
}]
};
}
logger.debug('Successfully retrieved 2D board view');
if (format === 'svg') {
return {
contents: [{
uri: uri.href,
text: result.imageData,
mimeType: "image/svg+xml"
}]
};
} else {
return {
contents: [{
uri: uri.href,
blob: result.imageData,
mimeType: format === "jpg" ? "image/jpeg" : "image/png"
}]
};
}
}
);
// ------------------------------------------------------
// Board 3D View Resource
// ------------------------------------------------------
server.resource(
"board_3d_view",
new ResourceTemplate("kicad://board/3d-view/{angle?}", {
list: async () => ({
resources: [
{ uri: "kicad://board/3d-view/isometric", name: "Isometric View" },
{ uri: "kicad://board/3d-view/top", name: "Top View" },
{ uri: "kicad://board/3d-view/bottom", name: "Bottom View" }
]
})
}),
async (uri, params) => {
const angle = params.angle || 'isometric';
const width = params.width ? parseInt(params.width as string) : undefined;
const height = params.height ? parseInt(params.height as string) : undefined;
logger.debug(`Retrieving 3D board view from ${angle} angle`);
const result = await callKicadScript("get_board_3d_view", {
width,
height,
angle
});
if (!result.success) {
logger.error(`Failed to retrieve 3D board view: ${result.errorDetails}`);
return {
contents: [{
uri: uri.href,
text: JSON.stringify({
error: "Failed to retrieve 3D board view",
details: result.errorDetails
}),
mimeType: "application/json"
}]
};
}
logger.debug('Successfully retrieved 3D board view');
return {
contents: [{
uri: uri.href,
blob: result.imageData,
mimeType: "image/png"
}]
};
}
);
// ------------------------------------------------------
// Board Statistics Resource
// ------------------------------------------------------
server.resource(
"board_statistics",
"kicad://board/statistics",
async (uri) => {
logger.debug('Generating board statistics');
// Get board info
const boardResult = await callKicadScript("get_board_info", {});
if (!boardResult.success) {
logger.error(`Failed to retrieve board information: ${boardResult.errorDetails}`);
return {
contents: [{
uri: uri.href,
text: JSON.stringify({
error: "Failed to generate board statistics",
details: boardResult.errorDetails
}),
mimeType: "application/json"
}]
};
}
// Get component list
const componentsResult = await callKicadScript("get_component_list", {});
if (!componentsResult.success) {
logger.error(`Failed to retrieve component list: ${componentsResult.errorDetails}`);
return {
contents: [{
uri: uri.href,
text: JSON.stringify({
error: "Failed to generate board statistics",
details: componentsResult.errorDetails
}),
mimeType: "application/json"
}]
};
}
// Get nets list
const netsResult = await callKicadScript("get_nets_list", {});
if (!netsResult.success) {
logger.error(`Failed to retrieve nets list: ${netsResult.errorDetails}`);
return {
contents: [{
uri: uri.href,
text: JSON.stringify({
error: "Failed to generate board statistics",
details: netsResult.errorDetails
}),
mimeType: "application/json"
}]
};
}
// Combine all information into statistics
const statistics = {
board: {
size: boardResult.size,
layers: boardResult.layers?.length || 0,
title: boardResult.title
},
components: {
count: componentsResult.components?.length || 0,
types: countComponentTypes(componentsResult.components || [])
},
nets: {
count: netsResult.nets?.length || 0
}
};
logger.debug('Successfully generated board statistics');
return {
contents: [{
uri: uri.href,
text: JSON.stringify(statistics),
mimeType: "application/json"
}]
};
}
);
logger.info('Board resources registered');
}
/**
* Helper function to count component types
*/
function countComponentTypes(components: any[]): Record<string, number> {
const typeCounts: Record<string, number> = {};
for (const component of components) {
const type = component.value?.split(' ')[0] || 'Unknown';
typeCounts[type] = (typeCounts[type] || 0) + 1;
}
return typeCounts;
}

249
src/resources/component.ts Normal file
View File

@@ -0,0 +1,249 @@
/**
* Component resources for KiCAD MCP server
*
* These resources provide information about components on the PCB
* to the LLM, enabling better context-aware assistance.
*/
import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
import { logger } from '../logger.js';
// Command function type for KiCAD script calls
type CommandFunction = (command: string, params: Record<string, unknown>) => Promise<any>;
/**
* Register component resources with the MCP server
*
* @param server MCP server instance
* @param callKicadScript Function to call KiCAD script commands
*/
export function registerComponentResources(server: McpServer, callKicadScript: CommandFunction): void {
logger.info('Registering component resources');
// ------------------------------------------------------
// Component List Resource
// ------------------------------------------------------
server.resource(
"component_list",
"kicad://components",
async (uri) => {
logger.debug('Retrieving component list');
const result = await callKicadScript("get_component_list", {});
if (!result.success) {
logger.error(`Failed to retrieve component list: ${result.errorDetails}`);
return {
contents: [{
uri: uri.href,
text: JSON.stringify({
error: "Failed to retrieve component list",
details: result.errorDetails
}),
mimeType: "application/json"
}]
};
}
logger.debug(`Successfully retrieved ${result.components?.length || 0} components`);
return {
contents: [{
uri: uri.href,
text: JSON.stringify(result),
mimeType: "application/json"
}]
};
}
);
// ------------------------------------------------------
// Component Details Resource
// ------------------------------------------------------
server.resource(
"component_details",
new ResourceTemplate("kicad://component/{reference}/details", {
list: undefined
}),
async (uri, params) => {
const { reference } = params;
logger.debug(`Retrieving details for component: ${reference}`);
const result = await callKicadScript("get_component_properties", {
reference
});
if (!result.success) {
logger.error(`Failed to retrieve component details: ${result.errorDetails}`);
return {
contents: [{
uri: uri.href,
text: JSON.stringify({
error: `Failed to retrieve details for component ${reference}`,
details: result.errorDetails
}),
mimeType: "application/json"
}]
};
}
logger.debug(`Successfully retrieved details for component: ${reference}`);
return {
contents: [{
uri: uri.href,
text: JSON.stringify(result),
mimeType: "application/json"
}]
};
}
);
// ------------------------------------------------------
// Component Connections Resource
// ------------------------------------------------------
server.resource(
"component_connections",
new ResourceTemplate("kicad://component/{reference}/connections", {
list: undefined
}),
async (uri, params) => {
const { reference } = params;
logger.debug(`Retrieving connections for component: ${reference}`);
const result = await callKicadScript("get_component_connections", {
reference
});
if (!result.success) {
logger.error(`Failed to retrieve component connections: ${result.errorDetails}`);
return {
contents: [{
uri: uri.href,
text: JSON.stringify({
error: `Failed to retrieve connections for component ${reference}`,
details: result.errorDetails
}),
mimeType: "application/json"
}]
};
}
logger.debug(`Successfully retrieved connections for component: ${reference}`);
return {
contents: [{
uri: uri.href,
text: JSON.stringify(result),
mimeType: "application/json"
}]
};
}
);
// ------------------------------------------------------
// Component Placement Resource
// ------------------------------------------------------
server.resource(
"component_placement",
"kicad://components/placement",
async (uri) => {
logger.debug('Retrieving component placement information');
const result = await callKicadScript("get_component_placement", {});
if (!result.success) {
logger.error(`Failed to retrieve component placement: ${result.errorDetails}`);
return {
contents: [{
uri: uri.href,
text: JSON.stringify({
error: "Failed to retrieve component placement information",
details: result.errorDetails
}),
mimeType: "application/json"
}]
};
}
logger.debug('Successfully retrieved component placement information');
return {
contents: [{
uri: uri.href,
text: JSON.stringify(result),
mimeType: "application/json"
}]
};
}
);
// ------------------------------------------------------
// Component Groups Resource
// ------------------------------------------------------
server.resource(
"component_groups",
"kicad://components/groups",
async (uri) => {
logger.debug('Retrieving component groups');
const result = await callKicadScript("get_component_groups", {});
if (!result.success) {
logger.error(`Failed to retrieve component groups: ${result.errorDetails}`);
return {
contents: [{
uri: uri.href,
text: JSON.stringify({
error: "Failed to retrieve component groups",
details: result.errorDetails
}),
mimeType: "application/json"
}]
};
}
logger.debug(`Successfully retrieved ${result.groups?.length || 0} component groups`);
return {
contents: [{
uri: uri.href,
text: JSON.stringify(result),
mimeType: "application/json"
}]
};
}
);
// ------------------------------------------------------
// Component Visualization Resource
// ------------------------------------------------------
server.resource(
"component_visualization",
new ResourceTemplate("kicad://component/{reference}/visualization", {
list: undefined
}),
async (uri, params) => {
const { reference } = params;
logger.debug(`Generating visualization for component: ${reference}`);
const result = await callKicadScript("get_component_visualization", {
reference
});
if (!result.success) {
logger.error(`Failed to generate component visualization: ${result.errorDetails}`);
return {
contents: [{
uri: uri.href,
text: JSON.stringify({
error: `Failed to generate visualization for component ${reference}`,
details: result.errorDetails
}),
mimeType: "application/json"
}]
};
}
logger.debug(`Successfully generated visualization for component: ${reference}`);
return {
contents: [{
uri: uri.href,
blob: result.imageData, // Base64 encoded image data
mimeType: "image/png"
}]
};
}
);
logger.info('Component resources registered');
}

10
src/resources/index.ts Normal file
View File

@@ -0,0 +1,10 @@
/**
* Resources index for KiCAD MCP server
*
* Exports all resource registration functions
*/
export { registerProjectResources } from './project.js';
export { registerBoardResources } from './board.js';
export { registerComponentResources } from './component.js';
export { registerLibraryResources } from './library.js';

290
src/resources/library.ts Normal file
View File

@@ -0,0 +1,290 @@
/**
* Library resources for KiCAD MCP server
*
* These resources provide information about KiCAD component libraries
* to the LLM, enabling better context-aware assistance.
*/
import { McpServer, ResourceTemplate } 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 library resources with the MCP server
*
* @param server MCP server instance
* @param callKicadScript Function to call KiCAD script commands
*/
export function registerLibraryResources(server: McpServer, callKicadScript: CommandFunction): void {
logger.info('Registering library resources');
// ------------------------------------------------------
// Component Library Resource
// ------------------------------------------------------
server.resource(
"component_library",
new ResourceTemplate("kicad://components/{filter?}/{library?}", {
list: async () => ({
resources: [
{ uri: "kicad://components", name: "All Components" }
]
})
}),
async (uri, params) => {
const filter = params.filter || '';
const library = params.library || '';
const limit = Number(params.limit) || undefined;
logger.debug(`Retrieving component library${filter ? ` with filter: ${filter}` : ''}${library ? ` from library: ${library}` : ''}`);
const result = await callKicadScript("get_component_library", {
filter,
library,
limit
});
if (!result.success) {
logger.error(`Failed to retrieve component library: ${result.errorDetails}`);
return {
contents: [{
uri: uri.href,
text: JSON.stringify({
error: "Failed to retrieve component library",
details: result.errorDetails
}),
mimeType: "application/json"
}]
};
}
logger.debug(`Successfully retrieved ${result.components?.length || 0} components from library`);
return {
contents: [{
uri: uri.href,
text: JSON.stringify(result),
mimeType: "application/json"
}]
};
}
);
// ------------------------------------------------------
// Library List Resource
// ------------------------------------------------------
server.resource(
"library_list",
"kicad://libraries",
async (uri) => {
logger.debug('Retrieving library list');
const result = await callKicadScript("get_library_list", {});
if (!result.success) {
logger.error(`Failed to retrieve library list: ${result.errorDetails}`);
return {
contents: [{
uri: uri.href,
text: JSON.stringify({
error: "Failed to retrieve library list",
details: result.errorDetails
}),
mimeType: "application/json"
}]
};
}
logger.debug(`Successfully retrieved ${result.libraries?.length || 0} libraries`);
return {
contents: [{
uri: uri.href,
text: JSON.stringify(result),
mimeType: "application/json"
}]
};
}
);
// ------------------------------------------------------
// Component Details Resource
// ------------------------------------------------------
server.resource(
"component_details",
new ResourceTemplate("kicad://component/{componentId}/{library?}", {
list: undefined
}),
async (uri, params) => {
const { componentId, library } = params;
logger.debug(`Retrieving details for component: ${componentId}${library ? ` from library: ${library}` : ''}`);
const result = await callKicadScript("get_component_details", {
componentId,
library
});
if (!result.success) {
logger.error(`Failed to retrieve component details: ${result.errorDetails}`);
return {
contents: [{
uri: uri.href,
text: JSON.stringify({
error: `Failed to retrieve details for component ${componentId}`,
details: result.errorDetails
}),
mimeType: "application/json"
}]
};
}
logger.debug(`Successfully retrieved details for component: ${componentId}`);
return {
contents: [{
uri: uri.href,
text: JSON.stringify(result),
mimeType: "application/json"
}]
};
}
);
// ------------------------------------------------------
// Component Footprint Resource
// ------------------------------------------------------
server.resource(
"component_footprint",
new ResourceTemplate("kicad://footprint/{componentId}/{footprint?}", {
list: undefined
}),
async (uri, params) => {
const { componentId, footprint } = params;
logger.debug(`Retrieving footprint for component: ${componentId}${footprint ? ` (${footprint})` : ''}`);
const result = await callKicadScript("get_component_footprint", {
componentId,
footprint
});
if (!result.success) {
logger.error(`Failed to retrieve component footprint: ${result.errorDetails}`);
return {
contents: [{
uri: uri.href,
text: JSON.stringify({
error: `Failed to retrieve footprint for component ${componentId}`,
details: result.errorDetails
}),
mimeType: "application/json"
}]
};
}
logger.debug(`Successfully retrieved footprint for component: ${componentId}`);
return {
contents: [{
uri: uri.href,
text: JSON.stringify(result),
mimeType: "application/json"
}]
};
}
);
// ------------------------------------------------------
// Component Symbol Resource
// ------------------------------------------------------
server.resource(
"component_symbol",
new ResourceTemplate("kicad://symbol/{componentId}", {
list: undefined
}),
async (uri, params) => {
const { componentId } = params;
logger.debug(`Retrieving symbol for component: ${componentId}`);
const result = await callKicadScript("get_component_symbol", {
componentId
});
if (!result.success) {
logger.error(`Failed to retrieve component symbol: ${result.errorDetails}`);
return {
contents: [{
uri: uri.href,
text: JSON.stringify({
error: `Failed to retrieve symbol for component ${componentId}`,
details: result.errorDetails
}),
mimeType: "application/json"
}]
};
}
logger.debug(`Successfully retrieved symbol for component: ${componentId}`);
// If the result includes SVG data, return it as SVG
if (result.svgData) {
return {
contents: [{
uri: uri.href,
text: result.svgData,
mimeType: "image/svg+xml"
}]
};
}
// Otherwise return the JSON result
return {
contents: [{
uri: uri.href,
text: JSON.stringify(result),
mimeType: "application/json"
}]
};
}
);
// ------------------------------------------------------
// Component 3D Model Resource
// ------------------------------------------------------
server.resource(
"component_3d_model",
new ResourceTemplate("kicad://3d-model/{componentId}/{footprint?}", {
list: undefined
}),
async (uri, params) => {
const { componentId, footprint } = params;
logger.debug(`Retrieving 3D model for component: ${componentId}${footprint ? ` (${footprint})` : ''}`);
const result = await callKicadScript("get_component_3d_model", {
componentId,
footprint
});
if (!result.success) {
logger.error(`Failed to retrieve component 3D model: ${result.errorDetails}`);
return {
contents: [{
uri: uri.href,
text: JSON.stringify({
error: `Failed to retrieve 3D model for component ${componentId}`,
details: result.errorDetails
}),
mimeType: "application/json"
}]
};
}
logger.debug(`Successfully retrieved 3D model for component: ${componentId}`);
return {
contents: [{
uri: uri.href,
text: JSON.stringify(result),
mimeType: "application/json"
}]
};
}
);
logger.info('Library resources registered');
}

260
src/resources/project.ts Normal file
View File

@@ -0,0 +1,260 @@
/**
* Project resources for KiCAD MCP server
*
* These resources provide information about the KiCAD project
* to the LLM, enabling better context-aware assistance.
*/
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { logger } from '../logger.js';
// Command function type for KiCAD script calls
type CommandFunction = (command: string, params: Record<string, unknown>) => Promise<any>;
/**
* Register project resources with the MCP server
*
* @param server MCP server instance
* @param callKicadScript Function to call KiCAD script commands
*/
export function registerProjectResources(server: McpServer, callKicadScript: CommandFunction): void {
logger.info('Registering project resources');
// ------------------------------------------------------
// Project Information Resource
// ------------------------------------------------------
server.resource(
"project_info",
"kicad://project/info",
async (uri) => {
logger.debug('Retrieving project information');
const result = await callKicadScript("get_project_info", {});
if (!result.success) {
logger.error(`Failed to retrieve project information: ${result.errorDetails}`);
return {
contents: [{
uri: uri.href,
text: JSON.stringify({
error: "Failed to retrieve project information",
details: result.errorDetails
}),
mimeType: "application/json"
}]
};
}
logger.debug('Successfully retrieved project information');
return {
contents: [{
uri: uri.href,
text: JSON.stringify(result),
mimeType: "application/json"
}]
};
}
);
// ------------------------------------------------------
// Project Properties Resource
// ------------------------------------------------------
server.resource(
"project_properties",
"kicad://project/properties",
async (uri) => {
logger.debug('Retrieving project properties');
const result = await callKicadScript("get_project_properties", {});
if (!result.success) {
logger.error(`Failed to retrieve project properties: ${result.errorDetails}`);
return {
contents: [{
uri: uri.href,
text: JSON.stringify({
error: "Failed to retrieve project properties",
details: result.errorDetails
}),
mimeType: "application/json"
}]
};
}
logger.debug('Successfully retrieved project properties');
return {
contents: [{
uri: uri.href,
text: JSON.stringify(result),
mimeType: "application/json"
}]
};
}
);
// ------------------------------------------------------
// Project Files Resource
// ------------------------------------------------------
server.resource(
"project_files",
"kicad://project/files",
async (uri) => {
logger.debug('Retrieving project files');
const result = await callKicadScript("get_project_files", {});
if (!result.success) {
logger.error(`Failed to retrieve project files: ${result.errorDetails}`);
return {
contents: [{
uri: uri.href,
text: JSON.stringify({
error: "Failed to retrieve project files",
details: result.errorDetails
}),
mimeType: "application/json"
}]
};
}
logger.debug(`Successfully retrieved ${result.files?.length || 0} project files`);
return {
contents: [{
uri: uri.href,
text: JSON.stringify(result),
mimeType: "application/json"
}]
};
}
);
// ------------------------------------------------------
// Project Status Resource
// ------------------------------------------------------
server.resource(
"project_status",
"kicad://project/status",
async (uri) => {
logger.debug('Retrieving project status');
const result = await callKicadScript("get_project_status", {});
if (!result.success) {
logger.error(`Failed to retrieve project status: ${result.errorDetails}`);
return {
contents: [{
uri: uri.href,
text: JSON.stringify({
error: "Failed to retrieve project status",
details: result.errorDetails
}),
mimeType: "application/json"
}]
};
}
logger.debug('Successfully retrieved project status');
return {
contents: [{
uri: uri.href,
text: JSON.stringify(result),
mimeType: "application/json"
}]
};
}
);
// ------------------------------------------------------
// Project Summary Resource
// ------------------------------------------------------
server.resource(
"project_summary",
"kicad://project/summary",
async (uri) => {
logger.debug('Generating project summary');
// Get project info
const infoResult = await callKicadScript("get_project_info", {});
if (!infoResult.success) {
logger.error(`Failed to retrieve project information: ${infoResult.errorDetails}`);
return {
contents: [{
uri: uri.href,
text: JSON.stringify({
error: "Failed to generate project summary",
details: infoResult.errorDetails
}),
mimeType: "application/json"
}]
};
}
// Get board info
const boardResult = await callKicadScript("get_board_info", {});
if (!boardResult.success) {
logger.error(`Failed to retrieve board information: ${boardResult.errorDetails}`);
return {
contents: [{
uri: uri.href,
text: JSON.stringify({
error: "Failed to generate project summary",
details: boardResult.errorDetails
}),
mimeType: "application/json"
}]
};
}
// Get component list
const componentsResult = await callKicadScript("get_component_list", {});
if (!componentsResult.success) {
logger.error(`Failed to retrieve component list: ${componentsResult.errorDetails}`);
return {
contents: [{
uri: uri.href,
text: JSON.stringify({
error: "Failed to generate project summary",
details: componentsResult.errorDetails
}),
mimeType: "application/json"
}]
};
}
// Combine all information into a summary
const summary = {
project: infoResult.project,
board: {
size: boardResult.size,
layers: boardResult.layers?.length || 0,
title: boardResult.title
},
components: {
count: componentsResult.components?.length || 0,
types: countComponentTypes(componentsResult.components || [])
}
};
logger.debug('Successfully generated project summary');
return {
contents: [{
uri: uri.href,
text: JSON.stringify(summary),
mimeType: "application/json"
}]
};
}
);
logger.info('Project resources registered');
}
/**
* Helper function to count component types
*/
function countComponentTypes(components: any[]): Record<string, number> {
const typeCounts: Record<string, number> = {};
for (const component of components) {
const type = component.value?.split(' ')[0] || 'Unknown';
typeCounts[type] = (typeCounts[type] || 0) + 1;
}
return typeCounts;
}

308
src/server.ts Normal file
View File

@@ -0,0 +1,308 @@
/**
* KiCAD MCP Server implementation
*/
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import express from 'express';
import { spawn, ChildProcess } from 'child_process';
import { existsSync } from 'fs';
import { logger } from './logger.js';
// Import tool registration functions
import { registerProjectTools } from './tools/project.js';
import { registerBoardTools } from './tools/board.js';
import { registerComponentTools } from './tools/component.js';
import { registerRoutingTools } from './tools/routing.js';
import { registerDesignRuleTools } from './tools/design-rules.js';
import { registerExportTools } from './tools/export.js';
// Import resource registration functions
import { registerProjectResources } from './resources/project.js';
import { registerBoardResources } from './resources/board.js';
import { registerComponentResources } from './resources/component.js';
import { registerLibraryResources } from './resources/library.js';
// Import prompt registration functions
import { registerComponentPrompts } from './prompts/component.js';
import { registerRoutingPrompts } from './prompts/routing.js';
import { registerDesignPrompts } from './prompts/design.js';
/**
* KiCAD MCP Server class
*/
export class KiCADMcpServer {
private server: McpServer;
private pythonProcess: ChildProcess | null = null;
private kicadScriptPath: string;
private stdioTransport!: StdioServerTransport;
private requestQueue: Array<{ request: any, resolve: Function, reject: Function }> = [];
private processingRequest = false;
/**
* Constructor for the KiCAD MCP Server
* @param kicadScriptPath Path to the Python KiCAD interface script
* @param logLevel Log level for the server
*/
constructor(
kicadScriptPath: string,
logLevel: 'error' | 'warn' | 'info' | 'debug' = 'info'
) {
// Set up the logger
logger.setLogLevel(logLevel);
// Check if KiCAD script exists
this.kicadScriptPath = kicadScriptPath;
if (!existsSync(this.kicadScriptPath)) {
throw new Error(`KiCAD interface script not found: ${this.kicadScriptPath}`);
}
// Initialize the MCP server
this.server = new McpServer({
name: 'kicad-mcp-server',
version: '1.0.0',
description: 'MCP server for KiCAD PCB design operations'
});
// Initialize STDIO transport
this.stdioTransport = new StdioServerTransport();
logger.info('Using STDIO transport for local communication');
// Register tools, resources, and prompts
this.registerAll();
}
/**
* Register all tools, resources, and prompts
*/
private registerAll(): void {
logger.info('Registering KiCAD tools, resources, and prompts...');
// Register all tools
registerProjectTools(this.server, this.callKicadScript.bind(this));
registerBoardTools(this.server, this.callKicadScript.bind(this));
registerComponentTools(this.server, this.callKicadScript.bind(this));
registerRoutingTools(this.server, this.callKicadScript.bind(this));
registerDesignRuleTools(this.server, this.callKicadScript.bind(this));
registerExportTools(this.server, this.callKicadScript.bind(this));
// Register all resources
registerProjectResources(this.server, this.callKicadScript.bind(this));
registerBoardResources(this.server, this.callKicadScript.bind(this));
registerComponentResources(this.server, this.callKicadScript.bind(this));
registerLibraryResources(this.server, this.callKicadScript.bind(this));
// Register all prompts
registerComponentPrompts(this.server);
registerRoutingPrompts(this.server);
registerDesignPrompts(this.server);
logger.info('All KiCAD tools, resources, and prompts registered');
}
/**
* Start the MCP server and the Python KiCAD interface
*/
async start(): Promise<void> {
try {
logger.info('Starting KiCAD MCP server...');
// Start the Python process for KiCAD scripting
logger.info(`Starting Python process with script: ${this.kicadScriptPath}`);
const pythonExe = process.env.PYTHONPATH ?
'C:\\Program Files\\KiCad\\9.0\\bin\\python.exe' : 'python';
logger.info(`Using Python executable: ${pythonExe}`);
this.pythonProcess = spawn(pythonExe, [this.kicadScriptPath], {
stdio: ['pipe', 'pipe', 'pipe'],
env: {
...process.env,
PYTHONPATH: process.env.PYTHONPATH || 'C:/Program Files/KiCad/9.0/lib/python3/dist-packages'
}
});
// Listen for process exit
this.pythonProcess.on('exit', (code, signal) => {
logger.warn(`Python process exited with code ${code} and signal ${signal}`);
this.pythonProcess = null;
});
// Listen for process errors
this.pythonProcess.on('error', (err) => {
logger.error(`Python process error: ${err.message}`);
});
// Set up error logging for stderr
if (this.pythonProcess.stderr) {
this.pythonProcess.stderr.on('data', (data: Buffer) => {
logger.error(`Python stderr: ${data.toString()}`);
});
}
// Connect server to STDIO transport
logger.info('Connecting MCP server to STDIO transport...');
try {
await this.server.connect(this.stdioTransport);
logger.info('Successfully connected to STDIO transport');
} catch (error) {
logger.error(`Failed to connect to STDIO transport: ${error}`);
throw error;
}
// Write a ready message to stderr (for debugging)
process.stderr.write('KiCAD MCP SERVER READY\n');
logger.info('KiCAD MCP server started and ready');
} catch (error) {
logger.error(`Failed to start KiCAD MCP server: ${error}`);
throw error;
}
}
/**
* Stop the MCP server and clean up resources
*/
async stop(): Promise<void> {
logger.info('Stopping KiCAD MCP server...');
// Kill the Python process if it's running
if (this.pythonProcess) {
this.pythonProcess.kill();
this.pythonProcess = null;
}
logger.info('KiCAD MCP server stopped');
}
/**
* Call the KiCAD scripting interface to execute commands
*
* @param command The command to execute
* @param params The parameters for the command
* @returns The result of the command execution
*/
private async callKicadScript(command: string, params: any): Promise<any> {
return new Promise((resolve, reject) => {
// Check if Python process is running
if (!this.pythonProcess) {
logger.error('Python process is not running');
reject(new Error("Python process for KiCAD scripting is not running"));
return;
}
// Add request to queue
this.requestQueue.push({
request: { command, params },
resolve,
reject
});
// Process the queue if not already processing
if (!this.processingRequest) {
this.processNextRequest();
}
});
}
/**
* Process the next request in the queue
*/
private processNextRequest(): void {
// If no more requests or already processing, return
if (this.requestQueue.length === 0 || this.processingRequest) {
return;
}
// Set processing flag
this.processingRequest = true;
// Get the next request
const { request, resolve, reject } = this.requestQueue.shift()!;
try {
logger.debug(`Processing KiCAD command: ${request.command}`);
// Format the command and parameters as JSON
const requestStr = JSON.stringify(request);
// Set up response handling
let responseData = '';
// Clear any previous listeners
if (this.pythonProcess?.stdout) {
this.pythonProcess.stdout.removeAllListeners('data');
this.pythonProcess.stdout.removeAllListeners('end');
}
// Set up new listeners
if (this.pythonProcess?.stdout) {
this.pythonProcess.stdout.on('data', (data: Buffer) => {
const chunk = data.toString();
logger.debug(`Received data chunk: ${chunk.length} bytes`);
responseData += chunk;
// Check if we have a complete response
try {
// Try to parse the response as JSON
const result = JSON.parse(responseData);
// If we get here, we have a valid JSON response
logger.debug(`Completed KiCAD command: ${request.command} with result: ${result.success ? 'success' : 'failure'}`);
// Reset processing flag
this.processingRequest = false;
// Process next request if any
setTimeout(() => this.processNextRequest(), 0);
// Clear listeners
if (this.pythonProcess?.stdout) {
this.pythonProcess.stdout.removeAllListeners('data');
this.pythonProcess.stdout.removeAllListeners('end');
}
// Resolve the promise with the result
resolve(result);
} catch (e) {
// Not a complete JSON yet, keep collecting data
}
});
}
// Set a timeout
const timeout = setTimeout(() => {
logger.error(`Command timeout: ${request.command}`);
// Clear listeners
if (this.pythonProcess?.stdout) {
this.pythonProcess.stdout.removeAllListeners('data');
this.pythonProcess.stdout.removeAllListeners('end');
}
// Reset processing flag
this.processingRequest = false;
// Process next request
setTimeout(() => this.processNextRequest(), 0);
// Reject the promise
reject(new Error(`Command timeout: ${request.command}`));
}, 30000); // 30 seconds timeout
// Write the request to the Python process
logger.debug(`Sending request: ${requestStr}`);
this.pythonProcess?.stdin?.write(requestStr + '\n');
} catch (error) {
logger.error(`Error processing request: ${error}`);
// Reset processing flag
this.processingRequest = false;
// Process next request
setTimeout(() => this.processNextRequest(), 0);
// Reject the promise
reject(error);
}
}
}

345
src/tools/board.ts Normal file
View 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
View 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
View 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
View 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
View 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
View 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';