Skip to content

fix(client): honour explicit Path casing in stdio env on Windows - #2871

Open
sean-park-funda wants to merge 1 commit into
modelcontextprotocol:mainfrom
sean-park-funda:fix/stdio-windows-env-case-precedence
Open

sean-park-funda wants to merge 1 commit into
modelcontextprotocol:mainfrom
sean-park-funda:fix/stdio-windows-env-case-precedence

Conversation

@sean-park-funda

Copy link
Copy Markdown

Fixes #2859.

The problem

Windows environment variables are case-insensitive, but an object spread is not:

env: {
    ...getDefaultEnvironment(),   // emits 'PATH'
    ...this._serverParams.env     // caller passes 'Path'
},

Both keys survive the merge. Node's child_process then resolves the duplicate by taking the first in sorted order, and PATH sorts before Path, so the inherited value reaches the child and the caller's configured one is silently dropped. Path is the casing Windows itself uses when listing process.env and in many config files, so callers hit this without doing anything unusual.

The change

mergeDefaultEnvironment drops an inherited default when the caller supplied the same variable under any casing — on win32 only. Off Windows, PATH and Path are genuinely two different variables and dropping either would be wrong, so the plain spread is kept there.

The helper is exported from src/client/stdio so the tests can reach it, but deliberately not added to the client/stdio subpath barrel: this is a fix, not a new public API.

On verification

I could not run the Windows repro from the issue — I am on macOS. What I did instead was pin the mechanism in a unit test and confirm it goes red without the fix:

AssertionError: expected [ 'PATH', 'Path' ] to deeply equal [ 'Path' ]

That is the duplicate-key state the issue describes, reproduced directly. The remaining step — that child_process prefers PATH once both keys are present — is the part I am taking from the issue's repro and Node's documented behaviour rather than having observed myself. Worth a maintainer with a Windows box confirming end to end before merge.

Platform is mocked with Object.defineProperty(process, 'platform', ...), matching the existing pattern in crossSpawn.test.ts.

Testing

Four cases in packages/client/test/client/stdio.test.ts: the undefined env copy, the win32 case-mismatch override, a win32 exact-case override with unrelated defaults preserved, and the POSIX case where both names must stay.

vitest run in packages/client — 38 files, 895 tests, all passing. pnpm typecheck, eslint and prettier --check clean. DEFAULT_INHERITED_ENV_VARS is untouched, so the stdioEnvPins behaviour-surface pins are unaffected. Changeset included (@modelcontextprotocol/client: patch).


This PR was authored by an AI agent at Vibement Inc. The diff has been read and understood before sending, and I can answer design questions about it.

Windows environment variables are case-insensitive, but an object spread
is not: the PATH produced by getDefaultEnvironment and a caller's Path
survived the merge as two separate keys. Node's child_process resolves
that duplicate by taking the first in sorted order, so the inherited PATH
reached the child and the configured one was silently dropped.

Path is the casing Windows itself uses when listing process.env and in
many config files, so callers hit this without doing anything unusual.

Drop an inherited default when the caller supplied the same variable
under any casing, on win32 only. Elsewhere the two names are genuinely
different variables and both are kept.

Fixes modelcontextprotocol#2859

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@sean-park-funda
sean-park-funda requested a review from a team as a code owner September 25, 2026 07:43
@changeset-bot

changeset-bot Bot commented Sep 25, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 4365653

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 6 packages
Name Type
@modelcontextprotocol/client Patch
@modelcontextprotocol/codemod Patch
@modelcontextprotocol/core Patch
@modelcontextprotocol/server-legacy Patch
@modelcontextprotocol/server Patch
@modelcontextprotocol/core-internal Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new

pkg-pr-new Bot commented Sep 25, 2026

Copy link
Copy Markdown

Open in StackBlitz

@modelcontextprotocol/client

npm i https://pkg.pr.new/@modelcontextprotocol/client@2871

@modelcontextprotocol/codemod

npm i https://pkg.pr.new/@modelcontextprotocol/codemod@2871

@modelcontextprotocol/core

npm i https://pkg.pr.new/@modelcontextprotocol/core@2871

@modelcontextprotocol/server

npm i https://pkg.pr.new/@modelcontextprotocol/server@2871

@modelcontextprotocol/server-legacy

npm i https://pkg.pr.new/@modelcontextprotocol/server-legacy@2871

@modelcontextprotocol/express

npm i https://pkg.pr.new/@modelcontextprotocol/express@2871

@modelcontextprotocol/fastify

npm i https://pkg.pr.new/@modelcontextprotocol/fastify@2871

@modelcontextprotocol/hono

npm i https://pkg.pr.new/@modelcontextprotocol/hono@2871

@modelcontextprotocol/node

npm i https://pkg.pr.new/@modelcontextprotocol/node@2871

commit: 4365653

@claude claude Bot added the v2 Ideas, requests and plans for v2 of the SDK which will incorporate major changes and fixes label Sep 25, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

v2 Ideas, requests and plans for v2 of the SDK which will incorporate major changes and fixes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

StdioClientTransport: explicit Path in env is ignored on Windows (inherited PATH wins)

1 participant