fix: reconnect IPC backend after KiCad starts (#140)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
scorp508
2026-05-22 17:41:47 -04:00
committed by GitHub
parent f65eaf2798
commit 6113e0c27a
10 changed files with 927 additions and 102 deletions

View File

@@ -6,7 +6,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import express from "express";
import { spawn, exec, execSync, ChildProcess } from "child_process";
import { existsSync } from "fs";
import { existsSync, readdirSync } from "fs";
import { join, dirname } from "path";
import { logger } from "./logger.js";
@@ -40,9 +40,41 @@ import { registerRoutingPrompts } from "./prompts/routing.js";
import { registerDesignPrompts } from "./prompts/design.js";
import { registerFootprintPrompts } from "./prompts/footprint.js";
function getWindowsKiCadPythonCandidates(): string[] {
const roots = [
process.env.LOCALAPPDATA ? join(process.env.LOCALAPPDATA, "Programs", "KiCad") : undefined,
"C:\\Program Files\\KiCad",
"C:\\Program Files (x86)\\KiCad",
].filter((root): root is string => Boolean(root));
const candidates: string[] = [];
for (const root of roots) {
if (!existsSync(root)) {
continue;
}
try {
const versionDirs = readdirSync(root, { withFileTypes: true })
.filter((entry) => entry.isDirectory())
.map((entry) => entry.name)
.sort((a, b) => b.localeCompare(a, undefined, { numeric: true }));
for (const versionDir of versionDirs) {
candidates.push(join(root, versionDir, "bin", "python.exe"));
}
} catch (error: any) {
logger.warn(`Failed to inspect KiCAD install directory ${root}: ${error.message}`);
}
}
return [...new Set(candidates)];
}
/**
* Find the Python executable to use
* Prioritizes virtual environment if available, falls back to system Python
* Find the Python executable to use.
* Prioritizes project venvs, then explicit overrides, then KiCAD-bundled Python
* before falling back to system Python.
*/
function findPythonExecutable(scriptPath: string): string {
const isWindows = process.platform === "win32";
@@ -73,11 +105,12 @@ function findPythonExecutable(scriptPath: string): string {
// Platform-specific KiCAD bundled Python detection
if (isWindows) {
// Windows: Always prefer KiCAD's bundled Python (pcbnew.pyd is compiled for it)
const kicadPython = "C:\\Program Files\\KiCad\\9.0\\bin\\python.exe";
if (existsSync(kicadPython)) {
logger.info(`Found KiCAD bundled Python at: ${kicadPython}`);
return kicadPython;
// Windows: Always prefer KiCAD's bundled Python (pcbnew.pyd is compiled for it).
for (const kicadPython of getWindowsKiCadPythonCandidates()) {
if (existsSync(kicadPython)) {
logger.info(`Found KiCAD bundled Python at: ${kicadPython}`);
return kicadPython;
}
}
} else if (isMac) {
// macOS: Try KiCAD's bundled Python (check multiple versions and locations)
@@ -259,10 +292,12 @@ export class KiCADMcpServer {
// Check if Python executable exists (for absolute paths) or is executable (for commands)
const isAbsolutePath =
pythonExe.startsWith("/") || pythonExe.startsWith("C:") || pythonExe.startsWith("\\");
let pythonExecutableAvailable = true;
if (isAbsolutePath) {
// Absolute path: use existsSync
if (!existsSync(pythonExe)) {
pythonExecutableAvailable = false;
errors.push(`Python executable not found: ${pythonExe}`);
if (isWindows) {
@@ -299,6 +334,7 @@ export class KiCADMcpServer {
logger.info(`Python version check passed: ${stdout.trim()}`);
} catch (error: any) {
pythonExecutableAvailable = false;
errors.push(`Python executable not found in PATH: ${pythonExe}`);
errors.push(`Error: ${error.message}`);
errors.push("Set KICAD_PYTHON environment variable to specify full path");
@@ -325,7 +361,7 @@ export class KiCADMcpServer {
}
// Try to test pcbnew import (quick validation)
if (existsSync(pythonExe) && existsSync(this.kicadScriptPath)) {
if (pythonExecutableAvailable && existsSync(this.kicadScriptPath)) {
logger.info("Validating pcbnew module access...");
const testCommand = `"${pythonExe}" -c "import pcbnew; print('OK')"`;