style: apply Prettier formatting to TS/JS/JSON/MD files
Add Prettier as a dev dependency with .prettierrc.json config and .prettierignore. Hook added via mirrors-prettier in pre-commit config. All TypeScript, JSON, Markdown, and YAML files auto-formatted. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,31 +2,31 @@
|
||||
* 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';
|
||||
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');
|
||||
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'),
|
||||
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()
|
||||
logLevel: z.enum(["error", "warn", "info", "debug"]).default("info"),
|
||||
logDir: z.string().optional(),
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -52,7 +52,7 @@ export async function loadConfig(configPath?: string): Promise<Config> {
|
||||
}
|
||||
|
||||
// Read and parse configuration
|
||||
const configData = await readFile(filePath, 'utf-8');
|
||||
const configData = await readFile(filePath, "utf-8");
|
||||
const config = JSON.parse(configData);
|
||||
|
||||
// Validate configuration
|
||||
|
||||
38
src/index.ts
38
src/index.ts
@@ -3,11 +3,11 @@
|
||||
* 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';
|
||||
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);
|
||||
@@ -26,13 +26,10 @@ async function main() {
|
||||
const config = await loadConfig(options.configPath);
|
||||
|
||||
// Path to the Python script that interfaces with KiCAD
|
||||
const kicadScriptPath = join(dirname(__dirname), 'python', 'kicad_interface.py');
|
||||
const kicadScriptPath = join(dirname(__dirname), "python", "kicad_interface.py");
|
||||
|
||||
// Create the server
|
||||
const server = new KiCADMcpServer(
|
||||
kicadScriptPath,
|
||||
config.logLevel
|
||||
);
|
||||
const server = new KiCADMcpServer(kicadScriptPath, config.logLevel);
|
||||
|
||||
// Start the server
|
||||
await server.start();
|
||||
@@ -40,8 +37,7 @@ async function main() {
|
||||
// Setup graceful shutdown
|
||||
setupGracefulShutdown(server);
|
||||
|
||||
logger.info('KiCAD MCP server started with STDIO transport');
|
||||
|
||||
logger.info("KiCAD MCP server started with STDIO transport");
|
||||
} catch (error) {
|
||||
logger.error(`Failed to start KiCAD MCP server: ${error}`);
|
||||
process.exit(1);
|
||||
@@ -55,7 +51,7 @@ function parseCommandLineArgs(args: string[]) {
|
||||
let configPath = undefined;
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
if (args[i] === '--config' && i + 1 < args.length) {
|
||||
if (args[i] === "--config" && i + 1 < args.length) {
|
||||
configPath = args[i + 1];
|
||||
i++;
|
||||
}
|
||||
@@ -69,24 +65,24 @@ function parseCommandLineArgs(args: string[]) {
|
||||
*/
|
||||
function setupGracefulShutdown(server: KiCADMcpServer) {
|
||||
// Handle termination signals
|
||||
process.on('SIGINT', async () => {
|
||||
logger.info('Received SIGINT signal. Shutting down...');
|
||||
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...');
|
||||
process.on("SIGTERM", async () => {
|
||||
logger.info("Received SIGTERM signal. Shutting down...");
|
||||
await shutdownServer(server);
|
||||
});
|
||||
|
||||
// Handle uncaught exceptions
|
||||
process.on('uncaughtException', async (error) => {
|
||||
process.on("uncaughtException", async (error) => {
|
||||
logger.error(`Uncaught exception: ${error}`);
|
||||
await shutdownServer(server);
|
||||
});
|
||||
|
||||
// Handle unhandled promise rejections
|
||||
process.on('unhandledRejection', async (reason) => {
|
||||
process.on("unhandledRejection", async (reason) => {
|
||||
logger.error(`Unhandled promise rejection: ${reason}`);
|
||||
await shutdownServer(server);
|
||||
});
|
||||
@@ -97,9 +93,9 @@ function setupGracefulShutdown(server: KiCADMcpServer) {
|
||||
*/
|
||||
async function shutdownServer(server: KiCADMcpServer) {
|
||||
try {
|
||||
logger.info('Shutting down KiCAD MCP server...');
|
||||
logger.info("Shutting down KiCAD MCP server...");
|
||||
await server.stop();
|
||||
logger.info('Server shutdown complete. Exiting...');
|
||||
logger.info("Server shutdown complete. Exiting...");
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
logger.error(`Error during shutdown: ${error}`);
|
||||
|
||||
1004
src/kicad-server.ts
1004
src/kicad-server.ts
File diff suppressed because it is too large
Load Diff
@@ -2,21 +2,21 @@
|
||||
* Logger for KiCAD MCP server
|
||||
*/
|
||||
|
||||
import { existsSync, mkdirSync, appendFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import * as os from 'os';
|
||||
import { existsSync, mkdirSync, appendFileSync } from "fs";
|
||||
import { join } from "path";
|
||||
import * as os from "os";
|
||||
|
||||
// Log levels
|
||||
type LogLevel = 'error' | 'warn' | 'info' | 'debug';
|
||||
type LogLevel = "error" | "warn" | "info" | "debug";
|
||||
|
||||
// Default log directory
|
||||
const DEFAULT_LOG_DIR = join(os.homedir(), '.kicad-mcp', 'logs');
|
||||
const DEFAULT_LOG_DIR = join(os.homedir(), ".kicad-mcp", "logs");
|
||||
|
||||
/**
|
||||
* Logger class for KiCAD MCP server
|
||||
*/
|
||||
class Logger {
|
||||
private logLevel: LogLevel = 'info';
|
||||
private logLevel: LogLevel = "info";
|
||||
private logDir: string = DEFAULT_LOG_DIR;
|
||||
|
||||
/**
|
||||
@@ -45,7 +45,7 @@ class Logger {
|
||||
* @param message Message to log
|
||||
*/
|
||||
error(message: string): void {
|
||||
this.log('error', message);
|
||||
this.log("error", message);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -53,8 +53,8 @@ class Logger {
|
||||
* @param message Message to log
|
||||
*/
|
||||
warn(message: string): void {
|
||||
if (['error', 'warn', 'info', 'debug'].includes(this.logLevel)) {
|
||||
this.log('warn', message);
|
||||
if (["error", "warn", "info", "debug"].includes(this.logLevel)) {
|
||||
this.log("warn", message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,8 +63,8 @@ class Logger {
|
||||
* @param message Message to log
|
||||
*/
|
||||
info(message: string): void {
|
||||
if (['info', 'debug'].includes(this.logLevel)) {
|
||||
this.log('info', message);
|
||||
if (["info", "debug"].includes(this.logLevel)) {
|
||||
this.log("info", message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,8 +73,8 @@ class Logger {
|
||||
* @param message Message to log
|
||||
*/
|
||||
debug(message: string): void {
|
||||
if (this.logLevel === 'debug') {
|
||||
this.log('debug', message);
|
||||
if (this.logLevel === "debug") {
|
||||
this.log("debug", message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,8 +85,9 @@ class Logger {
|
||||
*/
|
||||
private log(level: LogLevel, message: string): void {
|
||||
const now = new Date();
|
||||
const pad = (n: number, w = 2) => String(n).padStart(w, '0');
|
||||
const timestamp = `${now.getFullYear()}-${pad(now.getMonth()+1)}-${pad(now.getDate())} ` +
|
||||
const pad = (n: number, w = 2) => String(n).padStart(w, "0");
|
||||
const timestamp =
|
||||
`${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ` +
|
||||
`${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())},${pad(now.getMilliseconds(), 3)}`;
|
||||
const formattedMessage = `[${timestamp}] [${level.toUpperCase()}] ${message}`;
|
||||
|
||||
@@ -101,8 +102,8 @@ class Logger {
|
||||
mkdirSync(this.logDir, { recursive: true });
|
||||
}
|
||||
|
||||
const logFile = join(this.logDir, `kicad-mcp-${new Date().toISOString().split('T')[0]}.log`);
|
||||
appendFileSync(logFile, formattedMessage + '\n');
|
||||
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}`);
|
||||
}
|
||||
|
||||
@@ -1,231 +1,237 @@
|
||||
/**
|
||||
* 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');
|
||||
}
|
||||
/**
|
||||
* 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");
|
||||
}
|
||||
|
||||
@@ -1,321 +1,345 @@
|
||||
/**
|
||||
* 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');
|
||||
}
|
||||
/**
|
||||
* 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");
|
||||
}
|
||||
|
||||
@@ -23,10 +23,7 @@ export function registerFootprintPrompts(server: McpServer): void {
|
||||
.describe(
|
||||
"Component description, e.g. 'SOT-23 NPN transistor' or '2-pin JST XH 2.5mm connector'",
|
||||
),
|
||||
libraryPath: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("Target .pretty library path (optional)"),
|
||||
libraryPath: z.string().optional().describe("Target .pretty library path (optional)"),
|
||||
},
|
||||
() => ({
|
||||
messages: [
|
||||
@@ -107,9 +104,7 @@ Now create the footprint for: {{component}}`,
|
||||
server.prompt(
|
||||
"footprint_ipc_checklist",
|
||||
{
|
||||
footprintPath: z
|
||||
.string()
|
||||
.describe("Path to the .kicad_mod file to review"),
|
||||
footprintPath: z.string().describe("Path to the .kicad_mod file to review"),
|
||||
},
|
||||
() => ({
|
||||
messages: [
|
||||
|
||||
@@ -1,288 +1,308 @@
|
||||
/**
|
||||
* 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');
|
||||
}
|
||||
/**
|
||||
* 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");
|
||||
}
|
||||
|
||||
@@ -1,354 +1,372 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
* to the LLM, enabling better context-aware assistance.
|
||||
*/
|
||||
|
||||
import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import { logger } from '../logger.js';
|
||||
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>;
|
||||
@@ -17,43 +17,46 @@ type CommandFunction = (command: string, params: Record<string, unknown>) => Pro
|
||||
* @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');
|
||||
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", {});
|
||||
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: [{
|
||||
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
|
||||
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"
|
||||
}]
|
||||
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
|
||||
@@ -61,38 +64,42 @@ export function registerComponentResources(server: McpServer, callKicadScript: C
|
||||
server.resource(
|
||||
"component_details",
|
||||
new ResourceTemplate("kicad://component/{reference}/details", {
|
||||
list: undefined
|
||||
list: undefined,
|
||||
}),
|
||||
async (uri, params) => {
|
||||
const { reference } = params;
|
||||
logger.debug(`Retrieving details for component: ${reference}`);
|
||||
const result = await callKicadScript("get_component_properties", {
|
||||
reference
|
||||
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"
|
||||
}]
|
||||
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"
|
||||
}]
|
||||
contents: [
|
||||
{
|
||||
uri: uri.href,
|
||||
text: JSON.stringify(result),
|
||||
mimeType: "application/json",
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
@@ -101,109 +108,113 @@ export function registerComponentResources(server: McpServer, callKicadScript: C
|
||||
server.resource(
|
||||
"component_connections",
|
||||
new ResourceTemplate("kicad://component/{reference}/connections", {
|
||||
list: undefined
|
||||
list: undefined,
|
||||
}),
|
||||
async (uri, params) => {
|
||||
const { reference } = params;
|
||||
logger.debug(`Retrieving connections for component: ${reference}`);
|
||||
const result = await callKicadScript("get_component_connections", {
|
||||
reference
|
||||
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"
|
||||
}]
|
||||
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"
|
||||
}]
|
||||
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", {});
|
||||
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: [{
|
||||
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
|
||||
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"
|
||||
}]
|
||||
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", {});
|
||||
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: [{
|
||||
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
|
||||
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"
|
||||
}]
|
||||
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
|
||||
@@ -211,39 +222,43 @@ export function registerComponentResources(server: McpServer, callKicadScript: C
|
||||
server.resource(
|
||||
"component_visualization",
|
||||
new ResourceTemplate("kicad://component/{reference}/visualization", {
|
||||
list: undefined
|
||||
list: undefined,
|
||||
}),
|
||||
async (uri, params) => {
|
||||
const { reference } = params;
|
||||
logger.debug(`Generating visualization for component: ${reference}`);
|
||||
const result = await callKicadScript("get_component_visualization", {
|
||||
reference
|
||||
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"
|
||||
}]
|
||||
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"
|
||||
}]
|
||||
contents: [
|
||||
{
|
||||
uri: uri.href,
|
||||
blob: result.imageData, // Base64 encoded image data
|
||||
mimeType: "image/png",
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
logger.info('Component resources registered');
|
||||
logger.info("Component resources registered");
|
||||
}
|
||||
|
||||
@@ -1,10 +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';
|
||||
/**
|
||||
* 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";
|
||||
|
||||
@@ -1,290 +1,323 @@
|
||||
/**
|
||||
* 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"
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Library Component Details Resource
|
||||
// ------------------------------------------------------
|
||||
server.resource(
|
||||
"library_component_details",
|
||||
new ResourceTemplate("kicad://library/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');
|
||||
}
|
||||
/**
|
||||
* 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",
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Library Component Details Resource
|
||||
// ------------------------------------------------------
|
||||
server.resource(
|
||||
"library_component_details",
|
||||
new ResourceTemplate("kicad://library/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");
|
||||
}
|
||||
|
||||
@@ -1,260 +1,267 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
112
src/server.ts
112
src/server.ts
@@ -54,18 +54,8 @@ function findPythonExecutable(scriptPath: string): string {
|
||||
|
||||
// Check for virtual environment
|
||||
const venvPaths = [
|
||||
join(
|
||||
projectRoot,
|
||||
"venv",
|
||||
isWindows ? "Scripts" : "bin",
|
||||
isWindows ? "python.exe" : "python",
|
||||
),
|
||||
join(
|
||||
projectRoot,
|
||||
".venv",
|
||||
isWindows ? "Scripts" : "bin",
|
||||
isWindows ? "python.exe" : "python",
|
||||
),
|
||||
join(projectRoot, "venv", isWindows ? "Scripts" : "bin", isWindows ? "python.exe" : "python"),
|
||||
join(projectRoot, ".venv", isWindows ? "Scripts" : "bin", isWindows ? "python.exe" : "python"),
|
||||
];
|
||||
|
||||
for (const venvPath of venvPaths) {
|
||||
@@ -77,9 +67,7 @@ function findPythonExecutable(scriptPath: string): string {
|
||||
|
||||
// Allow override via KICAD_PYTHON environment variable (any platform)
|
||||
if (process.env.KICAD_PYTHON) {
|
||||
logger.info(
|
||||
`Using KICAD_PYTHON environment variable: ${process.env.KICAD_PYTHON}`,
|
||||
);
|
||||
logger.info(`Using KICAD_PYTHON environment variable: ${process.env.KICAD_PYTHON}`);
|
||||
return process.env.KICAD_PYTHON;
|
||||
}
|
||||
|
||||
@@ -123,9 +111,7 @@ function findPythonExecutable(scriptPath: string): string {
|
||||
|
||||
for (const path of homebrewPaths) {
|
||||
if (existsSync(path)) {
|
||||
logger.info(
|
||||
`Found Homebrew Python at: ${path} (ensure pcbnew is importable)`,
|
||||
);
|
||||
logger.info(`Found Homebrew Python at: ${path} (ensure pcbnew is importable)`);
|
||||
return path;
|
||||
}
|
||||
}
|
||||
@@ -196,19 +182,14 @@ export class KiCADMcpServer {
|
||||
* @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",
|
||||
) {
|
||||
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}`,
|
||||
);
|
||||
throw new Error(`KiCAD interface script not found: ${this.kicadScriptPath}`);
|
||||
}
|
||||
|
||||
// Initialize the MCP server
|
||||
@@ -267,9 +248,7 @@ export class KiCADMcpServer {
|
||||
registerFootprintPrompts(this.server);
|
||||
|
||||
logger.info("All KiCAD tools, resources, and prompts registered");
|
||||
logger.info(
|
||||
"Router pattern enabled: 4 router tools + direct tools for discovery",
|
||||
);
|
||||
logger.info("Router pattern enabled: 4 router tools + direct tools for discovery");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -277,15 +256,12 @@ export class KiCADMcpServer {
|
||||
*/
|
||||
private async validatePrerequisites(pythonExe: string): Promise<boolean> {
|
||||
const isWindows = process.platform === "win32";
|
||||
const isLinux =
|
||||
process.platform !== "win32" && process.platform !== "darwin";
|
||||
const isLinux = process.platform !== "win32" && process.platform !== "darwin";
|
||||
const errors: string[] = [];
|
||||
|
||||
// Check if Python executable exists (for absolute paths) or is executable (for commands)
|
||||
const isAbsolutePath =
|
||||
pythonExe.startsWith("/") ||
|
||||
pythonExe.startsWith("C:") ||
|
||||
pythonExe.startsWith("\\");
|
||||
pythonExe.startsWith("/") || pythonExe.startsWith("C:") || pythonExe.startsWith("\\");
|
||||
|
||||
if (isAbsolutePath) {
|
||||
// Absolute path: use existsSync
|
||||
@@ -293,16 +269,10 @@ export class KiCADMcpServer {
|
||||
errors.push(`Python executable not found: ${pythonExe}`);
|
||||
|
||||
if (isWindows) {
|
||||
errors.push(
|
||||
"Windows: Install KiCAD 9.0+ from https://www.kicad.org/download/windows/",
|
||||
);
|
||||
errors.push(
|
||||
"Or run: .\\setup-windows.ps1 for automatic configuration",
|
||||
);
|
||||
errors.push("Windows: Install KiCAD 9.0+ from https://www.kicad.org/download/windows/");
|
||||
errors.push("Or run: .\\setup-windows.ps1 for automatic configuration");
|
||||
} else if (isLinux) {
|
||||
errors.push(
|
||||
"Linux: Install KiCAD 9.0+ or set KICAD_PYTHON environment variable",
|
||||
);
|
||||
errors.push("Linux: Install KiCAD 9.0+ or set KICAD_PYTHON environment variable");
|
||||
errors.push("Set KICAD_PYTHON to specify a custom Python path");
|
||||
}
|
||||
}
|
||||
@@ -334,20 +304,14 @@ export class KiCADMcpServer {
|
||||
} catch (error: any) {
|
||||
errors.push(`Python executable not found in PATH: ${pythonExe}`);
|
||||
errors.push(`Error: ${error.message}`);
|
||||
errors.push(
|
||||
"Set KICAD_PYTHON environment variable to specify full path",
|
||||
);
|
||||
errors.push("Set KICAD_PYTHON environment variable to specify full path");
|
||||
|
||||
if (isLinux) {
|
||||
errors.push("");
|
||||
errors.push("Linux troubleshooting:");
|
||||
errors.push("1. Check if python3 is installed: which python3");
|
||||
errors.push(
|
||||
"2. Install KiCAD: sudo apt install kicad (Ubuntu/Debian)",
|
||||
);
|
||||
errors.push(
|
||||
"3. Set KICAD_PYTHON=/usr/bin/python3 in your MCP config",
|
||||
);
|
||||
errors.push("2. Install KiCAD: sudo apt install kicad (Ubuntu/Debian)");
|
||||
errors.push("3. Set KICAD_PYTHON=/usr/bin/python3 in your MCP config");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -358,11 +322,7 @@ export class KiCADMcpServer {
|
||||
}
|
||||
|
||||
// Check if dist/index.js exists (if running from compiled code)
|
||||
const distPath = join(
|
||||
dirname(dirname(this.kicadScriptPath)),
|
||||
"dist",
|
||||
"index.js",
|
||||
);
|
||||
const distPath = join(dirname(dirname(this.kicadScriptPath)), "dist", "index.js");
|
||||
if (!existsSync(distPath)) {
|
||||
errors.push("Project not built. Run: npm run build");
|
||||
}
|
||||
@@ -458,9 +418,7 @@ export class KiCADMcpServer {
|
||||
logger.info("Starting KiCAD MCP server...");
|
||||
|
||||
// Start the Python process for KiCAD scripting
|
||||
logger.info(
|
||||
`Starting Python process with script: ${this.kicadScriptPath}`,
|
||||
);
|
||||
logger.info(`Starting Python process with script: ${this.kicadScriptPath}`);
|
||||
const pythonExe = findPythonExecutable(this.kicadScriptPath);
|
||||
|
||||
logger.info(`Using Python executable: ${pythonExe}`);
|
||||
@@ -468,25 +426,20 @@ export class KiCADMcpServer {
|
||||
// Validate prerequisites
|
||||
const isValid = await this.validatePrerequisites(pythonExe);
|
||||
if (!isValid) {
|
||||
throw new Error(
|
||||
"Prerequisites validation failed. See logs above for details.",
|
||||
);
|
||||
throw new Error("Prerequisites validation failed. See logs above for details.");
|
||||
}
|
||||
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",
|
||||
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}`,
|
||||
);
|
||||
logger.warn(`Python process exited with code ${code} and signal ${signal}`);
|
||||
this.pythonProcess = null;
|
||||
});
|
||||
|
||||
@@ -563,17 +516,10 @@ export class KiCADMcpServer {
|
||||
// Determine timeout based on command type
|
||||
// DRC and export operations need longer timeouts for large boards
|
||||
let commandTimeout = 30000; // Default 30 seconds
|
||||
const longRunningCommands = [
|
||||
"run_drc",
|
||||
"export_gerber",
|
||||
"export_pdf",
|
||||
"export_3d",
|
||||
];
|
||||
const longRunningCommands = ["run_drc", "export_gerber", "export_pdf", "export_3d"];
|
||||
if (longRunningCommands.includes(command)) {
|
||||
commandTimeout = 600000; // 10 minutes for long operations
|
||||
logger.info(
|
||||
`Using extended timeout (${commandTimeout / 1000}s) for command: ${command}`,
|
||||
);
|
||||
logger.info(`Using extended timeout (${commandTimeout / 1000}s) for command: ${command}`);
|
||||
}
|
||||
|
||||
// Add request to queue with timeout info
|
||||
@@ -678,12 +624,8 @@ export class KiCADMcpServer {
|
||||
// Set a timeout (use command-specific timeout or default)
|
||||
const timeoutDuration = request.timeout || 30000;
|
||||
const timeoutHandle = setTimeout(() => {
|
||||
logger.error(
|
||||
`Command timeout after ${timeoutDuration / 1000}s: ${request.command}`,
|
||||
);
|
||||
logger.error(
|
||||
`Buffer contents: ${this.responseBuffer.substring(0, 200)}...`,
|
||||
);
|
||||
logger.error(`Command timeout after ${timeoutDuration / 1000}s: ${request.command}`);
|
||||
logger.error(`Buffer contents: ${this.responseBuffer.substring(0, 200)}...`);
|
||||
|
||||
// Clear state
|
||||
this.responseBuffer = "";
|
||||
@@ -691,11 +633,7 @@ export class KiCADMcpServer {
|
||||
this.processingRequest = false;
|
||||
|
||||
// Reject the promise
|
||||
reject(
|
||||
new Error(
|
||||
`Command timeout after ${timeoutDuration / 1000}s: ${request.command}`,
|
||||
),
|
||||
);
|
||||
reject(new Error(`Command timeout after ${timeoutDuration / 1000}s: ${request.command}`));
|
||||
|
||||
// Process next request
|
||||
setTimeout(() => this.processNextRequest(), 0);
|
||||
|
||||
@@ -1,384 +1,431 @@
|
||||
/**
|
||||
* 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", "rounded_rectangle"]).describe("Shape of the outline"),
|
||||
params: z.object({
|
||||
// For rectangle / rounded_rectangle
|
||||
width: z.number().optional().describe("Width of rectangle"),
|
||||
height: z.number().optional().describe("Height of rectangle"),
|
||||
cornerRadius: z.number().optional().describe("Corner radius for rounded_rectangle (mm)"),
|
||||
// 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"),
|
||||
// Position: top-left corner for rectangles/rounded_rectangle, center for circle
|
||||
x: z.number().describe("X coordinate of top-left corner for rectangles (default: 0)"),
|
||||
y: z.number().describe("Y coordinate of top-left corner for rectangles (default: 0)"),
|
||||
unit: z.enum(["mm", "inch"]).describe("Unit of measurement")
|
||||
}).describe("Parameters for the outline shape")
|
||||
},
|
||||
async ({ shape, params }) => {
|
||||
logger.debug(`Adding ${shape} board outline`);
|
||||
// Pass x/y as-is to Python; outline.py treats them as top-left corner
|
||||
// and computes the center internally (center = x + width/2, y + height/2).
|
||||
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');
|
||||
|
||||
// Import SVG logo onto PCB layer (silkscreen)
|
||||
server.tool(
|
||||
"import_svg_logo",
|
||||
"Imports an SVG file as filled graphic polygons onto a KiCAD PCB layer (default F.SilkS / front silkscreen). Curves are linearised automatically. Ideal for placing a company or project logo on the board.",
|
||||
{
|
||||
pcbPath: z.string().describe("Path to the .kicad_pcb file"),
|
||||
svgPath: z.string().describe("Path to the SVG logo file"),
|
||||
x: z.number().describe("X position of the logo top-left corner in mm"),
|
||||
y: z.number().describe("Y position of the logo top-left corner in mm"),
|
||||
width: z.number().describe("Target width of the logo in mm (height is scaled to preserve aspect ratio)"),
|
||||
layer: z.string().optional().describe("PCB layer name, e.g. F.SilkS or B.SilkS (default: F.SilkS)"),
|
||||
strokeWidth: z.number().optional().describe("Outline stroke width in mm (0 = no outline, default 0)"),
|
||||
filled: z.boolean().optional().describe("Fill polygons with solid colour (default true)"),
|
||||
},
|
||||
async (args: { pcbPath: string; svgPath: string; x: number; y: number; width: number; layer?: string; strokeWidth?: number; filled?: boolean }) => {
|
||||
const result = await callKicadScript("import_svg_logo", args);
|
||||
if (result.success) {
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: [
|
||||
result.message,
|
||||
`Polygons: ${result.polygon_count}`,
|
||||
`Size: ${result.logo_width_mm?.toFixed(2)} × ${result.logo_height_mm?.toFixed(2)} mm`,
|
||||
`Layer: ${result.layer}`,
|
||||
].join("\n"),
|
||||
}],
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
content: [{ type: "text", text: `SVG import failed: ${result.message || "Unknown error"}` }],
|
||||
};
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
/**
|
||||
* 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", "rounded_rectangle"])
|
||||
.describe("Shape of the outline"),
|
||||
params: z
|
||||
.object({
|
||||
// For rectangle / rounded_rectangle
|
||||
width: z.number().optional().describe("Width of rectangle"),
|
||||
height: z.number().optional().describe("Height of rectangle"),
|
||||
cornerRadius: z.number().optional().describe("Corner radius for rounded_rectangle (mm)"),
|
||||
// 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"),
|
||||
// Position: top-left corner for rectangles/rounded_rectangle, center for circle
|
||||
x: z.number().describe("X coordinate of top-left corner for rectangles (default: 0)"),
|
||||
y: z.number().describe("Y coordinate of top-left corner for rectangles (default: 0)"),
|
||||
unit: z.enum(["mm", "inch"]).describe("Unit of measurement"),
|
||||
})
|
||||
.describe("Parameters for the outline shape"),
|
||||
},
|
||||
async ({ shape, params }) => {
|
||||
logger.debug(`Adding ${shape} board outline`);
|
||||
// Pass x/y as-is to Python; outline.py treats them as top-left corner
|
||||
// and computes the center internally (center = x + width/2, y + height/2).
|
||||
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");
|
||||
|
||||
// Import SVG logo onto PCB layer (silkscreen)
|
||||
server.tool(
|
||||
"import_svg_logo",
|
||||
"Imports an SVG file as filled graphic polygons onto a KiCAD PCB layer (default F.SilkS / front silkscreen). Curves are linearised automatically. Ideal for placing a company or project logo on the board.",
|
||||
{
|
||||
pcbPath: z.string().describe("Path to the .kicad_pcb file"),
|
||||
svgPath: z.string().describe("Path to the SVG logo file"),
|
||||
x: z.number().describe("X position of the logo top-left corner in mm"),
|
||||
y: z.number().describe("Y position of the logo top-left corner in mm"),
|
||||
width: z
|
||||
.number()
|
||||
.describe("Target width of the logo in mm (height is scaled to preserve aspect ratio)"),
|
||||
layer: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("PCB layer name, e.g. F.SilkS or B.SilkS (default: F.SilkS)"),
|
||||
strokeWidth: z
|
||||
.number()
|
||||
.optional()
|
||||
.describe("Outline stroke width in mm (0 = no outline, default 0)"),
|
||||
filled: z.boolean().optional().describe("Fill polygons with solid colour (default true)"),
|
||||
},
|
||||
async (args: {
|
||||
pcbPath: string;
|
||||
svgPath: string;
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
layer?: string;
|
||||
strokeWidth?: number;
|
||||
filled?: boolean;
|
||||
}) => {
|
||||
const result = await callKicadScript("import_svg_logo", args);
|
||||
if (result.success) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: [
|
||||
result.message,
|
||||
`Polygons: ${result.polygon_count}`,
|
||||
`Size: ${result.logo_width_mm?.toFixed(2)} × ${result.logo_height_mm?.toFixed(2)} mm`,
|
||||
`Layer: ${result.layer}`,
|
||||
].join("\n"),
|
||||
},
|
||||
],
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
content: [
|
||||
{ type: "text", text: `SVG import failed: ${result.message || "Unknown error"}` },
|
||||
],
|
||||
};
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,10 +7,7 @@ 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>;
|
||||
type CommandFunction = (command: string, params: Record<string, unknown>) => Promise<any>;
|
||||
|
||||
/**
|
||||
* Register component management tools with the MCP server
|
||||
@@ -18,10 +15,7 @@ type CommandFunction = (
|
||||
* @param server MCP server instance
|
||||
* @param callKicadScript Function to call KiCAD script commands
|
||||
*/
|
||||
export function registerComponentTools(
|
||||
server: McpServer,
|
||||
callKicadScript: CommandFunction,
|
||||
): void {
|
||||
export function registerComponentTools(server: McpServer, callKicadScript: CommandFunction): void {
|
||||
logger.info("Registering component management tools");
|
||||
|
||||
// ------------------------------------------------------
|
||||
@@ -40,38 +34,19 @@ export function registerComponentTools(
|
||||
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"),
|
||||
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')"),
|
||||
layer: z.string().optional().describe("Optional layer (e.g., 'F.Cu', 'B.SilkS')"),
|
||||
boardPath: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("Path to the .kicad_pcb file – required when using project-local footprint libraries"),
|
||||
.describe(
|
||||
"Path to the .kicad_pcb file – required when using project-local footprint libraries",
|
||||
),
|
||||
},
|
||||
async ({
|
||||
componentId,
|
||||
position,
|
||||
reference,
|
||||
value,
|
||||
footprint,
|
||||
rotation,
|
||||
layer,
|
||||
boardPath,
|
||||
}) => {
|
||||
async ({ componentId, position, reference, value, footprint, rotation, layer, boardPath }) => {
|
||||
logger.debug(
|
||||
`Placing component: ${componentId} at ${position.x},${position.y} ${position.unit}`,
|
||||
);
|
||||
@@ -103,9 +78,7 @@ export function registerComponentTools(
|
||||
server.tool(
|
||||
"move_component",
|
||||
{
|
||||
reference: z
|
||||
.string()
|
||||
.describe("Reference designator of the component (e.g., 'R5')"),
|
||||
reference: z.string().describe("Reference designator of the component (e.g., 'R5')"),
|
||||
position: z
|
||||
.object({
|
||||
x: z.number().describe("X coordinate"),
|
||||
@@ -113,10 +86,7 @@ export function registerComponentTools(
|
||||
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"),
|
||||
rotation: z.number().optional().describe("Optional new rotation in degrees"),
|
||||
layer: z
|
||||
.string()
|
||||
.optional()
|
||||
@@ -150,12 +120,8 @@ export function registerComponentTools(
|
||||
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)"),
|
||||
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`);
|
||||
@@ -183,9 +149,7 @@ export function registerComponentTools(
|
||||
{
|
||||
reference: z
|
||||
.string()
|
||||
.describe(
|
||||
"Reference designator of the component to delete (e.g., 'R5')",
|
||||
),
|
||||
.describe("Reference designator of the component to delete (e.g., 'R5')"),
|
||||
},
|
||||
async ({ reference }) => {
|
||||
logger.debug(`Deleting component: ${reference}`);
|
||||
@@ -208,13 +172,8 @@ export function registerComponentTools(
|
||||
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"),
|
||||
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"),
|
||||
},
|
||||
@@ -244,10 +203,7 @@ export function registerComponentTools(
|
||||
server.tool(
|
||||
"find_component",
|
||||
{
|
||||
reference: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("Reference designator to search for"),
|
||||
reference: z.string().optional().describe("Reference designator to search for"),
|
||||
value: z.string().optional().describe("Component value to search for"),
|
||||
},
|
||||
async ({ reference, value }) => {
|
||||
@@ -276,9 +232,7 @@ export function registerComponentTools(
|
||||
server.tool(
|
||||
"get_component_properties",
|
||||
{
|
||||
reference: z
|
||||
.string()
|
||||
.describe("Reference designator of the component (e.g., 'R5')"),
|
||||
reference: z.string().describe("Reference designator of the component (e.g., 'R5')"),
|
||||
},
|
||||
async ({ reference }) => {
|
||||
logger.debug(`Getting properties for component: ${reference}`);
|
||||
@@ -303,9 +257,7 @@ export function registerComponentTools(
|
||||
server.tool(
|
||||
"add_component_annotation",
|
||||
{
|
||||
reference: z
|
||||
.string()
|
||||
.describe("Reference designator of the component (e.g., 'R5')"),
|
||||
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()
|
||||
@@ -337,15 +289,11 @@ export function registerComponentTools(
|
||||
server.tool(
|
||||
"group_components",
|
||||
{
|
||||
references: z
|
||||
.array(z.string())
|
||||
.describe("Reference designators of components to group"),
|
||||
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}`,
|
||||
);
|
||||
logger.debug(`Grouping components: ${references.join(", ")} as ${groupName}`);
|
||||
const result = await callKicadScript("group_components", {
|
||||
references,
|
||||
groupName,
|
||||
@@ -368,9 +316,7 @@ export function registerComponentTools(
|
||||
server.tool(
|
||||
"replace_component",
|
||||
{
|
||||
reference: z
|
||||
.string()
|
||||
.describe("Reference designator of the component to replace"),
|
||||
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"),
|
||||
@@ -401,13 +347,8 @@ export function registerComponentTools(
|
||||
server.tool(
|
||||
"get_component_pads",
|
||||
{
|
||||
reference: z
|
||||
.string()
|
||||
.describe("Reference designator of the component (e.g., 'U1')"),
|
||||
unit: z
|
||||
.enum(["mm", "inch"])
|
||||
.optional()
|
||||
.describe("Unit for coordinates (default: mm)"),
|
||||
reference: z.string().describe("Reference designator of the component (e.g., 'U1')"),
|
||||
unit: z.enum(["mm", "inch"]).optional().describe("Unit for coordinates (default: mm)"),
|
||||
},
|
||||
async ({ reference, unit }) => {
|
||||
logger.debug(`Getting pads for component: ${reference}`);
|
||||
@@ -433,10 +374,7 @@ export function registerComponentTools(
|
||||
server.tool(
|
||||
"get_component_list",
|
||||
{
|
||||
layer: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("Filter by layer (e.g., 'F.Cu', 'B.Cu')"),
|
||||
layer: z.string().optional().describe("Filter by layer (e.g., 'F.Cu', 'B.Cu')"),
|
||||
boundingBox: z
|
||||
.object({
|
||||
x1: z.number(),
|
||||
@@ -447,10 +385,7 @@ export function registerComponentTools(
|
||||
})
|
||||
.optional()
|
||||
.describe("Filter by bounding box region"),
|
||||
unit: z
|
||||
.enum(["mm", "inch"])
|
||||
.optional()
|
||||
.describe("Unit for coordinates (default: mm)"),
|
||||
unit: z.enum(["mm", "inch"]).optional().describe("Unit for coordinates (default: mm)"),
|
||||
},
|
||||
async ({ layer, boundingBox, unit }) => {
|
||||
logger.debug("Getting component list");
|
||||
@@ -477,14 +412,9 @@ export function registerComponentTools(
|
||||
server.tool(
|
||||
"get_pad_position",
|
||||
{
|
||||
reference: z
|
||||
.string()
|
||||
.describe("Component reference designator (e.g., 'U1')"),
|
||||
reference: z.string().describe("Component reference designator (e.g., 'U1')"),
|
||||
pad: z.string().describe("Pad number or name (e.g., '1', 'A1')"),
|
||||
unit: z
|
||||
.enum(["mm", "inch"])
|
||||
.optional()
|
||||
.describe("Unit for coordinates (default: mm)"),
|
||||
unit: z.enum(["mm", "inch"]).optional().describe("Unit for coordinates (default: mm)"),
|
||||
},
|
||||
async ({ reference, pad, unit }) => {
|
||||
logger.debug(`Getting pad position for ${reference} pad ${pad}`);
|
||||
@@ -523,10 +453,7 @@ export function registerComponentTools(
|
||||
columns: z.number().describe("Number of columns"),
|
||||
rowSpacing: z.number().describe("Spacing between rows"),
|
||||
columnSpacing: z.number().describe("Spacing between columns"),
|
||||
startReference: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("Starting reference (e.g., 'R1')"),
|
||||
startReference: z.string().optional().describe("Starting reference (e.g., 'R1')"),
|
||||
footprint: z.string().optional().describe("Footprint name"),
|
||||
value: z.string().optional().describe("Component value"),
|
||||
rotation: z.number().optional().describe("Rotation in degrees"),
|
||||
@@ -543,9 +470,7 @@ export function registerComponentTools(
|
||||
value,
|
||||
rotation,
|
||||
}) => {
|
||||
logger.debug(
|
||||
`Placing component array: ${rows}x${columns} of ${componentId}`,
|
||||
);
|
||||
logger.debug(`Placing component array: ${rows}x${columns} of ${componentId}`);
|
||||
const result = await callKicadScript("place_component_array", {
|
||||
componentId,
|
||||
startPosition,
|
||||
@@ -576,20 +501,10 @@ export function registerComponentTools(
|
||||
server.tool(
|
||||
"align_components",
|
||||
{
|
||||
references: z
|
||||
.array(z.string())
|
||||
.describe("Array of component references to align"),
|
||||
alignmentType: z
|
||||
.enum(["horizontal", "vertical", "grid"])
|
||||
.describe("Type of alignment"),
|
||||
spacing: z
|
||||
.number()
|
||||
.optional()
|
||||
.describe("Spacing between components in mm"),
|
||||
referenceComponent: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("Reference component for alignment"),
|
||||
references: z.array(z.string()).describe("Array of component references to align"),
|
||||
alignmentType: z.enum(["horizontal", "vertical", "grid"]).describe("Type of alignment"),
|
||||
spacing: z.number().optional().describe("Spacing between components in mm"),
|
||||
referenceComponent: z.string().optional().describe("Reference component for alignment"),
|
||||
},
|
||||
async ({ references, alignmentType, spacing, referenceComponent }) => {
|
||||
logger.debug(`Aligning components: ${references.join(", ")}`);
|
||||
@@ -626,10 +541,7 @@ export function registerComponentTools(
|
||||
})
|
||||
.describe("Offset from original position"),
|
||||
newReference: z.string().optional().describe("New reference designator"),
|
||||
count: z
|
||||
.number()
|
||||
.optional()
|
||||
.describe("Number of duplicates (default: 1)"),
|
||||
count: z.number().optional().describe("Number of duplicates (default: 1)"),
|
||||
},
|
||||
async ({ reference, offset, newReference, count }) => {
|
||||
logger.debug(`Duplicating component: ${reference}`);
|
||||
|
||||
@@ -8,10 +8,7 @@
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { z } from "zod";
|
||||
|
||||
export function registerDatasheetTools(
|
||||
server: McpServer,
|
||||
callKicadScript: Function,
|
||||
) {
|
||||
export function registerDatasheetTools(server: McpServer, callKicadScript: Function) {
|
||||
// ── enrich_datasheets ──────────────────────────────────────────────────────
|
||||
server.tool(
|
||||
"enrich_datasheets",
|
||||
@@ -30,16 +27,12 @@ No API key or internet lookup required – the URL is constructed directly.
|
||||
|
||||
Use dry_run=true to preview changes without writing.`,
|
||||
{
|
||||
schematic_path: z
|
||||
.string()
|
||||
.describe("Path to the .kicad_sch file to enrich"),
|
||||
schematic_path: z.string().describe("Path to the .kicad_sch file to enrich"),
|
||||
dry_run: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.default(false)
|
||||
.describe(
|
||||
"If true, show what would be changed without writing to disk (default: false)",
|
||||
),
|
||||
.describe("If true, show what would be changed without writing to disk (default: false)"),
|
||||
},
|
||||
async (args: { schematic_path: string; dry_run?: boolean }) => {
|
||||
const result = await callKicadScript("enrich_datasheets", args);
|
||||
@@ -65,9 +58,7 @@ Use dry_run=true to preview changes without writing.`,
|
||||
}
|
||||
|
||||
if (result.updated === 0 && !args.dry_run) {
|
||||
lines.push(
|
||||
"\nNo changes needed – all LCSC components already have a Datasheet URL.",
|
||||
);
|
||||
lines.push("\nNo changes needed – all LCSC components already have a Datasheet URL.");
|
||||
}
|
||||
|
||||
return { content: [{ type: "text", text: lines.join("\n") }] };
|
||||
@@ -96,9 +87,7 @@ Example: get_datasheet_url("C179739")
|
||||
{
|
||||
lcsc: z
|
||||
.string()
|
||||
.describe(
|
||||
'LCSC part number, with or without "C" prefix (e.g. "C179739" or "179739")',
|
||||
),
|
||||
.describe('LCSC part number, with or without "C" prefix (e.g. "C179739" or "179739")'),
|
||||
},
|
||||
async (args: { lcsc: string }) => {
|
||||
const result = await callKicadScript("get_datasheet_url", { lcsc: args.lcsc });
|
||||
|
||||
@@ -1,261 +1,314 @@
|
||||
/**
|
||||
* 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');
|
||||
}
|
||||
/**
|
||||
* 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");
|
||||
}
|
||||
|
||||
@@ -1,260 +1,317 @@
|
||||
/**
|
||||
* 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');
|
||||
}
|
||||
/**
|
||||
* 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");
|
||||
}
|
||||
|
||||
@@ -62,10 +62,7 @@ const RectSchema = z.object({
|
||||
|
||||
// ---- tool registration --------------------------------------------------- //
|
||||
|
||||
export function registerFootprintTools(
|
||||
server: McpServer,
|
||||
callKicadScript: Function,
|
||||
) {
|
||||
export function registerFootprintTools(server: McpServer, callKicadScript: Function) {
|
||||
// ── create_footprint ──────────────────────────────────────────────────── //
|
||||
server.tool(
|
||||
"create_footprint",
|
||||
@@ -80,10 +77,7 @@ export function registerFootprintTools(
|
||||
),
|
||||
name: z.string().describe("Footprint name, e.g. 'R_0603_Custom'"),
|
||||
description: z.string().optional().describe("Human-readable description"),
|
||||
tags: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("Space-separated tag string, e.g. 'resistor SMD 0603'"),
|
||||
tags: z.string().optional().describe("Space-separated tag string, e.g. 'resistor SMD 0603'"),
|
||||
pads: z
|
||||
.array(PadSchema)
|
||||
.optional()
|
||||
@@ -91,9 +85,7 @@ export function registerFootprintTools(
|
||||
courtyard: RectSchema.optional().describe(
|
||||
"Courtyard rectangle on F.CrtYd (recommended: 0.25 mm clearance around pads)",
|
||||
),
|
||||
silkscreen: RectSchema.optional().describe(
|
||||
"Silkscreen rectangle on F.SilkS",
|
||||
),
|
||||
silkscreen: RectSchema.optional().describe("Silkscreen rectangle on F.SilkS"),
|
||||
fabLayer: RectSchema.optional().describe(
|
||||
"Fab-layer rectangle on F.Fab (shows component body)",
|
||||
),
|
||||
@@ -139,9 +131,7 @@ export function registerFootprintTools(
|
||||
footprintPath: z
|
||||
.string()
|
||||
.describe("Full path to the .kicad_mod file, e.g. C:/MyLib.pretty/R_Custom.kicad_mod"),
|
||||
padNumber: z
|
||||
.union([z.string(), z.number()])
|
||||
.describe("Pad number to edit, e.g. '1' or 2"),
|
||||
padNumber: z.union([z.string(), z.number()]).describe("Pad number to edit, e.g. '1' or 2"),
|
||||
size: PadSize.optional().describe("New pad size in mm"),
|
||||
at: PadPosition.optional().describe("New pad position in mm"),
|
||||
drill: z
|
||||
@@ -151,10 +141,7 @@ export function registerFootprintTools(
|
||||
])
|
||||
.optional()
|
||||
.describe("New drill size (for THT pads)"),
|
||||
shape: z
|
||||
.enum(["rect", "circle", "oval", "roundrect"])
|
||||
.optional()
|
||||
.describe("New pad shape"),
|
||||
shape: z.enum(["rect", "circle", "oval", "roundrect"]).optional().describe("New pad shape"),
|
||||
},
|
||||
async (args: {
|
||||
footprintPath: string;
|
||||
@@ -177,9 +164,7 @@ export function registerFootprintTools(
|
||||
"Register a .pretty footprint library in KiCAD's fp-lib-table so KiCAD can find the footprints. " +
|
||||
"Run this after create_footprint when KiCAD shows 'library not found in footprint library table'.",
|
||||
{
|
||||
libraryPath: z
|
||||
.string()
|
||||
.describe("Full path to the .pretty directory to register"),
|
||||
libraryPath: z.string().describe("Full path to the .pretty directory to register"),
|
||||
libraryName: z
|
||||
.string()
|
||||
.optional()
|
||||
|
||||
@@ -7,33 +7,21 @@
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { z } from "zod";
|
||||
|
||||
export function registerFreeroutingTools(
|
||||
server: McpServer,
|
||||
callKicadScript: Function,
|
||||
) {
|
||||
export function registerFreeroutingTools(server: McpServer, callKicadScript: Function) {
|
||||
// Full autoroute: export DSN -> run Freerouting -> import SES
|
||||
server.tool(
|
||||
"autoroute",
|
||||
"Run Freerouting autorouter on the current PCB. Exports to Specctra DSN, runs Freerouting CLI, and imports the routed SES result. Requires Java 11+ and freerouting.jar (see check_freerouting).",
|
||||
{
|
||||
boardPath: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("Path to .kicad_pcb file (default: current board)"),
|
||||
boardPath: z.string().optional().describe("Path to .kicad_pcb file (default: current board)"),
|
||||
freeroutingJar: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
"Path to freerouting.jar (default: ~/.kicad-mcp/freerouting.jar or FREEROUTING_JAR env)",
|
||||
),
|
||||
maxPasses: z
|
||||
.number()
|
||||
.optional()
|
||||
.describe("Maximum routing passes (default: 20)"),
|
||||
timeout: z
|
||||
.number()
|
||||
.optional()
|
||||
.describe("Timeout in seconds (default: 300)"),
|
||||
maxPasses: z.number().optional().describe("Maximum routing passes (default: 20)"),
|
||||
timeout: z.number().optional().describe("Timeout in seconds (default: 300)"),
|
||||
},
|
||||
async (args: any) => {
|
||||
const result = await callKicadScript("autoroute", args);
|
||||
@@ -53,10 +41,7 @@ export function registerFreeroutingTools(
|
||||
"export_dsn",
|
||||
"Export the current PCB to Specctra DSN format. Useful for manual Freerouting workflow or external autorouters.",
|
||||
{
|
||||
boardPath: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("Path to .kicad_pcb file (default: current board)"),
|
||||
boardPath: z.string().optional().describe("Path to .kicad_pcb file (default: current board)"),
|
||||
outputPath: z
|
||||
.string()
|
||||
.optional()
|
||||
@@ -81,10 +66,7 @@ export function registerFreeroutingTools(
|
||||
"Import a Specctra SES (session) file into the current PCB. Use after running Freerouting externally.",
|
||||
{
|
||||
sesPath: z.string().describe("Path to the .ses file to import"),
|
||||
boardPath: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("Path to .kicad_pcb file (default: current board)"),
|
||||
boardPath: z.string().optional().describe("Path to .kicad_pcb file (default: current board)"),
|
||||
},
|
||||
async (args: any) => {
|
||||
const result = await callKicadScript("import_ses", args);
|
||||
@@ -104,10 +86,7 @@ export function registerFreeroutingTools(
|
||||
"check_freerouting",
|
||||
"Check if Java and Freerouting JAR are available on the system. Run this before autoroute to verify prerequisites.",
|
||||
{
|
||||
freeroutingJar: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("Path to freerouting.jar to check"),
|
||||
freeroutingJar: z.string().optional().describe("Path to freerouting.jar to check"),
|
||||
},
|
||||
async (args: any) => {
|
||||
const result = await callKicadScript("check_freerouting", args);
|
||||
|
||||
@@ -1,245 +1,295 @@
|
||||
/**
|
||||
* JLCPCB API tools for KiCAD MCP server
|
||||
* Provides access to JLCPCB's complete parts catalog via their API
|
||||
*/
|
||||
|
||||
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import { z } from 'zod';
|
||||
|
||||
export function registerJLCPCBApiTools(server: McpServer, callKicadScript: Function) {
|
||||
// Download JLCPCB parts database
|
||||
server.tool(
|
||||
"download_jlcpcb_database",
|
||||
`Download the complete JLCPCB parts catalog to local database.
|
||||
|
||||
This is a one-time setup that downloads ~2.5M+ parts from JLCSearch API.
|
||||
No API credentials required - uses public JLCSearch API.
|
||||
|
||||
The download takes 5-10 minutes and creates a local SQLite database
|
||||
for fast offline searching.`,
|
||||
{
|
||||
force: z.boolean().optional().default(false)
|
||||
.describe("Force re-download even if database exists")
|
||||
},
|
||||
async (args: { force?: boolean }) => {
|
||||
const result = await callKicadScript("download_jlcpcb_database", args);
|
||||
if (result.success) {
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: `✓ Successfully downloaded JLCPCB parts database\n\n` +
|
||||
`Total parts: ${result.total_parts}\n` +
|
||||
`Basic parts: ${result.basic_parts}\n` +
|
||||
`Extended parts: ${result.extended_parts}\n` +
|
||||
`Database size: ${result.db_size_mb} MB\n` +
|
||||
`Database path: ${result.db_path}`
|
||||
}]
|
||||
};
|
||||
}
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: `✗ Failed to download JLCPCB database: ${result.message || 'Unknown error'}\n\n` +
|
||||
`Make sure JLCPCB_API_KEY and JLCPCB_API_SECRET environment variables are set.`
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// Search JLCPCB parts
|
||||
server.tool(
|
||||
"search_jlcpcb_parts",
|
||||
`Search JLCPCB parts catalog by specifications.
|
||||
|
||||
Searches the local JLCPCB database (must be downloaded first with download_jlcpcb_database).
|
||||
Provides real pricing, stock info, and library type (Basic parts = free assembly).
|
||||
|
||||
Use this to find components with exact specifications and cost optimization.`,
|
||||
{
|
||||
query: z.string().optional()
|
||||
.describe("Free-text search (e.g., '10k resistor 0603', 'ESP32', 'STM32F103')"),
|
||||
category: z.string().optional()
|
||||
.describe("Filter by category (e.g., 'Resistors', 'Capacitors', 'Microcontrollers')"),
|
||||
package: z.string().optional()
|
||||
.describe("Filter by package type (e.g., '0603', 'SOT-23', 'QFN-32')"),
|
||||
library_type: z.enum(["Basic", "Extended", "Preferred", "All"]).optional().default("All")
|
||||
.describe("Filter by library type (Basic = free assembly at JLCPCB)"),
|
||||
manufacturer: z.string().optional()
|
||||
.describe("Filter by manufacturer name"),
|
||||
in_stock: z.boolean().optional().default(true)
|
||||
.describe("Only show parts with available stock"),
|
||||
limit: z.number().optional().default(20)
|
||||
.describe("Maximum number of results to return")
|
||||
},
|
||||
async (args: any) => {
|
||||
const result = await callKicadScript("search_jlcpcb_parts", args);
|
||||
if (result.success && result.parts) {
|
||||
if (result.parts.length === 0) {
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: `No JLCPCB parts found matching your criteria.\n\n` +
|
||||
`Try broadening your search or check if the database is populated.`
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
const partsList = result.parts.map((p: any) => {
|
||||
const priceInfo = p.price_breaks && p.price_breaks.length > 0
|
||||
? ` - $${p.price_breaks[0].price}/ea`
|
||||
: '';
|
||||
const stockInfo = p.stock > 0 ? ` (${p.stock} in stock)` : ' (out of stock)';
|
||||
return `${p.lcsc}: ${p.mfr_part} - ${p.description} [${p.library_type}]${priceInfo}${stockInfo}`;
|
||||
}).join('\n');
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: `Found ${result.count} JLCPCB parts:\n\n${partsList}\n\n` +
|
||||
`💡 Basic parts have free assembly. Extended parts charge $3 setup fee per unique part.`
|
||||
}]
|
||||
};
|
||||
}
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: `Failed to search JLCPCB parts: ${result.message || 'Unknown error'}\n\n` +
|
||||
`Make sure you've downloaded the database first using download_jlcpcb_database.`
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// Get JLCPCB part details
|
||||
server.tool(
|
||||
"get_jlcpcb_part",
|
||||
"Get detailed information about a specific JLCPCB part by LCSC number",
|
||||
{
|
||||
lcsc_number: z.string()
|
||||
.describe("LCSC part number (e.g., 'C25804', 'C2286')")
|
||||
},
|
||||
async (args: { lcsc_number: string }) => {
|
||||
const result = await callKicadScript("get_jlcpcb_part", args);
|
||||
if (result.success && result.part) {
|
||||
const p = result.part;
|
||||
const priceTable = p.price_breaks && p.price_breaks.length > 0
|
||||
? '\n\nPrice Breaks:\n' + p.price_breaks.map((pb: any) =>
|
||||
` ${pb.qty}+: $${pb.price}/ea`
|
||||
).join('\n')
|
||||
: '';
|
||||
|
||||
const footprints = result.footprints && result.footprints.length > 0
|
||||
? '\n\nSuggested KiCAD Footprints:\n' + result.footprints.map((f: string) =>
|
||||
` - ${f}`
|
||||
).join('\n')
|
||||
: '';
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: `LCSC: ${p.lcsc}\n` +
|
||||
`MFR Part: ${p.mfr_part}\n` +
|
||||
`Manufacturer: ${p.manufacturer}\n` +
|
||||
`Category: ${p.category} / ${p.subcategory}\n` +
|
||||
`Package: ${p.package}\n` +
|
||||
`Description: ${p.description}\n` +
|
||||
`Library Type: ${p.library_type} ${p.library_type === 'Basic' ? '(Free assembly!)' : ''}\n` +
|
||||
`Stock: ${p.stock}\n` +
|
||||
(p.datasheet ? `Datasheet: ${p.datasheet}\n` : '') +
|
||||
priceTable +
|
||||
footprints
|
||||
}]
|
||||
};
|
||||
}
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: `Part not found: ${args.lcsc_number}\n\n` +
|
||||
`Make sure you've downloaded the JLCPCB database first.`
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// Get JLCPCB database statistics
|
||||
server.tool(
|
||||
"get_jlcpcb_database_stats",
|
||||
"Get statistics about the local JLCPCB parts database",
|
||||
{},
|
||||
async () => {
|
||||
const result = await callKicadScript("get_jlcpcb_database_stats", {});
|
||||
if (result.success) {
|
||||
const stats = result.stats;
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: `JLCPCB Database Statistics:\n\n` +
|
||||
`Total parts: ${stats.total_parts.toLocaleString()}\n` +
|
||||
`Basic parts: ${stats.basic_parts.toLocaleString()} (free assembly)\n` +
|
||||
`Extended parts: ${stats.extended_parts.toLocaleString()} ($3 setup fee each)\n` +
|
||||
`In stock: ${stats.in_stock.toLocaleString()}\n` +
|
||||
`Database path: ${stats.db_path}`
|
||||
}]
|
||||
};
|
||||
}
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: `JLCPCB database not found or empty.\n\n` +
|
||||
`Run download_jlcpcb_database first to populate the database.`
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// Suggest alternative parts
|
||||
server.tool(
|
||||
"suggest_jlcpcb_alternatives",
|
||||
`Suggest alternative JLCPCB parts for a given component.
|
||||
|
||||
Finds similar parts that may be cheaper, have more stock, or are Basic library type.
|
||||
Useful for cost optimization and finding alternatives when parts are out of stock.`,
|
||||
{
|
||||
lcsc_number: z.string()
|
||||
.describe("Reference LCSC part number to find alternatives for"),
|
||||
limit: z.number().optional().default(5)
|
||||
.describe("Maximum number of alternatives to return")
|
||||
},
|
||||
async (args: { lcsc_number: string; limit?: number }) => {
|
||||
const result = await callKicadScript("suggest_jlcpcb_alternatives", args);
|
||||
if (result.success && result.alternatives) {
|
||||
if (result.alternatives.length === 0) {
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: `No alternatives found for ${args.lcsc_number}`
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
const altsList = result.alternatives.map((p: any, i: number) => {
|
||||
const priceInfo = p.price_breaks && p.price_breaks.length > 0
|
||||
? ` - $${p.price_breaks[0].price}/ea`
|
||||
: '';
|
||||
const savings = result.reference_price && p.price_breaks && p.price_breaks.length > 0
|
||||
? ` (${((1 - p.price_breaks[0].price / result.reference_price) * 100).toFixed(0)}% cheaper)`
|
||||
: '';
|
||||
return `${i + 1}. ${p.lcsc}: ${p.mfr_part} [${p.library_type}]${priceInfo}${savings}\n ${p.description}\n Stock: ${p.stock}`;
|
||||
}).join('\n\n');
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: `Alternative parts for ${args.lcsc_number}:\n\n${altsList}`
|
||||
}]
|
||||
};
|
||||
}
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: `Failed to find alternatives: ${result.message || 'Unknown error'}`
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
/**
|
||||
* JLCPCB API tools for KiCAD MCP server
|
||||
* Provides access to JLCPCB's complete parts catalog via their API
|
||||
*/
|
||||
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { z } from "zod";
|
||||
|
||||
export function registerJLCPCBApiTools(server: McpServer, callKicadScript: Function) {
|
||||
// Download JLCPCB parts database
|
||||
server.tool(
|
||||
"download_jlcpcb_database",
|
||||
`Download the complete JLCPCB parts catalog to local database.
|
||||
|
||||
This is a one-time setup that downloads ~2.5M+ parts from JLCSearch API.
|
||||
No API credentials required - uses public JLCSearch API.
|
||||
|
||||
The download takes 5-10 minutes and creates a local SQLite database
|
||||
for fast offline searching.`,
|
||||
{
|
||||
force: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.default(false)
|
||||
.describe("Force re-download even if database exists"),
|
||||
},
|
||||
async (args: { force?: boolean }) => {
|
||||
const result = await callKicadScript("download_jlcpcb_database", args);
|
||||
if (result.success) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text:
|
||||
`✓ Successfully downloaded JLCPCB parts database\n\n` +
|
||||
`Total parts: ${result.total_parts}\n` +
|
||||
`Basic parts: ${result.basic_parts}\n` +
|
||||
`Extended parts: ${result.extended_parts}\n` +
|
||||
`Database size: ${result.db_size_mb} MB\n` +
|
||||
`Database path: ${result.db_path}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text:
|
||||
`✗ Failed to download JLCPCB database: ${result.message || "Unknown error"}\n\n` +
|
||||
`Make sure JLCPCB_API_KEY and JLCPCB_API_SECRET environment variables are set.`,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
// Search JLCPCB parts
|
||||
server.tool(
|
||||
"search_jlcpcb_parts",
|
||||
`Search JLCPCB parts catalog by specifications.
|
||||
|
||||
Searches the local JLCPCB database (must be downloaded first with download_jlcpcb_database).
|
||||
Provides real pricing, stock info, and library type (Basic parts = free assembly).
|
||||
|
||||
Use this to find components with exact specifications and cost optimization.`,
|
||||
{
|
||||
query: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("Free-text search (e.g., '10k resistor 0603', 'ESP32', 'STM32F103')"),
|
||||
category: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("Filter by category (e.g., 'Resistors', 'Capacitors', 'Microcontrollers')"),
|
||||
package: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("Filter by package type (e.g., '0603', 'SOT-23', 'QFN-32')"),
|
||||
library_type: z
|
||||
.enum(["Basic", "Extended", "Preferred", "All"])
|
||||
.optional()
|
||||
.default("All")
|
||||
.describe("Filter by library type (Basic = free assembly at JLCPCB)"),
|
||||
manufacturer: z.string().optional().describe("Filter by manufacturer name"),
|
||||
in_stock: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.default(true)
|
||||
.describe("Only show parts with available stock"),
|
||||
limit: z.number().optional().default(20).describe("Maximum number of results to return"),
|
||||
},
|
||||
async (args: any) => {
|
||||
const result = await callKicadScript("search_jlcpcb_parts", args);
|
||||
if (result.success && result.parts) {
|
||||
if (result.parts.length === 0) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text:
|
||||
`No JLCPCB parts found matching your criteria.\n\n` +
|
||||
`Try broadening your search or check if the database is populated.`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
const partsList = result.parts
|
||||
.map((p: any) => {
|
||||
const priceInfo =
|
||||
p.price_breaks && p.price_breaks.length > 0
|
||||
? ` - $${p.price_breaks[0].price}/ea`
|
||||
: "";
|
||||
const stockInfo = p.stock > 0 ? ` (${p.stock} in stock)` : " (out of stock)";
|
||||
return `${p.lcsc}: ${p.mfr_part} - ${p.description} [${p.library_type}]${priceInfo}${stockInfo}`;
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text:
|
||||
`Found ${result.count} JLCPCB parts:\n\n${partsList}\n\n` +
|
||||
`💡 Basic parts have free assembly. Extended parts charge $3 setup fee per unique part.`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text:
|
||||
`Failed to search JLCPCB parts: ${result.message || "Unknown error"}\n\n` +
|
||||
`Make sure you've downloaded the database first using download_jlcpcb_database.`,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
// Get JLCPCB part details
|
||||
server.tool(
|
||||
"get_jlcpcb_part",
|
||||
"Get detailed information about a specific JLCPCB part by LCSC number",
|
||||
{
|
||||
lcsc_number: z.string().describe("LCSC part number (e.g., 'C25804', 'C2286')"),
|
||||
},
|
||||
async (args: { lcsc_number: string }) => {
|
||||
const result = await callKicadScript("get_jlcpcb_part", args);
|
||||
if (result.success && result.part) {
|
||||
const p = result.part;
|
||||
const priceTable =
|
||||
p.price_breaks && p.price_breaks.length > 0
|
||||
? "\n\nPrice Breaks:\n" +
|
||||
p.price_breaks.map((pb: any) => ` ${pb.qty}+: $${pb.price}/ea`).join("\n")
|
||||
: "";
|
||||
|
||||
const footprints =
|
||||
result.footprints && result.footprints.length > 0
|
||||
? "\n\nSuggested KiCAD Footprints:\n" +
|
||||
result.footprints.map((f: string) => ` - ${f}`).join("\n")
|
||||
: "";
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text:
|
||||
`LCSC: ${p.lcsc}\n` +
|
||||
`MFR Part: ${p.mfr_part}\n` +
|
||||
`Manufacturer: ${p.manufacturer}\n` +
|
||||
`Category: ${p.category} / ${p.subcategory}\n` +
|
||||
`Package: ${p.package}\n` +
|
||||
`Description: ${p.description}\n` +
|
||||
`Library Type: ${p.library_type} ${p.library_type === "Basic" ? "(Free assembly!)" : ""}\n` +
|
||||
`Stock: ${p.stock}\n` +
|
||||
(p.datasheet ? `Datasheet: ${p.datasheet}\n` : "") +
|
||||
priceTable +
|
||||
footprints,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text:
|
||||
`Part not found: ${args.lcsc_number}\n\n` +
|
||||
`Make sure you've downloaded the JLCPCB database first.`,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
// Get JLCPCB database statistics
|
||||
server.tool(
|
||||
"get_jlcpcb_database_stats",
|
||||
"Get statistics about the local JLCPCB parts database",
|
||||
{},
|
||||
async () => {
|
||||
const result = await callKicadScript("get_jlcpcb_database_stats", {});
|
||||
if (result.success) {
|
||||
const stats = result.stats;
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text:
|
||||
`JLCPCB Database Statistics:\n\n` +
|
||||
`Total parts: ${stats.total_parts.toLocaleString()}\n` +
|
||||
`Basic parts: ${stats.basic_parts.toLocaleString()} (free assembly)\n` +
|
||||
`Extended parts: ${stats.extended_parts.toLocaleString()} ($3 setup fee each)\n` +
|
||||
`In stock: ${stats.in_stock.toLocaleString()}\n` +
|
||||
`Database path: ${stats.db_path}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text:
|
||||
`JLCPCB database not found or empty.\n\n` +
|
||||
`Run download_jlcpcb_database first to populate the database.`,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
// Suggest alternative parts
|
||||
server.tool(
|
||||
"suggest_jlcpcb_alternatives",
|
||||
`Suggest alternative JLCPCB parts for a given component.
|
||||
|
||||
Finds similar parts that may be cheaper, have more stock, or are Basic library type.
|
||||
Useful for cost optimization and finding alternatives when parts are out of stock.`,
|
||||
{
|
||||
lcsc_number: z.string().describe("Reference LCSC part number to find alternatives for"),
|
||||
limit: z.number().optional().default(5).describe("Maximum number of alternatives to return"),
|
||||
},
|
||||
async (args: { lcsc_number: string; limit?: number }) => {
|
||||
const result = await callKicadScript("suggest_jlcpcb_alternatives", args);
|
||||
if (result.success && result.alternatives) {
|
||||
if (result.alternatives.length === 0) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `No alternatives found for ${args.lcsc_number}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
const altsList = result.alternatives
|
||||
.map((p: any, i: number) => {
|
||||
const priceInfo =
|
||||
p.price_breaks && p.price_breaks.length > 0
|
||||
? ` - $${p.price_breaks[0].price}/ea`
|
||||
: "";
|
||||
const savings =
|
||||
result.reference_price && p.price_breaks && p.price_breaks.length > 0
|
||||
? ` (${((1 - p.price_breaks[0].price / result.reference_price) * 100).toFixed(0)}% cheaper)`
|
||||
: "";
|
||||
return `${i + 1}. ${p.lcsc}: ${p.mfr_part} [${p.library_type}]${priceInfo}${savings}\n ${p.description}\n Stock: ${p.stock}`;
|
||||
})
|
||||
.join("\n\n");
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Alternative parts for ${args.lcsc_number}:\n\n${altsList}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Failed to find alternatives: ${result.message || "Unknown error"}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
* Provides search/browse access to local KiCad symbol libraries
|
||||
*/
|
||||
|
||||
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import { z } from 'zod';
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { z } from "zod";
|
||||
|
||||
export function registerSymbolLibraryTools(server: McpServer, callKicadScript: Function) {
|
||||
// List available symbol libraries
|
||||
@@ -19,20 +19,20 @@ export function registerSymbolLibraryTools(server: McpServer, callKicadScript: F
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Found ${result.count} symbol libraries:\n${result.libraries.join('\n')}`
|
||||
}
|
||||
]
|
||||
text: `Found ${result.count} symbol libraries:\n${result.libraries.join("\n")}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Failed to list symbol libraries: ${result.message || 'Unknown error'}`
|
||||
}
|
||||
]
|
||||
text: `Failed to list symbol libraries: ${result.message || "Unknown error"}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Search for symbols across all libraries
|
||||
@@ -45,12 +45,12 @@ Use this to find components already in your local libraries (e.g., JLCPCB-KiCad-
|
||||
|
||||
Returns symbol references that can be used directly in schematics.`,
|
||||
{
|
||||
query: z.string()
|
||||
.describe("Search query (e.g., 'ESP32', 'STM32F103', 'C8734' for LCSC ID)"),
|
||||
library: z.string().optional()
|
||||
query: z.string().describe("Search query (e.g., 'ESP32', 'STM32F103', 'C8734' for LCSC ID)"),
|
||||
library: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("Optional: filter to specific library name pattern (e.g., 'JLCPCB')"),
|
||||
limit: z.number().optional().default(20)
|
||||
.describe("Maximum number of results to return")
|
||||
limit: z.number().optional().default(20).describe("Maximum number of results to return"),
|
||||
},
|
||||
async (args: { query: string; library?: string; limit?: number }) => {
|
||||
const result = await callKicadScript("search_symbols", args);
|
||||
@@ -60,38 +60,40 @@ Returns symbol references that can be used directly in schematics.`,
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `No symbols found matching "${args.query}"${args.library ? ` in libraries matching "${args.library}"` : ''}`
|
||||
}
|
||||
]
|
||||
text: `No symbols found matching "${args.query}"${args.library ? ` in libraries matching "${args.library}"` : ""}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
const symbolList = result.symbols.map((s: any) => {
|
||||
const parts = [`${s.full_ref}`];
|
||||
if (s.lcsc_id) parts.push(`LCSC: ${s.lcsc_id}`);
|
||||
if (s.description) parts.push(s.description);
|
||||
else if (s.value) parts.push(s.value);
|
||||
return parts.join(' | ');
|
||||
}).join('\n');
|
||||
const symbolList = result.symbols
|
||||
.map((s: any) => {
|
||||
const parts = [`${s.full_ref}`];
|
||||
if (s.lcsc_id) parts.push(`LCSC: ${s.lcsc_id}`);
|
||||
if (s.description) parts.push(s.description);
|
||||
else if (s.value) parts.push(s.value);
|
||||
return parts.join(" | ");
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Found ${result.count} symbols matching "${args.query}":\n\n${symbolList}`
|
||||
}
|
||||
]
|
||||
text: `Found ${result.count} symbols matching "${args.query}":\n\n${symbolList}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Failed to search symbols: ${result.message || 'Unknown error'}`
|
||||
}
|
||||
]
|
||||
text: `Failed to search symbols: ${result.message || "Unknown error"}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// List symbols in a specific library
|
||||
@@ -99,36 +101,37 @@ Returns symbol references that can be used directly in schematics.`,
|
||||
"list_library_symbols",
|
||||
"List all symbols in a specific KiCAD symbol library",
|
||||
{
|
||||
library: z.string()
|
||||
.describe("Library name (e.g., 'Device', 'PCM_JLCPCB-MCUs')")
|
||||
library: z.string().describe("Library name (e.g., 'Device', 'PCM_JLCPCB-MCUs')"),
|
||||
},
|
||||
async (args: { library: string }) => {
|
||||
const result = await callKicadScript("list_library_symbols", args);
|
||||
if (result.success && result.symbols) {
|
||||
const symbolList = result.symbols.map((s: any) => {
|
||||
const parts = [` - ${s.name}`];
|
||||
if (s.lcsc_id) parts.push(`(LCSC: ${s.lcsc_id})`);
|
||||
return parts.join(' ');
|
||||
}).join('\n');
|
||||
const symbolList = result.symbols
|
||||
.map((s: any) => {
|
||||
const parts = [` - ${s.name}`];
|
||||
if (s.lcsc_id) parts.push(`(LCSC: ${s.lcsc_id})`);
|
||||
return parts.join(" ");
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Library "${args.library}" contains ${result.count} symbols:\n${symbolList}`
|
||||
}
|
||||
]
|
||||
text: `Library "${args.library}" contains ${result.count} symbols:\n${symbolList}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Failed to list symbols in library ${args.library}: ${result.message || 'Unknown error'}`
|
||||
}
|
||||
]
|
||||
text: `Failed to list symbols in library ${args.library}: ${result.message || "Unknown error"}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Get detailed information about a specific symbol
|
||||
@@ -136,8 +139,9 @@ Returns symbol references that can be used directly in schematics.`,
|
||||
"get_symbol_info",
|
||||
"Get detailed information about a specific symbol",
|
||||
{
|
||||
symbol: z.string()
|
||||
.describe("Symbol specification (e.g., 'Device:R' or 'PCM_JLCPCB-MCUs:STM32F103C8T6')")
|
||||
symbol: z
|
||||
.string()
|
||||
.describe("Symbol specification (e.g., 'Device:R' or 'PCM_JLCPCB-MCUs:STM32F103C8T6')"),
|
||||
},
|
||||
async (args: { symbol: string }) => {
|
||||
const result = await callKicadScript("get_symbol_info", args);
|
||||
@@ -145,34 +149,36 @@ Returns symbol references that can be used directly in schematics.`,
|
||||
const info = result.symbol_info;
|
||||
const details = [
|
||||
`Symbol: ${info.full_ref}`,
|
||||
info.value ? `Value: ${info.value}` : '',
|
||||
info.description ? `Description: ${info.description}` : '',
|
||||
info.lcsc_id ? `LCSC: ${info.lcsc_id}` : '',
|
||||
info.manufacturer ? `Manufacturer: ${info.manufacturer}` : '',
|
||||
info.mpn ? `MPN: ${info.mpn}` : '',
|
||||
info.footprint ? `Footprint: ${info.footprint}` : '',
|
||||
info.category ? `Category: ${info.category}` : '',
|
||||
info.lib_class ? `Class: ${info.lib_class}` : '',
|
||||
info.datasheet ? `Datasheet: ${info.datasheet}` : '',
|
||||
].filter(line => line).join('\n');
|
||||
info.value ? `Value: ${info.value}` : "",
|
||||
info.description ? `Description: ${info.description}` : "",
|
||||
info.lcsc_id ? `LCSC: ${info.lcsc_id}` : "",
|
||||
info.manufacturer ? `Manufacturer: ${info.manufacturer}` : "",
|
||||
info.mpn ? `MPN: ${info.mpn}` : "",
|
||||
info.footprint ? `Footprint: ${info.footprint}` : "",
|
||||
info.category ? `Category: ${info.category}` : "",
|
||||
info.lib_class ? `Class: ${info.lib_class}` : "",
|
||||
info.datasheet ? `Datasheet: ${info.datasheet}` : "",
|
||||
]
|
||||
.filter((line) => line)
|
||||
.join("\n");
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: details
|
||||
}
|
||||
]
|
||||
text: details,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Failed to get symbol info: ${result.message || 'Unknown error'}`
|
||||
}
|
||||
]
|
||||
text: `Failed to get symbol info: ${result.message || "Unknown error"}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,10 +6,7 @@
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { z } from "zod";
|
||||
|
||||
export function registerLibraryTools(
|
||||
server: McpServer,
|
||||
callKicadScript: Function,
|
||||
) {
|
||||
export function registerLibraryTools(server: McpServer, callKicadScript: Function) {
|
||||
// List available footprint libraries
|
||||
server.tool(
|
||||
"list_libraries",
|
||||
@@ -48,18 +45,9 @@ export function registerLibraryTools(
|
||||
"search_footprints",
|
||||
"Search for footprints matching a pattern across all libraries",
|
||||
{
|
||||
search_term: z
|
||||
.string()
|
||||
.describe("Search term or pattern to match footprint names"),
|
||||
library: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("Optional specific library to search in"),
|
||||
limit: z
|
||||
.number()
|
||||
.optional()
|
||||
.default(50)
|
||||
.describe("Maximum number of results to return"),
|
||||
search_term: z.string().describe("Search term or pattern to match footprint names"),
|
||||
library: z.string().optional().describe("Optional specific library to search in"),
|
||||
limit: z.number().optional().default(50).describe("Maximum number of results to return"),
|
||||
},
|
||||
async (args: { search_term: string; library?: string; limit?: number }) => {
|
||||
const result = await callKicadScript("search_footprints", {
|
||||
@@ -99,25 +87,14 @@ export function registerLibraryTools(
|
||||
"list_library_footprints",
|
||||
"List all footprints in a specific KiCAD library",
|
||||
{
|
||||
library_name: z
|
||||
.string()
|
||||
.describe("Name of the library to list footprints from"),
|
||||
filter: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("Optional filter pattern for footprint names"),
|
||||
limit: z
|
||||
.number()
|
||||
.optional()
|
||||
.default(100)
|
||||
.describe("Maximum number of footprints to list"),
|
||||
library_name: z.string().describe("Name of the library to list footprints from"),
|
||||
filter: z.string().optional().describe("Optional filter pattern for footprint names"),
|
||||
limit: z.number().optional().default(100).describe("Maximum number of footprints to list"),
|
||||
},
|
||||
async (args: { library_name: string; filter?: string; limit?: number }) => {
|
||||
const result = await callKicadScript("list_library_footprints", args);
|
||||
if (result.success && result.footprints) {
|
||||
const footprintList = result.footprints
|
||||
.map((fp: string) => ` - ${fp}`)
|
||||
.join("\n");
|
||||
const footprintList = result.footprints.map((fp: string) => ` - ${fp}`).join("\n");
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
@@ -143,12 +120,8 @@ export function registerLibraryTools(
|
||||
"get_footprint_info",
|
||||
"Get detailed information about a specific footprint",
|
||||
{
|
||||
library_name: z
|
||||
.string()
|
||||
.describe("Name of the library containing the footprint"),
|
||||
footprint_name: z
|
||||
.string()
|
||||
.describe("Name of the footprint to get information about"),
|
||||
library_name: z.string().describe("Name of the library containing the footprint"),
|
||||
footprint_name: z.string().describe("Name of the footprint to get information about"),
|
||||
},
|
||||
async (args: { library_name: string; footprint_name: string }) => {
|
||||
const result = await callKicadScript("get_footprint_info", args);
|
||||
@@ -156,15 +129,16 @@ export function registerLibraryTools(
|
||||
const info = result.info;
|
||||
|
||||
// pads is a list of {number, type, shape} objects
|
||||
const padsArray: Array<{ number: string; type: string; shape: string }> =
|
||||
Array.isArray(info.pads) ? info.pads : [];
|
||||
const padsArray: Array<{ number: string; type: string; shape: string }> = Array.isArray(
|
||||
info.pads,
|
||||
)
|
||||
? info.pads
|
||||
: [];
|
||||
const padsSummary = padsArray.length
|
||||
? `${padsArray.length} pads: ${padsArray.map((p) => p.number).join(", ")}`
|
||||
: "";
|
||||
const padsDetail = padsArray.length
|
||||
? padsArray
|
||||
.map((p) => ` pad ${p.number}: ${p.type} ${p.shape}`)
|
||||
.join("\n")
|
||||
? padsArray.map((p) => ` pad ${p.number}: ${p.type} ${p.shape}`).join("\n")
|
||||
: "";
|
||||
|
||||
const details = [
|
||||
@@ -178,9 +152,7 @@ export function registerLibraryTools(
|
||||
info.courtyard
|
||||
? `Courtyard size: ${info.courtyard.width}mm x ${info.courtyard.height}mm`
|
||||
: "",
|
||||
info.attributes
|
||||
? `Attributes: ${JSON.stringify(info.attributes)}`
|
||||
: "",
|
||||
info.attributes ? `Attributes: ${JSON.stringify(info.attributes)}` : "",
|
||||
]
|
||||
.filter((line) => line)
|
||||
.join("\n");
|
||||
|
||||
@@ -1,99 +1,116 @@
|
||||
/**
|
||||
* Project management tools for KiCAD MCP server
|
||||
*/
|
||||
|
||||
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import { z } from 'zod';
|
||||
|
||||
export function registerProjectTools(server: McpServer, callKicadScript: Function) {
|
||||
// Create project tool
|
||||
server.tool(
|
||||
"create_project",
|
||||
"Create a new KiCAD project",
|
||||
{
|
||||
path: z.string().describe("Project directory path"),
|
||||
name: z.string().describe("Project name"),
|
||||
},
|
||||
async (args: { path: string; name: string }) => {
|
||||
const result = await callKicadScript("create_project", args);
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result, null, 2)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// Open project tool
|
||||
server.tool(
|
||||
"open_project",
|
||||
"Open an existing KiCAD project",
|
||||
{
|
||||
filename: z.string().describe("Path to .kicad_pro or .kicad_pcb file"),
|
||||
},
|
||||
async (args: { filename: string }) => {
|
||||
const result = await callKicadScript("open_project", args);
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result, null, 2)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// Save project tool
|
||||
server.tool(
|
||||
"save_project",
|
||||
"Save the current KiCAD project",
|
||||
{
|
||||
path: z.string().optional().describe("Optional new path to save to"),
|
||||
},
|
||||
async (args: { path?: string }) => {
|
||||
const result = await callKicadScript("save_project", args);
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result, null, 2)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// Get project info tool
|
||||
server.tool(
|
||||
"get_project_info",
|
||||
"Get information about the current KiCAD project",
|
||||
{},
|
||||
async () => {
|
||||
const result = await callKicadScript("get_project_info", {});
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result, null, 2)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// Snapshot project tool — saves a named checkpoint as PDF/image
|
||||
server.tool(
|
||||
"snapshot_project",
|
||||
"Save a named checkpoint snapshot of the current project state (renders board to PDF and records step label). Call after completing each major step — e.g. after Step 1 (schematic_ok) and Step 2 (layout_ok). Required by the demo workflow before waiting for user confirmation.",
|
||||
{
|
||||
step: z.string().describe("Step number or identifier, e.g. '1' or '2'"),
|
||||
label: z.string().describe("Short label for this checkpoint, e.g. 'schematic_ok' or 'layout_ok'"),
|
||||
prompt: z.string().optional().describe("Full prompt text to save as PROMPT_step{step}_{timestamp}.md alongside the snapshot"),
|
||||
},
|
||||
async (args: { step: string; label: string; prompt?: string }) => {
|
||||
const result = await callKicadScript("snapshot_project", args);
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result, null, 2)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Project management tools for KiCAD MCP server
|
||||
*/
|
||||
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { z } from "zod";
|
||||
|
||||
export function registerProjectTools(server: McpServer, callKicadScript: Function) {
|
||||
// Create project tool
|
||||
server.tool(
|
||||
"create_project",
|
||||
"Create a new KiCAD project",
|
||||
{
|
||||
path: z.string().describe("Project directory path"),
|
||||
name: z.string().describe("Project name"),
|
||||
},
|
||||
async (args: { path: string; name: string }) => {
|
||||
const result = await callKicadScript("create_project", args);
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: JSON.stringify(result, null, 2),
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
// Open project tool
|
||||
server.tool(
|
||||
"open_project",
|
||||
"Open an existing KiCAD project",
|
||||
{
|
||||
filename: z.string().describe("Path to .kicad_pro or .kicad_pcb file"),
|
||||
},
|
||||
async (args: { filename: string }) => {
|
||||
const result = await callKicadScript("open_project", args);
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: JSON.stringify(result, null, 2),
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
// Save project tool
|
||||
server.tool(
|
||||
"save_project",
|
||||
"Save the current KiCAD project",
|
||||
{
|
||||
path: z.string().optional().describe("Optional new path to save to"),
|
||||
},
|
||||
async (args: { path?: string }) => {
|
||||
const result = await callKicadScript("save_project", args);
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: JSON.stringify(result, null, 2),
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
// Get project info tool
|
||||
server.tool(
|
||||
"get_project_info",
|
||||
"Get information about the current KiCAD project",
|
||||
{},
|
||||
async () => {
|
||||
const result = await callKicadScript("get_project_info", {});
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: JSON.stringify(result, null, 2),
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
// Snapshot project tool — saves a named checkpoint as PDF/image
|
||||
server.tool(
|
||||
"snapshot_project",
|
||||
"Save a named checkpoint snapshot of the current project state (renders board to PDF and records step label). Call after completing each major step — e.g. after Step 1 (schematic_ok) and Step 2 (layout_ok). Required by the demo workflow before waiting for user confirmation.",
|
||||
{
|
||||
step: z.string().describe("Step number or identifier, e.g. '1' or '2'"),
|
||||
label: z
|
||||
.string()
|
||||
.describe("Short label for this checkpoint, e.g. 'schematic_ok' or 'layout_ok'"),
|
||||
prompt: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
"Full prompt text to save as PROMPT_step{step}_{timestamp}.md alongside the snapshot",
|
||||
),
|
||||
},
|
||||
async (args: { step: string; label: string; prompt?: string }) => {
|
||||
const result = await callKicadScript("snapshot_project", args);
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: JSON.stringify(result, null, 2),
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,308 +1,295 @@
|
||||
/**
|
||||
* Tool Registry for KiCAD MCP Server
|
||||
*
|
||||
* Centralizes all tool definitions and provides lookup/search functionality
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
|
||||
export interface ToolDefinition {
|
||||
name: string;
|
||||
description: string;
|
||||
inputSchema: z.ZodObject<any> | z.ZodType<any>;
|
||||
// Handler will be registered separately in the existing tool files
|
||||
}
|
||||
|
||||
export interface ToolCategory {
|
||||
name: string;
|
||||
description: string;
|
||||
tools: string[]; // Tool names in this category
|
||||
}
|
||||
|
||||
/**
|
||||
* Tool category definitions
|
||||
* Each category groups related tools for better organization
|
||||
*/
|
||||
export const toolCategories: ToolCategory[] = [
|
||||
{
|
||||
name: "board",
|
||||
description: "Board configuration: layers, mounting holes, zones, visualization",
|
||||
tools: [
|
||||
"add_layer",
|
||||
"set_active_layer",
|
||||
"get_layer_list",
|
||||
"add_mounting_hole",
|
||||
"add_board_text",
|
||||
"add_zone",
|
||||
"get_board_extents",
|
||||
"get_board_2d_view",
|
||||
"launch_kicad_ui"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "component",
|
||||
description: "Advanced component operations: edit, delete, search, group, annotate",
|
||||
tools: [
|
||||
"rotate_component",
|
||||
"delete_component",
|
||||
"edit_component",
|
||||
"find_component",
|
||||
"get_component_properties",
|
||||
"add_component_annotation",
|
||||
"group_components",
|
||||
"replace_component"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "export",
|
||||
description: "File export for fabrication and documentation: Gerber, PDF, BOM, 3D models",
|
||||
tools: [
|
||||
"export_gerber",
|
||||
"export_pdf",
|
||||
"export_svg",
|
||||
"export_3d",
|
||||
"export_bom",
|
||||
"export_netlist",
|
||||
"export_position_file",
|
||||
"export_vrml"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "drc",
|
||||
description: "Design rule checking and electrical validation: DRC, net classes, clearances",
|
||||
tools: [
|
||||
"set_design_rules",
|
||||
"get_design_rules",
|
||||
"run_drc",
|
||||
"add_net_class",
|
||||
"assign_net_to_class",
|
||||
"set_layer_constraints",
|
||||
"check_clearance",
|
||||
"get_drc_violations"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "schematic",
|
||||
description: "Schematic operations: create, inspect, add/edit/delete components, wire connections, netlists, annotation",
|
||||
tools: [
|
||||
"create_schematic",
|
||||
"add_schematic_component",
|
||||
"list_schematic_components",
|
||||
"move_schematic_component",
|
||||
"rotate_schematic_component",
|
||||
"annotate_schematic",
|
||||
"add_schematic_wire",
|
||||
"delete_schematic_wire",
|
||||
"add_schematic_junction",
|
||||
"add_schematic_net_label",
|
||||
"delete_schematic_net_label",
|
||||
"connect_to_net",
|
||||
"connect_passthrough",
|
||||
"get_net_connections",
|
||||
"list_schematic_nets",
|
||||
"list_schematic_wires",
|
||||
"list_schematic_labels",
|
||||
"get_wire_connections",
|
||||
"generate_netlist",
|
||||
"sync_schematic_to_board",
|
||||
"get_schematic_view",
|
||||
"export_schematic_svg",
|
||||
"export_schematic_pdf"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "library",
|
||||
description: "Footprint library access: search, browse, get footprint information",
|
||||
tools: [
|
||||
"list_libraries",
|
||||
"search_footprints",
|
||||
"list_library_footprints",
|
||||
"get_footprint_info"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "routing",
|
||||
description: "Advanced routing operations: vias, copper pours",
|
||||
tools: [
|
||||
"add_via",
|
||||
"add_copper_pour"
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "autoroute",
|
||||
description: "Freerouting autorouter: automatic PCB routing via Specctra DSN/SES",
|
||||
tools: [
|
||||
"autoroute",
|
||||
"export_dsn",
|
||||
"import_ses",
|
||||
"check_freerouting"
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
* Direct tools that are always visible (not routed)
|
||||
* These are the most frequently used tools
|
||||
*/
|
||||
export const directToolNames = [
|
||||
// Project lifecycle
|
||||
"create_project",
|
||||
"open_project",
|
||||
"save_project",
|
||||
"snapshot_project",
|
||||
"get_project_info",
|
||||
|
||||
// Core PCB operations
|
||||
"place_component",
|
||||
"move_component",
|
||||
"add_net",
|
||||
"route_trace",
|
||||
"get_board_info",
|
||||
"set_board_size",
|
||||
|
||||
// Board setup
|
||||
"add_board_outline",
|
||||
|
||||
// Schematic essentials (always visible so AI uses them correctly)
|
||||
"add_schematic_component",
|
||||
"list_schematic_components",
|
||||
"annotate_schematic",
|
||||
"connect_passthrough",
|
||||
"connect_to_net",
|
||||
"add_schematic_net_label",
|
||||
|
||||
// Schematic <-> PCB sync (F8 equivalent)
|
||||
"sync_schematic_to_board",
|
||||
|
||||
// UI management
|
||||
"check_kicad_ui"
|
||||
];
|
||||
|
||||
// Build lookup maps at module load time
|
||||
const categoryMap = new Map<string, ToolCategory>();
|
||||
const toolCategoryMap = new Map<string, string>();
|
||||
|
||||
export function initializeRegistry() {
|
||||
// Build category map
|
||||
for (const category of toolCategories) {
|
||||
categoryMap.set(category.name, category);
|
||||
|
||||
// Build tool -> category map
|
||||
for (const toolName of category.tools) {
|
||||
toolCategoryMap.set(toolName, category.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a category by name
|
||||
*/
|
||||
export function getCategory(name: string): ToolCategory | undefined {
|
||||
return categoryMap.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the category name for a tool
|
||||
*/
|
||||
export function getToolCategory(toolName: string): string | undefined {
|
||||
return toolCategoryMap.get(toolName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all categories
|
||||
*/
|
||||
export function getAllCategories(): ToolCategory[] {
|
||||
return toolCategories;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all routed tool names (excludes direct tools)
|
||||
*/
|
||||
export function getRoutedToolNames(): string[] {
|
||||
const allRoutedTools: string[] = [];
|
||||
for (const category of toolCategories) {
|
||||
allRoutedTools.push(...category.tools);
|
||||
}
|
||||
return allRoutedTools;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a tool is a direct tool
|
||||
*/
|
||||
export function isDirectTool(toolName: string): boolean {
|
||||
return directToolNames.includes(toolName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a tool is a routed tool
|
||||
*/
|
||||
export function isRoutedTool(toolName: string): boolean {
|
||||
return toolCategoryMap.has(toolName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for tools by keyword
|
||||
* Searches tool names, descriptions, and category names
|
||||
*/
|
||||
export interface SearchResult {
|
||||
category: string;
|
||||
tool: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export function searchTools(query: string): SearchResult[] {
|
||||
const q = query.toLowerCase();
|
||||
const matches: SearchResult[] = [];
|
||||
|
||||
// Search direct tools first
|
||||
for (const toolName of directToolNames) {
|
||||
if (toolName.toLowerCase().includes(q)) {
|
||||
matches.push({
|
||||
category: "direct",
|
||||
tool: toolName,
|
||||
description: `${toolName} (direct tool — call directly, no execute_tool needed)`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Search routed tools by name and category
|
||||
for (const category of toolCategories) {
|
||||
const categoryMatch =
|
||||
category.name.toLowerCase().includes(q) ||
|
||||
category.description.toLowerCase().includes(q);
|
||||
|
||||
for (const toolName of category.tools) {
|
||||
if (toolName.toLowerCase().includes(q) || categoryMatch) {
|
||||
matches.push({
|
||||
category: category.name,
|
||||
tool: toolName,
|
||||
description: `${toolName} (${category.name})`
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return matches.slice(0, 20); // Limit results
|
||||
}
|
||||
|
||||
/**
|
||||
* Get statistics about the tool registry
|
||||
*/
|
||||
export function getRegistryStats() {
|
||||
const routedToolCount = getRoutedToolNames().length;
|
||||
const directToolCount = directToolNames.length;
|
||||
|
||||
return {
|
||||
total_categories: toolCategories.length,
|
||||
total_routed_tools: routedToolCount,
|
||||
total_direct_tools: directToolCount,
|
||||
total_tools: routedToolCount + directToolCount,
|
||||
categories: toolCategories.map(c => ({
|
||||
name: c.name,
|
||||
tool_count: c.tools.length
|
||||
}))
|
||||
};
|
||||
}
|
||||
|
||||
// Initialize on module load
|
||||
initializeRegistry();
|
||||
/**
|
||||
* Tool Registry for KiCAD MCP Server
|
||||
*
|
||||
* Centralizes all tool definitions and provides lookup/search functionality
|
||||
*/
|
||||
|
||||
import { z } from "zod";
|
||||
|
||||
export interface ToolDefinition {
|
||||
name: string;
|
||||
description: string;
|
||||
inputSchema: z.ZodObject<any> | z.ZodType<any>;
|
||||
// Handler will be registered separately in the existing tool files
|
||||
}
|
||||
|
||||
export interface ToolCategory {
|
||||
name: string;
|
||||
description: string;
|
||||
tools: string[]; // Tool names in this category
|
||||
}
|
||||
|
||||
/**
|
||||
* Tool category definitions
|
||||
* Each category groups related tools for better organization
|
||||
*/
|
||||
export const toolCategories: ToolCategory[] = [
|
||||
{
|
||||
name: "board",
|
||||
description: "Board configuration: layers, mounting holes, zones, visualization",
|
||||
tools: [
|
||||
"add_layer",
|
||||
"set_active_layer",
|
||||
"get_layer_list",
|
||||
"add_mounting_hole",
|
||||
"add_board_text",
|
||||
"add_zone",
|
||||
"get_board_extents",
|
||||
"get_board_2d_view",
|
||||
"launch_kicad_ui",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "component",
|
||||
description: "Advanced component operations: edit, delete, search, group, annotate",
|
||||
tools: [
|
||||
"rotate_component",
|
||||
"delete_component",
|
||||
"edit_component",
|
||||
"find_component",
|
||||
"get_component_properties",
|
||||
"add_component_annotation",
|
||||
"group_components",
|
||||
"replace_component",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "export",
|
||||
description: "File export for fabrication and documentation: Gerber, PDF, BOM, 3D models",
|
||||
tools: [
|
||||
"export_gerber",
|
||||
"export_pdf",
|
||||
"export_svg",
|
||||
"export_3d",
|
||||
"export_bom",
|
||||
"export_netlist",
|
||||
"export_position_file",
|
||||
"export_vrml",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "drc",
|
||||
description: "Design rule checking and electrical validation: DRC, net classes, clearances",
|
||||
tools: [
|
||||
"set_design_rules",
|
||||
"get_design_rules",
|
||||
"run_drc",
|
||||
"add_net_class",
|
||||
"assign_net_to_class",
|
||||
"set_layer_constraints",
|
||||
"check_clearance",
|
||||
"get_drc_violations",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "schematic",
|
||||
description:
|
||||
"Schematic operations: create, inspect, add/edit/delete components, wire connections, netlists, annotation",
|
||||
tools: [
|
||||
"create_schematic",
|
||||
"add_schematic_component",
|
||||
"list_schematic_components",
|
||||
"move_schematic_component",
|
||||
"rotate_schematic_component",
|
||||
"annotate_schematic",
|
||||
"add_schematic_wire",
|
||||
"delete_schematic_wire",
|
||||
"add_schematic_junction",
|
||||
"add_schematic_net_label",
|
||||
"delete_schematic_net_label",
|
||||
"connect_to_net",
|
||||
"connect_passthrough",
|
||||
"get_net_connections",
|
||||
"list_schematic_nets",
|
||||
"list_schematic_wires",
|
||||
"list_schematic_labels",
|
||||
"get_wire_connections",
|
||||
"generate_netlist",
|
||||
"sync_schematic_to_board",
|
||||
"get_schematic_view",
|
||||
"export_schematic_svg",
|
||||
"export_schematic_pdf",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "library",
|
||||
description: "Footprint library access: search, browse, get footprint information",
|
||||
tools: ["list_libraries", "search_footprints", "list_library_footprints", "get_footprint_info"],
|
||||
},
|
||||
{
|
||||
name: "routing",
|
||||
description: "Advanced routing operations: vias, copper pours",
|
||||
tools: ["add_via", "add_copper_pour"],
|
||||
},
|
||||
{
|
||||
name: "autoroute",
|
||||
description: "Freerouting autorouter: automatic PCB routing via Specctra DSN/SES",
|
||||
tools: ["autoroute", "export_dsn", "import_ses", "check_freerouting"],
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Direct tools that are always visible (not routed)
|
||||
* These are the most frequently used tools
|
||||
*/
|
||||
export const directToolNames = [
|
||||
// Project lifecycle
|
||||
"create_project",
|
||||
"open_project",
|
||||
"save_project",
|
||||
"snapshot_project",
|
||||
"get_project_info",
|
||||
|
||||
// Core PCB operations
|
||||
"place_component",
|
||||
"move_component",
|
||||
"add_net",
|
||||
"route_trace",
|
||||
"get_board_info",
|
||||
"set_board_size",
|
||||
|
||||
// Board setup
|
||||
"add_board_outline",
|
||||
|
||||
// Schematic essentials (always visible so AI uses them correctly)
|
||||
"add_schematic_component",
|
||||
"list_schematic_components",
|
||||
"annotate_schematic",
|
||||
"connect_passthrough",
|
||||
"connect_to_net",
|
||||
"add_schematic_net_label",
|
||||
|
||||
// Schematic <-> PCB sync (F8 equivalent)
|
||||
"sync_schematic_to_board",
|
||||
|
||||
// UI management
|
||||
"check_kicad_ui",
|
||||
];
|
||||
|
||||
// Build lookup maps at module load time
|
||||
const categoryMap = new Map<string, ToolCategory>();
|
||||
const toolCategoryMap = new Map<string, string>();
|
||||
|
||||
export function initializeRegistry() {
|
||||
// Build category map
|
||||
for (const category of toolCategories) {
|
||||
categoryMap.set(category.name, category);
|
||||
|
||||
// Build tool -> category map
|
||||
for (const toolName of category.tools) {
|
||||
toolCategoryMap.set(toolName, category.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a category by name
|
||||
*/
|
||||
export function getCategory(name: string): ToolCategory | undefined {
|
||||
return categoryMap.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the category name for a tool
|
||||
*/
|
||||
export function getToolCategory(toolName: string): string | undefined {
|
||||
return toolCategoryMap.get(toolName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all categories
|
||||
*/
|
||||
export function getAllCategories(): ToolCategory[] {
|
||||
return toolCategories;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all routed tool names (excludes direct tools)
|
||||
*/
|
||||
export function getRoutedToolNames(): string[] {
|
||||
const allRoutedTools: string[] = [];
|
||||
for (const category of toolCategories) {
|
||||
allRoutedTools.push(...category.tools);
|
||||
}
|
||||
return allRoutedTools;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a tool is a direct tool
|
||||
*/
|
||||
export function isDirectTool(toolName: string): boolean {
|
||||
return directToolNames.includes(toolName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a tool is a routed tool
|
||||
*/
|
||||
export function isRoutedTool(toolName: string): boolean {
|
||||
return toolCategoryMap.has(toolName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for tools by keyword
|
||||
* Searches tool names, descriptions, and category names
|
||||
*/
|
||||
export interface SearchResult {
|
||||
category: string;
|
||||
tool: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export function searchTools(query: string): SearchResult[] {
|
||||
const q = query.toLowerCase();
|
||||
const matches: SearchResult[] = [];
|
||||
|
||||
// Search direct tools first
|
||||
for (const toolName of directToolNames) {
|
||||
if (toolName.toLowerCase().includes(q)) {
|
||||
matches.push({
|
||||
category: "direct",
|
||||
tool: toolName,
|
||||
description: `${toolName} (direct tool — call directly, no execute_tool needed)`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Search routed tools by name and category
|
||||
for (const category of toolCategories) {
|
||||
const categoryMatch =
|
||||
category.name.toLowerCase().includes(q) || category.description.toLowerCase().includes(q);
|
||||
|
||||
for (const toolName of category.tools) {
|
||||
if (toolName.toLowerCase().includes(q) || categoryMatch) {
|
||||
matches.push({
|
||||
category: category.name,
|
||||
tool: toolName,
|
||||
description: `${toolName} (${category.name})`,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return matches.slice(0, 20); // Limit results
|
||||
}
|
||||
|
||||
/**
|
||||
* Get statistics about the tool registry
|
||||
*/
|
||||
export function getRegistryStats() {
|
||||
const routedToolCount = getRoutedToolNames().length;
|
||||
const directToolCount = directToolNames.length;
|
||||
|
||||
return {
|
||||
total_categories: toolCategories.length,
|
||||
total_routed_tools: routedToolCount,
|
||||
total_direct_tools: directToolCount,
|
||||
total_tools: routedToolCount + directToolCount,
|
||||
categories: toolCategories.map((c) => ({
|
||||
name: c.name,
|
||||
tool_count: c.tools.length,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
// Initialize on module load
|
||||
initializeRegistry();
|
||||
|
||||
@@ -1,251 +1,296 @@
|
||||
/**
|
||||
* Router Tools for KiCAD MCP Server
|
||||
*
|
||||
* Provides discovery and execution of routed tools
|
||||
*/
|
||||
|
||||
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import { z } from 'zod';
|
||||
import { logger } from '../logger.js';
|
||||
import {
|
||||
getAllCategories,
|
||||
getCategory,
|
||||
getToolCategory,
|
||||
searchTools as registrySearchTools,
|
||||
getRegistryStats
|
||||
} from './registry.js';
|
||||
|
||||
// Command function type for KiCAD script calls
|
||||
type CommandFunction = (command: string, params: Record<string, unknown>) => Promise<any>;
|
||||
|
||||
// Map to store tool execution handlers
|
||||
// This will be populated by registerToolHandler()
|
||||
const toolHandlers = new Map<string, (params: any) => Promise<any>>();
|
||||
|
||||
/**
|
||||
* Register a tool handler for execution via execute_tool
|
||||
* This should be called by each tool registration function
|
||||
*/
|
||||
export function registerToolHandler(
|
||||
toolName: string,
|
||||
handler: (params: any) => Promise<any>
|
||||
): void {
|
||||
toolHandlers.set(toolName, handler);
|
||||
logger.debug(`Registered handler for routed tool: ${toolName}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register all router tools with the MCP server
|
||||
*/
|
||||
export function registerRouterTools(server: McpServer, callKicadScript: CommandFunction): void {
|
||||
logger.info('Registering router tools');
|
||||
|
||||
// ============================================================================
|
||||
// list_tool_categories
|
||||
// ============================================================================
|
||||
server.tool(
|
||||
"list_tool_categories",
|
||||
{
|
||||
// No parameters
|
||||
},
|
||||
async () => {
|
||||
logger.debug('Listing tool categories');
|
||||
|
||||
const stats = getRegistryStats();
|
||||
const categories = getAllCategories();
|
||||
|
||||
const result = {
|
||||
total_categories: stats.total_categories,
|
||||
total_routed_tools: stats.total_routed_tools,
|
||||
total_direct_tools: stats.total_direct_tools,
|
||||
note: "Use get_category_tools to see tools in each category. Direct tools are always available.",
|
||||
categories: categories.map(c => ({
|
||||
name: c.name,
|
||||
description: c.description,
|
||||
tool_count: c.tools.length
|
||||
}))
|
||||
};
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result, null, 2)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ============================================================================
|
||||
// get_category_tools
|
||||
// ============================================================================
|
||||
server.tool(
|
||||
"get_category_tools",
|
||||
{
|
||||
category: z.string().describe("Category name from list_tool_categories")
|
||||
},
|
||||
async ({ category }) => {
|
||||
logger.debug(`Getting tools for category: ${category}`);
|
||||
|
||||
const categoryData = getCategory(category);
|
||||
|
||||
if (!categoryData) {
|
||||
const availableCategories = getAllCategories().map(c => c.name);
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify({
|
||||
error: `Unknown category: ${category}`,
|
||||
available_categories: availableCategories
|
||||
}, null, 2)
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
// Return tool names and basic info
|
||||
// Full schema is available via tool introspection once tool is called
|
||||
const result = {
|
||||
category: categoryData.name,
|
||||
description: categoryData.description,
|
||||
tool_count: categoryData.tools.length,
|
||||
tools: categoryData.tools.map(toolName => ({
|
||||
name: toolName,
|
||||
description: `Use execute_tool with tool_name="${toolName}" to run this tool`
|
||||
})),
|
||||
note: "Use execute_tool to run any of these tools with appropriate parameters"
|
||||
};
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result, null, 2)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ============================================================================
|
||||
// execute_tool
|
||||
// ============================================================================
|
||||
server.tool(
|
||||
"execute_tool",
|
||||
{
|
||||
tool_name: z.string().describe("Tool name from get_category_tools"),
|
||||
params: z.record(z.unknown()).optional().describe("Tool parameters (optional)")
|
||||
},
|
||||
async ({ tool_name, params }) => {
|
||||
logger.info(`Executing routed tool: ${tool_name}`);
|
||||
|
||||
// Check if tool exists in registry
|
||||
const category = getToolCategory(tool_name);
|
||||
|
||||
if (!category) {
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify({
|
||||
error: `Unknown tool: ${tool_name}`,
|
||||
hint: "Use list_tool_categories and get_category_tools to find available tools"
|
||||
}, null, 2)
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
// Get the handler
|
||||
const handler = toolHandlers.get(tool_name);
|
||||
|
||||
if (!handler) {
|
||||
// Tool is in registry but handler not registered yet
|
||||
// This means the tool exists but hasn't been migrated to router pattern yet
|
||||
// Fall back to calling KiCAD script directly
|
||||
logger.warn(`Tool ${tool_name} in registry but no handler registered, falling back to direct call`);
|
||||
|
||||
try {
|
||||
const result = await callKicadScript(tool_name, params || {});
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify({
|
||||
tool: tool_name,
|
||||
category: category,
|
||||
result: result
|
||||
}, null, 2)
|
||||
}]
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify({
|
||||
error: `Tool execution failed: ${(error as Error).message}`,
|
||||
tool: tool_name,
|
||||
category: category
|
||||
}, null, 2)
|
||||
}]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Execute the tool via its handler
|
||||
try {
|
||||
const result = await handler(params || {});
|
||||
|
||||
// The handler already returns MCP-formatted response
|
||||
// Just add metadata
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify({
|
||||
tool: tool_name,
|
||||
category: category,
|
||||
...result
|
||||
}, null, 2)
|
||||
}]
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify({
|
||||
error: `Tool execution failed: ${(error as Error).message}`,
|
||||
tool: tool_name,
|
||||
category: category
|
||||
}, null, 2)
|
||||
}]
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// ============================================================================
|
||||
// search_tools
|
||||
// ============================================================================
|
||||
server.tool(
|
||||
"search_tools",
|
||||
{
|
||||
query: z.string().describe("Search term (e.g., 'gerber', 'zone', 'export', 'drc')")
|
||||
},
|
||||
async ({ query }) => {
|
||||
logger.debug(`Searching tools for: ${query}`);
|
||||
|
||||
const matches = registrySearchTools(query);
|
||||
|
||||
const result = {
|
||||
query: query,
|
||||
count: matches.length,
|
||||
matches: matches,
|
||||
note: matches.length > 0
|
||||
? "Use execute_tool with the tool name to run it"
|
||||
: "No tools found matching your query. Try list_tool_categories to browse all categories."
|
||||
};
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result, null, 2)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
logger.info('Router tools registered successfully');
|
||||
}
|
||||
/**
|
||||
* Router Tools for KiCAD MCP Server
|
||||
*
|
||||
* Provides discovery and execution of routed tools
|
||||
*/
|
||||
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { z } from "zod";
|
||||
import { logger } from "../logger.js";
|
||||
import {
|
||||
getAllCategories,
|
||||
getCategory,
|
||||
getToolCategory,
|
||||
searchTools as registrySearchTools,
|
||||
getRegistryStats,
|
||||
} from "./registry.js";
|
||||
|
||||
// Command function type for KiCAD script calls
|
||||
type CommandFunction = (command: string, params: Record<string, unknown>) => Promise<any>;
|
||||
|
||||
// Map to store tool execution handlers
|
||||
// This will be populated by registerToolHandler()
|
||||
const toolHandlers = new Map<string, (params: any) => Promise<any>>();
|
||||
|
||||
/**
|
||||
* Register a tool handler for execution via execute_tool
|
||||
* This should be called by each tool registration function
|
||||
*/
|
||||
export function registerToolHandler(
|
||||
toolName: string,
|
||||
handler: (params: any) => Promise<any>,
|
||||
): void {
|
||||
toolHandlers.set(toolName, handler);
|
||||
logger.debug(`Registered handler for routed tool: ${toolName}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register all router tools with the MCP server
|
||||
*/
|
||||
export function registerRouterTools(server: McpServer, callKicadScript: CommandFunction): void {
|
||||
logger.info("Registering router tools");
|
||||
|
||||
// ============================================================================
|
||||
// list_tool_categories
|
||||
// ============================================================================
|
||||
server.tool(
|
||||
"list_tool_categories",
|
||||
{
|
||||
// No parameters
|
||||
},
|
||||
async () => {
|
||||
logger.debug("Listing tool categories");
|
||||
|
||||
const stats = getRegistryStats();
|
||||
const categories = getAllCategories();
|
||||
|
||||
const result = {
|
||||
total_categories: stats.total_categories,
|
||||
total_routed_tools: stats.total_routed_tools,
|
||||
total_direct_tools: stats.total_direct_tools,
|
||||
note: "Use get_category_tools to see tools in each category. Direct tools are always available.",
|
||||
categories: categories.map((c) => ({
|
||||
name: c.name,
|
||||
description: c.description,
|
||||
tool_count: c.tools.length,
|
||||
})),
|
||||
};
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: JSON.stringify(result, null, 2),
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
// ============================================================================
|
||||
// get_category_tools
|
||||
// ============================================================================
|
||||
server.tool(
|
||||
"get_category_tools",
|
||||
{
|
||||
category: z.string().describe("Category name from list_tool_categories"),
|
||||
},
|
||||
async ({ category }) => {
|
||||
logger.debug(`Getting tools for category: ${category}`);
|
||||
|
||||
const categoryData = getCategory(category);
|
||||
|
||||
if (!categoryData) {
|
||||
const availableCategories = getAllCategories().map((c) => c.name);
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: JSON.stringify(
|
||||
{
|
||||
error: `Unknown category: ${category}`,
|
||||
available_categories: availableCategories,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
// Return tool names and basic info
|
||||
// Full schema is available via tool introspection once tool is called
|
||||
const result = {
|
||||
category: categoryData.name,
|
||||
description: categoryData.description,
|
||||
tool_count: categoryData.tools.length,
|
||||
tools: categoryData.tools.map((toolName) => ({
|
||||
name: toolName,
|
||||
description: `Use execute_tool with tool_name="${toolName}" to run this tool`,
|
||||
})),
|
||||
note: "Use execute_tool to run any of these tools with appropriate parameters",
|
||||
};
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: JSON.stringify(result, null, 2),
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
// ============================================================================
|
||||
// execute_tool
|
||||
// ============================================================================
|
||||
server.tool(
|
||||
"execute_tool",
|
||||
{
|
||||
tool_name: z.string().describe("Tool name from get_category_tools"),
|
||||
params: z.record(z.unknown()).optional().describe("Tool parameters (optional)"),
|
||||
},
|
||||
async ({ tool_name, params }) => {
|
||||
logger.info(`Executing routed tool: ${tool_name}`);
|
||||
|
||||
// Check if tool exists in registry
|
||||
const category = getToolCategory(tool_name);
|
||||
|
||||
if (!category) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: JSON.stringify(
|
||||
{
|
||||
error: `Unknown tool: ${tool_name}`,
|
||||
hint: "Use list_tool_categories and get_category_tools to find available tools",
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
// Get the handler
|
||||
const handler = toolHandlers.get(tool_name);
|
||||
|
||||
if (!handler) {
|
||||
// Tool is in registry but handler not registered yet
|
||||
// This means the tool exists but hasn't been migrated to router pattern yet
|
||||
// Fall back to calling KiCAD script directly
|
||||
logger.warn(
|
||||
`Tool ${tool_name} in registry but no handler registered, falling back to direct call`,
|
||||
);
|
||||
|
||||
try {
|
||||
const result = await callKicadScript(tool_name, params || {});
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: JSON.stringify(
|
||||
{
|
||||
tool: tool_name,
|
||||
category: category,
|
||||
result: result,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: JSON.stringify(
|
||||
{
|
||||
error: `Tool execution failed: ${(error as Error).message}`,
|
||||
tool: tool_name,
|
||||
category: category,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Execute the tool via its handler
|
||||
try {
|
||||
const result = await handler(params || {});
|
||||
|
||||
// The handler already returns MCP-formatted response
|
||||
// Just add metadata
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: JSON.stringify(
|
||||
{
|
||||
tool: tool_name,
|
||||
category: category,
|
||||
...result,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: JSON.stringify(
|
||||
{
|
||||
error: `Tool execution failed: ${(error as Error).message}`,
|
||||
tool: tool_name,
|
||||
category: category,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// ============================================================================
|
||||
// search_tools
|
||||
// ============================================================================
|
||||
server.tool(
|
||||
"search_tools",
|
||||
{
|
||||
query: z.string().describe("Search term (e.g., 'gerber', 'zone', 'export', 'drc')"),
|
||||
},
|
||||
async ({ query }) => {
|
||||
logger.debug(`Searching tools for: ${query}`);
|
||||
|
||||
const matches = registrySearchTools(query);
|
||||
|
||||
const result = {
|
||||
query: query,
|
||||
count: matches.length,
|
||||
matches: matches,
|
||||
note:
|
||||
matches.length > 0
|
||||
? "Use execute_tool with the tool name to run it"
|
||||
: "No tools found matching your query. Try list_tool_categories to browse all categories.",
|
||||
};
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: JSON.stringify(result, null, 2),
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
logger.info("Router tools registered successfully");
|
||||
}
|
||||
|
||||
@@ -5,10 +5,7 @@
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { z } from "zod";
|
||||
|
||||
export function registerRoutingTools(
|
||||
server: McpServer,
|
||||
callKicadScript: Function,
|
||||
) {
|
||||
export function registerRoutingTools(server: McpServer, callKicadScript: Function) {
|
||||
// Add net tool
|
||||
server.tool(
|
||||
"add_net",
|
||||
@@ -79,10 +76,7 @@ export function registerRoutingTools(
|
||||
})
|
||||
.describe("Via position"),
|
||||
net: z.string().describe("Net name"),
|
||||
viaType: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("Via type (through, blind, buried)"),
|
||||
viaType: z.string().optional().describe("Via type (through, blind, buried)"),
|
||||
},
|
||||
async (args: any) => {
|
||||
const result = await callKicadScript("add_via", args);
|
||||
@@ -130,10 +124,7 @@ export function registerRoutingTools(
|
||||
"delete_trace",
|
||||
"Delete traces from the PCB. Can delete by UUID, position, or bulk-delete all traces on a net.",
|
||||
{
|
||||
traceUuid: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("UUID of a specific trace to delete"),
|
||||
traceUuid: z.string().optional().describe("UUID of a specific trace to delete"),
|
||||
position: z
|
||||
.object({
|
||||
x: z.number(),
|
||||
@@ -142,18 +133,9 @@ export function registerRoutingTools(
|
||||
})
|
||||
.optional()
|
||||
.describe("Delete trace nearest to this position"),
|
||||
net: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("Delete all traces on this net (bulk delete)"),
|
||||
layer: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("Filter by layer when using net-based deletion"),
|
||||
includeVias: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe("Include vias in net-based deletion"),
|
||||
net: z.string().optional().describe("Delete all traces on this net (bulk delete)"),
|
||||
layer: z.string().optional().describe("Filter by layer when using net-based deletion"),
|
||||
includeVias: z.boolean().optional().describe("Include vias in net-based deletion"),
|
||||
},
|
||||
async (args: any) => {
|
||||
const result = await callKicadScript("delete_trace", args);
|
||||
@@ -209,10 +191,7 @@ export function registerRoutingTools(
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe("Include statistics (track count, total length, etc.)"),
|
||||
unit: z
|
||||
.enum(["mm", "inch"])
|
||||
.optional()
|
||||
.describe("Unit for length measurements"),
|
||||
unit: z.enum(["mm", "inch"]).optional().describe("Unit for length measurements"),
|
||||
},
|
||||
async (args: any) => {
|
||||
const result = await callKicadScript("get_nets_list", args);
|
||||
@@ -334,9 +313,13 @@ export function registerRoutingTools(
|
||||
"PREFERRED tool for pad-to-pad routing. Looks up pad positions automatically, detects the net from the pad, and — critically — if the two pads are on different copper layers (e.g. J1 on F.Cu and J2 on B.Cu) automatically inserts a via at the midpoint so the connection is complete. Always use this instead of route_trace when routing between named component pads.",
|
||||
{
|
||||
fromRef: z.string().describe("Reference of the source component (e.g. 'U2')"),
|
||||
fromPad: z.union([z.string(), z.number()]).describe("Pad number on the source component (e.g. '6' or 6)"),
|
||||
fromPad: z
|
||||
.union([z.string(), z.number()])
|
||||
.describe("Pad number on the source component (e.g. '6' or 6)"),
|
||||
toRef: z.string().describe("Reference of the target component (e.g. 'U1')"),
|
||||
toPad: z.union([z.string(), z.number()]).describe("Pad number on the target component (e.g. '15' or 15)"),
|
||||
toPad: z
|
||||
.union([z.string(), z.number()])
|
||||
.describe("Pad number on the target component (e.g. '15' or 15)"),
|
||||
layer: z.string().optional().describe("PCB layer (default: F.Cu)"),
|
||||
width: z.number().optional().describe("Trace width in mm (default: board default)"),
|
||||
net: z.string().optional().describe("Net name override (default: auto-detected from pad)"),
|
||||
@@ -362,10 +345,7 @@ export function registerRoutingTools(
|
||||
.describe(
|
||||
"References of the target components in same order as sourceRefs (e.g. ['U2', 'R2', 'C2'])",
|
||||
),
|
||||
includeVias: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe("Also copy vias (default: true)"),
|
||||
includeVias: z.boolean().optional().describe("Also copy vias (default: true)"),
|
||||
traceWidth: z
|
||||
.number()
|
||||
.optional()
|
||||
|
||||
@@ -5,10 +5,7 @@
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { z } from "zod";
|
||||
|
||||
export function registerSchematicTools(
|
||||
server: McpServer,
|
||||
callKicadScript: Function,
|
||||
) {
|
||||
export function registerSchematicTools(server: McpServer, callKicadScript: Function) {
|
||||
// Create schematic tool
|
||||
server.tool(
|
||||
"create_schematic",
|
||||
@@ -38,9 +35,7 @@ export function registerSchematicTools(
|
||||
schematicPath: z.string().describe("Path to the schematic file"),
|
||||
symbol: z
|
||||
.string()
|
||||
.describe(
|
||||
"Symbol library:name reference (e.g., Device:R, EDA-MCP:ESP32-C3)",
|
||||
),
|
||||
.describe("Symbol library:name reference (e.g., Device:R, EDA-MCP:ESP32-C3)"),
|
||||
reference: z.string().describe("Component reference (e.g., R1, U1)"),
|
||||
value: z.string().optional().describe("Component value"),
|
||||
footprint: z
|
||||
@@ -82,10 +77,7 @@ export function registerSchematicTools(
|
||||
},
|
||||
};
|
||||
|
||||
const result = await callKicadScript(
|
||||
"add_schematic_component",
|
||||
transformed,
|
||||
);
|
||||
const result = await callKicadScript("add_schematic_component", transformed);
|
||||
if (result.success) {
|
||||
return {
|
||||
content: [
|
||||
@@ -122,9 +114,7 @@ To remove a footprint from a PCB, use delete_component instead.`,
|
||||
schematicPath: z.string().describe("Path to the .kicad_sch file"),
|
||||
reference: z
|
||||
.string()
|
||||
.describe(
|
||||
"Reference designator of the component to remove (e.g. R1, U3)",
|
||||
),
|
||||
.describe("Reference designator of the component to remove (e.g. R1, U3)"),
|
||||
},
|
||||
async (args: { schematicPath: string; reference: string }) => {
|
||||
const result = await callKicadScript("delete_schematic_component", args);
|
||||
@@ -162,14 +152,27 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
|
||||
{
|
||||
schematicPath: z.string().describe("Path to the .kicad_sch file"),
|
||||
reference: z.string().describe("Current reference designator of the component (e.g. R1, U3)"),
|
||||
footprint: z.string().optional().describe("New KiCAD footprint string (e.g. Resistor_SMD:R_0603_1608Metric)"),
|
||||
footprint: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("New KiCAD footprint string (e.g. Resistor_SMD:R_0603_1608Metric)"),
|
||||
value: z.string().optional().describe("New value string (e.g. 10k, 100nF)"),
|
||||
newReference: z.string().optional().describe("Rename the reference designator (e.g. R1 → R10)"),
|
||||
fieldPositions: z.record(z.object({
|
||||
x: z.number(),
|
||||
y: z.number(),
|
||||
angle: z.number().optional().default(0),
|
||||
})).optional().describe("Reposition field labels: map of field name to {x, y, angle} (e.g. {\"Reference\": {\"x\": 12.5, \"y\": 17.0}})"),
|
||||
newReference: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("Rename the reference designator (e.g. R1 → R10)"),
|
||||
fieldPositions: z
|
||||
.record(
|
||||
z.object({
|
||||
x: z.number(),
|
||||
y: z.number(),
|
||||
angle: z.number().optional().default(0),
|
||||
}),
|
||||
)
|
||||
.optional()
|
||||
.describe(
|
||||
'Reposition field labels: map of field name to {x, y, angle} (e.g. {"Reference": {"x": 12.5, "y": 17.0}})',
|
||||
),
|
||||
},
|
||||
async (args: {
|
||||
schematicPath: string;
|
||||
@@ -220,20 +223,24 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
|
||||
: "unknown";
|
||||
const fieldLines = Object.entries(result.fields ?? {}).map(
|
||||
([name, f]: [string, any]) =>
|
||||
` ${name}: "${f.value}" @ (${f.x}, ${f.y}, angle=${f.angle}°)`
|
||||
` ${name}: "${f.value}" @ (${f.x}, ${f.y}, angle=${f.angle}°)`,
|
||||
);
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: `Component ${result.reference} at ${pos}\nFields:\n${fieldLines.join("\n")}`,
|
||||
}],
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Component ${result.reference} at ${pos}\nFields:\n${fieldLines.join("\n")}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: `Failed to get component: ${result.message || "Unknown error"}`,
|
||||
}],
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: `Failed to get component: ${result.message || "Unknown error"}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
);
|
||||
@@ -251,13 +258,8 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
|
||||
snapToPins: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe(
|
||||
"Snap the first and last waypoints to the nearest pin (default: true)",
|
||||
),
|
||||
snapTolerance: z
|
||||
.number()
|
||||
.optional()
|
||||
.describe("Maximum snap distance in mm (default: 1.0)"),
|
||||
.describe("Snap the first and last waypoints to the nearest pin (default: true)"),
|
||||
snapTolerance: z.number().optional().describe("Maximum snap distance in mm (default: 1.0)"),
|
||||
},
|
||||
async (args: {
|
||||
schematicPath: string;
|
||||
@@ -294,10 +296,7 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
|
||||
"Place a junction dot at a wire intersection in the schematic. Required at T-branch and X-cross points so KiCAD recognises the electrical connection.",
|
||||
{
|
||||
schematicPath: z.string().describe("Path to the .kicad_sch file"),
|
||||
position: z
|
||||
.array(z.number())
|
||||
.length(2)
|
||||
.describe("Junction position [x, y] in mm"),
|
||||
position: z.array(z.number()).length(2).describe("Junction position [x, y] in mm"),
|
||||
},
|
||||
async (args: { schematicPath: string; position: number[] }) => {
|
||||
const result = await callKicadScript("add_schematic_junction", args);
|
||||
@@ -329,19 +328,10 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
|
||||
"Add a net label to the schematic",
|
||||
{
|
||||
schematicPath: z.string().describe("Path to the schematic file"),
|
||||
netName: z
|
||||
.string()
|
||||
.describe("Name of the net (e.g., VCC, GND, SIGNAL_1)"),
|
||||
position: z
|
||||
.array(z.number())
|
||||
.length(2)
|
||||
.describe("Position [x, y] for the label"),
|
||||
netName: z.string().describe("Name of the net (e.g., VCC, GND, SIGNAL_1)"),
|
||||
position: z.array(z.number()).length(2).describe("Position [x, y] for the label"),
|
||||
},
|
||||
async (args: {
|
||||
schematicPath: string;
|
||||
netName: string;
|
||||
position: number[];
|
||||
}) => {
|
||||
async (args: { schematicPath: string; netName: string; position: number[] }) => {
|
||||
const result = await callKicadScript("add_schematic_net_label", args);
|
||||
if (result.success) {
|
||||
return {
|
||||
@@ -451,9 +441,7 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
|
||||
async (args: { schematicPath: string; x: number; y: number }) => {
|
||||
const result = await callKicadScript("get_wire_connections", args);
|
||||
if (result.success && result.pins) {
|
||||
const pinList = result.pins
|
||||
.map((p: any) => ` - ${p.component}/${p.pin}`)
|
||||
.join("\n");
|
||||
const pinList = result.pins.map((p: any) => ` - ${p.component}/${p.pin}`).join("\n");
|
||||
const wireList = (result.wires ?? [])
|
||||
.map((w: any) => ` - (${w.start.x},${w.start.y}) → (${w.end.x},${w.end.y})`)
|
||||
.join("\n");
|
||||
@@ -484,9 +472,7 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
|
||||
"Returns the exact x/y coordinates of every pin on a schematic component. Use this before add_schematic_net_label to place labels correctly on pin endpoints.",
|
||||
{
|
||||
schematicPath: z.string().describe("Path to the schematic file"),
|
||||
reference: z
|
||||
.string()
|
||||
.describe("Component reference designator (e.g. U1, R1, J2)"),
|
||||
reference: z.string().describe("Component reference designator (e.g. U1, R1, J2)"),
|
||||
},
|
||||
async (args: { schematicPath: string; reference: string }) => {
|
||||
const result = await callKicadScript("get_schematic_pin_locations", args);
|
||||
@@ -541,23 +527,16 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
|
||||
pinOffset?: number;
|
||||
}) => {
|
||||
const result = await callKicadScript("connect_passthrough", args);
|
||||
if (
|
||||
result.success !== false ||
|
||||
(result.connected && result.connected.length > 0)
|
||||
) {
|
||||
if (result.success !== false || (result.connected && result.connected.length > 0)) {
|
||||
const lines: string[] = [];
|
||||
if (result.connected?.length)
|
||||
lines.push(
|
||||
`Connected (${result.connected.length}): ${result.connected.slice(0, 5).join(", ")}${result.connected.length > 5 ? " ..." : ""}`,
|
||||
);
|
||||
if (result.failed?.length)
|
||||
lines.push(
|
||||
`Failed (${result.failed.length}): ${result.failed.join(", ")}`,
|
||||
);
|
||||
lines.push(`Failed (${result.failed.length}): ${result.failed.join(", ")}`);
|
||||
return {
|
||||
content: [
|
||||
{ type: "text", text: result.message + "\n" + lines.join("\n") },
|
||||
],
|
||||
content: [{ type: "text", text: result.message + "\n" + lines.join("\n") }],
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
@@ -581,7 +560,10 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
|
||||
filter: z
|
||||
.object({
|
||||
libId: z.string().optional().describe("Filter by library ID (e.g., 'Device:R')"),
|
||||
referencePrefix: z.string().optional().describe("Filter by reference prefix (e.g., 'R', 'C', 'U')"),
|
||||
referencePrefix: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("Filter by reference prefix (e.g., 'R', 'C', 'U')"),
|
||||
})
|
||||
.optional()
|
||||
.describe("Optional filters"),
|
||||
@@ -640,9 +622,7 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
|
||||
};
|
||||
}
|
||||
const lines = nets.map((n: any) => {
|
||||
const conns = (n.connections || [])
|
||||
.map((c: any) => `${c.component}/${c.pin}`)
|
||||
.join(", ");
|
||||
const conns = (n.connections || []).map((c: any) => `${c.component}/${c.pin}`).join(", ");
|
||||
return ` ${n.name}: ${conns || "(no connections)"}`;
|
||||
});
|
||||
return {
|
||||
@@ -680,8 +660,7 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
|
||||
};
|
||||
}
|
||||
const lines = wires.map(
|
||||
(w: any) =>
|
||||
` (${w.start.x}, ${w.start.y}) → (${w.end.x}, ${w.end.y})`,
|
||||
(w: any) => ` (${w.start.x}, ${w.start.y}) → (${w.end.x}, ${w.end.y})`,
|
||||
);
|
||||
return {
|
||||
content: [
|
||||
@@ -718,8 +697,7 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
|
||||
};
|
||||
}
|
||||
const lines = labels.map(
|
||||
(l: any) =>
|
||||
` [${l.type}] ${l.name} at (${l.position.x}, ${l.position.y})`,
|
||||
(l: any) => ` [${l.type}] ${l.name} at (${l.position.x}, ${l.position.y})`,
|
||||
);
|
||||
return {
|
||||
content: [
|
||||
@@ -789,10 +767,7 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
|
||||
schematicPath: z.string().describe("Path to the .kicad_sch file"),
|
||||
reference: z.string().describe("Reference designator (e.g., R1, U1)"),
|
||||
angle: z.number().describe("Rotation angle in degrees (0, 90, 180, 270)"),
|
||||
mirror: z
|
||||
.enum(["x", "y"])
|
||||
.optional()
|
||||
.describe("Optional mirror axis"),
|
||||
mirror: z.enum(["x", "y"]).optional().describe("Optional mirror axis"),
|
||||
},
|
||||
async (args: {
|
||||
schematicPath: string;
|
||||
@@ -836,14 +811,10 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
|
||||
const annotated = result.annotated || [];
|
||||
if (annotated.length === 0) {
|
||||
return {
|
||||
content: [
|
||||
{ type: "text", text: "All components are already annotated." },
|
||||
],
|
||||
content: [{ type: "text", text: "All components are already annotated." }],
|
||||
};
|
||||
}
|
||||
const lines = annotated.map(
|
||||
(a: any) => ` ${a.oldReference} → ${a.newReference}`,
|
||||
);
|
||||
const lines = annotated.map((a: any) => ` ${a.oldReference} → ${a.newReference}`);
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
@@ -871,12 +842,8 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
|
||||
"Remove a wire from the schematic by start and end coordinates.",
|
||||
{
|
||||
schematicPath: z.string().describe("Path to the .kicad_sch file"),
|
||||
start: z
|
||||
.object({ x: z.number(), y: z.number() })
|
||||
.describe("Wire start position"),
|
||||
end: z
|
||||
.object({ x: z.number(), y: z.number() })
|
||||
.describe("Wire end position"),
|
||||
start: z.object({ x: z.number(), y: z.number() }).describe("Wire start position"),
|
||||
end: z.object({ x: z.number(), y: z.number() }).describe("Wire end position"),
|
||||
},
|
||||
async (args: {
|
||||
schematicPath: string;
|
||||
@@ -953,16 +920,9 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
|
||||
{
|
||||
schematicPath: z.string().describe("Path to the .kicad_sch file"),
|
||||
outputPath: z.string().describe("Output SVG file path"),
|
||||
blackAndWhite: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe("Export in black and white"),
|
||||
blackAndWhite: z.boolean().optional().describe("Export in black and white"),
|
||||
},
|
||||
async (args: {
|
||||
schematicPath: string;
|
||||
outputPath: string;
|
||||
blackAndWhite?: boolean;
|
||||
}) => {
|
||||
async (args: { schematicPath: string; outputPath: string; blackAndWhite?: boolean }) => {
|
||||
const result = await callKicadScript("export_schematic_svg", args);
|
||||
if (result.success) {
|
||||
return {
|
||||
@@ -993,16 +953,9 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
|
||||
{
|
||||
schematicPath: z.string().describe("Path to the .kicad_sch file"),
|
||||
outputPath: z.string().describe("Output PDF file path"),
|
||||
blackAndWhite: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe("Export in black and white"),
|
||||
blackAndWhite: z.boolean().optional().describe("Export in black and white"),
|
||||
},
|
||||
async (args: {
|
||||
schematicPath: string;
|
||||
outputPath: string;
|
||||
blackAndWhite?: boolean;
|
||||
}) => {
|
||||
async (args: { schematicPath: string; outputPath: string; blackAndWhite?: boolean }) => {
|
||||
const result = await callKicadScript("export_schematic_pdf", args);
|
||||
if (result.success) {
|
||||
return {
|
||||
@@ -1032,10 +985,7 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
|
||||
"Return a rasterized image of the schematic (PNG by default, or SVG). Uses kicad-cli to export SVG, then converts to PNG via cairosvg. Use this for visual feedback after placing or wiring components.",
|
||||
{
|
||||
schematicPath: z.string().describe("Path to the .kicad_sch file"),
|
||||
format: z
|
||||
.enum(["png", "svg"])
|
||||
.optional()
|
||||
.describe("Output format (default: png)"),
|
||||
format: z.enum(["png", "svg"]).optional().describe("Output format (default: png)"),
|
||||
width: z.number().optional().describe("Image width in pixels (default: 1200)"),
|
||||
height: z.number().optional().describe("Image height in pixels (default: 900)"),
|
||||
},
|
||||
@@ -1086,17 +1036,13 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
|
||||
"run_erc",
|
||||
"Runs the KiCAD Electrical Rules Check (ERC) on a schematic and returns all violations. Use after wiring to verify the schematic before generating a netlist.",
|
||||
{
|
||||
schematicPath: z
|
||||
.string()
|
||||
.describe("Path to the .kicad_sch schematic file"),
|
||||
schematicPath: z.string().describe("Path to the .kicad_sch schematic file"),
|
||||
},
|
||||
async (args: { schematicPath: string }) => {
|
||||
const result = await callKicadScript("run_erc", args);
|
||||
if (result.success) {
|
||||
const violations: any[] = result.violations || [];
|
||||
const lines: string[] = [
|
||||
`ERC result: ${violations.length} violation(s)`,
|
||||
];
|
||||
const lines: string[] = [`ERC result: ${violations.length} violation(s)`];
|
||||
if (result.summary?.by_severity) {
|
||||
const s = result.summary.by_severity;
|
||||
lines.push(
|
||||
@@ -1183,12 +1129,8 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
|
||||
"sync_schematic_to_board",
|
||||
"Import the schematic netlist into the PCB board — equivalent to pressing F8 in KiCAD (Tools → Update PCB from Schematic). MUST be called after the schematic is complete and before placing or routing components on the PCB. Without this step, the board has no footprints and no net assignments — place_component and route_pad_to_pad will produce an empty, unroutable board.",
|
||||
{
|
||||
schematicPath: z
|
||||
.string()
|
||||
.describe("Absolute path to the .kicad_sch schematic file"),
|
||||
boardPath: z
|
||||
.string()
|
||||
.describe("Absolute path to the .kicad_pcb board file"),
|
||||
schematicPath: z.string().describe("Absolute path to the .kicad_sch schematic file"),
|
||||
boardPath: z.string().describe("Absolute path to the .kicad_pcb board file"),
|
||||
},
|
||||
async (args: { schematicPath: string; boardPath: string }) => {
|
||||
const result = await callKicadScript("sync_schematic_to_board", args);
|
||||
@@ -1218,8 +1160,13 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
|
||||
},
|
||||
async (args: {
|
||||
schematicPath: string;
|
||||
x1: number; y1: number; x2: number; y2: number;
|
||||
format?: string; width?: number; height?: number;
|
||||
x1: number;
|
||||
y1: number;
|
||||
x2: number;
|
||||
y2: number;
|
||||
format?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
}) => {
|
||||
const result = await callKicadScript("get_schematic_view_region", args);
|
||||
if (result.success && result.imageData) {
|
||||
@@ -1227,11 +1174,13 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
|
||||
return { content: [{ type: "text", text: result.imageData }] };
|
||||
}
|
||||
return {
|
||||
content: [{
|
||||
type: "image",
|
||||
data: result.imageData,
|
||||
mimeType: "image/png",
|
||||
}],
|
||||
content: [
|
||||
{
|
||||
type: "image",
|
||||
data: result.imageData,
|
||||
mimeType: "image/png",
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
return {
|
||||
@@ -1240,14 +1189,18 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
// Find overlapping elements
|
||||
server.tool(
|
||||
"find_overlapping_elements",
|
||||
"Detect spatially overlapping symbols, wires, and labels in the schematic. Finds duplicate power symbols at the same position, collinear overlapping wires, and labels stacked on top of each other.",
|
||||
{
|
||||
schematicPath: z.string().describe("Path to the .kicad_sch schematic file"),
|
||||
tolerance: z.number().optional().describe("Distance threshold in mm for label proximity and wire collinearity checks. Symbol overlap uses bounding-box intersection. (default: 0.5)"),
|
||||
tolerance: z
|
||||
.number()
|
||||
.optional()
|
||||
.describe(
|
||||
"Distance threshold in mm for label proximity and wire collinearity checks. Symbol overlap uses bounding-box intersection. (default: 0.5)",
|
||||
),
|
||||
},
|
||||
async (args: { schematicPath: string; tolerance?: number }) => {
|
||||
const result = await callKicadScript("find_overlapping_elements", args);
|
||||
@@ -1259,7 +1212,9 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
|
||||
if (syms.length) {
|
||||
lines.push(`\nOverlapping symbols (${syms.length}):`);
|
||||
syms.slice(0, 20).forEach((o: any) => {
|
||||
lines.push(` ${o.element1.reference} ↔ ${o.element2.reference} (${o.distance}mm) [${o.type}]`);
|
||||
lines.push(
|
||||
` ${o.element1.reference} ↔ ${o.element2.reference} (${o.distance}mm) [${o.type}]`,
|
||||
);
|
||||
});
|
||||
}
|
||||
if (lbls.length) {
|
||||
@@ -1271,7 +1226,9 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
|
||||
if (wires.length) {
|
||||
lines.push(`\nOverlapping wires (${wires.length}):`);
|
||||
wires.slice(0, 20).forEach((o: any) => {
|
||||
lines.push(` wire @ (${o.wire1.start.x},${o.wire1.start.y})→(${o.wire1.end.x},${o.wire1.end.y}) overlaps with another`);
|
||||
lines.push(
|
||||
` wire @ (${o.wire1.start.x},${o.wire1.start.y})→(${o.wire1.end.x},${o.wire1.end.y}) overlaps with another`,
|
||||
);
|
||||
});
|
||||
}
|
||||
return { content: [{ type: "text", text: lines.join("\n") }] };
|
||||
@@ -1293,20 +1250,21 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
|
||||
x2: z.number().describe("Right X coordinate of the region in mm"),
|
||||
y2: z.number().describe("Bottom Y coordinate of the region in mm"),
|
||||
},
|
||||
async (args: {
|
||||
schematicPath: string;
|
||||
x1: number; y1: number; x2: number; y2: number;
|
||||
}) => {
|
||||
async (args: { schematicPath: string; x1: number; y1: number; x2: number; y2: number }) => {
|
||||
const result = await callKicadScript("get_elements_in_region", args);
|
||||
if (result.success) {
|
||||
const c = result.counts;
|
||||
const lines = [`Region (${args.x1},${args.y1})→(${args.x2},${args.y2}): ${c.symbols} symbols, ${c.wires} wires, ${c.labels} labels`];
|
||||
const lines = [
|
||||
`Region (${args.x1},${args.y1})→(${args.x2},${args.y2}): ${c.symbols} symbols, ${c.wires} wires, ${c.labels} labels`,
|
||||
];
|
||||
const syms: any[] = result.symbols || [];
|
||||
if (syms.length) {
|
||||
lines.push("\nSymbols:");
|
||||
syms.forEach((s: any) => {
|
||||
const pinCount = s.pins ? Object.keys(s.pins).length : 0;
|
||||
lines.push(` ${s.reference} (${s.libId}) @ (${s.position.x}, ${s.position.y}) [${pinCount} pins]`);
|
||||
lines.push(
|
||||
` ${s.reference} (${s.libId}) @ (${s.position.x}, ${s.position.y}) [${pinCount} pins]`,
|
||||
);
|
||||
});
|
||||
}
|
||||
const wires: any[] = result.wires || [];
|
||||
@@ -1346,7 +1304,7 @@ Note: operates on .kicad_sch files only. To modify a PCB footprint use edit_comp
|
||||
const lines = [`Found ${collisions.length} wire(s) crossing symbols:`];
|
||||
collisions.slice(0, 30).forEach((c: any, i: number) => {
|
||||
lines.push(
|
||||
` ${i + 1}. Wire (${c.wire.start.x},${c.wire.start.y})→(${c.wire.end.x},${c.wire.end.y}) crosses ${c.component.reference} (${c.component.libId})`
|
||||
` ${i + 1}. Wire (${c.wire.start.x},${c.wire.start.y})→(${c.wire.end.x},${c.wire.end.y}) crosses ${c.component.reference} (${c.component.libId})`,
|
||||
);
|
||||
});
|
||||
if (collisions.length > 30) lines.push(` ... and ${collisions.length - 30} more`);
|
||||
|
||||
@@ -15,45 +15,67 @@ const PinSchema = z.object({
|
||||
number: z.union([z.string(), z.number()]).describe("Pin number, e.g. '1', '2', 'A1'"),
|
||||
type: z
|
||||
.enum([
|
||||
"input", "output", "bidirectional", "tri_state", "passive",
|
||||
"free", "unspecified", "power_in", "power_out",
|
||||
"open_collector", "open_emitter", "no_connect",
|
||||
"input",
|
||||
"output",
|
||||
"bidirectional",
|
||||
"tri_state",
|
||||
"passive",
|
||||
"free",
|
||||
"unspecified",
|
||||
"power_in",
|
||||
"power_out",
|
||||
"open_collector",
|
||||
"open_emitter",
|
||||
"no_connect",
|
||||
])
|
||||
.describe("Electrical pin type"),
|
||||
at: z.object({
|
||||
x: z.number().describe("X position in mm"),
|
||||
y: z.number().describe("Y position in mm"),
|
||||
angle: z.number().describe(
|
||||
"Direction the pin wire extends FROM the symbol body: 0=right, 90=up, 180=left, 270=down"
|
||||
),
|
||||
}).describe("Pin endpoint position (where the wire connects)"),
|
||||
at: z
|
||||
.object({
|
||||
x: z.number().describe("X position in mm"),
|
||||
y: z.number().describe("Y position in mm"),
|
||||
angle: z
|
||||
.number()
|
||||
.describe(
|
||||
"Direction the pin wire extends FROM the symbol body: 0=right, 90=up, 180=left, 270=down",
|
||||
),
|
||||
})
|
||||
.describe("Pin endpoint position (where the wire connects)"),
|
||||
length: z.number().optional().describe("Pin length in mm (default 2.54)"),
|
||||
shape: z
|
||||
.enum(["line", "inverted", "clock", "inverted_clock", "input_low",
|
||||
"clock_low", "output_low", "falling_edge_clock", "non_logic"])
|
||||
.enum([
|
||||
"line",
|
||||
"inverted",
|
||||
"clock",
|
||||
"inverted_clock",
|
||||
"input_low",
|
||||
"clock_low",
|
||||
"output_low",
|
||||
"falling_edge_clock",
|
||||
"non_logic",
|
||||
])
|
||||
.optional()
|
||||
.describe("Pin graphic shape (default: line)"),
|
||||
});
|
||||
|
||||
const RectSchema = z.object({
|
||||
x1: z.number(), y1: z.number(),
|
||||
x2: z.number(), y2: z.number(),
|
||||
x1: z.number(),
|
||||
y1: z.number(),
|
||||
x2: z.number(),
|
||||
y2: z.number(),
|
||||
width: z.number().optional().describe("Stroke width in mm (default 0.254)"),
|
||||
fill: z.enum(["none", "outline", "background"]).optional()
|
||||
fill: z
|
||||
.enum(["none", "outline", "background"])
|
||||
.optional()
|
||||
.describe("Fill type (default: background)"),
|
||||
});
|
||||
|
||||
const PolylineSchema = z.object({
|
||||
points: z.array(z.object({ x: z.number(), y: z.number() }))
|
||||
.describe("List of XY points in mm"),
|
||||
points: z.array(z.object({ x: z.number(), y: z.number() })).describe("List of XY points in mm"),
|
||||
width: z.number().optional().describe("Stroke width in mm (default 0.254)"),
|
||||
fill: z.enum(["none", "outline", "background"]).optional(),
|
||||
});
|
||||
|
||||
export function registerSymbolCreatorTools(
|
||||
server: McpServer,
|
||||
callKicadScript: Function,
|
||||
) {
|
||||
export function registerSymbolCreatorTools(server: McpServer, callKicadScript: Function) {
|
||||
// ── create_symbol ────────────────────────────────────────────────────── //
|
||||
server.tool(
|
||||
"create_symbol",
|
||||
@@ -68,14 +90,14 @@ export function registerSymbolCreatorTools(
|
||||
"- Pins on bottom: at.y = body_bottom - length, angle=90 (wire goes up)\n" +
|
||||
"- Standard pin length: 2.54 mm, standard grid: 2.54 mm",
|
||||
{
|
||||
libraryPath: z
|
||||
.string()
|
||||
.describe("Path to the .kicad_sym file (created if missing)"),
|
||||
libraryPath: z.string().describe("Path to the .kicad_sym file (created if missing)"),
|
||||
name: z.string().describe("Symbol name, e.g. 'TMC2209', 'MyOpAmp'"),
|
||||
referencePrefix: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("Schematic reference prefix: 'U' (IC), 'R' (resistor), 'J' (connector), etc. Default: 'U'"),
|
||||
.describe(
|
||||
"Schematic reference prefix: 'U' (IC), 'R' (resistor), 'J' (connector), etc. Default: 'U'",
|
||||
),
|
||||
description: z.string().optional().describe("Human-readable description"),
|
||||
keywords: z.string().optional().describe("Space-separated search keywords"),
|
||||
datasheet: z.string().optional().describe("Datasheet URL or '~'"),
|
||||
@@ -161,9 +183,7 @@ export function registerSymbolCreatorTools(
|
||||
"Register a .kicad_sym library in KiCAD's sym-lib-table so symbols can be used in schematics. " +
|
||||
"Run this after create_symbol when KiCAD shows 'library not found'.",
|
||||
{
|
||||
libraryPath: z
|
||||
.string()
|
||||
.describe("Full path to the .kicad_sym file"),
|
||||
libraryPath: z.string().describe("Full path to the .kicad_sym file"),
|
||||
libraryName: z
|
||||
.string()
|
||||
.optional()
|
||||
|
||||
100
src/tools/ui.ts
100
src/tools/ui.ts
@@ -1,48 +1,52 @@
|
||||
/**
|
||||
* UI/Process management tools for KiCAD MCP server
|
||||
*/
|
||||
|
||||
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import { z } from 'zod';
|
||||
import { logger } from '../logger.js';
|
||||
|
||||
export function registerUITools(server: McpServer, callKicadScript: Function) {
|
||||
// Check if KiCAD UI is running
|
||||
server.tool(
|
||||
"check_kicad_ui",
|
||||
"Check if KiCAD UI is currently running",
|
||||
{},
|
||||
async () => {
|
||||
logger.info('Checking KiCAD UI status');
|
||||
const result = await callKicadScript("check_kicad_ui", {});
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result, null, 2)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// Launch KiCAD UI
|
||||
server.tool(
|
||||
"launch_kicad_ui",
|
||||
"Launch KiCAD UI, optionally with a project file",
|
||||
{
|
||||
projectPath: z.string().optional().describe("Optional path to .kicad_pcb file to open"),
|
||||
autoLaunch: z.boolean().optional().describe("Whether to launch KiCAD if not running (default: true)")
|
||||
},
|
||||
async (args: { projectPath?: string; autoLaunch?: boolean }) => {
|
||||
logger.info(`Launching KiCAD UI${args.projectPath ? ' with project: ' + args.projectPath : ''}`);
|
||||
const result = await callKicadScript("launch_kicad_ui", args);
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(result, null, 2)
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
logger.info('UI management tools registered');
|
||||
}
|
||||
/**
|
||||
* UI/Process management tools for KiCAD MCP server
|
||||
*/
|
||||
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { z } from "zod";
|
||||
import { logger } from "../logger.js";
|
||||
|
||||
export function registerUITools(server: McpServer, callKicadScript: Function) {
|
||||
// Check if KiCAD UI is running
|
||||
server.tool("check_kicad_ui", "Check if KiCAD UI is currently running", {}, async () => {
|
||||
logger.info("Checking KiCAD UI status");
|
||||
const result = await callKicadScript("check_kicad_ui", {});
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: JSON.stringify(result, null, 2),
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
// Launch KiCAD UI
|
||||
server.tool(
|
||||
"launch_kicad_ui",
|
||||
"Launch KiCAD UI, optionally with a project file",
|
||||
{
|
||||
projectPath: z.string().optional().describe("Optional path to .kicad_pcb file to open"),
|
||||
autoLaunch: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe("Whether to launch KiCAD if not running (default: true)"),
|
||||
},
|
||||
async (args: { projectPath?: string; autoLaunch?: boolean }) => {
|
||||
logger.info(
|
||||
`Launching KiCAD UI${args.projectPath ? " with project: " + args.projectPath : ""}`,
|
||||
);
|
||||
const result = await callKicadScript("launch_kicad_ui", args);
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: JSON.stringify(result, null, 2),
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
logger.info("UI management tools registered");
|
||||
}
|
||||
|
||||
@@ -1,61 +1,71 @@
|
||||
/**
|
||||
* Resource helper utilities for MCP resources
|
||||
*/
|
||||
|
||||
/**
|
||||
* Create a JSON response for MCP resources
|
||||
*
|
||||
* @param data Data to serialize as JSON
|
||||
* @param uri Optional URI for the resource
|
||||
* @returns MCP resource response object
|
||||
*/
|
||||
export function createJsonResponse(data: any, uri?: string) {
|
||||
return {
|
||||
contents: [{
|
||||
uri: uri || "data:application/json",
|
||||
mimeType: "application/json",
|
||||
text: JSON.stringify(data, null, 2)
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a binary response for MCP resources
|
||||
*
|
||||
* @param data Binary data (Buffer or base64 string)
|
||||
* @param mimeType MIME type of the binary data
|
||||
* @param uri Optional URI for the resource
|
||||
* @returns MCP resource response object
|
||||
*/
|
||||
export function createBinaryResponse(data: Buffer | string, mimeType: string, uri?: string) {
|
||||
const blob = typeof data === 'string' ? data : data.toString('base64');
|
||||
|
||||
return {
|
||||
contents: [{
|
||||
uri: uri || `data:${mimeType}`,
|
||||
mimeType: mimeType,
|
||||
blob: blob
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an error response for MCP resources
|
||||
*
|
||||
* @param error Error message
|
||||
* @param details Optional error details
|
||||
* @param uri Optional URI for the resource
|
||||
* @returns MCP resource error response
|
||||
*/
|
||||
export function createErrorResponse(error: string, details?: string, uri?: string) {
|
||||
return {
|
||||
contents: [{
|
||||
uri: uri || "data:application/json",
|
||||
mimeType: "application/json",
|
||||
text: JSON.stringify({
|
||||
error,
|
||||
details
|
||||
}, null, 2)
|
||||
}]
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Resource helper utilities for MCP resources
|
||||
*/
|
||||
|
||||
/**
|
||||
* Create a JSON response for MCP resources
|
||||
*
|
||||
* @param data Data to serialize as JSON
|
||||
* @param uri Optional URI for the resource
|
||||
* @returns MCP resource response object
|
||||
*/
|
||||
export function createJsonResponse(data: any, uri?: string) {
|
||||
return {
|
||||
contents: [
|
||||
{
|
||||
uri: uri || "data:application/json",
|
||||
mimeType: "application/json",
|
||||
text: JSON.stringify(data, null, 2),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a binary response for MCP resources
|
||||
*
|
||||
* @param data Binary data (Buffer or base64 string)
|
||||
* @param mimeType MIME type of the binary data
|
||||
* @param uri Optional URI for the resource
|
||||
* @returns MCP resource response object
|
||||
*/
|
||||
export function createBinaryResponse(data: Buffer | string, mimeType: string, uri?: string) {
|
||||
const blob = typeof data === "string" ? data : data.toString("base64");
|
||||
|
||||
return {
|
||||
contents: [
|
||||
{
|
||||
uri: uri || `data:${mimeType}`,
|
||||
mimeType: mimeType,
|
||||
blob: blob,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an error response for MCP resources
|
||||
*
|
||||
* @param error Error message
|
||||
* @param details Optional error details
|
||||
* @param uri Optional URI for the resource
|
||||
* @returns MCP resource error response
|
||||
*/
|
||||
export function createErrorResponse(error: string, details?: string, uri?: string) {
|
||||
return {
|
||||
contents: [
|
||||
{
|
||||
uri: uri || "data:application/json",
|
||||
mimeType: "application/json",
|
||||
text: JSON.stringify(
|
||||
{
|
||||
error,
|
||||
details,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user