feat: Week 1 complete - Linux support + IPC API prep
🎉 Major v2.0 rebuild kickoff - Week 1 accomplished! ## Highlights ### Cross-Platform Support 🌍 - ✅ Linux primary platform (Ubuntu/Debian tested) - ✅ Windows fully supported - ✅ macOS experimental support - ✅ Platform-agnostic path handling (XDG spec) - ✅ Auto-detection of KiCAD installation ### Infrastructure 🏗️ - ✅ GitHub Actions CI/CD pipeline - ✅ Pytest framework with 20+ tests - ✅ Pre-commit hooks (Black, MyPy, ESLint) - ✅ Automated Linux installation script - ✅ Enhanced npm scripts ### IPC API Migration Prep 🚀 - ✅ Comprehensive migration plan (30 pages) - ✅ Backend abstraction layer (800+ lines) - ✅ Factory pattern with auto-detection - ✅ SWIG backward compatibility wrapper - ✅ IPC backend skeleton ready ### Documentation 📚 - ✅ Updated README (Linux installation) - ✅ CONTRIBUTING.md guide - ✅ Linux compatibility audit - ✅ IPC API migration plan - ✅ Session summaries - ✅ Platform-specific config templates ## Files Changed - 27 files created - ~3,000 lines of code/docs - 8 comprehensive documentation pages - 20+ unit tests - 5 abstraction layer modules ## Next Steps - Week 2: IPC API migration (project.py → component.py → routing.py) - Migrate from deprecated SWIG to official IPC API - JLCPCB/Digikey integration prep 🤖 Generated with Claude Code https://claude.com/claude-code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
354
src/resources/board.ts
Normal file
354
src/resources/board.ts
Normal file
@@ -0,0 +1,354 @@
|
||||
/**
|
||||
* Board resources for KiCAD MCP server
|
||||
*
|
||||
* These resources provide information about the PCB board
|
||||
* to the LLM, enabling better context-aware assistance.
|
||||
*/
|
||||
|
||||
import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
|
||||
import { z } from 'zod';
|
||||
import { logger } from '../logger.js';
|
||||
import { createJsonResponse, createBinaryResponse } from '../utils/resource-helpers.js';
|
||||
|
||||
// Command function type for KiCAD script calls
|
||||
type CommandFunction = (command: string, params: Record<string, unknown>) => Promise<any>;
|
||||
|
||||
/**
|
||||
* Register board resources with the MCP server
|
||||
*
|
||||
* @param server MCP server instance
|
||||
* @param callKicadScript Function to call KiCAD script commands
|
||||
*/
|
||||
export function registerBoardResources(server: McpServer, callKicadScript: CommandFunction): void {
|
||||
logger.info('Registering board resources');
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Board Information Resource
|
||||
// ------------------------------------------------------
|
||||
server.resource(
|
||||
"board_info",
|
||||
"kicad://board/info",
|
||||
async (uri) => {
|
||||
logger.debug('Retrieving board information');
|
||||
const result = await callKicadScript("get_board_info", {});
|
||||
|
||||
if (!result.success) {
|
||||
logger.error(`Failed to retrieve board information: ${result.errorDetails}`);
|
||||
return {
|
||||
contents: [{
|
||||
uri: uri.href,
|
||||
text: JSON.stringify({
|
||||
error: "Failed to retrieve board information",
|
||||
details: result.errorDetails
|
||||
}),
|
||||
mimeType: "application/json"
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
logger.debug('Successfully retrieved board information');
|
||||
return {
|
||||
contents: [{
|
||||
uri: uri.href,
|
||||
text: JSON.stringify(result),
|
||||
mimeType: "application/json"
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Layer List Resource
|
||||
// ------------------------------------------------------
|
||||
server.resource(
|
||||
"layer_list",
|
||||
"kicad://board/layers",
|
||||
async (uri) => {
|
||||
logger.debug('Retrieving layer list');
|
||||
const result = await callKicadScript("get_layer_list", {});
|
||||
|
||||
if (!result.success) {
|
||||
logger.error(`Failed to retrieve layer list: ${result.errorDetails}`);
|
||||
return {
|
||||
contents: [{
|
||||
uri: uri.href,
|
||||
text: JSON.stringify({
|
||||
error: "Failed to retrieve layer list",
|
||||
details: result.errorDetails
|
||||
}),
|
||||
mimeType: "application/json"
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
logger.debug(`Successfully retrieved ${result.layers?.length || 0} layers`);
|
||||
return {
|
||||
contents: [{
|
||||
uri: uri.href,
|
||||
text: JSON.stringify(result),
|
||||
mimeType: "application/json"
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Board Extents Resource
|
||||
// ------------------------------------------------------
|
||||
server.resource(
|
||||
"board_extents",
|
||||
new ResourceTemplate("kicad://board/extents/{unit?}", {
|
||||
list: async () => ({
|
||||
resources: [
|
||||
{ uri: "kicad://board/extents/mm", name: "Millimeters" },
|
||||
{ uri: "kicad://board/extents/inch", name: "Inches" }
|
||||
]
|
||||
})
|
||||
}),
|
||||
async (uri, params) => {
|
||||
const unit = params.unit || 'mm';
|
||||
|
||||
logger.debug(`Retrieving board extents in ${unit}`);
|
||||
const result = await callKicadScript("get_board_extents", { unit });
|
||||
|
||||
if (!result.success) {
|
||||
logger.error(`Failed to retrieve board extents: ${result.errorDetails}`);
|
||||
return {
|
||||
contents: [{
|
||||
uri: uri.href,
|
||||
text: JSON.stringify({
|
||||
error: "Failed to retrieve board extents",
|
||||
details: result.errorDetails
|
||||
}),
|
||||
mimeType: "application/json"
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
logger.debug('Successfully retrieved board extents');
|
||||
return {
|
||||
contents: [{
|
||||
uri: uri.href,
|
||||
text: JSON.stringify(result),
|
||||
mimeType: "application/json"
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Board 2D View Resource
|
||||
// ------------------------------------------------------
|
||||
server.resource(
|
||||
"board_2d_view",
|
||||
new ResourceTemplate("kicad://board/2d-view/{format?}", {
|
||||
list: async () => ({
|
||||
resources: [
|
||||
{ uri: "kicad://board/2d-view/png", name: "PNG Format" },
|
||||
{ uri: "kicad://board/2d-view/jpg", name: "JPEG Format" },
|
||||
{ uri: "kicad://board/2d-view/svg", name: "SVG Format" }
|
||||
]
|
||||
})
|
||||
}),
|
||||
async (uri, params) => {
|
||||
const format = (params.format || 'png') as 'png' | 'jpg' | 'svg';
|
||||
const width = params.width ? parseInt(params.width as string) : undefined;
|
||||
const height = params.height ? parseInt(params.height as string) : undefined;
|
||||
// Handle layers parameter - could be string or array
|
||||
const layers = typeof params.layers === 'string' ? params.layers.split(',') : params.layers;
|
||||
|
||||
logger.debug('Retrieving 2D board view');
|
||||
const result = await callKicadScript("get_board_2d_view", {
|
||||
layers,
|
||||
width,
|
||||
height,
|
||||
format
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
logger.error(`Failed to retrieve 2D board view: ${result.errorDetails}`);
|
||||
return {
|
||||
contents: [{
|
||||
uri: uri.href,
|
||||
text: JSON.stringify({
|
||||
error: "Failed to retrieve 2D board view",
|
||||
details: result.errorDetails
|
||||
}),
|
||||
mimeType: "application/json"
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
logger.debug('Successfully retrieved 2D board view');
|
||||
|
||||
if (format === 'svg') {
|
||||
return {
|
||||
contents: [{
|
||||
uri: uri.href,
|
||||
text: result.imageData,
|
||||
mimeType: "image/svg+xml"
|
||||
}]
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
contents: [{
|
||||
uri: uri.href,
|
||||
blob: result.imageData,
|
||||
mimeType: format === "jpg" ? "image/jpeg" : "image/png"
|
||||
}]
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Board 3D View Resource
|
||||
// ------------------------------------------------------
|
||||
server.resource(
|
||||
"board_3d_view",
|
||||
new ResourceTemplate("kicad://board/3d-view/{angle?}", {
|
||||
list: async () => ({
|
||||
resources: [
|
||||
{ uri: "kicad://board/3d-view/isometric", name: "Isometric View" },
|
||||
{ uri: "kicad://board/3d-view/top", name: "Top View" },
|
||||
{ uri: "kicad://board/3d-view/bottom", name: "Bottom View" }
|
||||
]
|
||||
})
|
||||
}),
|
||||
async (uri, params) => {
|
||||
const angle = params.angle || 'isometric';
|
||||
const width = params.width ? parseInt(params.width as string) : undefined;
|
||||
const height = params.height ? parseInt(params.height as string) : undefined;
|
||||
|
||||
logger.debug(`Retrieving 3D board view from ${angle} angle`);
|
||||
const result = await callKicadScript("get_board_3d_view", {
|
||||
width,
|
||||
height,
|
||||
angle
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
logger.error(`Failed to retrieve 3D board view: ${result.errorDetails}`);
|
||||
return {
|
||||
contents: [{
|
||||
uri: uri.href,
|
||||
text: JSON.stringify({
|
||||
error: "Failed to retrieve 3D board view",
|
||||
details: result.errorDetails
|
||||
}),
|
||||
mimeType: "application/json"
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
logger.debug('Successfully retrieved 3D board view');
|
||||
return {
|
||||
contents: [{
|
||||
uri: uri.href,
|
||||
blob: result.imageData,
|
||||
mimeType: "image/png"
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
// ------------------------------------------------------
|
||||
// Board Statistics Resource
|
||||
// ------------------------------------------------------
|
||||
server.resource(
|
||||
"board_statistics",
|
||||
"kicad://board/statistics",
|
||||
async (uri) => {
|
||||
logger.debug('Generating board statistics');
|
||||
|
||||
// Get board info
|
||||
const boardResult = await callKicadScript("get_board_info", {});
|
||||
if (!boardResult.success) {
|
||||
logger.error(`Failed to retrieve board information: ${boardResult.errorDetails}`);
|
||||
return {
|
||||
contents: [{
|
||||
uri: uri.href,
|
||||
text: JSON.stringify({
|
||||
error: "Failed to generate board statistics",
|
||||
details: boardResult.errorDetails
|
||||
}),
|
||||
mimeType: "application/json"
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
// Get component list
|
||||
const componentsResult = await callKicadScript("get_component_list", {});
|
||||
if (!componentsResult.success) {
|
||||
logger.error(`Failed to retrieve component list: ${componentsResult.errorDetails}`);
|
||||
return {
|
||||
contents: [{
|
||||
uri: uri.href,
|
||||
text: JSON.stringify({
|
||||
error: "Failed to generate board statistics",
|
||||
details: componentsResult.errorDetails
|
||||
}),
|
||||
mimeType: "application/json"
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
// Get nets list
|
||||
const netsResult = await callKicadScript("get_nets_list", {});
|
||||
if (!netsResult.success) {
|
||||
logger.error(`Failed to retrieve nets list: ${netsResult.errorDetails}`);
|
||||
return {
|
||||
contents: [{
|
||||
uri: uri.href,
|
||||
text: JSON.stringify({
|
||||
error: "Failed to generate board statistics",
|
||||
details: netsResult.errorDetails
|
||||
}),
|
||||
mimeType: "application/json"
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
// Combine all information into statistics
|
||||
const statistics = {
|
||||
board: {
|
||||
size: boardResult.size,
|
||||
layers: boardResult.layers?.length || 0,
|
||||
title: boardResult.title
|
||||
},
|
||||
components: {
|
||||
count: componentsResult.components?.length || 0,
|
||||
types: countComponentTypes(componentsResult.components || [])
|
||||
},
|
||||
nets: {
|
||||
count: netsResult.nets?.length || 0
|
||||
}
|
||||
};
|
||||
|
||||
logger.debug('Successfully generated board statistics');
|
||||
return {
|
||||
contents: [{
|
||||
uri: uri.href,
|
||||
text: JSON.stringify(statistics),
|
||||
mimeType: "application/json"
|
||||
}]
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
logger.info('Board resources registered');
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to count component types
|
||||
*/
|
||||
function countComponentTypes(components: any[]): Record<string, number> {
|
||||
const typeCounts: Record<string, number> = {};
|
||||
|
||||
for (const component of components) {
|
||||
const type = component.value?.split(' ')[0] || 'Unknown';
|
||||
typeCounts[type] = (typeCounts[type] || 0) + 1;
|
||||
}
|
||||
|
||||
return typeCounts;
|
||||
}
|
||||
Reference in New Issue
Block a user