kicad-mcp/CLAUDE.md
Ryan Malloy 4ae38fed59 Rebuild on FastMCP 3 with src-layout and kicad-sch-api integration
Migrate from FastMCP 2.14.5 to 3.1.0 with complete architectural
overhaul. Adopt src-layout packaging, lazy config functions to
eliminate .env race condition, and decorator-based tool registration.

Consolidate 14 tool modules into 8 focused modules (33 tools total).
Add 9 new schematic tools via kicad-sch-api for creating and
manipulating .kicad_sch files. Drop pandas dependency (BOM uses
stdlib csv). Remove ~17k lines of stubs, over-engineering, and
dead code.

All checks pass: ruff clean, mypy 0 errors, 17/17 tests green.
2026-03-03 18:26:54 -07:00

5.4 KiB

CLAUDE.md

This file provides guidance to Claude Code when working with the mckicad codebase.

Development Commands

  • make install - Install dependencies with uv (creates .venv)
  • make run - Start the MCP server (uv run python main.py)
  • make test - Run all tests (uv run pytest tests/ -v)
  • make test <file> - Run a specific test file
  • make lint - Lint with ruff + mypy (src/mckicad/ and tests/)
  • make format - Auto-format with ruff
  • make build - Build package
  • make clean - Remove build artifacts and caches

Python 3.10+ required. Uses uv for everything. Configure via .env (copy .env.example).

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

src/mckicad/
  __init__.py           # __version__ only
  server.py             # FastMCP 3 server + lifespan + module imports
  config.py             # Lazy config functions (no module-level env reads)
  tools/
    schematic.py        # kicad-sch-api: create/edit schematics (9 tools)
    project.py          # Project discovery and structure (3 tools)
    drc.py              # DRC checking + manufacturing constraints (4 tools)
    bom.py              # BOM generation and export (2 tools)
    export.py           # Gerber, drill, PDF, SVG via kicad-cli (4 tools)
    routing.py          # FreeRouting autorouter integration (3 tools)
    analysis.py         # Board validation + real-time analysis (3 tools)
    pcb.py              # IPC-based PCB manipulation via kipy (5 tools)
  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
tests/
  conftest.py           # Shared fixtures (tmp dirs, project paths)
  test_*.py             # Per-module test files
main.py                 # Entry point: .env loader + server start

Key Design Decisions

Lazy config (config.py): All environment-dependent values are accessed via functions (get_search_paths(), get_kicad_user_dir()) called at runtime, not at import time. Static constants (KICAD_EXTENSIONS, TIMEOUT_CONSTANTS, COMMON_LIBRARIES) remain as module-level dicts since they don't read env vars. This eliminates the .env load-order race condition.

Decorator-based tool registration: Each tool module imports mcp from server.py and decorates functions with @mcp.tool() at module level. server.py imports the modules to trigger registration. No register_*_tools() boilerplate.

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: PCB tools work via IPC (kipy, requires running KiCad) or CLI (kicad-cli, batch mode). Tools degrade gracefully when KiCad isn't running.

Tool Registration Pattern

# 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": "..."}

Tool Return Convention

All tools return dicts with at least success: bool. On failure, include error: str. On success, include relevant data fields.

Adding New Features

  1. Choose the right module (or create one in tools/)
  2. Import mcp from mckicad.server
  3. Decorate with @mcp.tool() and add a clear docstring
  4. If new module: add import in server.py
  5. Write tests in tests/test_<module>.py

Security

  • All file paths validated via utils/path_validator.py before access
  • External commands run through utils/secure_subprocess.py with timeouts
  • KiCad CLI commands sanitized — no shell injection
  • main.py inline .env loader runs before any mckicad imports

Environment Variables

  • KICAD_USER_DIR - KiCad user config directory
  • KICAD_SEARCH_PATHS - Comma-separated project search paths
  • KICAD_CLI_PATH - Explicit kicad-cli path
  • FREEROUTING_JAR_PATH - Path to FreeRouting JAR
  • LOG_LEVEL - Logging level (default: INFO)

Testing

Markers: unit, integration, requires_kicad, slow, performance

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

Entry Point

[project.scripts]
mckicad = "mckicad.server:main"

Run via uvx mckicad, uv run mckicad, or uv run python main.py.

FreeRouting Setup

  1. Download JAR from https://freerouting.app/
  2. Place at ~/freerouting.jar, /usr/local/bin/freerouting.jar, or /opt/freerouting/freerouting.jar
  3. Install Java runtime
  4. Verify with check_routing_capability() tool
  5. Or set FREEROUTING_JAR_PATH in .env

Logging

Logs go to mckicad.log in project root, overwritten each start. Never use print() — MCP uses stdin/stdout for JSON-RPC transport.