/** * 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) => Promise; /** * 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 { const typeCounts: Record = {}; for (const component of components) { const type = component.value?.split(" ")[0] || "Unknown"; typeCounts[type] = (typeCounts[type] || 0) + 1; } return typeCounts; }