From a555e2e961c6fa7a62f124f96f617a0ee15e4f54 Mon Sep 17 00:00:00 2001 From: mixelpixx Date: Thu, 1 Oct 2026 07:27:15 -0400 Subject: [PATCH] feat(release): ship the stdio bridge inside the packaged app The bridge was only usable from a source checkout: release zips did not contain it, although the skill and the bridge header said it could run from an install. electron-builder now copies the bridge and the four compiled modules it loads (mcp-instructions, mcp-prompts, mcp-tool-router, mcp-tools) into resources/mcp-bridge/, keeping the bridge/ + lib/common/ layout so its existing ../lib/common require resolves unchanged. No bridge code changes. A new bridge test guards against drift: it derives every module the bridge loads (and their relative requires) and fails if extraResources does not copy one. README, bridge README and skill now give the installed path per OS. Co-Authored-By: Claude Opus 5.5 --- README.md | 13 ++++++--- arduino-mcp-extension/bridge/README.md | 20 ++++++++++---- .../test/bridge/bridge.test.js | 27 +++++++++++++++++++ electron-app/package.json | 17 ++++++++++++ skills/arduino-agent/SKILL.md | 5 ++-- 5 files changed, 71 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index b1b2f24ccbd..d2f82e766fb 100644 --- a/README.md +++ b/README.md @@ -85,15 +85,20 @@ older builds are on the > "mcpServers": { > "arduino": { > "command": "node", - > "args": ["/path/to/arduino-mcp-extension/bridge/arduino-agent-bridge.js"] + > "args": ["/resources/mcp-bridge/bridge/arduino-agent-bridge.js"] > } > } > } > ``` > - > It needs no token configuration (it reads `~/.arduinoIDE/mcp-token` itself) - > and no dependencies. See [the bridge README](arduino-mcp-extension/bridge/README.md) - > for auto-launch and other options. + > Releases after v0.6.1 ship the bridge inside the app: `` is the + > unzipped folder on Windows/Linux; on macOS the path is + > `Arduino IDE.app/Contents/Resources/mcp-bridge/bridge/arduino-agent-bridge.js`. + > From a source checkout, use `arduino-mcp-extension/bridge/arduino-agent-bridge.js` + > after building the extension. It needs no token configuration (it reads + > `~/.arduinoIDE/mcp-token` itself) and no dependencies. See + > [the bridge README](arduino-mcp-extension/bridge/README.md) for auto-launch + > and other options. > **Claude Code users:** the server sends workflow guidance automatically, > and ships three slash commands (`/bringup`, `/debug-serial`, diff --git a/arduino-mcp-extension/bridge/README.md b/arduino-mcp-extension/bridge/README.md index 9e737259d9b..1038ced373f 100644 --- a/arduino-mcp-extension/bridge/README.md +++ b/arduino-mcp-extension/bridge/README.md @@ -29,18 +29,28 @@ is closed: "mcpServers": { "arduino": { "command": "node", - "args": ["/path/to/arduino-mcp-extension/bridge/arduino-agent-bridge.js"], + "args": ["/path/to/mcp-bridge/bridge/arduino-agent-bridge.js"], "env": { "ARDUINO_AGENT_PATH": "/path/to/Arduino IDE executable" } } } } ``` +Where to find it: + +| Running from | Bridge path | +|--------------|-------------| +| Release, Windows / Linux | `/resources/mcp-bridge/bridge/arduino-agent-bridge.js` | +| Release, macOS | `Arduino IDE.app/Contents/Resources/mcp-bridge/bridge/arduino-agent-bridge.js` | +| Source checkout | `arduino-mcp-extension/bridge/arduino-agent-bridge.js`, after `yarn build` in `arduino-mcp-extension` | + +Releases after v0.6.1 include the bridge; earlier zips don't, so use a checkout +with those. Either way the bridge sits next to the compiled extension modules it +loads (`../lib/common/`), which supply the offline tool list and answers. + No dependencies (node builtins only) and no token setup — it reads `~/.arduinoIDE/mcp-token` itself, re-reading per request so it survives the IDE -regenerating the token on restart. Run it from a **built** checkout or install -(`yarn build` in `arduino-mcp-extension`): the offline tool list and answers -come from the compiled extension in `lib/`. +regenerating the token on restart. ## Options @@ -62,7 +72,7 @@ Example on Windows: "mcpServers": { "arduino": { "command": "node", - "args": ["C:/path/to/bridge/arduino-agent-bridge.js"], + "args": ["C:/Arduino/resources/mcp-bridge/bridge/arduino-agent-bridge.js"], "env": { "ARDUINO_AGENT_PATH": "C:/Arduino/Arduino IDE.exe" } } } diff --git a/arduino-mcp-extension/test/bridge/bridge.test.js b/arduino-mcp-extension/test/bridge/bridge.test.js index a56995fcd3a..d93e4d9a24c 100644 --- a/arduino-mcp-extension/test/bridge/bridge.test.js +++ b/arduino-mcp-extension/test/bridge/bridge.test.js @@ -310,3 +310,30 @@ test('a rejected token gives a readable tool error, and tools/list still answers await ide.close(); } }); + +test('release packaging ships the bridge with every compiled module it loads', needsLib, () => { + // The bridge loads ../lib/common/ at runtime; electron-builder's + // extraResources must copy each of those (and what they require) next to it. + const loaded = [...fs.readFileSync(BRIDGE, 'utf8').matchAll(/loadCompiled\('([\w-]+)'\)/g)].map((m) => m[1]); + assert.ok(loaded.length > 0); + const needed = new Set(); + const visit = (name) => { + if (needed.has(name)) return; + needed.add(name); + const src = fs.readFileSync(path.join(LIB, `${name}.js`), 'utf8'); + for (const m of src.matchAll(/require\("\.\/([\w-]+)"\)/g)) visit(m[1]); + }; + loaded.forEach(visit); + + const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '..', '..', '..', 'electron-app', 'package.json'), 'utf8')); + const resources = pkg.build.extraResources; + const bridgeEntry = resources.find((r) => r.from === '../arduino-mcp-extension/bridge'); + const libEntry = resources.find((r) => r.from === '../arduino-mcp-extension/lib/common'); + assert.ok(bridgeEntry && libEntry, 'electron-app extraResources must copy bridge/ and lib/common/'); + assert.ok(bridgeEntry.filter.includes(path.basename(BRIDGE))); + for (const name of needed) { + assert.ok(libEntry.filter.includes(`${name}.js`), `extraResources is missing lib/common/${name}.js`); + } + // Same relative layout as the checkout, so the bridge's ../lib/common still resolves. + assert.strictEqual(path.posix.dirname(bridgeEntry.to), path.posix.dirname(path.posix.dirname(libEntry.to))); +}); diff --git a/electron-app/package.json b/electron-app/package.json index 0d3e168330f..1356afeb71b 100644 --- a/electron-app/package.json +++ b/electron-app/package.json @@ -132,6 +132,23 @@ { "from": "plugins", "to": "app/plugins" + }, + { + "from": "../arduino-mcp-extension/bridge", + "to": "mcp-bridge/bridge", + "filter": [ + "arduino-agent-bridge.js" + ] + }, + { + "from": "../arduino-mcp-extension/lib/common", + "to": "mcp-bridge/lib/common", + "filter": [ + "mcp-instructions.js", + "mcp-prompts.js", + "mcp-tool-router.js", + "mcp-tools.js" + ] } ], "win": { diff --git a/skills/arduino-agent/SKILL.md b/skills/arduino-agent/SKILL.md index ff14a48385b..8338a9983bf 100644 --- a/skills/arduino-agent/SKILL.md +++ b/skills/arduino-agent/SKILL.md @@ -16,8 +16,9 @@ skill adds the judgment that comes from real hardware sessions. If the MCP config points at `http://127.0.0.1:3847/mcp` directly, the whole server shows "failed to connect" whenever the IDE is closed. Prefer the stdio -bridge (`arduino-mcp-extension/bridge/arduino-agent-bridge.js` in the repo or -install): it always connects with the full tool list, starts the IDE when a tool +bridge (`resources/mcp-bridge/bridge/arduino-agent-bridge.js` inside a release +install newer than v0.6.1, or `arduino-mcp-extension/bridge/` in a built +checkout): it always connects with the full tool list, starts the IDE when a tool needs it (with `ARDUINO_AGENT_PATH` set; otherwise it returns "open the IDE" as a normal tool error), and recovers without a client restart.