Repository navigation
fix(mcp): resolve a bare python3 to a real interpreter on Windows - #1472
Conversation
A stdio MCP server configured as `python3 server.py` never starts on Windows, even with Python installed. The `python3` on a default Windows PATH is the Microsoft Store app execution alias in %LOCALAPPDATA%\Microsoft\WindowsApps. It is found, so there is no ENOENT; it prints an install hint and exits 9009. python.org and winget installs provide python.exe only, or reach PATH only through the py launcher, so no real python3.exe exists. The launcher passed `python3` through unchanged and the handshake failed with "mcp server exited with code 9009". Many MCP server READMEs use `python3 -m ...`, so a command that works on macOS and Linux fails here. resolveMcpStdioLaunch now maps a bare `python3`/`python` on Windows to the first python3/python on PATH outside WindowsApps, then `py -3`. If none exists the alias is kept, since with a Store Python it is the real interpreter. Commands with a path and other platforms are unchanged. Signed-off-by: FenjuFu <fufenjupku@gmail.com>
There was a problem hiding this comment.
🟡 Changes recommended
Bare python can incorrectly resolve to a later unrelated python3 executable instead of the intended environment.
1 open finding
What changed in this PR
Improves Windows MCP stdio startup by resolving bare Python commands past Microsoft Store aliases.
Changes:
- Adds Windows Python and
py -3resolution. - Adds cross-platform launcher tests.
- Documents the new resolution behavior.
| File | Description |
|---|---|
apps/desktop/electron/main/mcp-stdio-launch.ts |
Implements Windows Python discovery. |
apps/desktop/test/mcp-stdio-launch.test.mjs |
Tests Python launcher resolution. |
docs/adr/0038-plugin-mcp-bridge.md |
Records resolution rules. |
docs/plugin-development.md |
Documents plugin launcher behavior. |
🧠 Review effort: Balanced
Give feedback about Copilot approvals in this survey to enter a drawing for a $150 gift card.
| for (const name of ["python3", "python"]) { | ||
| const found = lookOnPath(name, dirs, paths, exts, fs.isFile); | ||
| if (found) return { command: found, args: [] }; |
When both executables are available, resolving python to python3 can bypass the active environment and change which interpreter runs. Keep the configured launcher first while retaining the alternate Python 3 fallback.
|
Thanks for the Windows 11 reproduction and the focused path-resolution tests. The reported Store alias failure is consistent with the current pass-through behavior. During review I also found that a configured |
|
The failed E2E log is available now. The suite reached the renderer check and failed on |
|
The candidate now includes the latest main through #1468. I rebuilt the Desktop bundle and reran local validation: launcher tests 19/19, related MCP tests 80/80, Desktop typecheck, lint, and trusted extensions E2E 57/57 all pass. The earlier screenshot timeout was on the prior head; current GitHub checks are running. The fix also preserves an explicitly configured |
|
Thanks for the Windows 11 reproduction and focused fix. I also fixed the discovered launcher-order regression so a configured |

A stdio MCP server configured as
python3 server.py(orpython3 -m pkg, as many MCP server READMEs show) never starts on Windows, even with Python installed. The handshake fails with:On a default Windows PATH,
python3is the Microsoft Store app execution alias in%LOCALAPPDATA%\Microsoft\WindowsApps. It is found, so the existing ENOENT path ("command not found … add it to PATH") never fires. It prints an install hint and exits 9009. python.org and winget installs providepython.exeonly, or reach PATH only through thepylauncher, so there is no realpython3.exeto find.resolveMcpStdioLaunchresolvesnpx/uvxbut passespython3through unchanged.resolveMcpStdioLaunchnow maps a barepython3/pythonon Windows to the firstpython3/pythonon PATH outsideWindowsApps, then topy -3(PATH,%SystemRoot%\py.exe,%LOCALAPPDATA%\Programs\Python\Launcher\py.exe). If none exists, the previous lookup result is kept: with a Store Python the alias is the real interpreter. Commands containing a path, and every non-Windows platform, take the old branch unchanged. ADR 0038 and the plugin guide, which list the launcher's resolution rules, are updated.Validation:
McpServerClientwith the trusted (user MCP) policy, commandpython3, args[echo_server.py]. The server is a minimal FastMCP server undermcp1.30. A venv'sScripts\directory was put first on PATH: it holdspython.exeand nopython3.exe, like a python.org install. The Store alias stays on PATH as on a default Windows profile. Unmodified upstream:mcp server exited with code 9009. This branch: handshake succeeds andtools/call echo {"text":"hello"}returns"hello".test/mcp-stdio-launch.test.mjs: 4 new cases with an injected filesystem, so they run on any OS: skip the Store alias for a realpython.exe, fall back topy -3, keep the alias when it is the only Python, and darwin unchanged. 18/18 pass.test/plugin-mcp.test.mjs,test/user-mcp.test.mjsandtest/windows-host-runtime.test.mjsshow the same 3 failures on unmodified upstream and on this branch. All three are local-environment issues: there is no workspace install, so@pi-desktop/shareddoes not resolve, and one stdio test reads a POSIX pid file.tsc --noEmit --strictonmcp-stdio-launch.tspasses.node scripts/check-architecture.mjsandgit diff --checkpass.pnpm typecheck,pnpm lintand the Electron E2E suites. Workspace dependencies are not installed here, and the repo rules say not to install them only for E2E. macOS and Linux were not run on hardware; the darwin case is covered by the unit test, and the new branch is gated onwin32.Environment: Windows 11 Pro 10.0.26200, Node 24.16.0.
Validation candidate:
6961deb5ddb9ffef60ed86ab0b1941d4ef5e53da; upstream main:177223a77.Found while fixing the same failure in a downstream fork that ships a bundled
python3MCP server (PancrePal-xiaoyibao/xyb-pi-agent#9).