Skip to content

Architecture

mckicad is a FastMCP 3 server for KiCad electronic design automation. It uses src-layout packaging with hatchling as the build backend.

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 resolution
tests/
conftest.py # Shared fixtures (tmp dirs, project paths)
test_*.py # Per-module test files
main.py # Entry point: .env loader + server start

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.

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.

tools/example.py
from mckicad.server import mcp
@mcp.tool()
def my_tool(param: str) -> dict:
"""Tool description for the calling LLM."""
return {"success": True, "data": "..."}

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.

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.

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.

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.

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.

  • All file paths are validated via utils/path_validator.py before access — prevents directory traversal
  • External commands run through utils/secure_subprocess.py with timeouts — prevents hangs and injection
  • KiCad CLI commands are sanitized — no shell injection possible
  • .env loading runs before any mckicad imports to avoid partial initialization
[project.scripts]
mckicad = "mckicad.server:main"

Can be run as uvx mckicad, uv run mckicad, or uv run python main.py.

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.

Terminal window
make install # Install dependencies with uv
make run # Start the MCP server
make test # Run all tests
make lint # Lint with ruff + mypy
make format # Auto-format with ruff

Use the MCP Inspector for interactive debugging:

Terminal window
npx @modelcontextprotocol/inspector uv --directory . run main.py

Markers: unit, integration, requires_kicad, slow, performance

Terminal window
make test # all tests
make test tests/test_schematic.py # one file
uv run pytest -m "unit" # by marker