Architecture
mckicad is a FastMCP 3 server for KiCad electronic design automation. It uses src-layout packaging with hatchling as the build backend.
Project structure
Section titled “Project structure”src/mckicad/ __init__.py # __version__ only server.py # FastMCP 3 server + lifespan + module imports config.py # Lazy config functions (no module-level env reads) autowire/ __init__.py # Package init strategy.py # Wiring decision tree: classify_net, crossing estimation planner.py # NetPlan -> apply_batch JSON conversion tools/ autowire.py # autowire_schematic MCP tool schematic.py # kicad-sch-api: create/edit schematics schematic_edit.py # Modify/remove schematic elements schematic_analysis.py # Connectivity, ERC, netlist, validation schematic_patterns.py # Circuit building block patterns batch.py # Atomic multi-operation batch tool power_symbols.py # Power symbol placement netlist.py # External netlist import project.py # Project discovery and structure drc.py # DRC checking + manufacturing constraints bom.py # BOM generation and export export.py # Gerber, drill, PDF, SVG via kicad-cli routing.py # FreeRouting autorouter integration analysis.py # Board validation + real-time analysis pcb.py # IPC-based PCB manipulation via kipy resources/ projects.py # kicad://projects resource files.py # kicad://project/{path} resource prompts/ templates.py # debug_pcb, analyze_bom, design_circuit, debug_schematic utils/ kicad_cli.py # KiCad CLI detection and execution path_validator.py # Path security / directory traversal prevention secure_subprocess.py # Safe subprocess execution with timeouts ipc_client.py # kipy IPC wrapper for live KiCad connection freerouting.py # FreeRouting JAR engine file_utils.py # Project file discovery kicad_utils.py # KiCad path detection, project search sexp_parser.py # S-expression parsing for pin position resolutiontests/ conftest.py # Shared fixtures (tmp dirs, project paths) test_*.py # Per-module test filesmain.py # Entry point: .env loader + server startKey design decisions
Section titled “Key design decisions”Lazy config
Section titled “Lazy config”config.py provides all environment-dependent values through functions (get_search_paths(), get_kicad_user_dir()) called at runtime, not at import time. Static constants (KICAD_EXTENSIONS, TIMEOUT_CONSTANTS, COMMON_LIBRARIES, BATCH_LIMITS, LABEL_DEFAULTS) remain as module-level dicts since they do not read environment variables.
This eliminates the .env load-order race condition — main.py loads .env before any mckicad imports, and config functions read os.environ lazily when first called.
Decorator-based tool registration
Section titled “Decorator-based tool registration”Each tool module imports mcp from server.py and decorates functions with @mcp.tool() at module level. server.py imports those modules to trigger registration. There is no register_*_tools() boilerplate.
from mckicad.server import mcp
@mcp.tool()def my_tool(param: str) -> dict: """Tool description for the calling LLM.""" return {"success": True, "data": "..."}Schematic abstraction point
Section titled “Schematic abstraction point”tools/schematic.py uses kicad-sch-api for file-level schematic manipulation. The _get_schematic_engine() helper exists as a swap point for when kipy adds schematic IPC support.
Dual-mode operation
Section titled “Dual-mode operation”PCB tools work via IPC (kipy, requires running KiCad) or CLI (kicad-cli, batch mode). Tools degrade gracefully when KiCad is not running — they return clear error messages rather than crashing.
Tool return convention
Section titled “Tool return convention”All tools return dicts with at least success: bool. On failure, include error: str. On success, include relevant data fields. This gives callers a consistent interface for error handling.
Batch processing
Section titled “Batch processing”The batch system (tools/batch.py) applies hundreds of operations in a single load-save cycle. Operations are processed in dependency order: components first (so pin references resolve), then power symbols, wires, labels, and no-connects.
Labels are handled as post-save sexp insertions because kicad-sch-api’s serializer has issues with label types. The batch tool generates sexp strings, saves the schematic through kicad-sch-api, then inserts the label sexp directly into the file.
Pin position resolution
Section titled “Pin position resolution”The utils/sexp_parser.py module provides pin position resolution that works even when kicad-sch-api’s API does not expose pin coordinates. It falls back to parsing the raw S-expression data from the schematic file and computing pin positions from component transforms.
Security
Section titled “Security”- All file paths are validated via
utils/path_validator.pybefore access — prevents directory traversal - External commands run through
utils/secure_subprocess.pywith timeouts — prevents hangs and injection - KiCad CLI commands are sanitized — no shell injection possible
.envloading runs before any mckicad imports to avoid partial initialization
Entry point
Section titled “Entry point”[project.scripts]mckicad = "mckicad.server:main"Can be run as uvx mckicad, uv run mckicad, or uv run python main.py.
Logging
Section titled “Logging”Logs go to mckicad.log in the project root, overwritten on each server start. Never use print() — MCP uses stdin/stdout for JSON-RPC transport, so any print output would corrupt the protocol stream.
Development environment
Section titled “Development environment”make install # Install dependencies with uvmake run # Start the MCP servermake test # Run all testsmake lint # Lint with ruff + mypymake format # Auto-format with ruffUse the MCP Inspector for interactive debugging:
npx @modelcontextprotocol/inspector uv --directory . run main.pyTesting
Section titled “Testing”Markers: unit, integration, requires_kicad, slow, performance
make test # all testsmake test tests/test_schematic.py # one fileuv run pytest -m "unit" # by marker