Merge pull request #59 from Kletternaut/demo/rpiCSI-videotest

docs+fix: v2.2.3 — snapshot logging, via placement fix, developer mode docs, privacy warning

More? Good lord you are busy.
This commit is contained in:
mixelpixx
2026-03-11 13:42:59 -04:00
committed by GitHub
7 changed files with 208 additions and 11 deletions

View File

@@ -2,6 +2,102 @@
All notable changes to the KiCAD MCP Server project are documented here.
## [2.2.3] - 2026-03-11
### Merged: PR #57 (Kletternaut/demo/rpiCSI-videotest → main)
This release incorporates 28 commits developed and live-tested during a full
Raspberry Pi CSI adapter PCB design session. All tools listed below were validated
end-to-end using Claude Desktop + KiCAD 9 on Windows.
### New MCP Tools
- `connect_passthrough` — Schematic-only tool that wires all pins of one connector
directly to the matching pins of another (e.g. J1 pin N → J2 pin N). Creates nets
named with a configurable prefix (`netPrefix`). Designed for FFC/ribbon cable
passthrough adapters. **Schematic only — do not call for PCB routing.**
- `sync_schematic_to_board` — Imports all net/pad assignments from the schematic
into the open PCB file. Required after `connect_passthrough` before routing can
start. Returns `pads_assigned` count for verification.
- `snapshot_project` — Saves a named checkpoint of the entire project folder into a
`snapshots/` subdirectory inside the project. Allows resuming from a known-good
state without redoing earlier steps. Accepts `step`, `label`, and optional `prompt`
parameters.
- `run_erc` — Runs KiCAD's Electrical Rules Check on the schematic and returns
violations as structured JSON.
- `import_svg_logo` — Converts an SVG file to PCB silkscreen polygons and places
them on a specified layer.
### Bug Fixes
- `route_pad_to_pad`: **Critical fix for B.Cu footprints in KiCAD 9.** `pad.GetLayerName()`
always returned `F.Cu` for SMD pads on flipped footprints (KiCAD 9 SWIG bug).
Fix: use `footprint.GetLayer()` instead, which correctly reflects the placed layer
after `Flip()`. Without this fix, no vias were inserted for back-to-back connectors.
- `route_pad_to_pad`: Via was placed at the geometric midpoint between the two pads.
For back-to-back mirrored connectors (J1 F.Cu / J2 B.Cu) this caused all 15 vias
to stack at the same X coordinate (board center). Fix: via is now placed at the
X coordinate of the start pad (`via_x = start_pos.x`), producing 15 parallel
vertical traces.
- `place_component` (B.Cu footprints): `Flip()` was called before `board.Add()`,
causing KiCAD 9 to hang for ~30 seconds. Fix: `board.Add()` first, then `Flip()`.
- `add_board_outline`: Three separate bugs fixed — incorrect cornerRadius fallback,
wrong top-left origin default, and broken arc delegation for IPC rounded rectangles.
- `snapshot_project`: Snapshots were saved one level above the project directory,
cluttering the parent folder. Fix: snapshots now go into `<project>/snapshots/`.
- MCP server log timestamp was always UTC/ISO. Fix: now uses local system time.
- `search_tools` (router pattern): direct tools like `snapshot_project` were invisible
to the router. Fix: direct tool names added to the router's known-tool list.
### Developer Mode (`KICAD_MCP_DEV=1`)
Set the environment variable `KICAD_MCP_DEV=1` in your Claude Desktop config to
enable developer features:
```json
"env": {
"KICAD_MCP_DEV": "1"
}
```
**What it does:**
- `export_gerber` automatically copies the current MCP session log into the project's
`logs/` subdirectory as `mcp_log_<timestamp>.txt`.
- `snapshot_project` copies the MCP session log into `logs/` at every checkpoint as
`mcp_log_step<N>_<timestamp>.txt`.
- If a `prompt` parameter is passed to `snapshot_project`, it is saved as
`PROMPT_step<N>_<timestamp>.md` alongside the log.
**Purpose:** Makes it easy to include the full tool call history when filing a bug
report or GitHub issue — just attach the log file from the project's `logs/` folder.
> ⚠️ **Privacy warning:** The MCP session log contains the **complete conversation
> history** between Claude and the MCP server, including all tool parameters and
> responses. When sharing a project directory (e.g. as a ZIP attachment in a GitHub
> issue), **review or delete the `logs/` folder first** to avoid accidentally
> disclosing sensitive file paths, component names, or design details.
### Snapshot Logging (always active)
Regardless of dev mode, `snapshot_project` now always saves a copy of the current
MCP session log into `<project>/logs/` at each checkpoint. This means every project
automatically retains a traceable record of which tools were called and in what order.
> ⚠️ **Same privacy note applies:** the `logs/` directory inside your project folder
> contains tool call history. Do not share it publicly without reviewing its contents.
---
## [2.2.2-alpha] - 2026-03-01
### New MCP Tools

