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:
66
src/config.ts
Normal file
66
src/config.ts
Normal 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
119
src/index.ts
Normal 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
500
src/kicad-server.ts
Normal 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
121
src/logger.ts
Normal 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
231
src/prompts/component.ts
Normal 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
321
src/prompts/design.ts
Normal 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
9
src/prompts/index.ts
Normal 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
288
src/prompts/routing.ts
Normal 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
354
src/resources/board.ts
Normal 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
249
src/resources/component.ts
Normal 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
10
src/resources/index.ts
Normal 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
290
src/resources/library.ts
Normal 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
260
src/resources/project.ts
Normal 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
308
src/server.ts
Normal 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
345
src/tools/board.ts
Normal file
@@ -0,0 +1,345 @@
|
||||
/**
|
||||
* Board management tools for KiCAD MCP server
|
||||
*
|
||||
* These tools handle board setup, layer management, and board properties
|
||||
*/
|
||||
|
||||
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import { z } from 'zod';
|
||||
import { logger } from '../logger.js';
|
||||
|
||||
// Command function type for KiCAD script calls
|
||||
type CommandFunction = (command: string, params: Record<string, unknown>) => Promise<any>;
|
||||
|
||||
/**
|
||||
* Register board management tools with the MCP server
|
||||
*
|
||||
* @param server MCP server instance
|
||||
* @param callKicadScript Function to call KiCAD script commands
|
||||
*/
|
||||
export function registerBoardTools(server: McpServer, callKicadScript: CommandFunction): void {
|
||||
logger.info('Registering board management tools');
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Set Board Size Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"set_board_size",
|
||||
{
|
||||
width: z.number().describe("Board width"),
|
||||
height: z.number().describe("Board height"),
|
||||
unit: z.enum(["mm", "inch"]).describe("Unit of measurement")
|
||||
},
|
||||
async ({ width, height, unit }) => {
|
||||
logger.debug(`Setting board size to ${width}x${height} ${unit}`);
|
||||
const result = await callKicadScript("set_board_size", {
|
||||
width,
|
||||
height,
|
||||
unit
|
||||
});
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Add Layer Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"add_layer",
|
||||
{
|
||||
name: z.string().describe("Layer name"),
|
||||
type: z.enum([
|
||||
"copper", "technical", "user", "signal"
|
||||
]).describe("Layer type"),
|
||||
position: z.enum([
|
||||
"top", "bottom", "inner"
|
||||
]).describe("Layer position"),
|
||||
number: z.number().optional().describe("Layer number (for inner layers)")
|
||||
},
|
||||
async ({ name, type, position, number }) => {
|
||||
logger.debug(`Adding ${type} layer: ${name}`);
|
||||
const result = await callKicadScript("add_layer", {
|
||||
name,
|
||||
type,
|
||||
position,
|
||||
number
|
||||
});
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Set Active Layer Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"set_active_layer",
|
||||
{
|
||||
layer: z.string().describe("Layer name to set as active")
|
||||
},
|
||||
async ({ layer }) => {
|
||||
logger.debug(`Setting active layer to: ${layer}`);
|
||||
const result = await callKicadScript("set_active_layer", { layer });
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Get Board Info Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"get_board_info",
|
||||
{},
|
||||
async () => {
|
||||
logger.debug('Getting board information');
|
||||
const result = await callKicadScript("get_board_info", {});
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Get Layer List Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"get_layer_list",
|
||||
{},
|
||||
async () => {
|
||||
logger.debug('Getting layer list');
|
||||
const result = await callKicadScript("get_layer_list", {});
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Add Board Outline Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"add_board_outline",
|
||||
{
|
||||
shape: z.enum(["rectangle", "circle", "polygon"]).describe("Shape of the outline"),
|
||||
params: z.object({
|
||||
// For rectangle
|
||||
width: z.number().optional().describe("Width of rectangle"),
|
||||
height: z.number().optional().describe("Height of rectangle"),
|
||||
// For circle
|
||||
radius: z.number().optional().describe("Radius of circle"),
|
||||
// For polygon
|
||||
points: z.array(
|
||||
z.object({
|
||||
x: z.number().describe("X coordinate"),
|
||||
y: z.number().describe("Y coordinate")
|
||||
})
|
||||
).optional().describe("Points of polygon"),
|
||||
// Common parameters
|
||||
x: z.number().describe("X coordinate of center/origin"),
|
||||
y: z.number().describe("Y coordinate of center/origin"),
|
||||
unit: z.enum(["mm", "inch"]).describe("Unit of measurement")
|
||||
}).describe("Parameters for the outline shape")
|
||||
},
|
||||
async ({ shape, params }) => {
|
||||
logger.debug(`Adding ${shape} board outline`);
|
||||
const result = await callKicadScript("add_board_outline", {
|
||||
shape,
|
||||
params
|
||||
});
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Add Mounting Hole Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"add_mounting_hole",
|
||||
{
|
||||
position: z.object({
|
||||
x: z.number().describe("X coordinate"),
|
||||
y: z.number().describe("Y coordinate"),
|
||||
unit: z.enum(["mm", "inch"]).describe("Unit of measurement")
|
||||
}).describe("Position of the mounting hole"),
|
||||
diameter: z.number().describe("Diameter of the hole"),
|
||||
padDiameter: z.number().optional().describe("Optional diameter of the pad around the hole")
|
||||
},
|
||||
async ({ position, diameter, padDiameter }) => {
|
||||
logger.debug(`Adding mounting hole at (${position.x},${position.y}) ${position.unit}`);
|
||||
const result = await callKicadScript("add_mounting_hole", {
|
||||
position,
|
||||
diameter,
|
||||
padDiameter
|
||||
});
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Add Text Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"add_board_text",
|
||||
{
|
||||
text: z.string().describe("Text content"),
|
||||
position: z.object({
|
||||
x: z.number().describe("X coordinate"),
|
||||
y: z.number().describe("Y coordinate"),
|
||||
unit: z.enum(["mm", "inch"]).describe("Unit of measurement")
|
||||
}).describe("Position of the text"),
|
||||
layer: z.string().describe("Layer to place the text on"),
|
||||
size: z.number().describe("Text size"),
|
||||
thickness: z.number().optional().describe("Line thickness"),
|
||||
rotation: z.number().optional().describe("Rotation angle in degrees"),
|
||||
style: z.enum(["normal", "italic", "bold"]).optional().describe("Text style")
|
||||
},
|
||||
async ({ text, position, layer, size, thickness, rotation, style }) => {
|
||||
logger.debug(`Adding text "${text}" at (${position.x},${position.y}) ${position.unit}`);
|
||||
const result = await callKicadScript("add_board_text", {
|
||||
text,
|
||||
position,
|
||||
layer,
|
||||
size,
|
||||
thickness,
|
||||
rotation,
|
||||
style
|
||||
});
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Add Zone Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"add_zone",
|
||||
{
|
||||
layer: z.string().describe("Layer for the zone"),
|
||||
net: z.string().describe("Net name for the zone"),
|
||||
points: z.array(
|
||||
z.object({
|
||||
x: z.number().describe("X coordinate"),
|
||||
y: z.number().describe("Y coordinate")
|
||||
})
|
||||
).describe("Points defining the zone outline"),
|
||||
unit: z.enum(["mm", "inch"]).describe("Unit of measurement"),
|
||||
clearance: z.number().optional().describe("Clearance value"),
|
||||
minWidth: z.number().optional().describe("Minimum width"),
|
||||
padConnection: z.enum(["thermal", "solid", "none"]).optional().describe("Pad connection type")
|
||||
},
|
||||
async ({ layer, net, points, unit, clearance, minWidth, padConnection }) => {
|
||||
logger.debug(`Adding zone on layer ${layer} for net ${net}`);
|
||||
const result = await callKicadScript("add_zone", {
|
||||
layer,
|
||||
net,
|
||||
points,
|
||||
unit,
|
||||
clearance,
|
||||
minWidth,
|
||||
padConnection
|
||||
});
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Get Board Extents Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"get_board_extents",
|
||||
{
|
||||
unit: z.enum(["mm", "inch"]).optional().describe("Unit of measurement for the result")
|
||||
},
|
||||
async ({ unit }) => {
|
||||
logger.debug('Getting board extents');
|
||||
const result = await callKicadScript("get_board_extents", { unit });
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Get Board 2D View Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"get_board_2d_view",
|
||||
{
|
||||
layers: z.array(z.string()).optional().describe("Optional array of layer names to include"),
|
||||
width: z.number().optional().describe("Optional width of the image in pixels"),
|
||||
height: z.number().optional().describe("Optional height of the image in pixels"),
|
||||
format: z.enum(["png", "jpg", "svg"]).optional().describe("Image format")
|
||||
},
|
||||
async ({ layers, width, height, format }) => {
|
||||
logger.debug('Getting 2D board view');
|
||||
const result = await callKicadScript("get_board_2d_view", {
|
||||
layers,
|
||||
width,
|
||||
height,
|
||||
format
|
||||
});
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
logger.info('Board management tools registered');
|
||||
}
|
||||
291
src/tools/component.ts
Normal file
291
src/tools/component.ts
Normal file
@@ -0,0 +1,291 @@
|
||||
/**
|
||||
* Component management tools for KiCAD MCP server
|
||||
*/
|
||||
|
||||
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import { z } from 'zod';
|
||||
import { logger } from '../logger.js';
|
||||
|
||||
// Command function type for KiCAD script calls
|
||||
type CommandFunction = (command: string, params: Record<string, unknown>) => Promise<any>;
|
||||
|
||||
/**
|
||||
* Register component management tools with the MCP server
|
||||
*
|
||||
* @param server MCP server instance
|
||||
* @param callKicadScript Function to call KiCAD script commands
|
||||
*/
|
||||
export function registerComponentTools(server: McpServer, callKicadScript: CommandFunction): void {
|
||||
logger.info('Registering component management tools');
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Place Component Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"place_component",
|
||||
{
|
||||
componentId: z.string().describe("Identifier for the component to place (e.g., 'R_0603_10k')"),
|
||||
position: z.object({
|
||||
x: z.number().describe("X coordinate"),
|
||||
y: z.number().describe("Y coordinate"),
|
||||
unit: z.enum(["mm", "inch"]).describe("Unit of measurement")
|
||||
}).describe("Position coordinates and unit"),
|
||||
reference: z.string().optional().describe("Optional desired reference (e.g., 'R5')"),
|
||||
value: z.string().optional().describe("Optional component value (e.g., '10k')"),
|
||||
footprint: z.string().optional().describe("Optional specific footprint name"),
|
||||
rotation: z.number().optional().describe("Optional rotation in degrees"),
|
||||
layer: z.string().optional().describe("Optional layer (e.g., 'F.Cu', 'B.SilkS')")
|
||||
},
|
||||
async ({ componentId, position, reference, value, footprint, rotation, layer }) => {
|
||||
logger.debug(`Placing component: ${componentId} at ${position.x},${position.y} ${position.unit}`);
|
||||
const result = await callKicadScript("place_component", {
|
||||
componentId,
|
||||
position,
|
||||
reference,
|
||||
value,
|
||||
footprint,
|
||||
rotation,
|
||||
layer
|
||||
});
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Move Component Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"move_component",
|
||||
{
|
||||
reference: z.string().describe("Reference designator of the component (e.g., 'R5')"),
|
||||
position: z.object({
|
||||
x: z.number().describe("X coordinate"),
|
||||
y: z.number().describe("Y coordinate"),
|
||||
unit: z.enum(["mm", "inch"]).describe("Unit of measurement")
|
||||
}).describe("New position coordinates and unit"),
|
||||
rotation: z.number().optional().describe("Optional new rotation in degrees")
|
||||
},
|
||||
async ({ reference, position, rotation }) => {
|
||||
logger.debug(`Moving component: ${reference} to ${position.x},${position.y} ${position.unit}`);
|
||||
const result = await callKicadScript("move_component", {
|
||||
reference,
|
||||
position,
|
||||
rotation
|
||||
});
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Rotate Component Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"rotate_component",
|
||||
{
|
||||
reference: z.string().describe("Reference designator of the component (e.g., 'R5')"),
|
||||
angle: z.number().describe("Rotation angle in degrees (absolute, not relative)")
|
||||
},
|
||||
async ({ reference, angle }) => {
|
||||
logger.debug(`Rotating component: ${reference} to ${angle} degrees`);
|
||||
const result = await callKicadScript("rotate_component", {
|
||||
reference,
|
||||
angle
|
||||
});
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Delete Component Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"delete_component",
|
||||
{
|
||||
reference: z.string().describe("Reference designator of the component to delete (e.g., 'R5')")
|
||||
},
|
||||
async ({ reference }) => {
|
||||
logger.debug(`Deleting component: ${reference}`);
|
||||
const result = await callKicadScript("delete_component", { reference });
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Edit Component Properties Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"edit_component",
|
||||
{
|
||||
reference: z.string().describe("Reference designator of the component (e.g., 'R5')"),
|
||||
newReference: z.string().optional().describe("Optional new reference designator"),
|
||||
value: z.string().optional().describe("Optional new component value"),
|
||||
footprint: z.string().optional().describe("Optional new footprint")
|
||||
},
|
||||
async ({ reference, newReference, value, footprint }) => {
|
||||
logger.debug(`Editing component: ${reference}`);
|
||||
const result = await callKicadScript("edit_component", {
|
||||
reference,
|
||||
newReference,
|
||||
value,
|
||||
footprint
|
||||
});
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Find Component Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"find_component",
|
||||
{
|
||||
reference: z.string().optional().describe("Reference designator to search for"),
|
||||
value: z.string().optional().describe("Component value to search for")
|
||||
},
|
||||
async ({ reference, value }) => {
|
||||
logger.debug(`Finding component with ${reference ? `reference: ${reference}` : `value: ${value}`}`);
|
||||
const result = await callKicadScript("find_component", { reference, value });
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Get Component Properties Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"get_component_properties",
|
||||
{
|
||||
reference: z.string().describe("Reference designator of the component (e.g., 'R5')")
|
||||
},
|
||||
async ({ reference }) => {
|
||||
logger.debug(`Getting properties for component: ${reference}`);
|
||||
const result = await callKicadScript("get_component_properties", { reference });
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Add Component Annotation Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"add_component_annotation",
|
||||
{
|
||||
reference: z.string().describe("Reference designator of the component (e.g., 'R5')"),
|
||||
annotation: z.string().describe("Annotation or comment text to add"),
|
||||
visible: z.boolean().optional().describe("Whether the annotation should be visible on the PCB")
|
||||
},
|
||||
async ({ reference, annotation, visible }) => {
|
||||
logger.debug(`Adding annotation to component: ${reference}`);
|
||||
const result = await callKicadScript("add_component_annotation", {
|
||||
reference,
|
||||
annotation,
|
||||
visible
|
||||
});
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Group Components Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"group_components",
|
||||
{
|
||||
references: z.array(z.string()).describe("Reference designators of components to group"),
|
||||
groupName: z.string().describe("Name for the component group")
|
||||
},
|
||||
async ({ references, groupName }) => {
|
||||
logger.debug(`Grouping components: ${references.join(', ')} as ${groupName}`);
|
||||
const result = await callKicadScript("group_components", {
|
||||
references,
|
||||
groupName
|
||||
});
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Replace Component Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"replace_component",
|
||||
{
|
||||
reference: z.string().describe("Reference designator of the component to replace"),
|
||||
newComponentId: z.string().describe("ID of the new component to use"),
|
||||
newFootprint: z.string().optional().describe("Optional new footprint"),
|
||||
newValue: z.string().optional().describe("Optional new component value")
|
||||
},
|
||||
async ({ reference, newComponentId, newFootprint, newValue }) => {
|
||||
logger.debug(`Replacing component: ${reference} with ${newComponentId}`);
|
||||
const result = await callKicadScript("replace_component", {
|
||||
reference,
|
||||
newComponentId,
|
||||
newFootprint,
|
||||
newValue
|
||||
});
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
logger.info('Component management tools registered');
|
||||
}
|
||||
26
src/tools/component.txt
Normal file
26
src/tools/component.txt
Normal file
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Component management tools for KiCAD MCP server
|
||||
*/
|
||||
|
||||
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import { z } from 'zod';
|
||||
import { logger } from '../logger.js';
|
||||
|
||||
// Command function type for KiCAD script calls
|
||||
type CommandFunction = (command: string, params: any) => Promise<any>;
|
||||
|
||||
/**
|
||||
* Register component management tools with the MCP server
|
||||
*
|
||||
* @param server MCP server instance
|
||||
* @param callKicadScript Function to call KiCAD script commands
|
||||
*/
|
||||
export function registerComponentTools(server: McpServer, callKicadScript: CommandFunction): void {
|
||||
logger.info('Registering component management tools');
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Place Component Tool
|
||||
// ------------------------------------------------------
|
||||
server.registerTool({
|
||||
name: "place_component",
|
||||
description: "Places a component on the PCB at the specified location",
|
||||
261
src/tools/design-rules.ts
Normal file
261
src/tools/design-rules.ts
Normal file
@@ -0,0 +1,261 @@
|
||||
/**
|
||||
* Design rules tools for KiCAD MCP server
|
||||
*
|
||||
* These tools handle design rule checking and configuration
|
||||
*/
|
||||
|
||||
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import { z } from 'zod';
|
||||
import { logger } from '../logger.js';
|
||||
|
||||
// Command function type for KiCAD script calls
|
||||
type CommandFunction = (command: string, params: Record<string, unknown>) => Promise<any>;
|
||||
|
||||
/**
|
||||
* Register design rule tools with the MCP server
|
||||
*
|
||||
* @param server MCP server instance
|
||||
* @param callKicadScript Function to call KiCAD script commands
|
||||
*/
|
||||
export function registerDesignRuleTools(server: McpServer, callKicadScript: CommandFunction): void {
|
||||
logger.info('Registering design rule tools');
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Set Design Rules Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"set_design_rules",
|
||||
{
|
||||
clearance: z.number().optional().describe("Minimum clearance between copper items (mm)"),
|
||||
trackWidth: z.number().optional().describe("Default track width (mm)"),
|
||||
viaDiameter: z.number().optional().describe("Default via diameter (mm)"),
|
||||
viaDrill: z.number().optional().describe("Default via drill size (mm)"),
|
||||
microViaDiameter: z.number().optional().describe("Default micro via diameter (mm)"),
|
||||
microViaDrill: z.number().optional().describe("Default micro via drill size (mm)"),
|
||||
minTrackWidth: z.number().optional().describe("Minimum track width (mm)"),
|
||||
minViaDiameter: z.number().optional().describe("Minimum via diameter (mm)"),
|
||||
minViaDrill: z.number().optional().describe("Minimum via drill size (mm)"),
|
||||
minMicroViaDiameter: z.number().optional().describe("Minimum micro via diameter (mm)"),
|
||||
minMicroViaDrill: z.number().optional().describe("Minimum micro via drill size (mm)"),
|
||||
minHoleDiameter: z.number().optional().describe("Minimum hole diameter (mm)"),
|
||||
requireCourtyard: z.boolean().optional().describe("Whether to require courtyards for all footprints"),
|
||||
courtyardClearance: z.number().optional().describe("Minimum clearance between courtyards (mm)")
|
||||
},
|
||||
async (params) => {
|
||||
logger.debug('Setting design rules');
|
||||
const result = await callKicadScript("set_design_rules", params);
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Get Design Rules Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"get_design_rules",
|
||||
{},
|
||||
async () => {
|
||||
logger.debug('Getting design rules');
|
||||
const result = await callKicadScript("get_design_rules", {});
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Run DRC Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"run_drc",
|
||||
{
|
||||
reportPath: z.string().optional().describe("Optional path to save the DRC report")
|
||||
},
|
||||
async ({ reportPath }) => {
|
||||
logger.debug('Running DRC check');
|
||||
const result = await callKicadScript("run_drc", { reportPath });
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Add Net Class Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"add_net_class",
|
||||
{
|
||||
name: z.string().describe("Name of the net class"),
|
||||
description: z.string().optional().describe("Optional description of the net class"),
|
||||
clearance: z.number().describe("Clearance for this net class (mm)"),
|
||||
trackWidth: z.number().describe("Track width for this net class (mm)"),
|
||||
viaDiameter: z.number().describe("Via diameter for this net class (mm)"),
|
||||
viaDrill: z.number().describe("Via drill size for this net class (mm)"),
|
||||
uvia_diameter: z.number().optional().describe("Micro via diameter for this net class (mm)"),
|
||||
uvia_drill: z.number().optional().describe("Micro via drill size for this net class (mm)"),
|
||||
diff_pair_width: z.number().optional().describe("Differential pair width for this net class (mm)"),
|
||||
diff_pair_gap: z.number().optional().describe("Differential pair gap for this net class (mm)"),
|
||||
nets: z.array(z.string()).optional().describe("Array of net names to assign to this class")
|
||||
},
|
||||
async ({ name, description, clearance, trackWidth, viaDiameter, viaDrill, uvia_diameter, uvia_drill, diff_pair_width, diff_pair_gap, nets }) => {
|
||||
logger.debug(`Adding net class: ${name}`);
|
||||
const result = await callKicadScript("add_net_class", {
|
||||
name,
|
||||
description,
|
||||
clearance,
|
||||
trackWidth,
|
||||
viaDiameter,
|
||||
viaDrill,
|
||||
uvia_diameter,
|
||||
uvia_drill,
|
||||
diff_pair_width,
|
||||
diff_pair_gap,
|
||||
nets
|
||||
});
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Assign Net to Class Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"assign_net_to_class",
|
||||
{
|
||||
net: z.string().describe("Name of the net"),
|
||||
netClass: z.string().describe("Name of the net class")
|
||||
},
|
||||
async ({ net, netClass }) => {
|
||||
logger.debug(`Assigning net ${net} to class ${netClass}`);
|
||||
const result = await callKicadScript("assign_net_to_class", {
|
||||
net,
|
||||
netClass
|
||||
});
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Set Layer Constraints Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"set_layer_constraints",
|
||||
{
|
||||
layer: z.string().describe("Layer name (e.g., 'F.Cu')"),
|
||||
minTrackWidth: z.number().optional().describe("Minimum track width for this layer (mm)"),
|
||||
minClearance: z.number().optional().describe("Minimum clearance for this layer (mm)"),
|
||||
minViaDiameter: z.number().optional().describe("Minimum via diameter for this layer (mm)"),
|
||||
minViaDrill: z.number().optional().describe("Minimum via drill size for this layer (mm)")
|
||||
},
|
||||
async ({ layer, minTrackWidth, minClearance, minViaDiameter, minViaDrill }) => {
|
||||
logger.debug(`Setting constraints for layer: ${layer}`);
|
||||
const result = await callKicadScript("set_layer_constraints", {
|
||||
layer,
|
||||
minTrackWidth,
|
||||
minClearance,
|
||||
minViaDiameter,
|
||||
minViaDrill
|
||||
});
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Check Clearance Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"check_clearance",
|
||||
{
|
||||
item1: z.object({
|
||||
type: z.enum(["track", "via", "pad", "zone", "component"]).describe("Type of the first item"),
|
||||
id: z.string().optional().describe("ID of the first item (if applicable)"),
|
||||
reference: z.string().optional().describe("Reference designator (for component)"),
|
||||
position: z.object({
|
||||
x: z.number().optional(),
|
||||
y: z.number().optional(),
|
||||
unit: z.enum(["mm", "inch"]).optional()
|
||||
}).optional().describe("Position to check (if ID not provided)")
|
||||
}).describe("First item to check"),
|
||||
item2: z.object({
|
||||
type: z.enum(["track", "via", "pad", "zone", "component"]).describe("Type of the second item"),
|
||||
id: z.string().optional().describe("ID of the second item (if applicable)"),
|
||||
reference: z.string().optional().describe("Reference designator (for component)"),
|
||||
position: z.object({
|
||||
x: z.number().optional(),
|
||||
y: z.number().optional(),
|
||||
unit: z.enum(["mm", "inch"]).optional()
|
||||
}).optional().describe("Position to check (if ID not provided)")
|
||||
}).describe("Second item to check")
|
||||
},
|
||||
async ({ item1, item2 }) => {
|
||||
logger.debug(`Checking clearance between ${item1.type} and ${item2.type}`);
|
||||
const result = await callKicadScript("check_clearance", {
|
||||
item1,
|
||||
item2
|
||||
});
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Get DRC Violations Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"get_drc_violations",
|
||||
{
|
||||
severity: z.enum(["error", "warning", "all"]).optional().describe("Filter violations by severity")
|
||||
},
|
||||
async ({ severity }) => {
|
||||
logger.debug('Getting DRC violations');
|
||||
const result = await callKicadScript("get_drc_violations", { severity });
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
logger.info('Design rule tools registered');
|
||||
}
|
||||
260
src/tools/export.ts
Normal file
260
src/tools/export.ts
Normal file
@@ -0,0 +1,260 @@
|
||||
/**
|
||||
* Export tools for KiCAD MCP server
|
||||
*
|
||||
* These tools handle exporting PCB data to various formats
|
||||
*/
|
||||
|
||||
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import { z } from 'zod';
|
||||
import { logger } from '../logger.js';
|
||||
|
||||
// Command function type for KiCAD script calls
|
||||
type CommandFunction = (command: string, params: Record<string, unknown>) => Promise<any>;
|
||||
|
||||
/**
|
||||
* Register export tools with the MCP server
|
||||
*
|
||||
* @param server MCP server instance
|
||||
* @param callKicadScript Function to call KiCAD script commands
|
||||
*/
|
||||
export function registerExportTools(server: McpServer, callKicadScript: CommandFunction): void {
|
||||
logger.info('Registering export tools');
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Export Gerber Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"export_gerber",
|
||||
{
|
||||
outputDir: z.string().describe("Directory to save Gerber files"),
|
||||
layers: z.array(z.string()).optional().describe("Optional array of layer names to export (default: all)"),
|
||||
useProtelExtensions: z.boolean().optional().describe("Whether to use Protel filename extensions"),
|
||||
generateDrillFiles: z.boolean().optional().describe("Whether to generate drill files"),
|
||||
generateMapFile: z.boolean().optional().describe("Whether to generate a map file"),
|
||||
useAuxOrigin: z.boolean().optional().describe("Whether to use auxiliary axis as origin")
|
||||
},
|
||||
async ({ outputDir, layers, useProtelExtensions, generateDrillFiles, generateMapFile, useAuxOrigin }) => {
|
||||
logger.debug(`Exporting Gerber files to: ${outputDir}`);
|
||||
const result = await callKicadScript("export_gerber", {
|
||||
outputDir,
|
||||
layers,
|
||||
useProtelExtensions,
|
||||
generateDrillFiles,
|
||||
generateMapFile,
|
||||
useAuxOrigin
|
||||
});
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Export PDF Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"export_pdf",
|
||||
{
|
||||
outputPath: z.string().describe("Path to save the PDF file"),
|
||||
layers: z.array(z.string()).optional().describe("Optional array of layer names to include (default: all)"),
|
||||
blackAndWhite: z.boolean().optional().describe("Whether to export in black and white"),
|
||||
frameReference: z.boolean().optional().describe("Whether to include frame reference"),
|
||||
pageSize: z.enum(["A4", "A3", "A2", "A1", "A0", "Letter", "Legal", "Tabloid"]).optional().describe("Page size")
|
||||
},
|
||||
async ({ outputPath, layers, blackAndWhite, frameReference, pageSize }) => {
|
||||
logger.debug(`Exporting PDF to: ${outputPath}`);
|
||||
const result = await callKicadScript("export_pdf", {
|
||||
outputPath,
|
||||
layers,
|
||||
blackAndWhite,
|
||||
frameReference,
|
||||
pageSize
|
||||
});
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Export SVG Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"export_svg",
|
||||
{
|
||||
outputPath: z.string().describe("Path to save the SVG file"),
|
||||
layers: z.array(z.string()).optional().describe("Optional array of layer names to include (default: all)"),
|
||||
blackAndWhite: z.boolean().optional().describe("Whether to export in black and white"),
|
||||
includeComponents: z.boolean().optional().describe("Whether to include component outlines")
|
||||
},
|
||||
async ({ outputPath, layers, blackAndWhite, includeComponents }) => {
|
||||
logger.debug(`Exporting SVG to: ${outputPath}`);
|
||||
const result = await callKicadScript("export_svg", {
|
||||
outputPath,
|
||||
layers,
|
||||
blackAndWhite,
|
||||
includeComponents
|
||||
});
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Export 3D Model Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"export_3d",
|
||||
{
|
||||
outputPath: z.string().describe("Path to save the 3D model file"),
|
||||
format: z.enum(["STEP", "STL", "VRML", "OBJ"]).describe("3D model format"),
|
||||
includeComponents: z.boolean().optional().describe("Whether to include 3D component models"),
|
||||
includeCopper: z.boolean().optional().describe("Whether to include copper layers"),
|
||||
includeSolderMask: z.boolean().optional().describe("Whether to include solder mask"),
|
||||
includeSilkscreen: z.boolean().optional().describe("Whether to include silkscreen")
|
||||
},
|
||||
async ({ outputPath, format, includeComponents, includeCopper, includeSolderMask, includeSilkscreen }) => {
|
||||
logger.debug(`Exporting 3D model to: ${outputPath}`);
|
||||
const result = await callKicadScript("export_3d", {
|
||||
outputPath,
|
||||
format,
|
||||
includeComponents,
|
||||
includeCopper,
|
||||
includeSolderMask,
|
||||
includeSilkscreen
|
||||
});
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Export BOM Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"export_bom",
|
||||
{
|
||||
outputPath: z.string().describe("Path to save the BOM file"),
|
||||
format: z.enum(["CSV", "XML", "HTML", "JSON"]).describe("BOM file format"),
|
||||
groupByValue: z.boolean().optional().describe("Whether to group components by value"),
|
||||
includeAttributes: z.array(z.string()).optional().describe("Optional array of additional attributes to include")
|
||||
},
|
||||
async ({ outputPath, format, groupByValue, includeAttributes }) => {
|
||||
logger.debug(`Exporting BOM to: ${outputPath}`);
|
||||
const result = await callKicadScript("export_bom", {
|
||||
outputPath,
|
||||
format,
|
||||
groupByValue,
|
||||
includeAttributes
|
||||
});
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Export Netlist Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"export_netlist",
|
||||
{
|
||||
outputPath: z.string().describe("Path to save the netlist file"),
|
||||
format: z.enum(["KiCad", "Spice", "Cadstar", "OrcadPCB2"]).optional().describe("Netlist format (default: KiCad)")
|
||||
},
|
||||
async ({ outputPath, format }) => {
|
||||
logger.debug(`Exporting netlist to: ${outputPath}`);
|
||||
const result = await callKicadScript("export_netlist", {
|
||||
outputPath,
|
||||
format
|
||||
});
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Export Position File Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"export_position_file",
|
||||
{
|
||||
outputPath: z.string().describe("Path to save the position file"),
|
||||
format: z.enum(["CSV", "ASCII"]).optional().describe("File format (default: CSV)"),
|
||||
units: z.enum(["mm", "inch"]).optional().describe("Units to use (default: mm)"),
|
||||
side: z.enum(["top", "bottom", "both"]).optional().describe("Which board side to include (default: both)")
|
||||
},
|
||||
async ({ outputPath, format, units, side }) => {
|
||||
logger.debug(`Exporting position file to: ${outputPath}`);
|
||||
const result = await callKicadScript("export_position_file", {
|
||||
outputPath,
|
||||
format,
|
||||
units,
|
||||
side
|
||||
});
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Export VRML Tool
|
||||
// ------------------------------------------------------
|
||||
server.tool(
|
||||
"export_vrml",
|
||||
{
|
||||
outputPath: z.string().describe("Path to save the VRML file"),
|
||||
includeComponents: z.boolean().optional().describe("Whether to include 3D component models"),
|
||||
useRelativePaths: z.boolean().optional().describe("Whether to use relative paths for 3D models")
|
||||
},
|
||||
async ({ outputPath, includeComponents, useRelativePaths }) => {
|
||||
logger.debug(`Exporting VRML to: ${outputPath}`);
|
||||
const result = await callKicadScript("export_vrml", {
|
||||
outputPath,
|
||||
includeComponents,
|
||||
useRelativePaths
|
||||
});
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
logger.info('Export tools registered');
|
||||
}
|
||||
13
src/tools/index.ts
Normal file
13
src/tools/index.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Tools index for KiCAD MCP server
|
||||
*
|
||||
* Exports all tool registration functions
|
||||
*/
|
||||
|
||||
export { registerProjectTools } from './project.js';
|
||||
export { registerBoardTools } from './board.js';
|
||||
export { registerComponentTools } from './component.js';
|
||||
export { registerRoutingTools } from './routing.js';
|
||||
export { registerDesignRuleTools } from './design-rules.js';
|
||||
export { registerExportTools } from './export.js';
|
||||
export { registerSchematicTools } from './schematic.js';
|
||||
Reference in New Issue
Block a user