Skip to content

fix(mcp): cross-platform launcher for Windows/macOS/Linux#18

Open
guoyucode wants to merge 1 commit into
qufei1993:mainfrom
guoyucode:fix/mcp-cross-platform
Open

fix(mcp): cross-platform launcher for Windows/macOS/Linux#18
guoyucode wants to merge 1 commit into
qufei1993:mainfrom
guoyucode:fix/mcp-cross-platform

Conversation

@guoyucode

@guoyucode guoyucode commented May 9, 2026

Copy link
Copy Markdown

Summary

  • Replace the shell wrapper in plugins/weixin/.mcp.json (currently bash -c "...") with a Node.js launcher (mcp-launch.cjs) so the plugin works on Windows in addition to macOS and Linux.
  • Preserves the macOS PATH fallback introduced in fix(mcp): add common bun install paths to PATH for macOS compatibility #14 (~/.bun/bin, ~/.volta/bin) and extends it to Windows (bun.exe under the same dirs).

Background

PR #14 fixed macOS GUI launches by wrapping the command in a POSIX shell that prepends bun install dirs to PATH (later evolved into the current bash -c form). However:

  • Windows typically has no POSIX shell on PATH, so the plugin fails to start there. The MCP server process is never spawned and WeChat messages are never delivered to Claude Code.
  • The Listening for channel messages from: plugin:weixin@cc-weixin banner is misleading — it only confirms channel registration, not that the server actually runs. So the failure is silent: no error surfaces to the user, the banner says everything is fine, and messages just disappear.
  • .mcp.json doesn't support per-OS branching, so a single command must work on all three OSes.

Changes

  • NEW plugins/weixin/mcp-launch.cjs (~90 lines, CommonJS for max compat):
    • Locate bun via PATH first, then fall back to ~/.bun/bin/bun(.exe) and ~/.volta/bin/bun(.exe).
    • Run bun install --no-summary with stdout redirected to stderr (fd 2) so non-protocol output like Saved lockfile cannot corrupt the MCP JSON-RPC stream over stdout. Surfaces spawnSync errors (e.g. ENOENT when bun isn't found) in the failure message.
    • Spawn bun server.ts with cwd = __dirname, inheriting stdio so the host can speak MCP over stdin/stdout.
    • Forward SIGINT / SIGTERM / SIGHUP to the child for graceful shutdown. When the child exits via signal, exit with the conventional 128 + signum code instead of re-raising the signal (which would re-enter our own listeners and hang the launcher).
  • MODIFIED plugins/weixin/.mcp.json:
    • command: bashnode
    • args: shell-script string → ["${CLAUDE_PLUGIN_ROOT}/mcp-launch.cjs"]

Trade-off

Introduces a Node.js dependency for the launcher itself. Bun cannot bootstrap itself when not on PATH, but Node.js is broadly preinstalled on developer machines (and trivially added if missing), so this is a pragmatic choice. Open to feedback if the maintainer prefers a pure-bun launcher (e.g. a small Rust/Go binary, or platform-specific shell scripts), but those add more friction than this single CommonJS file.

Testing

Verified locally on Windows 11 + PowerShell (where the previous shell-wrapper config fails silently):

> node mcp-launch.cjs
[weixin] Account loaded, starting poll loop...
[weixin] Starting message poll loop...

(stdout stays empty — confirmed by piping 1>stdout.log 2>stderr.log and checking stdout.log is zero bytes while stderr.log contains the lines above.)

After replacing .mcp.json and restarting Claude Code, WeChat messages are delivered as expected. Without this fix, the same setup on Windows shows the connection banner but never delivers any message.

macOS / Linux behavior should be unchanged — the launcher's discovery list is a superset of the previous PATH prepend, and stdout/stderr semantics are equivalent to the existing 1>&2 redirect.

Out of scope

  • No changes to .codex-mcp.json — Codex uses a separate start-codex.sh flow that already handles cross-platform concerns differently.
  • No changes to skills / docs.

Review feedback addressed

Both Copilot review comments addressed in the latest force-push:

  1. stdout pollution: bun install stdout redirected to stderr (fd 2); install.error now included in the failure message so users see the underlying cause (e.g. ENOENT when bun isn't found) instead of just an exit code.
  2. Signal-handler hang: child-exit handler no longer re-raises the signal to itself (which would re-enter our own SIGINT/SIGTERM/SIGHUP listeners and leave the launcher running). It now process.exit(128 + signum) directly.

Copilot AI review requested due to automatic review settings May 9, 2026 04:48

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the Claude Code Weixin MCP plugin startup flow to work cross-platform (Windows/macOS/Linux) by replacing the /bin/sh wrapper approach with a Node.js-based launcher that finds bun, installs deps, and starts server.ts over stdio.

Changes:

  • Add a cross-platform Node.js launcher (plugins/weixin/mcp-launch.cjs) to locate bun, run bun install, and spawn bun server.ts.
  • Update the Claude MCP configuration (plugins/weixin/.mcp.json) to invoke node with the launcher script.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
plugins/weixin/mcp-launch.cjs New Node-based launcher that bootstraps bun deps and starts the MCP server process.
plugins/weixin/.mcp.json Switch MCP startup command from /bin/sh wrapper to node launcher for Windows compatibility.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +47 to +56
// Step 1: install dependencies (idempotent, near-instant on no-op).
const install = spawnSync(bun, ["install", "--no-summary"], {
cwd: root,
stdio: ["ignore", "inherit", "inherit"],
});
if (install.status !== 0) {
process.stderr.write(
`[weixin] bun install failed (exit ${install.status ?? "?"})\n`,
);
process.exit(install.status ?? 1);
Comment thread plugins/weixin/mcp-launch.cjs Outdated
Comment on lines +69 to +76
process.on("SIGINT", () => forward("SIGINT"));
process.on("SIGTERM", () => forward("SIGTERM"));
process.on("SIGHUP", () => forward("SIGHUP"));

child.on("exit", (code, signal) => {
if (signal) {
process.kill(process.pid, signal);
return;
Replace the shell wrapper in .mcp.json (currently `bash -c "..."`) with
a Node.js launcher script (mcp-launch.cjs). Windows typically has no
POSIX shell on PATH, so the previous config failed to start the MCP
server entirely on Windows: the bun process was never spawned and
WeChat messages were never delivered to Claude Code (the
'Listening for channel messages from ...' banner only confirms channel
registration, not that the server actually runs).

The launcher preserves the macOS PATH fallback introduced in qufei1993#14
(~/.bun/bin, ~/.volta/bin) and extends it to Windows by also looking
for bun.exe under the same directories. It also redirects bun install
output to stderr so non-protocol lines like "Saved lockfile" cannot
corrupt the MCP JSON-RPC stream over stdout. .mcp.json cannot branch
on OS, so we centralize the discovery + spawn logic in one CommonJS
file that runs on all three platforms.
@guoyucode guoyucode force-pushed the fix/mcp-cross-platform branch from 0124b70 to 2b25b2f Compare May 9, 2026 11:01
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.

2 participants