View File

@@ -26,6 +26,50 @@ https://github.com/mixelpixx/arduino-ide
## What's New in v2.2.3
### New Tools: FFC/Ribbon Cable Passthrough Workflow
A complete workflow for designing passthrough adapter boards (e.g. Raspberry Pi CSI
cable adapters) is now supported:
1. `connect_passthrough` — wires all pins of one connector to the matching pins of
another in the schematic (J1 pin N → J2 pin N, auto-named nets).
2. `sync_schematic_to_board` — imports the net assignments into the PCB.
3. `route_pad_to_pad` — routes each connection with automatic via insertion when
pads are on opposite copper layers.
4. `snapshot_project` — saves a named checkpoint into `<project>/snapshots/`.
### Bug Fixes (KiCAD 9 / Windows)
- **Via insertion for B.Cu footprints** — `route_pad_to_pad` now correctly detects
when a footprint is on B.Cu and inserts the required via. (KiCAD 9 SWIG returned
`F.Cu` for all SMD pads regardless of layer — fixed.)
- **Board outline rounded corners** — `add_board_outline` now correctly applies
`cornerRadius` when `shape="rounded_rectangle"`.
- **B.Cu placement hang** — placing a footprint on B.Cu no longer causes a ~30s
freeze in KiCAD 9.
### Developer Mode
Set `KICAD_MCP_DEV=1` in your Claude Desktop MCP environment to automatically save
the MCP session log into the project's `logs/` folder on every `export_gerber` and
`snapshot_project` call. Useful for debugging and for attaching to GitHub issues.
```json
"env": {
"KICAD_MCP_DEV": "1"
}
```
> ⚠️ **Privacy warning:** The session log contains your full tool call history
> (including file paths and design details). **Review or delete `logs/` before
> sharing a project directory publicly.**
See [CHANGELOG](CHANGELOG.md) for the full list of changes in this release.
---
## What's New in v2.1.0
### Critical Schematic Workflow Fix + Complete Wiring System (Issue #26)

View File

@@ -695,7 +695,10 @@ class ExportCommands:
session_lines = all_lines[session_start:]
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
dest = os.path.join(project_dir, f"mcp_log_{timestamp}.txt")
from pathlib import Path
logs_dir = Path(project_dir) / "logs"
logs_dir.mkdir(exist_ok=True)
dest = str(logs_dir / f"mcp_log_{timestamp}.txt")
with open(dest, "w", encoding="utf-8") as f:
f.writelines(session_lines)

View File

@@ -144,9 +144,14 @@ class RoutingCommands:
if not net:
net = start_pad.GetNetname() or end_pad.GetNetname() or ""
# Detect if pads are on different copper layers → need via
start_layer = start_pad.GetLayerName()
end_layer = end_pad.GetLayerName()
# Detect if pads are on different copper layers → need via.
# SMD pad.GetLayer() reports F.Cu even on flipped B.Cu footprints in
# KiCAD 9 SWIG. Use footprint.GetLayer() instead — it always reflects
# the actual placed layer after Flip().
fp_start = footprints[from_ref]
fp_end = footprints[to_ref]
start_layer = self.board.GetLayerName(fp_start.GetLayer())
end_layer = self.board.GetLayerName(fp_end.GetLayer())
copper_layers = {"F.Cu", "B.Cu"}
needs_via = (
start_layer in copper_layers
@@ -155,8 +160,11 @@ class RoutingCommands:
)
if needs_via:
# Place via at midpoint between the two pads
via_x = (start_pos.x + end_pos.x) / 2 / scale
# Place via directly below the start pad (same X).
# Using the geometric midpoint X causes all vias to stack at
# the same X when pads are back-to-back mirrored (e.g. J1/J2
# on F.Cu/B.Cu): midpoint is always the board center.
via_x = start_pos.x / scale
via_y = (start_pos.y + end_pos.y) / 2 / scale
# Trace on start layer: start_pad → via

View File

