feat: Implement comprehensive MCP capabilities with tool schemas and resources
This major update brings the KiCAD MCP server to full MCP 2025-06-18 spec
compliance with proper tool schemas and resources capability.
## Dependencies Updated
- @modelcontextprotocol/sdk: 1.10.0 → 1.21.0 (critical update)
- dotenv: 16.0.3 → 17.0.0 (latest stable)
- typescript: 5.2.2 → 5.9.3
- zod: 3.22.2 → 3.25.0
- @types/node: 20.5.6 → 20.19.0
- @types/express: 5.0.1 → 5.0.5
- requests (Python): 2.31.0 → 2.32.5
- Added @cfworker/json-schema for MCP SDK compatibility
## Tool Schemas (52 tools)
Created comprehensive JSON Schema definitions for all tools organized by category:
- **Project Tools** (4): create_project, open_project, save_project, get_project_info
- **Board Tools** (9): set_board_size, add_board_outline, add_layer, etc.
- **Component Tools** (10): place_component, move_component, rotate_component, etc.
- **Routing Tools** (8): add_net, route_trace, add_via, route_differential_pair, etc.
- **Library Tools** (4): list_libraries, search_footprints, etc.
- **Design Rule Tools** (4): set_design_rules, run_drc, get_drc_violations, etc.
- **Export Tools** (5): export_gerber, export_pdf, export_svg, export_3d, export_bom
- **Schematic Tools** (6): create_schematic, add_schematic_component, etc.
- **UI Tools** (2): check_kicad_ui, launch_kicad_ui
Each tool now has:
- Detailed descriptions explaining purpose
- Complete JSON Schema for input validation
- Required/optional parameter specifications
- Type constraints and validation rules
## Resources Capability (8 resources)
Implemented MCP resources to expose project state:
- `kicad://project/current/info` - Project metadata
- `kicad://project/current/board` - Board properties
- `kicad://project/current/components` - Component list
- `kicad://project/current/nets` - Electrical nets
- `kicad://project/current/layers` - Layer stack
- `kicad://project/current/design-rules` - Design rules
- `kicad://project/current/drc-report` - DRC violations
- `kicad://board/preview.png` - Board preview image
## Protocol Compliance
- Updated initialize response with proper capabilities declaration
- Added `tools: { listChanged: true }`
- Added `resources: { subscribe: false, listChanged: true }`
- Enhanced serverInfo with title and version
- Added instructions field for user guidance
- Implemented resources/list method
- Implemented resources/read method with proper error handling
- All responses follow MCP 2025-06-18 spec exactly
## Benefits
✅ Claude/LLMs can now understand what each tool does
✅ Automatic parameter validation via JSON Schema
✅ Better error messages for invalid inputs
✅ Access to project state via resources (no need to call tools)
✅ Full MCP protocol compliance
✅ Better developer experience with latest SDK features
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
153
package-lock.json
generated
153
package-lock.json
generated
@@ -1,36 +1,47 @@
|
|||||||
{
|
{
|
||||||
"name": "kicad-mcp",
|
"name": "kicad-mcp",
|
||||||
"version": "2.0.0-alpha.1",
|
"version": "2.1.0-alpha",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "kicad-mcp",
|
"name": "kicad-mcp",
|
||||||
"version": "2.0.0-alpha.1",
|
"version": "2.1.0-alpha",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/sdk": "^1.10.0",
|
"@modelcontextprotocol/sdk": "^1.21.0",
|
||||||
"dotenv": "^16.0.3",
|
"dotenv": "^17.0.0",
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
"zod": "^3.22.2"
|
"zod": "^3.25.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/express": "^5.0.1",
|
"@cfworker/json-schema": "^4.1.1",
|
||||||
"@types/node": "^20.5.6",
|
"@types/express": "^5.0.5",
|
||||||
|
"@types/node": "^20.19.0",
|
||||||
"nodemon": "^3.0.1",
|
"nodemon": "^3.0.1",
|
||||||
"typescript": "^5.2.2"
|
"typescript": "^5.9.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@cfworker/json-schema": {
|
||||||
|
"version": "4.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@cfworker/json-schema/-/json-schema-4.1.1.tgz",
|
||||||
|
"integrity": "sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@modelcontextprotocol/sdk": {
|
"node_modules/@modelcontextprotocol/sdk": {
|
||||||
"version": "1.10.2",
|
"version": "1.21.0",
|
||||||
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.10.2.tgz",
|
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.21.0.tgz",
|
||||||
"integrity": "sha512-rb6AMp2DR4SN+kc6L1ta2NCpApyA9WYNx3CrTSZvGxq9wH71bRur+zRqPfg0vQ9mjywR7qZdX2RGHOPq3ss+tA==",
|
"integrity": "sha512-YFBsXJMFCyI1zP98u7gezMFKX4lgu/XpoZJk7ufI6UlFKXLj2hAMUuRlQX/nrmIPOmhRrG6tw2OQ2ZA/ZlXYpQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"ajv": "^8.17.1",
|
||||||
|
"ajv-formats": "^3.0.1",
|
||||||
"content-type": "^1.0.5",
|
"content-type": "^1.0.5",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"cross-spawn": "^7.0.3",
|
"cross-spawn": "^7.0.5",
|
||||||
"eventsource": "^3.0.2",
|
"eventsource": "^3.0.2",
|
||||||
|
"eventsource-parser": "^3.0.0",
|
||||||
"express": "^5.0.1",
|
"express": "^5.0.1",
|
||||||
"express-rate-limit": "^7.5.0",
|
"express-rate-limit": "^7.5.0",
|
||||||
"pkce-challenge": "^5.0.0",
|
"pkce-challenge": "^5.0.0",
|
||||||
@@ -40,6 +51,14 @@
|
|||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@cfworker/json-schema": "^4.1.1"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@cfworker/json-schema": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/body-parser": {
|
"node_modules/@types/body-parser": {
|
||||||
@@ -64,15 +83,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/express": {
|
"node_modules/@types/express": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.5.tgz",
|
||||||
"integrity": "sha512-UZUw8vjpWFXuDnjFTh7/5c2TWDlQqeXHi6hcN7F2XSVT5P+WmUnnbFS3KA6Jnc6IsEqI2qCVu2bK0R0J4A8ZQQ==",
|
"integrity": "sha512-LuIQOcb6UmnF7C1PCFmEU1u2hmiHL43fgFQX67sN3H4Z+0Yk0Neo++mFsBjhOAuLzvlQeqAAkeDOZrJs9rzumQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/body-parser": "*",
|
"@types/body-parser": "*",
|
||||||
"@types/express-serve-static-core": "^5.0.0",
|
"@types/express-serve-static-core": "^5.0.0",
|
||||||
"@types/serve-static": "*"
|
"@types/serve-static": "^1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/express-serve-static-core": {
|
"node_modules/@types/express-serve-static-core": {
|
||||||
@@ -103,13 +122,13 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "20.17.31",
|
"version": "20.19.24",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.31.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.24.tgz",
|
||||||
"integrity": "sha512-quODOCNXQAbNf1Q7V+fI8WyErOCh0D5Yd31vHnKu4GkSztGQ7rlltAaqXhHhLl33tlVyUXs2386MkANSwgDn6A==",
|
"integrity": "sha512-FE5u0ezmi6y9OZEzlJfg37mqqf6ZDSF2V/NLjUyGrR9uTZ7Sb9F7bLNZ03S4XVUNRWGA7Ck4c1kK+YnuWjl+DA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~6.19.2"
|
"undici-types": "~6.21.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/qs": {
|
"node_modules/@types/qs": {
|
||||||
@@ -162,6 +181,39 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ajv": {
|
||||||
|
"version": "8.17.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
|
||||||
|
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"fast-deep-equal": "^3.1.3",
|
||||||
|
"fast-uri": "^3.0.1",
|
||||||
|
"json-schema-traverse": "^1.0.0",
|
||||||
|
"require-from-string": "^2.0.2"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/epoberezkin"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ajv-formats": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ajv": "^8.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"ajv": "^8.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"ajv": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/anymatch": {
|
"node_modules/anymatch": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
||||||
@@ -403,9 +455,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/dotenv": {
|
"node_modules/dotenv": {
|
||||||
"version": "16.5.0",
|
"version": "17.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz",
|
||||||
"integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==",
|
"integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==",
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
@@ -566,6 +618,28 @@
|
|||||||
"express": "^4.11 || 5 || ^5.0.0-beta.1"
|
"express": "^4.11 || 5 || ^5.0.0-beta.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/fast-deep-equal": {
|
||||||
|
"version": "3.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||||
|
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/fast-uri": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/fastify"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/fastify"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
"node_modules/fill-range": {
|
"node_modules/fill-range": {
|
||||||
"version": "7.1.1",
|
"version": "7.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||||
@@ -842,6 +916,12 @@
|
|||||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/json-schema-traverse": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/math-intrinsics": {
|
"node_modules/math-intrinsics": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||||
@@ -1123,6 +1203,15 @@
|
|||||||
"node": ">=8.10.0"
|
"node": ">=8.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/require-from-string": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/router": {
|
"node_modules/router": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
|
||||||
@@ -1396,9 +1485,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/typescript": {
|
"node_modules/typescript": {
|
||||||
"version": "5.8.3",
|
"version": "5.9.3",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||||
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"bin": {
|
"bin": {
|
||||||
@@ -1417,9 +1506,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/undici-types": {
|
"node_modules/undici-types": {
|
||||||
"version": "6.19.8",
|
"version": "6.21.0",
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||||
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
|
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
@@ -1463,9 +1552,9 @@
|
|||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/zod": {
|
"node_modules/zod": {
|
||||||
"version": "3.24.3",
|
"version": "3.25.76",
|
||||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.24.3.tgz",
|
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
|
||||||
"integrity": "sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg==",
|
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
|
|||||||
13
package.json
13
package.json
@@ -33,15 +33,16 @@
|
|||||||
"author": "",
|
"author": "",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/sdk": "^1.10.0",
|
"@modelcontextprotocol/sdk": "^1.21.0",
|
||||||
"dotenv": "^16.0.3",
|
"dotenv": "^17.0.0",
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
"zod": "^3.22.2"
|
"zod": "^3.25.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/express": "^5.0.1",
|
"@cfworker/json-schema": "^4.1.1",
|
||||||
"@types/node": "^20.5.6",
|
"@types/express": "^5.0.5",
|
||||||
|
"@types/node": "^20.19.0",
|
||||||
"nodemon": "^3.0.1",
|
"nodemon": "^3.0.1",
|
||||||
"typescript": "^5.2.2"
|
"typescript": "^5.9.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ import logging
|
|||||||
import os
|
import os
|
||||||
from typing import Dict, Any, Optional
|
from typing import Dict, Any, Optional
|
||||||
|
|
||||||
|
# Import tool schemas and resource definitions
|
||||||
|
from schemas.tool_schemas import TOOL_SCHEMAS
|
||||||
|
from resources.resource_definitions import RESOURCE_DEFINITIONS, handle_resource_read
|
||||||
|
|
||||||
# Configure logging
|
# Configure logging
|
||||||
log_dir = os.path.join(os.path.expanduser('~'), '.kicad-mcp', 'logs')
|
log_dir = os.path.join(os.path.expanduser('~'), '.kicad-mcp', 'logs')
|
||||||
os.makedirs(log_dir, exist_ok=True)
|
os.makedirs(log_dir, exist_ok=True)
|
||||||
@@ -536,27 +540,44 @@ def main():
|
|||||||
'result': {
|
'result': {
|
||||||
'protocolVersion': '2025-06-18',
|
'protocolVersion': '2025-06-18',
|
||||||
'capabilities': {
|
'capabilities': {
|
||||||
'tools': {}
|
'tools': {
|
||||||
|
'listChanged': True
|
||||||
|
},
|
||||||
|
'resources': {
|
||||||
|
'subscribe': False,
|
||||||
|
'listChanged': True
|
||||||
|
}
|
||||||
},
|
},
|
||||||
'serverInfo': {
|
'serverInfo': {
|
||||||
'name': 'kicad-mcp-server',
|
'name': 'kicad-mcp-server',
|
||||||
'version': '0.1.0'
|
'title': 'KiCAD PCB Design Assistant',
|
||||||
}
|
'version': '2.1.0-alpha'
|
||||||
|
},
|
||||||
|
'instructions': 'AI-assisted PCB design with KiCAD. Use tools to create projects, design boards, place components, route traces, and export manufacturing files.'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
elif method == 'tools/list':
|
elif method == 'tools/list':
|
||||||
logger.info("Handling MCP tools/list")
|
logger.info("Handling MCP tools/list")
|
||||||
# Return list of available tools
|
# Return list of available tools with proper schemas
|
||||||
tools = []
|
tools = []
|
||||||
for cmd_name in interface.command_routes.keys():
|
for cmd_name in interface.command_routes.keys():
|
||||||
tools.append({
|
# Get schema from TOOL_SCHEMAS if available
|
||||||
'name': cmd_name,
|
if cmd_name in TOOL_SCHEMAS:
|
||||||
'description': f'KiCAD command: {cmd_name}',
|
tool_def = TOOL_SCHEMAS[cmd_name].copy()
|
||||||
'inputSchema': {
|
tools.append(tool_def)
|
||||||
'type': 'object',
|
else:
|
||||||
'properties': {}
|
# Fallback for tools without schemas
|
||||||
}
|
logger.warning(f"No schema defined for tool: {cmd_name}")
|
||||||
})
|
tools.append({
|
||||||
|
'name': cmd_name,
|
||||||
|
'description': f'KiCAD command: {cmd_name}',
|
||||||
|
'inputSchema': {
|
||||||
|
'type': 'object',
|
||||||
|
'properties': {}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
logger.info(f"Returning {len(tools)} tools")
|
||||||
response = {
|
response = {
|
||||||
'jsonrpc': '2.0',
|
'jsonrpc': '2.0',
|
||||||
'id': request_id,
|
'id': request_id,
|
||||||
@@ -584,6 +605,38 @@ def main():
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
elif method == 'resources/list':
|
||||||
|
logger.info("Handling MCP resources/list")
|
||||||
|
# Return list of available resources
|
||||||
|
response = {
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': request_id,
|
||||||
|
'result': {
|
||||||
|
'resources': RESOURCE_DEFINITIONS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elif method == 'resources/read':
|
||||||
|
logger.info("Handling MCP resources/read")
|
||||||
|
resource_uri = params.get('uri')
|
||||||
|
|
||||||
|
if not resource_uri:
|
||||||
|
response = {
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': request_id,
|
||||||
|
'error': {
|
||||||
|
'code': -32602,
|
||||||
|
'message': 'Missing required parameter: uri'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
# Read the resource
|
||||||
|
resource_data = handle_resource_read(resource_uri, interface)
|
||||||
|
|
||||||
|
response = {
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'id': request_id,
|
||||||
|
'result': resource_data
|
||||||
|
}
|
||||||
else:
|
else:
|
||||||
logger.error(f"Unknown JSON-RPC method: {method}")
|
logger.error(f"Unknown JSON-RPC method: {method}")
|
||||||
response = {
|
response = {
|
||||||
|
|||||||
7
python/resources/__init__.py
Normal file
7
python/resources/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
"""
|
||||||
|
Resource definitions for KiCAD MCP Server
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .resource_definitions import RESOURCE_DEFINITIONS, handle_resource_read
|
||||||
|
|
||||||
|
__all__ = ['RESOURCE_DEFINITIONS', 'handle_resource_read']
|
||||||
313
python/resources/resource_definitions.py
Normal file
313
python/resources/resource_definitions.py
Normal file
@@ -0,0 +1,313 @@
|
|||||||
|
"""
|
||||||
|
Resource definitions for exposing KiCAD project state via MCP
|
||||||
|
|
||||||
|
Resources follow the MCP 2025-06-18 specification, providing
|
||||||
|
read-only access to project data for LLM context.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import base64
|
||||||
|
from typing import Dict, Any, List, Optional
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger('kicad_interface')
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# RESOURCE DEFINITIONS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
RESOURCE_DEFINITIONS = [
|
||||||
|
{
|
||||||
|
"uri": "kicad://project/current/info",
|
||||||
|
"name": "Current Project Information",
|
||||||
|
"description": "Metadata about the currently open KiCAD project including paths, name, and status",
|
||||||
|
"mimeType": "application/json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uri": "kicad://project/current/board",
|
||||||
|
"name": "Board Properties",
|
||||||
|
"description": "Comprehensive board information including dimensions, layer count, and design rules",
|
||||||
|
"mimeType": "application/json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uri": "kicad://project/current/components",
|
||||||
|
"name": "Component List",
|
||||||
|
"description": "List of all components on the board with references, footprints, values, and positions",
|
||||||
|
"mimeType": "application/json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uri": "kicad://project/current/nets",
|
||||||
|
"name": "Electrical Nets",
|
||||||
|
"description": "List of all electrical nets and their connections",
|
||||||
|
"mimeType": "application/json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uri": "kicad://project/current/layers",
|
||||||
|
"name": "Layer Stack",
|
||||||
|
"description": "Board layer configuration and properties",
|
||||||
|
"mimeType": "application/json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uri": "kicad://project/current/design-rules",
|
||||||
|
"name": "Design Rules",
|
||||||
|
"description": "Current design rule settings for clearances, track widths, and constraints",
|
||||||
|
"mimeType": "application/json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uri": "kicad://project/current/drc-report",
|
||||||
|
"name": "DRC Violations",
|
||||||
|
"description": "Design Rule Check violations and warnings from last DRC run",
|
||||||
|
"mimeType": "application/json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uri": "kicad://board/preview.png",
|
||||||
|
"name": "Board Preview Image",
|
||||||
|
"description": "2D rendering of the current board state",
|
||||||
|
"mimeType": "image/png"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# RESOURCE READ HANDLERS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
def handle_resource_read(uri: str, interface) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Handle reading a resource by URI
|
||||||
|
|
||||||
|
Args:
|
||||||
|
uri: Resource URI to read
|
||||||
|
interface: KiCADInterface instance with access to board state
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict with resource contents following MCP spec
|
||||||
|
"""
|
||||||
|
logger.info(f"Reading resource: {uri}")
|
||||||
|
|
||||||
|
handlers = {
|
||||||
|
"kicad://project/current/info": _get_project_info,
|
||||||
|
"kicad://project/current/board": _get_board_info,
|
||||||
|
"kicad://project/current/components": _get_components,
|
||||||
|
"kicad://project/current/nets": _get_nets,
|
||||||
|
"kicad://project/current/layers": _get_layers,
|
||||||
|
"kicad://project/current/design-rules": _get_design_rules,
|
||||||
|
"kicad://project/current/drc-report": _get_drc_report,
|
||||||
|
"kicad://board/preview.png": _get_board_preview
|
||||||
|
}
|
||||||
|
|
||||||
|
handler = handlers.get(uri)
|
||||||
|
if handler:
|
||||||
|
try:
|
||||||
|
return handler(interface)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error reading resource {uri}: {str(e)}")
|
||||||
|
return {
|
||||||
|
"contents": [{
|
||||||
|
"uri": uri,
|
||||||
|
"mimeType": "text/plain",
|
||||||
|
"text": f"Error: {str(e)}"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return {
|
||||||
|
"contents": [{
|
||||||
|
"uri": uri,
|
||||||
|
"mimeType": "text/plain",
|
||||||
|
"text": f"Unknown resource: {uri}"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# INDIVIDUAL RESOURCE HANDLERS
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
def _get_project_info(interface) -> Dict[str, Any]:
|
||||||
|
"""Get current project information"""
|
||||||
|
result = interface.project_commands.get_project_info({})
|
||||||
|
|
||||||
|
if result.get("success"):
|
||||||
|
return {
|
||||||
|
"contents": [{
|
||||||
|
"uri": "kicad://project/current/info",
|
||||||
|
"mimeType": "application/json",
|
||||||
|
"text": json.dumps(result.get("project", {}), indent=2)
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return {
|
||||||
|
"contents": [{
|
||||||
|
"uri": "kicad://project/current/info",
|
||||||
|
"mimeType": "text/plain",
|
||||||
|
"text": "No project currently open"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
def _get_board_info(interface) -> Dict[str, Any]:
|
||||||
|
"""Get board properties and metadata"""
|
||||||
|
result = interface.board_commands.get_board_info({})
|
||||||
|
|
||||||
|
if result.get("success"):
|
||||||
|
return {
|
||||||
|
"contents": [{
|
||||||
|
"uri": "kicad://project/current/board",
|
||||||
|
"mimeType": "application/json",
|
||||||
|
"text": json.dumps(result.get("board", {}), indent=2)
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return {
|
||||||
|
"contents": [{
|
||||||
|
"uri": "kicad://project/current/board",
|
||||||
|
"mimeType": "text/plain",
|
||||||
|
"text": "No board currently loaded"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
def _get_components(interface) -> Dict[str, Any]:
|
||||||
|
"""Get list of all components"""
|
||||||
|
result = interface.component_commands.get_component_list({})
|
||||||
|
|
||||||
|
if result.get("success"):
|
||||||
|
components = result.get("components", [])
|
||||||
|
return {
|
||||||
|
"contents": [{
|
||||||
|
"uri": "kicad://project/current/components",
|
||||||
|
"mimeType": "application/json",
|
||||||
|
"text": json.dumps({
|
||||||
|
"count": len(components),
|
||||||
|
"components": components
|
||||||
|
}, indent=2)
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return {
|
||||||
|
"contents": [{
|
||||||
|
"uri": "kicad://project/current/components",
|
||||||
|
"mimeType": "application/json",
|
||||||
|
"text": json.dumps({"count": 0, "components": []}, indent=2)
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
def _get_nets(interface) -> Dict[str, Any]:
|
||||||
|
"""Get list of electrical nets"""
|
||||||
|
result = interface.routing_commands.get_nets_list({})
|
||||||
|
|
||||||
|
if result.get("success"):
|
||||||
|
nets = result.get("nets", [])
|
||||||
|
return {
|
||||||
|
"contents": [{
|
||||||
|
"uri": "kicad://project/current/nets",
|
||||||
|
"mimeType": "application/json",
|
||||||
|
"text": json.dumps({
|
||||||
|
"count": len(nets),
|
||||||
|
"nets": nets
|
||||||
|
}, indent=2)
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return {
|
||||||
|
"contents": [{
|
||||||
|
"uri": "kicad://project/current/nets",
|
||||||
|
"mimeType": "application/json",
|
||||||
|
"text": json.dumps({"count": 0, "nets": []}, indent=2)
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
def _get_layers(interface) -> Dict[str, Any]:
|
||||||
|
"""Get layer stack information"""
|
||||||
|
result = interface.board_commands.get_layer_list({})
|
||||||
|
|
||||||
|
if result.get("success"):
|
||||||
|
layers = result.get("layers", [])
|
||||||
|
return {
|
||||||
|
"contents": [{
|
||||||
|
"uri": "kicad://project/current/layers",
|
||||||
|
"mimeType": "application/json",
|
||||||
|
"text": json.dumps({
|
||||||
|
"count": len(layers),
|
||||||
|
"layers": layers
|
||||||
|
}, indent=2)
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return {
|
||||||
|
"contents": [{
|
||||||
|
"uri": "kicad://project/current/layers",
|
||||||
|
"mimeType": "application/json",
|
||||||
|
"text": json.dumps({"count": 0, "layers": []}, indent=2)
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
def _get_design_rules(interface) -> Dict[str, Any]:
|
||||||
|
"""Get design rule settings"""
|
||||||
|
result = interface.design_rule_commands.get_design_rules({})
|
||||||
|
|
||||||
|
if result.get("success"):
|
||||||
|
return {
|
||||||
|
"contents": [{
|
||||||
|
"uri": "kicad://project/current/design-rules",
|
||||||
|
"mimeType": "application/json",
|
||||||
|
"text": json.dumps(result.get("rules", {}), indent=2)
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return {
|
||||||
|
"contents": [{
|
||||||
|
"uri": "kicad://project/current/design-rules",
|
||||||
|
"mimeType": "text/plain",
|
||||||
|
"text": "Design rules not available"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
def _get_drc_report(interface) -> Dict[str, Any]:
|
||||||
|
"""Get DRC violations"""
|
||||||
|
result = interface.design_rule_commands.get_drc_violations({})
|
||||||
|
|
||||||
|
if result.get("success"):
|
||||||
|
violations = result.get("violations", [])
|
||||||
|
return {
|
||||||
|
"contents": [{
|
||||||
|
"uri": "kicad://project/current/drc-report",
|
||||||
|
"mimeType": "application/json",
|
||||||
|
"text": json.dumps({
|
||||||
|
"count": len(violations),
|
||||||
|
"violations": violations
|
||||||
|
}, indent=2)
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return {
|
||||||
|
"contents": [{
|
||||||
|
"uri": "kicad://project/current/drc-report",
|
||||||
|
"mimeType": "application/json",
|
||||||
|
"text": json.dumps({
|
||||||
|
"count": 0,
|
||||||
|
"violations": [],
|
||||||
|
"message": "Run DRC first to get violations"
|
||||||
|
}, indent=2)
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
def _get_board_preview(interface) -> Dict[str, Any]:
|
||||||
|
"""Get board preview as PNG image"""
|
||||||
|
result = interface.board_commands.get_board_2d_view({"width": 800, "height": 600})
|
||||||
|
|
||||||
|
if result.get("success") and "imageData" in result:
|
||||||
|
# Image data should already be base64 encoded
|
||||||
|
image_data = result.get("imageData", "")
|
||||||
|
return {
|
||||||
|
"contents": [{
|
||||||
|
"uri": "kicad://board/preview.png",
|
||||||
|
"mimeType": "image/png",
|
||||||
|
"blob": image_data # Base64 encoded PNG
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
# Return a placeholder message
|
||||||
|
return {
|
||||||
|
"contents": [{
|
||||||
|
"uri": "kicad://board/preview.png",
|
||||||
|
"mimeType": "text/plain",
|
||||||
|
"text": "Board preview not available"
|
||||||
|
}]
|
||||||
|
}
|
||||||
7
python/schemas/__init__.py
Normal file
7
python/schemas/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
"""
|
||||||
|
Tool schema definitions for KiCAD MCP Server
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .tool_schemas import TOOL_SCHEMAS
|
||||||
|
|
||||||
|
__all__ = ['TOOL_SCHEMAS']
|
||||||
1250
python/schemas/tool_schemas.py
Normal file
1250
python/schemas/tool_schemas.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -20,7 +20,7 @@ colorlog>=6.7.0
|
|||||||
pydantic>=2.5.0
|
pydantic>=2.5.0
|
||||||
|
|
||||||
# HTTP requests (for JLCPCB/Digikey APIs - future)
|
# HTTP requests (for JLCPCB/Digikey APIs - future)
|
||||||
requests>=2.31.0
|
requests>=2.32.5
|
||||||
|
|
||||||
# Environment variable management
|
# Environment variable management
|
||||||
python-dotenv>=1.0.0
|
python-dotenv>=1.0.0
|
||||||
|
|||||||
Reference in New Issue
Block a user