Skip to content

fix(mcp): resolve a bare python3 to a real interpreter on Windows - #1472

Merged
vastsa merged 5 commits into
vastsa:mainfrom
FenjuFu:fix/mcp-windows-python3
Oct 8, 2026
Merged

vastsa merged 5 commits into
vastsa:mainfrom
FenjuFu:fix/mcp-windows-python3

Conversation

@FenjuFu

@FenjuFu FenjuFu commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

A stdio MCP server configured as python3 server.py (or python3 -m pkg, as many MCP server READMEs show) never starts on Windows, even with Python installed. The handshake fails with:

mcp server exited with code 9009: … run without arguments to install from the Microsoft Store, or disable this shortcut from Settings > Apps > Advanced app settings > App execution aliases.

On a default Windows PATH, python3 is 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 provide python.exe only, or reach PATH only through the py launcher, so there is no real python3.exe to find. resolveMcpStdioLaunch resolves npx/uvx but passes python3 through unchanged.

resolveMcpStdioLaunch now maps a bare python3/python on Windows to the first python3/python on PATH outside WindowsApps, then to py -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:

  • Real launch through McpServerClient with the trusted (user MCP) policy, command python3, args [echo_server.py]. The server is a minimal FastMCP server under mcp 1.30. A venv's Scripts\ directory was put first on PATH: it holds python.exe and no python3.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 and tools/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 real python.exe, fall back to py -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.mjs and test/windows-host-runtime.test.mjs show 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/shared does not resolve, and one stdio test reads a POSIX pid file.
  • tsc --noEmit --strict on mcp-stdio-launch.ts passes. node scripts/check-architecture.mjs and git diff --check pass.
  • NOT RUN: pnpm typecheck, pnpm lint and 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 on win32.

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 python3 MCP server (PancrePal-xiaoyibao/xyb-pi-agent#9).

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>

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.

🟡 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 -3 resolution.
  • 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.

Comment on lines +327 to +329
for (const name of ["python3", "python"]) {
const found = lookOnPath(name, dirs, paths, exts, fs.isFile);
if (found) return { command: found, args: [] };
vastsa added 2 commits October 8, 2026 14:54
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.
@vastsa

vastsa commented Oct 8, 2026

Copy link
Copy Markdown
Owner

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 python could be replaced by a later python3; commit a7d20ac preserves the requested launcher preference, with a regression test that failed before the change and passes now.\n\nLocal validation on the updated candidate: launcher tests 19/19, related MCP tests 80/80, Desktop typecheck, and pnpm lint passed. The current GitHub Trusted extensions Electron E2E check reports a failure; its run is still in progress and the log is not available yet. I will inspect that result and refresh the branch against the latest main before deciding on merge.

@vastsa

vastsa commented Oct 8, 2026

Copy link
Copy Markdown
Owner

The failed E2E log is available now. The suite reached the renderer check and failed on Page.captureScreenshot timed out; the launcher tests and earlier extension assertions passed, and the Electron/GPU process then exited. This appears unrelated to Python resolution, but it remains a required gate. I will rerun E2E on the refreshed integration candidate.

@vastsa

vastsa commented Oct 8, 2026

Copy link
Copy Markdown
Owner

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 python over a later-discovered python3, covered by a regression test.

@vastsa
vastsa merged commit 94c256f into vastsa:main Oct 8, 2026
5 checks passed
@vastsa

vastsa commented Oct 8, 2026

Copy link
Copy Markdown
Owner

Thanks for the Windows 11 reproduction and focused fix. I also fixed the discovered launcher-order regression so a configured python keeps precedence over a later python3. On the latest main candidate, launcher tests (19/19), related MCP tests (80/80), Desktop typecheck, lint, Desktop build, trusted extensions E2E (57/57), and all CI checks passed. The PR merge ref matched the validated candidate tree, so I merged it.

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