@@ -1679,9 +1679,11 @@ class KiCADInterface:
"""Copy the entire project folder to a snapshot directory for checkpoint/resume."""
import shutil
from datetime import datetime
from pathlib import Path
try:
step = params.get("step", "")
label = params.get("label", "")
prompt_text = params.get("prompt", "")
# Determine project directory from loaded board or explicit path
project_dir = None
if self.board:
@@ -1693,19 +1695,60 @@ class KiCADInterface:
if not project_dir or not os.path.isdir(project_dir):
return {"success": False, "message": "Could not determine project directory for snapshot"}
base_name = Path(project_dir).name
ts = datetime.now().strftime("%Y%m%d_%H%M%S")
# Save prompt + log into logs/ subdirectory before snapshotting
logs_dir = Path(project_dir) / "logs"
logs_dir.mkdir(exist_ok=True)
prompt_file = None
if prompt_text:
prompt_filename = f"PROMPT_step{step}_{ts}.md" if step else f"PROMPT_{ts}.md"
prompt_file = logs_dir / prompt_filename
prompt_file.write_text(prompt_text, encoding="utf-8")
logger.info(f"Prompt saved: {prompt_file}")
# Copy current MCP session log into logs/ before snapshotting
import platform
system = platform.system()
if system == "Windows":
mcp_log_dir = os.path.join(os.environ.get("APPDATA", ""), "Claude", "logs")
elif system == "Darwin":
mcp_log_dir = os.path.expanduser("~/Library/Logs/Claude")
else:
mcp_log_dir = os.path.expanduser("~/.config/Claude/logs")
mcp_log_src = os.path.join(mcp_log_dir, "mcp-server-kicad.log")
mcp_log_dest = None
if os.path.exists(mcp_log_src):
with open(mcp_log_src, "r", encoding="utf-8", errors="replace") as f:
all_lines = f.readlines()
session_start = 0
for i, line in enumerate(all_lines):
if "Initializing server" in line:
session_start = i
session_lines = all_lines[session_start:]
log_filename = f"mcp_log_step{step}_{ts}.txt" if step else f"mcp_log_{ts}.txt"
mcp_log_dest = logs_dir / log_filename
with open(mcp_log_dest, "w", encoding="utf-8") as f:
f.writelines(session_lines)
logger.info(f"MCP session log saved: {mcp_log_dest} ({len(session_lines)} lines)")
base_name = Path(project_dir).name
suffix_parts = [p for p in [f"step{step}" if step else "", label, ts] if p]
snapshot_name = base_name + "_snapshot_" + "_".join(suffix_parts)
snapshot_dir = str(Path(project_dir).parent / snapshot_name)
snapshots_base = Path(project_dir) / "snapshots"
snapshots_base.mkdir(exist_ok=True)
snapshot_dir = str(snapshots_base / snapshot_name)
shutil.copytree(project_dir, snapshot_dir)
shutil.copytree(project_dir, snapshot_dir, ignore=shutil.ignore_patterns("snapshots"))
logger.info(f"Project snapshot saved: {snapshot_dir}")
return {
"success": True,
"message": f"Snapshot saved: {snapshot_name}",
"snapshotPath": snapshot_dir,
"sourceDir": project_dir,
"promptSaved": str(prompt_file) if prompt_file else None,
"mcpLogSaved": str(mcp_log_dest) if mcp_log_dest else None,
}
except Exception as e:
logger.error(f"snapshot_project error: {e}")

View File

@@ -232,7 +232,9 @@ export class KiCADMcpServer {
logger.info("Registering KiCAD tools, resources, and prompts...");
// Register router tools FIRST (for tool discovery and execution)
registerRouterTools(this.server, this.callKicadScript.bind(this));
// NOTE: Router disabled — causes Claude to hallucinate tool schemas via search_tools/execute_tool.
// All tools are registered directly below and are immediately visible to Claude.
// registerRouterTools(this.server, this.callKicadScript.bind(this));
// Register all tools
registerProjectTools(this.server, this.callKicadScript.bind(this));

View File

@@ -84,8 +84,9 @@ export function registerProjectTools(server: McpServer, callKicadScript: Functio
{
step: z.string().describe("Step number or identifier, e.g. '1' or '2'"),
label: z.string().describe("Short label for this checkpoint, e.g. 'schematic_ok' or 'layout_ok'"),
prompt: z.string().optional().describe("Full prompt text to save as PROMPT_step{step}_{timestamp}.md alongside the snapshot"),
},
async (args: { step: string; label: string }) => {
async (args: { step: string; label: string; prompt?: string }) => {
const result = await callKicadScript("snapshot_project", args);
return {
content: [{