Skip to content

refactor(client): replace cross-spawn with tinyexec - #2746

Draft
andriyor wants to merge 1 commit into
modelcontextprotocol:mainfrom
andriyor:refactor/replace-cross-spawn-with-tinyexec
Draft

refactor(client): replace cross-spawn with tinyexec#2746
andriyor wants to merge 1 commit into
modelcontextprotocol:mainfrom
andriyor:refactor/replace-cross-spawn-with-tinyexec

Conversation

@andriyor

@andriyor andriyor commented Sep 2, 2026

Copy link
Copy Markdown

Motivation

StdioClientTransport spawns server processes through cross-spawn, which brings five transitive dependencies (which, isexe, path-key, shebang-command, shebang-regex) and has been a recurring advisory target for consumers (#469, #488).

tinyexec vendors cross-spawn's command normalization into its bundle and declares no dependencies of its own. Windows .cmd/.bat shim resolution and argument escaping are therefore unchanged, while the client's runtime tree drops from six packages to one.

Two tinyexec defaults had to be switched off

Both would otherwise have changed spawn behavior silently:

  • nodePath: false — tinyexec prepends every ancestor node_modules/.bin directory to the child's PATH, which would change how the server command resolves.
  • maskInheritedEnvironment() — tinyexec does {...process.env, ...yourEnv} with no opt-out, which would void the getDefaultEnvironment() safelist and leak the full parent environment to spawned servers. Masking every parent key with undefined cancels the merge, since Node's spawn drops undefined entries.

Verified the resulting child environment is byte-identical to the cross-spawn path (the __CF_USER_TEXT_ENCODING key present on macOS comes from CoreFoundation and appears with plain child_process.spawn too):

SECRET leaked? false
PATH unchanged? true
keys: HOME,PATH,__CF_USER_TEXT_ENCODING

No public API change

StdioClientTransport, StdioServerParameters, getDefaultEnvironment, and DEFAULT_INHERITED_ENV_VARS are unaffected. @modelcontextprotocol/client/stdio remains the process-spawning subpath, and the built root entry stays free of spawn dependencies.

crossSpawn.test.ts becomes tinyexec.test.ts with the same coverage plus pins for both defaults above, so a future tinyexec upgrade cannot quietly reintroduce either. @types/cross-spawn is dropped from the catalog and the three package.json files that referenced it; the barrelClean guards in client and server now watch for tinyexec.

Testing

build:all, lint:all, typecheck:all clean. test:all green except two pre-existing protocol:timeout:max-total [sse] failures, confirmed identical on unmodified main in a separate worktree.

Note

Per CONTRIBUTING I'd normally open an issue first — happy to do that, or to close this if the dependency direction doesn't fit the SDK's plans.

🤖 Generated with Claude Code

tinyexec vendors cross-spawn's command normalization and has no dependencies of
its own, so Windows .cmd/.bat handling is unchanged while the client's runtime
tree drops from six packages to one.

Two tinyexec defaults are switched off to keep spawn behavior identical:
`nodePath: false` (it otherwise prepends every ancestor node_modules/.bin to the
child's PATH) and a mask of parent env keys (it otherwise merges process.env in,
voiding the getDefaultEnvironment() safelist). The child environment is
byte-identical to the cross-spawn path.

No public API change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Sep 2, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 4d77b52

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 2, 2026

Copy link
Copy Markdown

Open in StackBlitz

@modelcontextprotocol/client

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

@modelcontextprotocol/codemod

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

@modelcontextprotocol/core

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

@modelcontextprotocol/server

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

@modelcontextprotocol/server-legacy

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

@modelcontextprotocol/express

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

@modelcontextprotocol/fastify

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

@modelcontextprotocol/hono

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

@modelcontextprotocol/node

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

commit: 4d77b52

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.

1 participant