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:
Eugene Mikhantyev
2026-03-29 12:58:36 +01:00
parent 41ae1ba82c
commit eee5bfb9ed
26 changed files with 322 additions and 385 deletions

View File

@@ -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

View File

@@ -986,9 +986,3 @@ If you use this project in your research or publication, please cite:
version = {2.2.3}
}
```

View File

@@ -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
View File

@@ -0,0 +1,4 @@
[project]
name = "kicad-mcp-server"
version = "2.1.0"
requires-python = ">=3.10"

View File

@@ -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

View File

@@ -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 = {

View File

@@ -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 = {

View File

@@ -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:

View File

@@ -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,

View File

@@ -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']}")

View File

@@ -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({});
}

View File

@@ -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}`);

View File

@@ -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);
}

View File

@@ -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) {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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;
}

View File

@@ -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: [{

View File

@@ -1,6 +1,6 @@
/**
* Resources index for KiCAD MCP server
*
*
* Exports all resource registration functions
*/

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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
},
);
}

View File

@@ -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",

View File

@@ -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",

View File

@@ -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",