diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2c72da3..a65cd00 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,71 +1,11 @@ -# Pre-commit hooks configuration -# See https://pre-commit.com for more information - -repos: - # Python code formatting - - repo: https://github.com/psf/black - rev: 23.7.0 - hooks: - - id: black - language_version: python3 - files: ^python/ - - # Python import sorting - - repo: https://github.com/pycqa/isort - rev: 5.12.0 - hooks: - - id: isort - files: ^python/ - args: ["--profile", "black"] - - # Python type checking - - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.5.0 - hooks: - - id: mypy - files: ^python/ - args: [--ignore-missing-imports] - - # Python linting - - repo: https://github.com/pycqa/flake8 - rev: 6.1.0 - hooks: - - id: flake8 - files: ^python/ - args: [--max-line-length=100, --extend-ignore=E203] - - # TypeScript/JavaScript formatting - - repo: https://github.com/pre-commit/mirrors-prettier - rev: v3.0.3 - hooks: - - id: prettier - types_or: [javascript, typescript, json, yaml, markdown] - files: \.(ts|js|json|ya?ml|md)$ - - # General file checks - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 - hooks: - - id: trailing-whitespace - - id: end-of-file-fixer - - id: check-yaml - - id: check-json - - id: check-added-large-files - args: [--maxkb=500] - - id: check-merge-conflict - - id: detect-private-key - - # Python security checks - - repo: https://github.com/PyCQA/bandit - rev: 1.7.5 - hooks: - - id: bandit - args: [-c, pyproject.toml] - files: ^python/ - - # Markdown linting - - repo: https://github.com/igorshubovych/markdownlint-cli - rev: v0.37.0 - hooks: - - id: markdownlint - args: [--fix] +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-json + - id: check-added-large-files + args: ['--maxkb=500'] + - id: check-merge-conflict diff --git a/README.md b/README.md index 086b253..1c88ddb 100644 --- a/README.md +++ b/README.md @@ -986,9 +986,3 @@ If you use this project in your research or publication, please cite: version = {2.2.3} } ``` - - - - - - diff --git a/docs/mcp-router-guide.md b/docs/mcp-router-guide.md index 39c83c5..bb1ae93 100644 --- a/docs/mcp-router-guide.md +++ b/docs/mcp-router-guide.md @@ -106,12 +106,12 @@ export const toolCategories: ToolCategory[] = [ inputSchema: { type: "object", properties: { - output_dir: { - type: "string", - description: "Output directory path" + output_dir: { + type: "string", + description: "Output directory path" }, - layers: { - type: "array", + layers: { + type: "array", items: { type: "string" }, description: "Layers to export (default: all copper + silkscreen + mask)" }, @@ -160,9 +160,9 @@ export const toolCategories: ToolCategory[] = [ inputSchema: { type: "object", properties: { - report_all: { - type: "boolean", - description: "Report all violations or stop at first" + report_all: { + type: "boolean", + description: "Report all violations or stop at first" } } }, @@ -313,17 +313,17 @@ These are the tools that enable discovery and execution. ```typescript // src/tools/router.ts -import { - getAllCategories, - getCategory, - getTool, - searchTools +import { + getAllCategories, + getCategory, + getTool, + searchTools } from "./registry.js"; export const routerTools = { list_tool_categories: { name: "list_tool_categories", - description: + description: "List all available tool categories. Use this to discover what operations " + "are available beyond the basic tools exposed directly.", inputSchema: { @@ -475,8 +475,8 @@ export const directTools: ToolDefinition[] = [ properties: { name: { type: "string", description: "Project name" }, path: { type: "string", description: "Directory path for project" }, - template: { - type: "string", + template: { + type: "string", description: "Optional template to use", enum: ["blank", "arduino", "raspberry-pi"] } @@ -804,19 +804,19 @@ Include: const categories = [ // Core operations (might be direct tools instead) { name: "project", description: "Project lifecycle: create, open, save, close" }, - + // Domain-specific operations { name: "analysis", description: "Analyze and inspect: find patterns, validate, check" }, { name: "modification", description: "Modify and transform: edit, rename, restructure" }, { name: "navigation", description: "Navigate and search: find, list, filter, locate" }, - + // Output operations { name: "export", description: "Export and generate: reports, files, documentation" }, { name: "import", description: "Import from external sources: files, formats, APIs" }, - + // Configuration { name: "config", description: "Configuration and settings: preferences, rules, templates" }, - + // Advanced/specialized { name: "advanced", description: "Advanced operations: batch processing, automation, scripting" }, ]; @@ -962,11 +962,11 @@ Claude: "I've added length tuning meanders to match the trace lengths" // tests/router.test.ts import { describe, it, expect } from "vitest"; -import { - searchTools, - getCategory, +import { + searchTools, + getCategory, getTool, - getAllCategories + getAllCategories } from "../src/tools/registry.js"; import { routerTools } from "../src/tools/router.js"; @@ -997,16 +997,16 @@ describe("Router Tools", () => { }); it("get_category_tools returns tools for valid category", async () => { - const result = await routerTools.get_category_tools.handler({ - category: "export" + const result = await routerTools.get_category_tools.handler({ + category: "export" }); expect(result.tools).toBeDefined(); expect(result.tools.length).toBeGreaterThan(0); }); it("get_category_tools returns error for invalid category", async () => { - const result = await routerTools.get_category_tools.handler({ - category: "nonexistent" + const result = await routerTools.get_category_tools.handler({ + category: "nonexistent" }); expect(result.error).toBeDefined(); }); diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..71f7950 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,4 @@ +[project] +name = "kicad-mcp-server" +version = "2.1.0" +requires-python = ">=3.10" diff --git a/python/commands/board/__init__.py b/python/commands/board/__init__.py index 08b974e..7a14e61 100644 --- a/python/commands/board/__init__.py +++ b/python/commands/board/__init__.py @@ -20,57 +20,57 @@ class BoardCommands: def __init__(self, board: Optional[pcbnew.BOARD] = None): """Initialize with optional board instance""" self.board = board - + # Initialize specialized command classes self.size_commands = BoardSizeCommands(board) self.layer_commands = BoardLayerCommands(board) self.outline_commands = BoardOutlineCommands(board) self.view_commands = BoardViewCommands(board) - + # Delegate board size commands def set_board_size(self, params: Dict[str, Any]) -> Dict[str, Any]: """Set the size of the PCB board""" self.size_commands.board = self.board return self.size_commands.set_board_size(params) - + # Delegate layer commands def add_layer(self, params: Dict[str, Any]) -> Dict[str, Any]: """Add a new layer to the PCB""" self.layer_commands.board = self.board return self.layer_commands.add_layer(params) - + def set_active_layer(self, params: Dict[str, Any]) -> Dict[str, Any]: """Set the active layer for PCB operations""" self.layer_commands.board = self.board return self.layer_commands.set_active_layer(params) - + def get_layer_list(self, params: Dict[str, Any]) -> Dict[str, Any]: """Get a list of all layers in the PCB""" self.layer_commands.board = self.board return self.layer_commands.get_layer_list(params) - + # Delegate board outline commands def add_board_outline(self, params: Dict[str, Any]) -> Dict[str, Any]: """Add a board outline to the PCB""" self.outline_commands.board = self.board return self.outline_commands.add_board_outline(params) - + def add_mounting_hole(self, params: Dict[str, Any]) -> Dict[str, Any]: """Add a mounting hole to the PCB""" self.outline_commands.board = self.board return self.outline_commands.add_mounting_hole(params) - + def add_text(self, params: Dict[str, Any]) -> Dict[str, Any]: """Add text annotation to the PCB""" self.outline_commands.board = self.board return self.outline_commands.add_text(params) - + # Delegate view commands def get_board_info(self, params: Dict[str, Any]) -> Dict[str, Any]: """Get information about the current board""" self.view_commands.board = self.board return self.view_commands.get_board_info(params) - + def get_board_2d_view(self, params: Dict[str, Any]) -> Dict[str, Any]: """Get a 2D image of the PCB""" self.view_commands.board = self.board diff --git a/python/commands/board/layers.py b/python/commands/board/layers.py index 1e407ec..f80f345 100644 --- a/python/commands/board/layers.py +++ b/python/commands/board/layers.py @@ -65,7 +65,7 @@ class BoardLayerCommands: # Set layer properties layer_stack.SetLayerName(layer_id, name) layer_stack.SetLayerType(layer_id, self._get_layer_type(layer_type)) - + # Enable the layer self.board.SetLayerEnabled(layer_id, True) @@ -168,7 +168,7 @@ class BoardLayerCommands: "message": "Failed to get layer list", "errorDetails": str(e) } - + def _get_layer_type(self, type_name: str) -> int: """Convert layer type name to KiCAD layer type constant""" type_map = { diff --git a/python/commands/board/view.py b/python/commands/board/view.py index aaad991..4aa5bfe 100644 --- a/python/commands/board/view.py +++ b/python/commands/board/view.py @@ -90,7 +90,7 @@ class BoardViewCommands: # Create plot controller plotter = pcbnew.PLOT_CONTROLLER(self.board) - + # Set up plot options plot_opts = plotter.GetPlotOptions() plot_opts.SetOutputDirectory(os.path.dirname(self.board.GetFileName())) @@ -100,7 +100,7 @@ class BoardViewCommands: plot_opts.SetPlotFrameRef(False) plot_opts.SetPlotValue(True) plot_opts.SetPlotReference(True) - + # Plot to SVG first (for vector output) # Note: KiCAD 9.0 prepends the project name to the filename, so we use GetPlotFileName() to get the actual path plotter.OpenPlotfile("temp_view", pcbnew.PLOT_FORMAT_SVG, "Temporary View") @@ -139,7 +139,7 @@ class BoardViewCommands: from cairosvg import svg2png png_data = svg2png(url=temp_svg, output_width=width, output_height=height) os.remove(temp_svg) - + if format == "jpg": # Convert PNG to JPG img = Image.open(io.BytesIO(png_data)) @@ -165,7 +165,7 @@ class BoardViewCommands: "message": "Failed to get board 2D view", "errorDetails": str(e) } - + def _get_layer_type_name(self, type_id: int) -> str: """Convert KiCAD layer type constant to name""" type_map = { diff --git a/python/commands/component.py b/python/commands/component.py index 472fec0..f707f10 100644 --- a/python/commands/component.py +++ b/python/commands/component.py @@ -752,14 +752,14 @@ class ComponentCommands: count = params.get("count") reference_prefix = params.get("referencePrefix", "U") value = params.get("value") - + if not component_id or not count: return { "success": False, "message": "Missing parameters", "errorDetails": "componentId and count are required" } - + if pattern == "grid": start_position = params.get("startPosition") rows = params.get("rows") @@ -768,21 +768,21 @@ class ComponentCommands: spacing_y = params.get("spacingY") rotation = params.get("rotation", 0) layer = params.get("layer", "F.Cu") - + if not start_position or not rows or not columns or not spacing_x or not spacing_y: return { "success": False, "message": "Missing grid parameters", "errorDetails": "For grid pattern, startPosition, rows, columns, spacingX, and spacingY are required" } - + if rows * columns != count: return { "success": False, "message": "Invalid grid parameters", "errorDetails": "rows * columns must equal count" } - + placed_components = self._place_grid_array( component_id, start_position, @@ -795,7 +795,7 @@ class ComponentCommands: rotation, layer ) - + elif pattern == "circular": center = params.get("center") radius = params.get("radius") @@ -803,14 +803,14 @@ class ComponentCommands: angle_step = params.get("angleStep") rotation_offset = params.get("rotationOffset", 0) layer = params.get("layer", "F.Cu") - + if not center or not radius or not angle_step: return { "success": False, "message": "Missing circular parameters", "errorDetails": "For circular pattern, center, radius, and angleStep are required" } - + placed_components = self._place_circular_array( component_id, center, @@ -823,7 +823,7 @@ class ComponentCommands: rotation_offset, layer ) - + else: return { "success": False, @@ -844,7 +844,7 @@ class ComponentCommands: "message": "Failed to place component array", "errorDetails": str(e) } - + def align_components(self, params: Dict[str, Any]) -> Dict[str, Any]: """Align multiple components along a line or distribute them evenly""" try: @@ -859,14 +859,14 @@ class ComponentCommands: alignment = params.get("alignment", "horizontal") # horizontal, vertical, or edge distribution = params.get("distribution", "none") # none, equal, or spacing spacing = params.get("spacing") - + if not references or len(references) < 2: return { "success": False, "message": "Missing references", "errorDetails": "At least two component references are required" } - + # Find all referenced components components = [] for ref in references: @@ -878,7 +878,7 @@ class ComponentCommands: "errorDetails": f"Could not find component: {ref}" } components.append(module) - + # Perform alignment based on selected option if alignment == "horizontal": self._align_components_horizontally(components, distribution, spacing) @@ -929,7 +929,7 @@ class ComponentCommands: "message": "Failed to align components", "errorDetails": str(e) } - + def duplicate_component(self, params: Dict[str, Any]) -> Dict[str, Any]: """Duplicate an existing component""" try: @@ -944,14 +944,14 @@ class ComponentCommands: new_reference = params.get("newReference") position = params.get("position") rotation = params.get("rotation") - + if not reference or not new_reference: return { "success": False, "message": "Missing parameters", "errorDetails": "reference and newReference are required" } - + # Find the source component source = self.board.FindFootprintByReference(reference) if not source: @@ -960,7 +960,7 @@ class ComponentCommands: "message": "Component not found", "errorDetails": f"Could not find component: {reference}" } - + # Check if new reference already exists if self.board.FindFootprintByReference(new_reference): return { @@ -968,7 +968,7 @@ class ComponentCommands: "message": "Reference already exists", "errorDetails": f"A component with reference {new_reference} already exists" } - + # Create new footprint with the same properties new_module = pcbnew.FOOTPRINT(self.board) # For KiCAD 9.x compatibility, use SetFPID instead of SetFootprintName @@ -976,13 +976,13 @@ class ComponentCommands: new_module.SetValue(source.GetValue()) new_module.SetReference(new_reference) new_module.SetLayer(source.GetLayer()) - + # Copy pads and other items for pad in source.Pads(): new_pad = pcbnew.PAD(new_module) new_pad.Copy(pad) new_module.Add(new_pad) - + # Set position if provided, otherwise use offset from original if position: scale = 1000000 if position.get("unit", "mm") == "mm" else 25400000 @@ -993,17 +993,17 @@ class ComponentCommands: # Offset by 5mm source_pos = source.GetPosition() new_module.SetPosition(pcbnew.VECTOR2I(source_pos.x + 5000000, source_pos.y)) - + # Set rotation if provided, otherwise use same as original if rotation is not None: rotation_angle = pcbnew.EDA_ANGLE(rotation, pcbnew.DEGREES_T) new_module.SetOrientation(rotation_angle) else: new_module.SetOrientation(source.GetOrientation()) - + # Add to board self.board.Add(new_module) - + # Get final position in mm pos = new_module.GetPosition() @@ -1031,32 +1031,32 @@ class ComponentCommands: "message": "Failed to duplicate component", "errorDetails": str(e) } - - def _place_grid_array(self, component_id: str, start_position: Dict[str, Any], + + def _place_grid_array(self, component_id: str, start_position: Dict[str, Any], rows: int, columns: int, spacing_x: float, spacing_y: float, reference_prefix: str, value: str, rotation: float, layer: str) -> List[Dict[str, Any]]: """Place components in a grid pattern and return the list of placed components""" placed = [] - + # Convert spacing to nm unit = start_position.get("unit", "mm") scale = 1000000 if unit == "mm" else 25400000 # mm or inch to nm spacing_x_nm = int(spacing_x * scale) spacing_y_nm = int(spacing_y * scale) - + # Get layer ID layer_id = self.board.GetLayerID(layer) - + for row in range(rows): for col in range(columns): # Calculate position x = start_position["x"] + (col * spacing_x) y = start_position["y"] + (row * spacing_y) - + # Generate reference index = row * columns + col + 1 component_reference = f"{reference_prefix}{index}" - + # Place component result = self.place_component({ "componentId": component_id, @@ -1066,37 +1066,37 @@ class ComponentCommands: "rotation": rotation, "layer": layer }) - + if result["success"]: placed.append(result["component"]) - + return placed - - def _place_circular_array(self, component_id: str, center: Dict[str, Any], - radius: float, count: int, angle_start: float, - angle_step: float, reference_prefix: str, + + def _place_circular_array(self, component_id: str, center: Dict[str, Any], + radius: float, count: int, angle_start: float, + angle_step: float, reference_prefix: str, value: str, rotation_offset: float, layer: str) -> List[Dict[str, Any]]: """Place components in a circular pattern and return the list of placed components""" placed = [] - + # Get unit unit = center.get("unit", "mm") - + for i in range(count): # Calculate angle for this component angle = angle_start + (i * angle_step) angle_rad = math.radians(angle) - + # Calculate position x = center["x"] + (radius * math.cos(angle_rad)) y = center["y"] + (radius * math.sin(angle_rad)) - + # Generate reference component_reference = f"{reference_prefix}{i+1}" - + # Calculate rotation (pointing outward from center) component_rotation = angle + rotation_offset - + # Place component result = self.place_component({ "componentId": component_id, @@ -1106,114 +1106,114 @@ class ComponentCommands: "rotation": component_rotation, "layer": layer }) - + if result["success"]: placed.append(result["component"]) - + return placed - - def _align_components_horizontally(self, components: List[pcbnew.FOOTPRINT], + + def _align_components_horizontally(self, components: List[pcbnew.FOOTPRINT], distribution: str, spacing: Optional[float]) -> None: """Align components horizontally and optionally distribute them""" if not components: return - + # Find the average Y coordinate y_sum = sum(module.GetPosition().y for module in components) y_avg = y_sum // len(components) - + # Sort components by X position components.sort(key=lambda m: m.GetPosition().x) - + # Set Y coordinate for all components for module in components: pos = module.GetPosition() module.SetPosition(pcbnew.VECTOR2I(pos.x, y_avg)) - + # Handle distribution if requested if distribution == "equal" and len(components) > 1: # Get leftmost and rightmost X coordinates x_min = components[0].GetPosition().x x_max = components[-1].GetPosition().x - + # Calculate equal spacing total_space = x_max - x_min spacing_nm = total_space // (len(components) - 1) - + # Set X positions with equal spacing for i in range(1, len(components) - 1): pos = components[i].GetPosition() new_x = x_min + (i * spacing_nm) components[i].SetPosition(pcbnew.VECTOR2I(new_x, pos.y)) - + elif distribution == "spacing" and spacing is not None: # Convert spacing to nanometers spacing_nm = int(spacing * 1000000) # assuming mm - + # Set X positions with the specified spacing x_current = components[0].GetPosition().x for i in range(1, len(components)): pos = components[i].GetPosition() x_current += spacing_nm components[i].SetPosition(pcbnew.VECTOR2I(x_current, pos.y)) - - def _align_components_vertically(self, components: List[pcbnew.FOOTPRINT], + + def _align_components_vertically(self, components: List[pcbnew.FOOTPRINT], distribution: str, spacing: Optional[float]) -> None: """Align components vertically and optionally distribute them""" if not components: return - + # Find the average X coordinate x_sum = sum(module.GetPosition().x for module in components) x_avg = x_sum // len(components) - + # Sort components by Y position components.sort(key=lambda m: m.GetPosition().y) - + # Set X coordinate for all components for module in components: pos = module.GetPosition() module.SetPosition(pcbnew.VECTOR2I(x_avg, pos.y)) - + # Handle distribution if requested if distribution == "equal" and len(components) > 1: # Get topmost and bottommost Y coordinates y_min = components[0].GetPosition().y y_max = components[-1].GetPosition().y - + # Calculate equal spacing total_space = y_max - y_min spacing_nm = total_space // (len(components) - 1) - + # Set Y positions with equal spacing for i in range(1, len(components) - 1): pos = components[i].GetPosition() new_y = y_min + (i * spacing_nm) components[i].SetPosition(pcbnew.VECTOR2I(pos.x, new_y)) - + elif distribution == "spacing" and spacing is not None: # Convert spacing to nanometers spacing_nm = int(spacing * 1000000) # assuming mm - + # Set Y positions with the specified spacing y_current = components[0].GetPosition().y for i in range(1, len(components)): pos = components[i].GetPosition() y_current += spacing_nm components[i].SetPosition(pcbnew.VECTOR2I(pos.x, y_current)) - + def _align_components_to_edge(self, components: List[pcbnew.FOOTPRINT], edge: str) -> None: """Align components to the specified edge of the board""" if not components: return - + # Get board bounds board_box = self.board.GetBoardEdgesBoundingBox() left = board_box.GetLeft() right = board_box.GetRight() top = board_box.GetTop() bottom = board_box.GetBottom() - + # Align based on specified edge if edge == "left": for module in components: diff --git a/python/commands/library.py b/python/commands/library.py index 927899a..c1d1582 100644 --- a/python/commands/library.py +++ b/python/commands/library.py @@ -522,7 +522,7 @@ class LibraryCommands: if path == library_path: library_nickname = nick break - + # Minimal info — always returned even if the parser fails info: Dict = { "library": library_nickname, diff --git a/python/commands/library_schematic.py b/python/commands/library_schematic.py index 66fb5d5..45c2ae9 100644 --- a/python/commands/library_schematic.py +++ b/python/commands/library_schematic.py @@ -31,7 +31,7 @@ class LibraryManager: # Extract library names from paths library_names = [os.path.splitext(os.path.basename(lib))[0] for lib in libraries] print(f"Found {len(library_names)} libraries: {', '.join(library_names[:10])}{'...' if len(library_names) > 10 else ''}") - + # Return both full paths and library names return {"paths": libraries, "names": library_names} @@ -43,7 +43,7 @@ class LibraryManager: # without loading each one. We might need to implement this using KiCAD's Python API # directly, or by using a different approach. # For now, this is a placeholder implementation. - + # A potential approach would be to load the library file using KiCAD's Python API # or by parsing the library file format. # KiCAD symbol libraries are .kicad_sym files which are S-expression format @@ -73,23 +73,23 @@ class LibraryManager: # 1. Getting a list of all libraries using list_available_libraries # 2. For each library, getting a list of all symbols # 3. Filtering symbols based on the query - + # For now, this is a placeholder implementation libraries = LibraryManager.list_available_libraries(search_paths) - + results = [] print(f"Searched for symbols matching '{query}'. This requires advanced implementation.") return results except Exception as e: print(f"Error searching for symbols matching '{query}': {e}") return [] - + @staticmethod def get_default_symbol_for_component_type(component_type, search_paths=None): """Get a recommended default symbol for a given component type""" # This method provides a simplified way to get a symbol for common component types # It's useful when the user doesn't specify a particular library/symbol - + # Define common mappings from component type to library/symbol common_mappings = { "resistor": {"library": "Device", "symbol": "R"}, @@ -103,19 +103,19 @@ class LibraryManager: "microcontroller": {"library": "MCU_Module", "symbol": "Arduino_UNO_R3"}, # Add more common components as needed } - + # Normalize input to lowercase component_type_lower = component_type.lower() - + # Try direct match first if component_type_lower in common_mappings: return common_mappings[component_type_lower] - + # Try partial matches for key, value in common_mappings.items(): if component_type_lower in key or key in component_type_lower: return value - + # Default fallback return {"library": "Device", "symbol": "R"} @@ -127,15 +127,15 @@ if __name__ == '__main__': first_lib = libraries["paths"][0] lib_name = libraries["names"][0] print(f"Testing with first library: {lib_name} ({first_lib})") - + # List symbols in the first library symbols = LibraryManager.list_library_symbols(first_lib) # This will report that it requires advanced implementation - + # Get default symbol for a component type resistor_sym = LibraryManager.get_default_symbol_for_component_type("resistor") print(f"Default symbol for resistor: {resistor_sym['library']}/{resistor_sym['symbol']}") - + # Try a partial match cap_sym = LibraryManager.get_default_symbol_for_component_type("cap") print(f"Default symbol for 'cap': {cap_sym['library']}/{cap_sym['symbol']}") diff --git a/src/config.ts b/src/config.ts index 7148515..38e6fb6 100644 --- a/src/config.ts +++ b/src/config.ts @@ -36,7 +36,7 @@ export type Config = z.infer; /** * Load configuration from file - * + * * @param configPath Path to the configuration file (optional) * @returns Loaded and validated configuration */ @@ -44,22 +44,22 @@ export async function loadConfig(configPath?: string): Promise { try { // Determine which config file to load const filePath = configPath || DEFAULT_CONFIG_PATH; - + // Check if file exists if (!existsSync(filePath)) { logger.warn(`Configuration file not found: ${filePath}, using defaults`); return ConfigSchema.parse({}); } - + // Read and parse configuration const configData = await readFile(filePath, 'utf-8'); const config = JSON.parse(configData); - + // Validate configuration return ConfigSchema.parse(config); } catch (error) { logger.error(`Error loading configuration: ${error}`); - + // Return default configuration return ConfigSchema.parse({}); } diff --git a/src/index.ts b/src/index.ts index 6f31054..05bbe7f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -21,27 +21,27 @@ async function main() { // Parse command line arguments const args = process.argv.slice(2); const options = parseCommandLineArgs(args); - + // Load configuration const config = await loadConfig(options.configPath); - + // Path to the Python script that interfaces with KiCAD const kicadScriptPath = join(dirname(__dirname), 'python', 'kicad_interface.py'); - + // Create the server const server = new KiCADMcpServer( kicadScriptPath, config.logLevel ); - + // Start the server await server.start(); - + // Setup graceful shutdown setupGracefulShutdown(server); - + logger.info('KiCAD MCP server started with STDIO transport'); - + } catch (error) { logger.error(`Failed to start KiCAD MCP server: ${error}`); process.exit(1); @@ -53,14 +53,14 @@ async function main() { */ function parseCommandLineArgs(args: string[]) { let configPath = undefined; - + for (let i = 0; i < args.length; i++) { if (args[i] === '--config' && i + 1 < args.length) { configPath = args[i + 1]; i++; } } - + return { configPath }; } @@ -73,18 +73,18 @@ function setupGracefulShutdown(server: KiCADMcpServer) { logger.info('Received SIGINT signal. Shutting down...'); await shutdownServer(server); }); - + process.on('SIGTERM', async () => { logger.info('Received SIGTERM signal. Shutting down...'); await shutdownServer(server); }); - + // Handle uncaught exceptions process.on('uncaughtException', async (error) => { logger.error(`Uncaught exception: ${error}`); await shutdownServer(server); }); - + // Handle unhandled promise rejections process.on('unhandledRejection', async (reason) => { logger.error(`Unhandled promise rejection: ${reason}`); diff --git a/src/kicad-server.ts b/src/kicad-server.ts index 348d1c5..dd633c4 100644 --- a/src/kicad-server.ts +++ b/src/kicad-server.ts @@ -26,12 +26,12 @@ class KiCADServer { // Set absolute path to the Python KiCAD interface script // Using a hardcoded path to avoid cwd() issues when running from Cline this.kicadScriptPath = 'c:/repo/KiCAD-MCP/python/kicad_interface.py'; - + // Check if script exists if (!existsSync(this.kicadScriptPath)) { throw new Error(`KiCAD interface script not found: ${this.kicadScriptPath}`); } - + // Initialize the server this.server = new Server( { @@ -46,7 +46,7 @@ class KiCADServer { } } ); - + // Initialize handler with direct pass-through to Python KiCAD interface // We don't register TypeScript tools since we'll handle everything in Python @@ -96,7 +96,7 @@ class KiCADServer { properties: {} } }, - + // Board tools { name: 'set_board_size', @@ -129,7 +129,7 @@ class KiCADServer { } } }, - + // Component tools { name: 'place_component', @@ -147,7 +147,7 @@ class KiCADServer { required: ['componentId', 'position'] } }, - + // Routing tools { name: 'add_net', @@ -176,7 +176,7 @@ class KiCADServer { required: ['start', 'end'] } }, - + // Schematic tools { name: 'create_schematic', @@ -209,8 +209,8 @@ class KiCADServer { type: 'object', properties: { schematicPath: { type: 'string', description: 'Path to the schematic file' }, - component: { - type: 'object', + component: { + type: 'object', description: 'Component definition', properties: { type: { type: 'string', description: 'Component type (e.g., R, C, LED)' }, @@ -235,15 +235,15 @@ class KiCADServer { type: 'object', properties: { schematicPath: { type: 'string', description: 'Path to the schematic file' }, - startPoint: { - type: 'array', + startPoint: { + type: 'array', description: 'Starting point coordinates [x, y]', items: { type: 'number' }, minItems: 2, maxItems: 2 }, - endPoint: { - type: 'array', + endPoint: { + type: 'array', description: 'Ending point coordinates [x, y]', items: { type: 'number' }, minItems: 2, @@ -259,8 +259,8 @@ class KiCADServer { inputSchema: { type: 'object', properties: { - searchPaths: { - type: 'array', + searchPaths: { + type: 'array', description: 'Optional search paths for libraries', items: { type: 'string' } } @@ -286,7 +286,7 @@ class KiCADServer { this.server.setRequestHandler(CallToolRequestSchema, async (request: any) => { const toolName = request.params.name; const args = request.params.arguments || {}; - + // Pass all commands directly to KiCAD Python interface try { return await this.callKicadScript(toolName, args); @@ -300,11 +300,11 @@ class KiCADServer { async start() { try { console.error('Starting KiCAD MCP server...'); - + // Start the Python process for KiCAD scripting console.error(`Starting Python process with script: ${this.kicadScriptPath}`); const pythonExe = 'C:\\Program Files\\KiCad\\9.0\\bin\\python.exe'; - + console.error(`Using Python executable: ${pythonExe}`); this.pythonProcess = spawn(pythonExe, [this.kicadScriptPath], { stdio: ['pipe', 'pipe', 'pipe'], @@ -313,30 +313,30 @@ class KiCADServer { PYTHONPATH: 'C:/Program Files/KiCad/9.0/lib/python3/dist-packages' } }); - + // Listen for process exit this.pythonProcess.on('exit', (code, signal) => { console.error(`Python process exited with code ${code} and signal ${signal}`); this.pythonProcess = null; }); - + // Listen for process errors this.pythonProcess.on('error', (err) => { console.error(`Python process error: ${err.message}`); }); - + // Set up error logging for stderr if (this.pythonProcess.stderr) { this.pythonProcess.stderr.on('data', (data: Buffer) => { console.error(`Python stderr: ${data.toString()}`); }); } - + // Connect to transport const transport = new StdioServerTransport(); await this.server.connect(transport); console.error('KiCAD MCP server running'); - + // Keep the process running process.on('SIGINT', () => { if (this.pythonProcess) { @@ -345,7 +345,7 @@ class KiCADServer { this.server.close().catch(console.error); process.exit(0); }); - + } catch (error: unknown) { if (error instanceof Error) { console.error('Failed to start MCP server:', error.message); @@ -364,73 +364,73 @@ class KiCADServer { reject(new Error("Python process for KiCAD scripting is not running")); return; } - + // Add request to queue this.requestQueue.push({ request: { command, params }, resolve, reject }); - + // Process the queue if not already processing if (!this.processingRequest) { this.processNextRequest(); } }); } - + private processNextRequest(): void { // If no more requests or already processing, return if (this.requestQueue.length === 0 || this.processingRequest) { return; } - + // Set processing flag this.processingRequest = true; - + // Get the next request const { request, resolve, reject } = this.requestQueue.shift()!; - + try { console.error(`Processing KiCAD command: ${request.command}`); - + // Format the command and parameters as JSON const requestStr = JSON.stringify(request); - + // Set up response handling let responseData = ''; - + // Clear any previous listeners if (this.pythonProcess?.stdout) { this.pythonProcess.stdout.removeAllListeners('data'); } - + // Set up new listeners if (this.pythonProcess?.stdout) { this.pythonProcess.stdout.on('data', (data: Buffer) => { const chunk = data.toString(); console.error(`Received data chunk: ${chunk.length} bytes`); responseData += chunk; - + // Check if we have a complete response try { // Try to parse the response as JSON const result = JSON.parse(responseData); - + // If we get here, we have a valid JSON response console.error(`Completed KiCAD command: ${request.command} with result: ${JSON.stringify(result)}`); - + // Reset processing flag this.processingRequest = false; - + // Process next request if any setTimeout(() => this.processNextRequest(), 0); - + // Clear listeners if (this.pythonProcess?.stdout) { this.pythonProcess.stdout.removeAllListeners('data'); } - + // Resolve with the expected MCP tool response format if (result.success) { resolve({ @@ -457,38 +457,38 @@ class KiCADServer { } }); } - + // Set a timeout const timeout = setTimeout(() => { console.error(`Command timeout: ${request.command}`); - + // Clear listeners if (this.pythonProcess?.stdout) { this.pythonProcess.stdout.removeAllListeners('data'); } - + // Reset processing flag this.processingRequest = false; - + // Process next request setTimeout(() => this.processNextRequest(), 0); - + // Reject the promise reject(new Error(`Command timeout: ${request.command}`)); }, 30000); // 30 seconds timeout - + // Write the request to the Python process console.error(`Sending request: ${requestStr}`); this.pythonProcess?.stdin?.write(requestStr + '\n'); } catch (error) { console.error(`Error processing request: ${error}`); - + // Reset processing flag this.processingRequest = false; - + // Process next request setTimeout(() => this.processNextRequest(), 0); - + // Reject the promise reject(error); } diff --git a/src/logger.ts b/src/logger.ts index f97e7c1..cb1cef7 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -18,7 +18,7 @@ const DEFAULT_LOG_DIR = join(os.homedir(), '.kicad-mcp', 'logs'); class Logger { private logLevel: LogLevel = 'info'; private logDir: string = DEFAULT_LOG_DIR; - + /** * Set the log level * @param level Log level to set @@ -26,20 +26,20 @@ class Logger { setLogLevel(level: LogLevel): void { this.logLevel = level; } - + /** * Set the log directory * @param dir Directory to store log files */ setLogDir(dir: string): void { this.logDir = dir; - + // Ensure log directory exists if (!existsSync(this.logDir)) { mkdirSync(this.logDir, { recursive: true }); } } - + /** * Log an error message * @param message Message to log @@ -47,7 +47,7 @@ class Logger { error(message: string): void { this.log('error', message); } - + /** * Log a warning message * @param message Message to log @@ -57,7 +57,7 @@ class Logger { this.log('warn', message); } } - + /** * Log an info message * @param message Message to log @@ -67,7 +67,7 @@ class Logger { this.log('info', message); } } - + /** * Log a debug message * @param message Message to log @@ -77,7 +77,7 @@ class Logger { this.log('debug', message); } } - + /** * Log a message with the specified level * @param level Log level @@ -93,14 +93,14 @@ class Logger { // Log to console.error (stderr) only - stdout is reserved for MCP protocol // All log levels go to stderr to avoid corrupting STDIO MCP transport console.error(formattedMessage); - + // Log to file try { // Ensure log directory exists if (!existsSync(this.logDir)) { mkdirSync(this.logDir, { recursive: true }); } - + const logFile = join(this.logDir, `kicad-mcp-${new Date().toISOString().split('T')[0]}.log`); appendFileSync(logFile, formattedMessage + '\n'); } catch (error) { diff --git a/src/prompts/component.ts b/src/prompts/component.ts index 7798bb2..4379609 100644 --- a/src/prompts/component.ts +++ b/src/prompts/component.ts @@ -1,6 +1,6 @@ /** * Component prompts for KiCAD MCP server - * + * * These prompts guide the LLM in providing assistance with component-related tasks * in KiCAD PCB design. */ @@ -11,7 +11,7 @@ import { logger } from '../logger.js'; /** * Register component prompts with the MCP server - * + * * @param server MCP server instance */ export function registerComponentPrompts(server: McpServer): void { diff --git a/src/prompts/design.ts b/src/prompts/design.ts index cc6cc7b..5c2f5cb 100644 --- a/src/prompts/design.ts +++ b/src/prompts/design.ts @@ -1,6 +1,6 @@ /** * Design prompts for KiCAD MCP server - * + * * These prompts guide the LLM in providing assistance with general PCB design tasks * in KiCAD. */ @@ -11,7 +11,7 @@ import { logger } from '../logger.js'; /** * Register design prompts with the MCP server - * + * * @param server MCP server instance */ export function registerDesignPrompts(server: McpServer): void { diff --git a/src/prompts/routing.ts b/src/prompts/routing.ts index b4e949b..cd8eb92 100644 --- a/src/prompts/routing.ts +++ b/src/prompts/routing.ts @@ -1,6 +1,6 @@ /** * Routing prompts for KiCAD MCP server - * + * * These prompts guide the LLM in providing assistance with routing-related tasks * in KiCAD PCB design. */ @@ -11,7 +11,7 @@ import { logger } from '../logger.js'; /** * Register routing prompts with the MCP server - * + * * @param server MCP server instance */ export function registerRoutingPrompts(server: McpServer): void { diff --git a/src/resources/board.ts b/src/resources/board.ts index 8b0bf1b..ce5d1f3 100644 --- a/src/resources/board.ts +++ b/src/resources/board.ts @@ -1,6 +1,6 @@ /** * Board resources for KiCAD MCP server - * + * * These resources provide information about the PCB board * to the LLM, enabling better context-aware assistance. */ @@ -15,7 +15,7 @@ type CommandFunction = (command: string, params: Record) => Pro /** * Register board resources with the MCP server - * + * * @param server MCP server instance * @param callKicadScript Function to call KiCAD script commands */ @@ -31,7 +31,7 @@ export function registerBoardResources(server: McpServer, callKicadScript: Comma 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 { @@ -45,7 +45,7 @@ export function registerBoardResources(server: McpServer, callKicadScript: Comma }] }; } - + logger.debug('Successfully retrieved board information'); return { contents: [{ @@ -66,7 +66,7 @@ export function registerBoardResources(server: McpServer, callKicadScript: Comma 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 { @@ -80,7 +80,7 @@ export function registerBoardResources(server: McpServer, callKicadScript: Comma }] }; } - + logger.debug(`Successfully retrieved ${result.layers?.length || 0} layers`); return { contents: [{ @@ -107,10 +107,10 @@ export function registerBoardResources(server: McpServer, callKicadScript: Comma }), 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 { @@ -124,7 +124,7 @@ export function registerBoardResources(server: McpServer, callKicadScript: Comma }] }; } - + logger.debug('Successfully retrieved board extents'); return { contents: [{ @@ -156,7 +156,7 @@ export function registerBoardResources(server: McpServer, callKicadScript: Comma 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, @@ -164,7 +164,7 @@ export function registerBoardResources(server: McpServer, callKicadScript: Comma height, format }); - + if (!result.success) { logger.error(`Failed to retrieve 2D board view: ${result.errorDetails}`); return { @@ -178,9 +178,9 @@ export function registerBoardResources(server: McpServer, callKicadScript: Comma }] }; } - + logger.debug('Successfully retrieved 2D board view'); - + if (format === 'svg') { return { contents: [{ @@ -219,14 +219,14 @@ export function registerBoardResources(server: McpServer, callKicadScript: Comma 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 { @@ -240,7 +240,7 @@ export function registerBoardResources(server: McpServer, callKicadScript: Comma }] }; } - + logger.debug('Successfully retrieved 3D board view'); return { contents: [{ @@ -260,7 +260,7 @@ export function registerBoardResources(server: McpServer, callKicadScript: Comma "kicad://board/statistics", async (uri) => { logger.debug('Generating board statistics'); - + // Get board info const boardResult = await callKicadScript("get_board_info", {}); if (!boardResult.success) { @@ -276,7 +276,7 @@ export function registerBoardResources(server: McpServer, callKicadScript: Comma }] }; } - + // Get component list const componentsResult = await callKicadScript("get_component_list", {}); if (!componentsResult.success) { @@ -292,7 +292,7 @@ export function registerBoardResources(server: McpServer, callKicadScript: Comma }] }; } - + // Get nets list const netsResult = await callKicadScript("get_nets_list", {}); if (!netsResult.success) { @@ -308,7 +308,7 @@ export function registerBoardResources(server: McpServer, callKicadScript: Comma }] }; } - + // Combine all information into statistics const statistics = { board: { @@ -324,7 +324,7 @@ export function registerBoardResources(server: McpServer, callKicadScript: Comma count: netsResult.nets?.length || 0 } }; - + logger.debug('Successfully generated board statistics'); return { contents: [{ @@ -344,11 +344,11 @@ export function registerBoardResources(server: McpServer, callKicadScript: Comma */ 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; } diff --git a/src/resources/component.ts b/src/resources/component.ts index 7e7c789..947c4a1 100644 --- a/src/resources/component.ts +++ b/src/resources/component.ts @@ -1,6 +1,6 @@ /** * Component resources for KiCAD MCP server - * + * * These resources provide information about components on the PCB * to the LLM, enabling better context-aware assistance. */ @@ -13,7 +13,7 @@ type CommandFunction = (command: string, params: Record) => Pro /** * Register component resources with the MCP server - * + * * @param server MCP server instance * @param callKicadScript Function to call KiCAD script commands */ @@ -29,7 +29,7 @@ export function registerComponentResources(server: McpServer, callKicadScript: C 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 { @@ -43,7 +43,7 @@ export function registerComponentResources(server: McpServer, callKicadScript: C }] }; } - + logger.debug(`Successfully retrieved ${result.components?.length || 0} components`); return { contents: [{ @@ -69,7 +69,7 @@ export function registerComponentResources(server: McpServer, callKicadScript: C const result = await callKicadScript("get_component_properties", { reference }); - + if (!result.success) { logger.error(`Failed to retrieve component details: ${result.errorDetails}`); return { @@ -83,7 +83,7 @@ export function registerComponentResources(server: McpServer, callKicadScript: C }] }; } - + logger.debug(`Successfully retrieved details for component: ${reference}`); return { contents: [{ @@ -109,7 +109,7 @@ export function registerComponentResources(server: McpServer, callKicadScript: C const result = await callKicadScript("get_component_connections", { reference }); - + if (!result.success) { logger.error(`Failed to retrieve component connections: ${result.errorDetails}`); return { @@ -123,7 +123,7 @@ export function registerComponentResources(server: McpServer, callKicadScript: C }] }; } - + logger.debug(`Successfully retrieved connections for component: ${reference}`); return { contents: [{ @@ -144,7 +144,7 @@ export function registerComponentResources(server: McpServer, callKicadScript: C 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 { @@ -158,7 +158,7 @@ export function registerComponentResources(server: McpServer, callKicadScript: C }] }; } - + logger.debug('Successfully retrieved component placement information'); return { contents: [{ @@ -179,7 +179,7 @@ export function registerComponentResources(server: McpServer, callKicadScript: C 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 { @@ -193,7 +193,7 @@ export function registerComponentResources(server: McpServer, callKicadScript: C }] }; } - + logger.debug(`Successfully retrieved ${result.groups?.length || 0} component groups`); return { contents: [{ @@ -219,7 +219,7 @@ export function registerComponentResources(server: McpServer, callKicadScript: C const result = await callKicadScript("get_component_visualization", { reference }); - + if (!result.success) { logger.error(`Failed to generate component visualization: ${result.errorDetails}`); return { @@ -233,7 +233,7 @@ export function registerComponentResources(server: McpServer, callKicadScript: C }] }; } - + logger.debug(`Successfully generated visualization for component: ${reference}`); return { contents: [{ diff --git a/src/resources/index.ts b/src/resources/index.ts index e443af9..c025fc3 100644 --- a/src/resources/index.ts +++ b/src/resources/index.ts @@ -1,6 +1,6 @@ /** * Resources index for KiCAD MCP server - * + * * Exports all resource registration functions */ diff --git a/src/resources/library.ts b/src/resources/library.ts index 0a101ab..1b7acde 100644 --- a/src/resources/library.ts +++ b/src/resources/library.ts @@ -1,6 +1,6 @@ /** * Library resources for KiCAD MCP server - * + * * These resources provide information about KiCAD component libraries * to the LLM, enabling better context-aware assistance. */ @@ -14,7 +14,7 @@ type CommandFunction = (command: string, params: Record) => Pro /** * Register library resources with the MCP server - * + * * @param server MCP server instance * @param callKicadScript Function to call KiCAD script commands */ @@ -39,7 +39,7 @@ export function registerLibraryResources(server: McpServer, callKicadScript: Com 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, @@ -117,7 +117,7 @@ export function registerLibraryResources(server: McpServer, callKicadScript: Com 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 @@ -159,7 +159,7 @@ export function registerLibraryResources(server: McpServer, callKicadScript: Com 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 @@ -201,7 +201,7 @@ export function registerLibraryResources(server: McpServer, callKicadScript: Com async (uri, params) => { const { componentId } = params; logger.debug(`Retrieving symbol for component: ${componentId}`); - + const result = await callKicadScript("get_component_symbol", { componentId }); @@ -255,7 +255,7 @@ export function registerLibraryResources(server: McpServer, callKicadScript: Com 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 diff --git a/src/resources/project.ts b/src/resources/project.ts index a2fbde4..e2226ce 100644 --- a/src/resources/project.ts +++ b/src/resources/project.ts @@ -1,6 +1,6 @@ /** * Project resources for KiCAD MCP server - * + * * These resources provide information about the KiCAD project * to the LLM, enabling better context-aware assistance. */ @@ -13,7 +13,7 @@ type CommandFunction = (command: string, params: Record) => Pro /** * Register project resources with the MCP server - * + * * @param server MCP server instance * @param callKicadScript Function to call KiCAD script commands */ @@ -29,7 +29,7 @@ export function registerProjectResources(server: McpServer, callKicadScript: Com 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 { @@ -43,7 +43,7 @@ export function registerProjectResources(server: McpServer, callKicadScript: Com }] }; } - + logger.debug('Successfully retrieved project information'); return { contents: [{ @@ -64,7 +64,7 @@ export function registerProjectResources(server: McpServer, callKicadScript: Com 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 { @@ -78,7 +78,7 @@ export function registerProjectResources(server: McpServer, callKicadScript: Com }] }; } - + logger.debug('Successfully retrieved project properties'); return { contents: [{ @@ -99,7 +99,7 @@ export function registerProjectResources(server: McpServer, callKicadScript: Com 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 { @@ -113,7 +113,7 @@ export function registerProjectResources(server: McpServer, callKicadScript: Com }] }; } - + logger.debug(`Successfully retrieved ${result.files?.length || 0} project files`); return { contents: [{ @@ -134,7 +134,7 @@ export function registerProjectResources(server: McpServer, callKicadScript: Com 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 { @@ -148,7 +148,7 @@ export function registerProjectResources(server: McpServer, callKicadScript: Com }] }; } - + logger.debug('Successfully retrieved project status'); return { contents: [{ @@ -168,7 +168,7 @@ export function registerProjectResources(server: McpServer, callKicadScript: Com "kicad://project/summary", async (uri) => { logger.debug('Generating project summary'); - + // Get project info const infoResult = await callKicadScript("get_project_info", {}); if (!infoResult.success) { @@ -184,7 +184,7 @@ export function registerProjectResources(server: McpServer, callKicadScript: Com }] }; } - + // Get board info const boardResult = await callKicadScript("get_board_info", {}); if (!boardResult.success) { @@ -200,7 +200,7 @@ export function registerProjectResources(server: McpServer, callKicadScript: Com }] }; } - + // Get component list const componentsResult = await callKicadScript("get_component_list", {}); if (!componentsResult.success) { @@ -216,7 +216,7 @@ export function registerProjectResources(server: McpServer, callKicadScript: Com }] }; } - + // Combine all information into a summary const summary = { project: infoResult.project, @@ -230,7 +230,7 @@ export function registerProjectResources(server: McpServer, callKicadScript: Com types: countComponentTypes(componentsResult.components || []) } }; - + logger.debug('Successfully generated project summary'); return { contents: [{ @@ -250,11 +250,11 @@ export function registerProjectResources(server: McpServer, callKicadScript: Com */ 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; } diff --git a/src/tools/board.ts b/src/tools/board.ts index 1fc4ef8..975781d 100644 --- a/src/tools/board.ts +++ b/src/tools/board.ts @@ -1,6 +1,6 @@ /** * Board management tools for KiCAD MCP server - * + * * These tools handle board setup, layer management, and board properties */ @@ -13,13 +13,13 @@ type CommandFunction = (command: string, params: Record) => Pro /** * 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 // ------------------------------------------------------ @@ -37,7 +37,7 @@ export function registerBoardTools(server: McpServer, callKicadScript: CommandFu height, unit }); - + return { content: [{ type: "text", @@ -70,7 +70,7 @@ export function registerBoardTools(server: McpServer, callKicadScript: CommandFu position, number }); - + return { content: [{ type: "text", @@ -91,7 +91,7 @@ export function registerBoardTools(server: McpServer, callKicadScript: CommandFu async ({ layer }) => { logger.debug(`Setting active layer to: ${layer}`); const result = await callKicadScript("set_active_layer", { layer }); - + return { content: [{ type: "text", @@ -110,7 +110,7 @@ export function registerBoardTools(server: McpServer, callKicadScript: CommandFu async () => { logger.debug('Getting board information'); const result = await callKicadScript("get_board_info", {}); - + return { content: [{ type: "text", @@ -129,7 +129,7 @@ export function registerBoardTools(server: McpServer, callKicadScript: CommandFu async () => { logger.debug('Getting layer list'); const result = await callKicadScript("get_layer_list", {}); - + return { content: [{ type: "text", @@ -205,7 +205,7 @@ export function registerBoardTools(server: McpServer, callKicadScript: CommandFu diameter, padDiameter }); - + return { content: [{ type: "text", @@ -244,7 +244,7 @@ export function registerBoardTools(server: McpServer, callKicadScript: CommandFu rotation, style }); - + return { content: [{ type: "text", @@ -284,7 +284,7 @@ export function registerBoardTools(server: McpServer, callKicadScript: CommandFu minWidth, padConnection }); - + return { content: [{ type: "text", @@ -305,7 +305,7 @@ export function registerBoardTools(server: McpServer, callKicadScript: CommandFu async ({ unit }) => { logger.debug('Getting board extents'); const result = await callKicadScript("get_board_extents", { unit }); - + return { content: [{ type: "text", @@ -334,7 +334,7 @@ export function registerBoardTools(server: McpServer, callKicadScript: CommandFu height, format }); - + return { content: [{ type: "text", @@ -382,4 +382,3 @@ export function registerBoardTools(server: McpServer, callKicadScript: CommandFu }, ); } - diff --git a/src/tools/component.txt b/src/tools/component.txt index 9757ea8..c9c322f 100644 --- a/src/tools/component.txt +++ b/src/tools/component.txt @@ -11,16 +11,16 @@ type CommandFunction = (command: string, params: any) => Promise; /** * Register component management tools with the MCP server - * + * * @param server MCP server instance * @param callKicadScript Function to call KiCAD script commands */ export function registerComponentTools(server: McpServer, callKicadScript: CommandFunction): void { logger.info('Registering component management tools'); - + // ------------------------------------------------------ // Place Component Tool // ------------------------------------------------------ server.registerTool({ name: "place_component", - description: "Places a component on the PCB at the specified location", \ No newline at end of file + description: "Places a component on the PCB at the specified location", diff --git a/src/tools/design-rules.ts b/src/tools/design-rules.ts index 034837b..2f028d4 100644 --- a/src/tools/design-rules.ts +++ b/src/tools/design-rules.ts @@ -1,6 +1,6 @@ /** * Design rules tools for KiCAD MCP server - * + * * These tools handle design rule checking and configuration */ @@ -13,13 +13,13 @@ type CommandFunction = (command: string, params: Record) => Pro /** * 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 // ------------------------------------------------------ @@ -44,7 +44,7 @@ export function registerDesignRuleTools(server: McpServer, callKicadScript: Comm async (params) => { logger.debug('Setting design rules'); const result = await callKicadScript("set_design_rules", params); - + return { content: [{ type: "text", @@ -63,7 +63,7 @@ export function registerDesignRuleTools(server: McpServer, callKicadScript: Comm async () => { logger.debug('Getting design rules'); const result = await callKicadScript("get_design_rules", {}); - + return { content: [{ type: "text", @@ -84,7 +84,7 @@ export function registerDesignRuleTools(server: McpServer, callKicadScript: Comm async ({ reportPath }) => { logger.debug('Running DRC check'); const result = await callKicadScript("run_drc", { reportPath }); - + return { content: [{ type: "text", @@ -127,7 +127,7 @@ export function registerDesignRuleTools(server: McpServer, callKicadScript: Comm diff_pair_gap, nets }); - + return { content: [{ type: "text", @@ -152,7 +152,7 @@ export function registerDesignRuleTools(server: McpServer, callKicadScript: Comm net, netClass }); - + return { content: [{ type: "text", @@ -183,7 +183,7 @@ export function registerDesignRuleTools(server: McpServer, callKicadScript: Comm minViaDiameter, minViaDrill }); - + return { content: [{ type: "text", @@ -226,7 +226,7 @@ export function registerDesignRuleTools(server: McpServer, callKicadScript: Comm item1, item2 }); - + return { content: [{ type: "text", @@ -247,7 +247,7 @@ export function registerDesignRuleTools(server: McpServer, callKicadScript: Comm async ({ severity }) => { logger.debug('Getting DRC violations'); const result = await callKicadScript("get_drc_violations", { severity }); - + return { content: [{ type: "text", diff --git a/src/tools/export.ts b/src/tools/export.ts index a179b33..e51beff 100644 --- a/src/tools/export.ts +++ b/src/tools/export.ts @@ -1,6 +1,6 @@ /** * Export tools for KiCAD MCP server - * + * * These tools handle exporting PCB data to various formats */ @@ -13,13 +13,13 @@ type CommandFunction = (command: string, params: Record) => Pro /** * 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 // ------------------------------------------------------ @@ -43,7 +43,7 @@ export function registerExportTools(server: McpServer, callKicadScript: CommandF generateMapFile, useAuxOrigin }); - + return { content: [{ type: "text", @@ -74,7 +74,7 @@ export function registerExportTools(server: McpServer, callKicadScript: CommandF frameReference, pageSize }); - + return { content: [{ type: "text", @@ -103,7 +103,7 @@ export function registerExportTools(server: McpServer, callKicadScript: CommandF blackAndWhite, includeComponents }); - + return { content: [{ type: "text", @@ -136,7 +136,7 @@ export function registerExportTools(server: McpServer, callKicadScript: CommandF includeSolderMask, includeSilkscreen }); - + return { content: [{ type: "text", @@ -165,7 +165,7 @@ export function registerExportTools(server: McpServer, callKicadScript: CommandF groupByValue, includeAttributes }); - + return { content: [{ type: "text", @@ -190,7 +190,7 @@ export function registerExportTools(server: McpServer, callKicadScript: CommandF outputPath, format }); - + return { content: [{ type: "text", @@ -219,7 +219,7 @@ export function registerExportTools(server: McpServer, callKicadScript: CommandF units, side }); - + return { content: [{ type: "text", @@ -246,7 +246,7 @@ export function registerExportTools(server: McpServer, callKicadScript: CommandF includeComponents, useRelativePaths }); - + return { content: [{ type: "text",