Skip to main content
Glama
kicad-server.ts18.3 kB
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);

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/mixelpixx/KiCAD-MCP-Server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server