Skip to content

Migrate KiCad MCP server to MCP 2026-07-28 - #371

Draft
kerby2000 wants to merge 3 commits into
mixelpixx:mainfrom
kerby2000:feat/mcp-2026-07-28-migration
Draft

Migrate KiCad MCP server to MCP 2026-07-28#371
kerby2000 wants to merge 3 commits into
mixelpixx:mainfrom
kerby2000:feat/mcp-2026-07-28-migration

Conversation

@kerby2000

@kerby2000 kerby2000 commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Summary

  • replace the monolithic MCP SDK v1 dependency with the split MCP server/client v2 packages and Zod 4
  • serve both legacy 2025-11-25 and modern 2026-07-28 clients from the existing STDIO entry point
  • add private cache hints plus optional, process-local project handles for project-bound tools
  • serialize project switches with project-bound calls and correlate Node/Python bridge responses so stale or late work cannot target the wrong project
  • harden KiCad IPC document detection and refuse the disk-rewrite/revert footprint path that could discard unsaved GUI edits
  • enforce the supported Node.js versions in package metadata, installers, and documentation
  • add hermetic CI protocol coverage while retaining a real-KiCad protocol verification path
  • route the historical kicad-server executable through the canonical migrated implementation

Why

MCP 2026-07-28 removes protocol-level session state, while KiCad's Python backend still owns one active in-memory project. The migration needs an explicit application-level project identity without breaking existing clients that rely on the implicit current project.

The review also found two integrity hazards around that identity: handle validation could race with a queued project switch, and a Python response arriving after a timeout could be assigned to the next request. Both are now protected end to end.

Root causes fixed

  • project handles were validated before queueing, then stripped, leaving a validation-to-dispatch race
  • the custom Python bridge had no request correlation after timeouts
  • four active-board mutators were omitted from project-handle coverage
  • get_project_info could not mint a recovery handle for an already-open project
  • IPC open-document detection relied on a protobuf display string and an incompatible zero-argument API call
  • hybrid footprint placement loaded the last-saved board, rewrote it, and reverted the live KiCad document
  • setup scripts still accepted Node.js 18 even though the migrated dependencies require newer runtimes

Compatibility and impact

  • existing client commands and configuration paths do not change
  • legacy clients continue negotiating 2025-11-25
  • modern clients can pin 2026-07-28 and receive private cache metadata
  • projectHandle remains optional for legacy compatibility but is recommended for modern project-bound calls
  • supported Node.js versions are ^20.19.0, ^22.13.0, or >=24.0.0
  • unsafe full-library-footprint transfer through the IPC/SWIG hybrid now fails closed instead of risking unsaved board edits

Validation

  • npm ci
  • npm run build
  • npm run lint:ts
  • npm run test:ts - 8 files, 82 tests passed
  • npm run test:protocol with the hermetic backend - both protocol eras, 221 tools, handle lifecycle, cache hints
  • npm run test:protocol with the real KiCad Python environment - both protocol eras and project-handle lifecycle passed
  • migration-focused Python validation - 84 relevant tests passed; final focused slice 15 passed
  • pre-commit run --all-files --show-diff-on-failure - all hooks passed
  • npm audit and npm audit --omit=dev - 0 vulnerabilities

A diagnostic full local Python run collected 1,816 tests: 1,768 passed and 35 skipped. The remaining 13 were local-environment failures (missing Java plus Windows inherited-handle/shared-log isolation); the affected tests pass in isolation where applicable, and CI provisions Java for the freerouting suites.

@mixelpixx

Copy link
Copy Markdown
Owner

Status update rather than a merge verdict. Two things happened to this draft's content today:

  1. Your request-correlation slice was extracted and landed as fix(bridge): correlate Python responses to requests by ID (#373) #382, with credit (Co-authored-by, and your two test files kept as written). It fixes live issue Windows: Python responses are dropped as "no pending request", causing MCP tool timeouts #373, whose reporter independently confirmed the ID-free bridge cross-resolving responses — your diagnosis and implementation were exactly right, and that fix could not wait for the full migration. The adaptation onto the SDK1 server was mechanical; the design (requestId echoed as _requestId, per-frame consume, stale discard) is yours.

  2. The migration itself is now tracked in Migrate to MCP SDK 2.0 / spec 2026-07-28 (maintainer-owned) #388 as maintainer-owned work. SDK v1 still negotiates fine with current clients, so there is no urgency forcing a 74-file review through a conflicting draft. When the migration happens, this branch is the reference implementation — the dual-era serveStdio factory approach and the engine pinning are both the right calls, and Migrate to MCP SDK 2.0 / spec 2026-07-28 (maintainer-owned) #388 says so.

That leaves this draft carrying two more independently valuable pieces: the IPC open-document detection and the disk-rewrite/revert refusal on the footprint path. Both are wanted as standalone PRs against current main if you have the time; they are small and would merge on their own evidence. I will leave this draft open a couple of weeks in case you want to slice those out yourself — otherwise I will extract them the same way as the correlation fix, with the same credit.

rossvonfange pushed a commit to rossvonfange/KiCAD-MCP-Server that referenced this pull request Aug 20, 2026
)

The Node-Python bridge was a single-slot, ID-free protocol. One 30s
timeout permanently desynced it: request A times out and its handler
slot is cleared, request B dispatches into the slot, A's late response
arrives and resolves B's handler with A's result — silent wrong-answer
cross-talk, off by one for every response after. The reporter's 'no
pending request' warnings were the second-order symptom; their
diagnosis of the mechanism was exactly right.

Requests now carry a bridge-local requestId echoed back by Python as
_requestId. Responses are consumed as newline-delimited frames; a
response whose ID does not match the pending request is discarded with
a log line. Timeout callbacks abandon only their own request, and the
response buffer is no longer cleared on dispatch or timeout, so a
partial frame from a timed-out command completes and is discarded by
ID instead of corrupting the next response. The warmup path uses the
same mechanism.

Second finding from the same report: concurrent server processes
rotating one shared log file collide on Windows (WinError 32). Logs
are now per-PID with a 7-day sweep of stale files.

Implementation adapted from PR mixelpixx#371 (Sergey Lukin / kerby2000), which
carries this fix inside a larger MCP SDK 2.0 migration; the
correlation slice stands alone and is taken here with credit, onto the
current SDK1 server rather than the migrated one. Their tests are
kept as written.

Fixes mixelpixx#373.

Co-authored-by: Sergey Lukin <lukin.s.v@gmail.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants