chore: set up pre-commit framework with general hooks
Add .pre-commit-config.yaml with pre-commit-hooks v5.0.0 (trailing whitespace, end-of-file fixer, yaml/json checks, large file guard, merge conflict detection). Add minimal pyproject.toml. Auto-fix trailing whitespace and missing end-of-file newlines across the codebase. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -986,9 +986,3 @@ If you use this project in your research or publication, please cite:
|
||||
version = {2.2.3}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
4
pyproject.toml
Normal file
4
pyproject.toml
Normal file
@@ -0,0 +1,4 @@
|
||||
[project]
|
||||
name = "kicad-mcp-server"
|
||||
version = "2.1.0"
|
||||
requires-python = ">=3.10"
|
||||
@@ -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
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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']}")
|
||||
|
||||
@@ -36,7 +36,7 @@ export type Config = z.infer<typeof ConfigSchema>;
|
||||
|
||||
/**
|
||||
* 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<Config> {
|
||||
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({});
|
||||
}
|
||||
|
||||
24
src/index.ts
24
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}`);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<string, unknown>) => 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<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;
|
||||
}
|
||||
|
||||
@@ -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<string, unknown>) => 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: [{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Resources index for KiCAD MCP server
|
||||
*
|
||||
*
|
||||
* Exports all resource registration functions
|
||||
*/
|
||||
|
||||
|
||||
@@ -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<string, unknown>) => 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
|
||||
|
||||
@@ -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<string, unknown>) => 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<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;
|
||||
}
|
||||
|
||||
@@ -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<string, unknown>) => 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
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,16 +11,16 @@ type CommandFunction = (command: string, params: any) => Promise<any>;
|
||||
|
||||
/**
|
||||
* 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",
|
||||
description: "Places a component on the PCB at the specified location",
|
||||
|
||||
@@ -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<string, unknown>) => 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",
|
||||
|
||||
@@ -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<string, unknown>) => 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",
|
||||
|
||||
Reference in New Issue
Block a user