Skip to content

refactor(client,server): move stdio transports to ./stdio subpath export - #1871

Merged
felixweinberger merged 10 commits into
mainfrom
fweinberger/browser-stdio-conditional-export
Apr 29, 2026
Merged

refactor(client,server): move stdio transports to ./stdio subpath export#1871
felixweinberger merged 10 commits into
mainfrom
fweinberger/browser-stdio-conditional-export

Conversation

@felixweinberger

@felixweinbergerfelixweinberger commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

Move stdio transports to a ./stdio subpath export so the package root no longer pulls in node:child_process, node:stream, or cross-spawn. Fixes bundling for browser and Cloudflare Workers consumers. Node.js, Bun, and Deno consumers update one import path.

// beforeimport{Client,StdioClientTransport}from'@modelcontextprotocol/client';import{McpServer,StdioServerTransport}from'@modelcontextprotocol/server';// afterimport{Client}from'@modelcontextprotocol/client';import{StdioClientTransport}from'@modelcontextprotocol/client/stdio';import{McpServer}from'@modelcontextprotocol/server';import{StdioServerTransport}from'@modelcontextprotocol/server/stdio';

Motivation and Context

The v2 root barrel exports StdioClientTransport, which top-level imports cross-spawn and node:child_process. Any bundler targeting browsers fails. v1 avoided this with deep subpath imports; this restores that boundary as a single ./stdio subpath.

How Has This Been Tested?

barrelClean.test.ts in both packages asserts the built dist/index.mjs (and its transitive chunks) contain zero process-spawning runtime imports, and that dist/stdio.mjs exports the moved symbols.

Breaking Changes

Yes — stdio symbols are no longer exported from the package root. Import from @modelcontextprotocol/{client,server}/stdio.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

Also moves getDefaultEnvironment, DEFAULT_INHERITED_ENV_VARS, and the StdioServerParameters type to the client ./stdio subpath.

@changeset-bot

changeset-botBot commented Apr 9, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 9b2b5d3

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

This PR includes changesets to release 6 packages
NameType
@modelcontextprotocol/clientMinor
@modelcontextprotocol/serverMinor
@modelcontextprotocol/expressMajor
@modelcontextprotocol/fastifyMajor
@modelcontextprotocol/honoMajor
@modelcontextprotocol/nodeMajor

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-newBot commented Apr 9, 2026

Copy link
Copy Markdown

Open in StackBlitz

@modelcontextprotocol/client

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

@modelcontextprotocol/server

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

@modelcontextprotocol/express

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

@modelcontextprotocol/fastify

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

@modelcontextprotocol/hono

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

@modelcontextprotocol/node

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

commit: 9b2b5d3

@felixweinberger

Copy link
Copy Markdown
ContributorAuthor

@claude review

@claudeclaudeBot 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.

LGTM — clean implementation following the existing _shims conditional-export pattern.

Extended reasoning...

Overview

The PR adds browser and workerd export conditions to the client and server package roots that swap the real stdio transports for throwing stubs. This prevents browser/CF Workers bundlers from pulling in node:child_process, node:stream, and cross-spawn. Changes span 14 files: two new index.browser.ts barrel files, two new stdioStub.ts files, updated package.json exports, build config updates, tests, a changeset, and a new SdkErrorCode.TransportNotSupported enum value in core.

Security Risks

None. The stubs throw a helpful error on use rather than silently failing. No auth, crypto, or permissions code is touched.

Level of Scrutiny

Moderate — package.json exports conditions are critical for correct module resolution across runtimes, but the change is additive (new conditions prepended before the existing default) and non-breaking. A diff of the browser vs. node barrel files shows the only difference is the stdio import source, confirming the mirrors are accurate.

Other Factors

The only reported bug is a cosmetic duplicate comment label in sdkErrors.ts, which has no functional impact. @modelcontextprotocol/core is a private (unpublished) workspace package, so the absence of a core entry in the changeset is correct. Tests verify the browser bundle excludes Node-only imports while still exporting the stub class. The approach follows the existing _shims conditional-export pattern already in the repo.

Comment threadpackages/core/src/errors/sdkErrors.ts
@felixweinberger
felixweinbergerforce-pushed the fweinberger/browser-stdio-conditional-export branch from 5350730 to 95b5ecaCompareApril 9, 2026 16:33
@felixweinbergerfelixweinberger changed the title fix(client,server): browser-conditional export for stdio transportsrefactor(client,server): move stdio transports to ./stdio subpath exportApr 9, 2026
Stdio transports require a process-spawning runtime (Node.js, Bun, Deno).
Exporting them from the package root meant browser and Cloudflare Workers
bundlers pulled in node:child_process, node:stream, and cross-spawn.
Move StdioClientTransport, getDefaultEnvironment, DEFAULT_INHERITED_ENV_VARS,
and StdioServerParameters to @modelcontextprotocol/client/stdio, and
StdioServerTransport to @modelcontextprotocol/server/stdio. The root entry
is now browser-safe; stdio consumers update one import path.
…stdio in quickstart tsconfig paths
- chunkImportsOf now BFS-walks the chunk import graph so the test name
('transitively imported') matches the implementation
- beforeAll builds the package if dist/ is missing (CI test job runs
pnpm test:all without a build step)
- examples/{client,server}-quickstart/tsconfig.json: add /stdio to paths
so tsc --noEmit resolves the new subpath without a prior build
…ntegration
CI test:all runs without building dist; vite-tsconfig-paths needs the
/stdio mapping to resolve to src/stdio.ts.
@felixweinberger

Copy link
Copy Markdown
ContributorAuthor

@claude review

Comment threadpackages/server/src/stdio.ts
…arify subpath comment
Server stdio uses only type-level node:stream imports (erased), so the
NODE_ONLY regex cannot detect re-export regressions. Add an explicit
symbol-absence check and correct the misleading comment in src/stdio.ts.
@felixweinberger

Copy link
Copy Markdown
ContributorAuthor

@claude review

1 similar comment
@felixweinberger

Copy link
Copy Markdown
ContributorAuthor

@claude review

Comment threaddocs/migration-SKILL.md Outdated

@KKonstantinovKKonstantinov left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Few comments, none of them blocking:

  • @types/cross-spawn in packages/server/package.json - could be cleaned-up, not used
  • packages/server/src/server/mcp.examples.ts - still imports from /stdio.js, but this file is synced via pnpm sync:snippets. Users who copy the example will get ./stdio.js, which is a path that only resolves inside the monorepo source tree, not from the published package
  • CLAUDE.md - probably would be good to add an explicit rule to the "Public API Exports" section as the PR establishes a clean invariant. E.g. something like

Exports whose module graph transitively touches unpolyfillable Node builtins (node:child_process, node:net, node:stream's runtime APIs, cross-spawn, etc.) must live at a named subpath export (e.g. ./stdio) and be covered by a barrelClean test in that package. The package root must stay runtime-neutral so browser and Cloudflare Workers bundlers can consume it.

  • Five tsconfigs now mirror thw ./stdio subpath by hand - it adds a drift risk, we could do some small follow-up lint/script that reads packages/{client,server}/package.jsonexports, derives the expected paths entries, and fails if any of the many tsconfigs are missing ones. Not needed for this PR, just an idea as the list grows longer

Comment threadpackages/server/test/server/barrelClean.test.ts Outdated
@felixweinberger
felixweinberger marked this pull request as ready for review April 15, 2026 19:39
@felixweinberger
felixweinberger requested a review from a team as a code ownerApril 15, 2026 19:39
@felixweinbergerfelixweinberger added the v2-bc v2 backwards-compatibility series label Apr 15, 2026
Comment threadpackages/server/src/index.ts Outdated
@felixweinbergerfelixweinberger added this to the v2.0.0-bc milestone Apr 15, 2026
…er index comment, align barrelClean NODE_ONLY regex
felixweinberger added a commit that referenced this pull request Apr 17, 2026
felixweinberger added a commit that referenced this pull request Apr 17, 2026
…ods+zod-schemas, take F1 callTool wording); SKILL.md --ours (preserves #1871/A1a/E-track content)
Comment threadpackages/server/src/index.ts
- README.md: split root-barrel StdioServerTransport import into /stdio subpath
- packages/server/package.json: drop unused @types/cross-spawn devDependency
- CLAUDE.md: document runtime-neutral root barrel rule under Public API Exports
@felixweinberger

Copy link
Copy Markdown
ContributorAuthor

@KKonstantinov thanks. Addressed in 9b2b5d3: dropped @types/cross-spawn from server, added the runtime-neutral root rule to CLAUDE.md, and fixed the README quickstart import while I was at it. Left mcp.examples.ts alone since sync:snippets only copies the #region body into JSDoc (the import line isn't synced) and all sibling .examples.ts files use the same relative pattern. Agree the tsconfig drift check would be a nice follow-up.

@claudeclaudeBot 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.

Thanks — 9b2b5d3 picks up the README quickstart import, and a repo-wide grep now finds zero remaining root-barrel Stdio*Transport imports. Everything I flagged is addressed; deferring to a maintainer for final sign-off on the breaking export-map change.

Extended reasoning...

Overview

This PR moves StdioClientTransport (plus getDefaultEnvironment, DEFAULT_INHERITED_ENV_VARS, StdioServerParameters) and StdioServerTransport from the package root barrels to new ./stdio subpath exports in @modelcontextprotocol/client and @modelcontextprotocol/server. It adds the subpath to both package.jsonexports maps and typesVersions, adds src/stdio.ts entry points to both tsdown configs, drops the unused @types/cross-spawn devDep from the server package, adds barrelClean.test.ts regression guards in both packages, and migrates every doc/example/integration-test callsite (36 files total). A CLAUDE.md rule codifies the runtime-neutral-root-entry constraint.

Since my last review (2026-04-27)

Commit 9b2b5d3 adds README.md to the diff with the split import, which was the last open item I'd raised. A fresh grep for Stdio(Server|Client)Transport.*from\s+['"]@modelcontextprotocol/(client|server)['"] across the repo now returns zero matches, and the moved helper symbols (getDefaultEnvironment, DEFAULT_INHERITED_ENV_VARS, StdioServerParameters) have no remaining root-barrel references outside the new subpath/tests. The changeset prose, server index.ts/stdio.ts comments, migration-SKILL.mdshared/stdio.js row, and server barrelClean symbol-absence assertion from earlier rounds are all in their corrected form.

Security risks

None. This is an export-map/packaging refactor; no auth, crypto, network, or input-handling code paths change. Runtime behavior of the transports themselves is untouched.

Level of scrutiny

Moderate-to-high. The implementation is mechanical, but it is an intentional breaking change to the public API surface of two published packages (removing four root exports and introducing a new subpath), wired through package.jsonexports, typesVersions, and the tsdown entry list. That falls under the repo's "every new export is intentional" / "removing from public API" bar and the v2-bc label, so a maintainer should give the final ack on the API shape rather than a bot.

Other factors

  • KKonstantinov reviewed an earlier revision and his NODE_ONLY-regex alignment request was applied (both barrelClean tests now share the same pattern plus the server symbol-absence check).
  • barrelClean tests give a build-output regression guard; integration tests and all type-checked *.examples.ts exercise the new import path.
  • Migration docs (migration.md, migration-SKILL.md) and the changeset cover the break per repo checklist.

@KKonstantinovKKonstantinov left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM!

@felixweinberger
felixweinberger merged commit 9fc9070 into mainApr 29, 2026
19 checks passed
@felixweinberger
felixweinberger deleted the fweinberger/browser-stdio-conditional-export branch April 29, 2026 13:48
@claudeclaudeBot mentioned this pull request May 5, 2026
6 tasks
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

v2-bcv2 backwards-compatibility series

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@felixweinberger@KKonstantinov
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
refactor(client,server): move stdio transports to ./stdio subpath export by felixweinberger · Pull Request #1871 · modelcontextprotocol/typescript-sdk · GitHub
Skip to content

refactor(client,server): move stdio transports to ./stdio subpath export - #1871

Merged
felixweinberger merged 10 commits into
mainfrom
fweinberger/browser-stdio-conditional-export
Apr 29, 2026
Merged

refactor(client,server): move stdio transports to ./stdio subpath export#1871
felixweinberger merged 10 commits into
mainfrom
fweinberger/browser-stdio-conditional-export

Conversation

@felixweinberger

@felixweinbergerfelixweinberger commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

Move stdio transports to a ./stdio subpath export so the package root no longer pulls in node:child_process, node:stream, or cross-spawn. Fixes bundling for browser and Cloudflare Workers consumers. Node.js, Bun, and Deno consumers update one import path.

// beforeimport{Client,StdioClientTransport}from'@modelcontextprotocol/client';import{McpServer,StdioServerTransport}from'@modelcontextprotocol/server';// afterimport{Client}from'@modelcontextprotocol/client';import{StdioClientTransport}from'@modelcontextprotocol/client/stdio';import{McpServer}from'@modelcontextprotocol/server';import{StdioServerTransport}from'@modelcontextprotocol/server/stdio';

Motivation and Context

The v2 root barrel exports StdioClientTransport, which top-level imports cross-spawn and node:child_process. Any bundler targeting browsers fails. v1 avoided this with deep subpath imports; this restores that boundary as a single ./stdio subpath.

How Has This Been Tested?

barrelClean.test.ts in both packages asserts the built dist/index.mjs (and its transitive chunks) contain zero process-spawning runtime imports, and that dist/stdio.mjs exports the moved symbols.

Breaking Changes

Yes — stdio symbols are no longer exported from the package root. Import from @modelcontextprotocol/{client,server}/stdio.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

Also moves getDefaultEnvironment, DEFAULT_INHERITED_ENV_VARS, and the StdioServerParameters type to the client ./stdio subpath.

@changeset-bot

changeset-botBot commented Apr 9, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 9b2b5d3

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

This PR includes changesets to release 6 packages
NameType
@modelcontextprotocol/clientMinor
@modelcontextprotocol/serverMinor
@modelcontextprotocol/expressMajor
@modelcontextprotocol/fastifyMajor
@modelcontextprotocol/honoMajor
@modelcontextprotocol/nodeMajor

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-newBot commented Apr 9, 2026

Copy link
Copy Markdown

Open in StackBlitz

@modelcontextprotocol/client

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

@modelcontextprotocol/server

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

@modelcontextprotocol/express

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

@modelcontextprotocol/fastify

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

@modelcontextprotocol/hono

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

@modelcontextprotocol/node

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

commit: 9b2b5d3

@felixweinberger

Copy link
Copy Markdown
ContributorAuthor

@claude review

@claudeclaudeBot 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.

LGTM — clean implementation following the existing _shims conditional-export pattern.

Extended reasoning...

Overview

The PR adds browser and workerd export conditions to the client and server package roots that swap the real stdio transports for throwing stubs. This prevents browser/CF Workers bundlers from pulling in node:child_process, node:stream, and cross-spawn. Changes span 14 files: two new index.browser.ts barrel files, two new stdioStub.ts files, updated package.json exports, build config updates, tests, a changeset, and a new SdkErrorCode.TransportNotSupported enum value in core.

Security Risks

None. The stubs throw a helpful error on use rather than silently failing. No auth, crypto, or permissions code is touched.

Level of Scrutiny

Moderate — package.json exports conditions are critical for correct module resolution across runtimes, but the change is additive (new conditions prepended before the existing default) and non-breaking. A diff of the browser vs. node barrel files shows the only difference is the stdio import source, confirming the mirrors are accurate.

Other Factors

The only reported bug is a cosmetic duplicate comment label in sdkErrors.ts, which has no functional impact. @modelcontextprotocol/core is a private (unpublished) workspace package, so the absence of a core entry in the changeset is correct. Tests verify the browser bundle excludes Node-only imports while still exporting the stub class. The approach follows the existing _shims conditional-export pattern already in the repo.

Comment threadpackages/core/src/errors/sdkErrors.ts
@felixweinberger
felixweinbergerforce-pushed the fweinberger/browser-stdio-conditional-export branch from 5350730 to 95b5ecaCompareApril 9, 2026 16:33
@felixweinbergerfelixweinberger changed the title fix(client,server): browser-conditional export for stdio transportsrefactor(client,server): move stdio transports to ./stdio subpath exportApr 9, 2026
Stdio transports require a process-spawning runtime (Node.js, Bun, Deno).
Exporting them from the package root meant browser and Cloudflare Workers
bundlers pulled in node:child_process, node:stream, and cross-spawn.
Move StdioClientTransport, getDefaultEnvironment, DEFAULT_INHERITED_ENV_VARS,
and StdioServerParameters to @modelcontextprotocol/client/stdio, and
StdioServerTransport to @modelcontextprotocol/server/stdio. The root entry
is now browser-safe; stdio consumers update one import path.
…stdio in quickstart tsconfig paths
- chunkImportsOf now BFS-walks the chunk import graph so the test name
('transitively imported') matches the implementation
- beforeAll builds the package if dist/ is missing (CI test job runs
pnpm test:all without a build step)
- examples/{client,server}-quickstart/tsconfig.json: add /stdio to paths
so tsc --noEmit resolves the new subpath without a prior build
…ntegration
CI test:all runs without building dist; vite-tsconfig-paths needs the
/stdio mapping to resolve to src/stdio.ts.
@felixweinberger

Copy link
Copy Markdown
ContributorAuthor

@claude review

Comment threadpackages/server/src/stdio.ts
…arify subpath comment
Server stdio uses only type-level node:stream imports (erased), so the
NODE_ONLY regex cannot detect re-export regressions. Add an explicit
symbol-absence check and correct the misleading comment in src/stdio.ts.
@felixweinberger

Copy link
Copy Markdown
ContributorAuthor

@claude review

1 similar comment
@felixweinberger

Copy link
Copy Markdown
ContributorAuthor

@claude review

Comment threaddocs/migration-SKILL.md Outdated

@KKonstantinovKKonstantinov left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Few comments, none of them blocking:

  • @types/cross-spawn in packages/server/package.json - could be cleaned-up, not used
  • packages/server/src/server/mcp.examples.ts - still imports from /stdio.js, but this file is synced via pnpm sync:snippets. Users who copy the example will get ./stdio.js, which is a path that only resolves inside the monorepo source tree, not from the published package
  • CLAUDE.md - probably would be good to add an explicit rule to the "Public API Exports" section as the PR establishes a clean invariant. E.g. something like

Exports whose module graph transitively touches unpolyfillable Node builtins (node:child_process, node:net, node:stream's runtime APIs, cross-spawn, etc.) must live at a named subpath export (e.g. ./stdio) and be covered by a barrelClean test in that package. The package root must stay runtime-neutral so browser and Cloudflare Workers bundlers can consume it.

  • Five tsconfigs now mirror thw ./stdio subpath by hand - it adds a drift risk, we could do some small follow-up lint/script that reads packages/{client,server}/package.jsonexports, derives the expected paths entries, and fails if any of the many tsconfigs are missing ones. Not needed for this PR, just an idea as the list grows longer

Comment threadpackages/server/test/server/barrelClean.test.ts Outdated
@felixweinberger
felixweinberger marked this pull request as ready for review April 15, 2026 19:39
@felixweinberger
felixweinberger requested a review from a team as a code ownerApril 15, 2026 19:39
@felixweinbergerfelixweinberger added the v2-bc v2 backwards-compatibility series label Apr 15, 2026
Comment threadpackages/server/src/index.ts Outdated
@felixweinbergerfelixweinberger added this to the v2.0.0-bc milestone Apr 15, 2026
…er index comment, align barrelClean NODE_ONLY regex
felixweinberger added a commit that referenced this pull request Apr 17, 2026
felixweinberger added a commit that referenced this pull request Apr 17, 2026
…ods+zod-schemas, take F1 callTool wording); SKILL.md --ours (preserves #1871/A1a/E-track content)
Comment threadpackages/server/src/index.ts
- README.md: split root-barrel StdioServerTransport import into /stdio subpath
- packages/server/package.json: drop unused @types/cross-spawn devDependency
- CLAUDE.md: document runtime-neutral root barrel rule under Public API Exports
@felixweinberger

Copy link
Copy Markdown
ContributorAuthor

@KKonstantinov thanks. Addressed in 9b2b5d3: dropped @types/cross-spawn from server, added the runtime-neutral root rule to CLAUDE.md, and fixed the README quickstart import while I was at it. Left mcp.examples.ts alone since sync:snippets only copies the #region body into JSDoc (the import line isn't synced) and all sibling .examples.ts files use the same relative pattern. Agree the tsconfig drift check would be a nice follow-up.

@claudeclaudeBot 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.

Thanks — 9b2b5d3 picks up the README quickstart import, and a repo-wide grep now finds zero remaining root-barrel Stdio*Transport imports. Everything I flagged is addressed; deferring to a maintainer for final sign-off on the breaking export-map change.

Extended reasoning...

Overview

This PR moves StdioClientTransport (plus getDefaultEnvironment, DEFAULT_INHERITED_ENV_VARS, StdioServerParameters) and StdioServerTransport from the package root barrels to new ./stdio subpath exports in @modelcontextprotocol/client and @modelcontextprotocol/server. It adds the subpath to both package.jsonexports maps and typesVersions, adds src/stdio.ts entry points to both tsdown configs, drops the unused @types/cross-spawn devDep from the server package, adds barrelClean.test.ts regression guards in both packages, and migrates every doc/example/integration-test callsite (36 files total). A CLAUDE.md rule codifies the runtime-neutral-root-entry constraint.

Since my last review (2026-04-27)

Commit 9b2b5d3 adds README.md to the diff with the split import, which was the last open item I'd raised. A fresh grep for Stdio(Server|Client)Transport.*from\s+['"]@modelcontextprotocol/(client|server)['"] across the repo now returns zero matches, and the moved helper symbols (getDefaultEnvironment, DEFAULT_INHERITED_ENV_VARS, StdioServerParameters) have no remaining root-barrel references outside the new subpath/tests. The changeset prose, server index.ts/stdio.ts comments, migration-SKILL.mdshared/stdio.js row, and server barrelClean symbol-absence assertion from earlier rounds are all in their corrected form.

Security risks

None. This is an export-map/packaging refactor; no auth, crypto, network, or input-handling code paths change. Runtime behavior of the transports themselves is untouched.

Level of scrutiny

Moderate-to-high. The implementation is mechanical, but it is an intentional breaking change to the public API surface of two published packages (removing four root exports and introducing a new subpath), wired through package.jsonexports, typesVersions, and the tsdown entry list. That falls under the repo's "every new export is intentional" / "removing from public API" bar and the v2-bc label, so a maintainer should give the final ack on the API shape rather than a bot.

Other factors

  • KKonstantinov reviewed an earlier revision and his NODE_ONLY-regex alignment request was applied (both barrelClean tests now share the same pattern plus the server symbol-absence check).
  • barrelClean tests give a build-output regression guard; integration tests and all type-checked *.examples.ts exercise the new import path.
  • Migration docs (migration.md, migration-SKILL.md) and the changeset cover the break per repo checklist.

@KKonstantinovKKonstantinov left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM!

@felixweinberger
felixweinberger merged commit 9fc9070 into mainApr 29, 2026
19 checks passed
@felixweinberger
felixweinberger deleted the fweinberger/browser-stdio-conditional-export branch April 29, 2026 13:48
@claudeclaudeBot mentioned this pull request May 5, 2026
6 tasks
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

v2-bcv2 backwards-compatibility series

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@felixweinberger@KKonstantinov
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' refactor(client,server): move stdio transports to ./stdio subpath export by felixweinberger · Pull Request #1871 · modelcontextprotocol/typescript-sdk · GitHub
Skip to content

refactor(client,server): move stdio transports to ./stdio subpath export - #1871

Merged
felixweinberger merged 10 commits into
mainfrom
fweinberger/browser-stdio-conditional-export
Apr 29, 2026
Merged

refactor(client,server): move stdio transports to ./stdio subpath export#1871
felixweinberger merged 10 commits into
mainfrom
fweinberger/browser-stdio-conditional-export

Conversation

@felixweinberger

@felixweinbergerfelixweinberger commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

Move stdio transports to a ./stdio subpath export so the package root no longer pulls in node:child_process, node:stream, or cross-spawn. Fixes bundling for browser and Cloudflare Workers consumers. Node.js, Bun, and Deno consumers update one import path.

// beforeimport{Client,StdioClientTransport}from'@modelcontextprotocol/client';import{McpServer,StdioServerTransport}from'@modelcontextprotocol/server';// afterimport{Client}from'@modelcontextprotocol/client';import{StdioClientTransport}from'@modelcontextprotocol/client/stdio';import{McpServer}from'@modelcontextprotocol/server';import{StdioServerTransport}from'@modelcontextprotocol/server/stdio';

Motivation and Context

The v2 root barrel exports StdioClientTransport, which top-level imports cross-spawn and node:child_process. Any bundler targeting browsers fails. v1 avoided this with deep subpath imports; this restores that boundary as a single ./stdio subpath.

How Has This Been Tested?

barrelClean.test.ts in both packages asserts the built dist/index.mjs (and its transitive chunks) contain zero process-spawning runtime imports, and that dist/stdio.mjs exports the moved symbols.

Breaking Changes

Yes — stdio symbols are no longer exported from the package root. Import from @modelcontextprotocol/{client,server}/stdio.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

Also moves getDefaultEnvironment, DEFAULT_INHERITED_ENV_VARS, and the StdioServerParameters type to the client ./stdio subpath.

@changeset-bot

changeset-botBot commented Apr 9, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 9b2b5d3

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

This PR includes changesets to release 6 packages
NameType
@modelcontextprotocol/clientMinor
@modelcontextprotocol/serverMinor
@modelcontextprotocol/expressMajor
@modelcontextprotocol/fastifyMajor
@modelcontextprotocol/honoMajor
@modelcontextprotocol/nodeMajor

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-newBot commented Apr 9, 2026

Copy link
Copy Markdown

Open in StackBlitz

@modelcontextprotocol/client

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

@modelcontextprotocol/server

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

@modelcontextprotocol/express

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

@modelcontextprotocol/fastify

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

@modelcontextprotocol/hono

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

@modelcontextprotocol/node

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

commit: 9b2b5d3

@felixweinberger

Copy link
Copy Markdown
ContributorAuthor

@claude review

@claudeclaudeBot 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.

LGTM — clean implementation following the existing _shims conditional-export pattern.

Extended reasoning...

Overview

The PR adds browser and workerd export conditions to the client and server package roots that swap the real stdio transports for throwing stubs. This prevents browser/CF Workers bundlers from pulling in node:child_process, node:stream, and cross-spawn. Changes span 14 files: two new index.browser.ts barrel files, two new stdioStub.ts files, updated package.json exports, build config updates, tests, a changeset, and a new SdkErrorCode.TransportNotSupported enum value in core.

Security Risks

None. The stubs throw a helpful error on use rather than silently failing. No auth, crypto, or permissions code is touched.

Level of Scrutiny

Moderate — package.json exports conditions are critical for correct module resolution across runtimes, but the change is additive (new conditions prepended before the existing default) and non-breaking. A diff of the browser vs. node barrel files shows the only difference is the stdio import source, confirming the mirrors are accurate.

Other Factors

The only reported bug is a cosmetic duplicate comment label in sdkErrors.ts, which has no functional impact. @modelcontextprotocol/core is a private (unpublished) workspace package, so the absence of a core entry in the changeset is correct. Tests verify the browser bundle excludes Node-only imports while still exporting the stub class. The approach follows the existing _shims conditional-export pattern already in the repo.

Comment threadpackages/core/src/errors/sdkErrors.ts
@felixweinberger
felixweinbergerforce-pushed the fweinberger/browser-stdio-conditional-export branch from 5350730 to 95b5ecaCompareApril 9, 2026 16:33
@felixweinbergerfelixweinberger changed the title fix(client,server): browser-conditional export for stdio transportsrefactor(client,server): move stdio transports to ./stdio subpath exportApr 9, 2026
Stdio transports require a process-spawning runtime (Node.js, Bun, Deno).
Exporting them from the package root meant browser and Cloudflare Workers
bundlers pulled in node:child_process, node:stream, and cross-spawn.
Move StdioClientTransport, getDefaultEnvironment, DEFAULT_INHERITED_ENV_VARS,
and StdioServerParameters to @modelcontextprotocol/client/stdio, and
StdioServerTransport to @modelcontextprotocol/server/stdio. The root entry
is now browser-safe; stdio consumers update one import path.
…stdio in quickstart tsconfig paths
- chunkImportsOf now BFS-walks the chunk import graph so the test name
('transitively imported') matches the implementation
- beforeAll builds the package if dist/ is missing (CI test job runs
pnpm test:all without a build step)
- examples/{client,server}-quickstart/tsconfig.json: add /stdio to paths
so tsc --noEmit resolves the new subpath without a prior build
…ntegration
CI test:all runs without building dist; vite-tsconfig-paths needs the
/stdio mapping to resolve to src/stdio.ts.
@felixweinberger

Copy link
Copy Markdown
ContributorAuthor

@claude review

Comment threadpackages/server/src/stdio.ts
…arify subpath comment
Server stdio uses only type-level node:stream imports (erased), so the
NODE_ONLY regex cannot detect re-export regressions. Add an explicit
symbol-absence check and correct the misleading comment in src/stdio.ts.
@felixweinberger

Copy link
Copy Markdown
ContributorAuthor

@claude review

1 similar comment
@felixweinberger

Copy link
Copy Markdown
ContributorAuthor

@claude review

Comment threaddocs/migration-SKILL.md Outdated

@KKonstantinovKKonstantinov left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Few comments, none of them blocking:

  • @types/cross-spawn in packages/server/package.json - could be cleaned-up, not used
  • packages/server/src/server/mcp.examples.ts - still imports from /stdio.js, but this file is synced via pnpm sync:snippets. Users who copy the example will get ./stdio.js, which is a path that only resolves inside the monorepo source tree, not from the published package
  • CLAUDE.md - probably would be good to add an explicit rule to the "Public API Exports" section as the PR establishes a clean invariant. E.g. something like

Exports whose module graph transitively touches unpolyfillable Node builtins (node:child_process, node:net, node:stream's runtime APIs, cross-spawn, etc.) must live at a named subpath export (e.g. ./stdio) and be covered by a barrelClean test in that package. The package root must stay runtime-neutral so browser and Cloudflare Workers bundlers can consume it.

  • Five tsconfigs now mirror thw ./stdio subpath by hand - it adds a drift risk, we could do some small follow-up lint/script that reads packages/{client,server}/package.jsonexports, derives the expected paths entries, and fails if any of the many tsconfigs are missing ones. Not needed for this PR, just an idea as the list grows longer

Comment threadpackages/server/test/server/barrelClean.test.ts Outdated
@felixweinberger
felixweinberger marked this pull request as ready for review April 15, 2026 19:39
@felixweinberger
felixweinberger requested a review from a team as a code ownerApril 15, 2026 19:39
@felixweinbergerfelixweinberger added the v2-bc v2 backwards-compatibility series label Apr 15, 2026
Comment threadpackages/server/src/index.ts Outdated
@felixweinbergerfelixweinberger added this to the v2.0.0-bc milestone Apr 15, 2026
…er index comment, align barrelClean NODE_ONLY regex
felixweinberger added a commit that referenced this pull request Apr 17, 2026
felixweinberger added a commit that referenced this pull request Apr 17, 2026
…ods+zod-schemas, take F1 callTool wording); SKILL.md --ours (preserves #1871/A1a/E-track content)
Comment threadpackages/server/src/index.ts
- README.md: split root-barrel StdioServerTransport import into /stdio subpath
- packages/server/package.json: drop unused @types/cross-spawn devDependency
- CLAUDE.md: document runtime-neutral root barrel rule under Public API Exports
@felixweinberger

Copy link
Copy Markdown
ContributorAuthor

@KKonstantinov thanks. Addressed in 9b2b5d3: dropped @types/cross-spawn from server, added the runtime-neutral root rule to CLAUDE.md, and fixed the README quickstart import while I was at it. Left mcp.examples.ts alone since sync:snippets only copies the #region body into JSDoc (the import line isn't synced) and all sibling .examples.ts files use the same relative pattern. Agree the tsconfig drift check would be a nice follow-up.

@claudeclaudeBot 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.

Thanks — 9b2b5d3 picks up the README quickstart import, and a repo-wide grep now finds zero remaining root-barrel Stdio*Transport imports. Everything I flagged is addressed; deferring to a maintainer for final sign-off on the breaking export-map change.

Extended reasoning...

Overview

This PR moves StdioClientTransport (plus getDefaultEnvironment, DEFAULT_INHERITED_ENV_VARS, StdioServerParameters) and StdioServerTransport from the package root barrels to new ./stdio subpath exports in @modelcontextprotocol/client and @modelcontextprotocol/server. It adds the subpath to both package.jsonexports maps and typesVersions, adds src/stdio.ts entry points to both tsdown configs, drops the unused @types/cross-spawn devDep from the server package, adds barrelClean.test.ts regression guards in both packages, and migrates every doc/example/integration-test callsite (36 files total). A CLAUDE.md rule codifies the runtime-neutral-root-entry constraint.

Since my last review (2026-04-27)

Commit 9b2b5d3 adds README.md to the diff with the split import, which was the last open item I'd raised. A fresh grep for Stdio(Server|Client)Transport.*from\s+['"]@modelcontextprotocol/(client|server)['"] across the repo now returns zero matches, and the moved helper symbols (getDefaultEnvironment, DEFAULT_INHERITED_ENV_VARS, StdioServerParameters) have no remaining root-barrel references outside the new subpath/tests. The changeset prose, server index.ts/stdio.ts comments, migration-SKILL.mdshared/stdio.js row, and server barrelClean symbol-absence assertion from earlier rounds are all in their corrected form.

Security risks

None. This is an export-map/packaging refactor; no auth, crypto, network, or input-handling code paths change. Runtime behavior of the transports themselves is untouched.

Level of scrutiny

Moderate-to-high. The implementation is mechanical, but it is an intentional breaking change to the public API surface of two published packages (removing four root exports and introducing a new subpath), wired through package.jsonexports, typesVersions, and the tsdown entry list. That falls under the repo's "every new export is intentional" / "removing from public API" bar and the v2-bc label, so a maintainer should give the final ack on the API shape rather than a bot.

Other factors

  • KKonstantinov reviewed an earlier revision and his NODE_ONLY-regex alignment request was applied (both barrelClean tests now share the same pattern plus the server symbol-absence check).
  • barrelClean tests give a build-output regression guard; integration tests and all type-checked *.examples.ts exercise the new import path.
  • Migration docs (migration.md, migration-SKILL.md) and the changeset cover the break per repo checklist.

@KKonstantinovKKonstantinov left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM!

@felixweinberger
felixweinberger merged commit 9fc9070 into mainApr 29, 2026
19 checks passed
@felixweinberger
felixweinberger deleted the fweinberger/browser-stdio-conditional-export branch April 29, 2026 13:48
@claudeclaudeBot mentioned this pull request May 5, 2026
6 tasks
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

v2-bcv2 backwards-compatibility series

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@felixweinberger@KKonstantinov
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' refactor(client,server): move stdio transports to ./stdio subpath export by felixweinberger · Pull Request #1871 · modelcontextprotocol/typescript-sdk · GitHub
Skip to content

refactor(client,server): move stdio transports to ./stdio subpath export - #1871

Merged
felixweinberger merged 10 commits into
mainfrom
fweinberger/browser-stdio-conditional-export
Apr 29, 2026
Merged

refactor(client,server): move stdio transports to ./stdio subpath export#1871
felixweinberger merged 10 commits into
mainfrom
fweinberger/browser-stdio-conditional-export

Conversation

@felixweinberger

@felixweinbergerfelixweinberger commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

Move stdio transports to a ./stdio subpath export so the package root no longer pulls in node:child_process, node:stream, or cross-spawn. Fixes bundling for browser and Cloudflare Workers consumers. Node.js, Bun, and Deno consumers update one import path.

// beforeimport{Client,StdioClientTransport}from'@modelcontextprotocol/client';import{McpServer,StdioServerTransport}from'@modelcontextprotocol/server';// afterimport{Client}from'@modelcontextprotocol/client';import{StdioClientTransport}from'@modelcontextprotocol/client/stdio';import{McpServer}from'@modelcontextprotocol/server';import{StdioServerTransport}from'@modelcontextprotocol/server/stdio';

Motivation and Context

The v2 root barrel exports StdioClientTransport, which top-level imports cross-spawn and node:child_process. Any bundler targeting browsers fails. v1 avoided this with deep subpath imports; this restores that boundary as a single ./stdio subpath.

How Has This Been Tested?

barrelClean.test.ts in both packages asserts the built dist/index.mjs (and its transitive chunks) contain zero process-spawning runtime imports, and that dist/stdio.mjs exports the moved symbols.

Breaking Changes

Yes — stdio symbols are no longer exported from the package root. Import from @modelcontextprotocol/{client,server}/stdio.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

Also moves getDefaultEnvironment, DEFAULT_INHERITED_ENV_VARS, and the StdioServerParameters type to the client ./stdio subpath.

@changeset-bot

changeset-botBot commented Apr 9, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 9b2b5d3

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

This PR includes changesets to release 6 packages
NameType
@modelcontextprotocol/clientMinor
@modelcontextprotocol/serverMinor
@modelcontextprotocol/expressMajor
@modelcontextprotocol/fastifyMajor
@modelcontextprotocol/honoMajor
@modelcontextprotocol/nodeMajor

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-newBot commented Apr 9, 2026

Copy link
Copy Markdown

Open in StackBlitz

@modelcontextprotocol/client

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

@modelcontextprotocol/server

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

@modelcontextprotocol/express

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

@modelcontextprotocol/fastify

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

@modelcontextprotocol/hono

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

@modelcontextprotocol/node

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

commit: 9b2b5d3

@felixweinberger

Copy link
Copy Markdown
ContributorAuthor

@claude review

@claudeclaudeBot 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.

LGTM — clean implementation following the existing _shims conditional-export pattern.

Extended reasoning...

Overview

The PR adds browser and workerd export conditions to the client and server package roots that swap the real stdio transports for throwing stubs. This prevents browser/CF Workers bundlers from pulling in node:child_process, node:stream, and cross-spawn. Changes span 14 files: two new index.browser.ts barrel files, two new stdioStub.ts files, updated package.json exports, build config updates, tests, a changeset, and a new SdkErrorCode.TransportNotSupported enum value in core.

Security Risks

None. The stubs throw a helpful error on use rather than silently failing. No auth, crypto, or permissions code is touched.

Level of Scrutiny

Moderate — package.json exports conditions are critical for correct module resolution across runtimes, but the change is additive (new conditions prepended before the existing default) and non-breaking. A diff of the browser vs. node barrel files shows the only difference is the stdio import source, confirming the mirrors are accurate.

Other Factors

The only reported bug is a cosmetic duplicate comment label in sdkErrors.ts, which has no functional impact. @modelcontextprotocol/core is a private (unpublished) workspace package, so the absence of a core entry in the changeset is correct. Tests verify the browser bundle excludes Node-only imports while still exporting the stub class. The approach follows the existing _shims conditional-export pattern already in the repo.

Comment threadpackages/core/src/errors/sdkErrors.ts
@felixweinberger
felixweinbergerforce-pushed the fweinberger/browser-stdio-conditional-export branch from 5350730 to 95b5ecaCompareApril 9, 2026 16:33
@felixweinbergerfelixweinberger changed the title fix(client,server): browser-conditional export for stdio transportsrefactor(client,server): move stdio transports to ./stdio subpath exportApr 9, 2026
Stdio transports require a process-spawning runtime (Node.js, Bun, Deno).
Exporting them from the package root meant browser and Cloudflare Workers
bundlers pulled in node:child_process, node:stream, and cross-spawn.
Move StdioClientTransport, getDefaultEnvironment, DEFAULT_INHERITED_ENV_VARS,
and StdioServerParameters to @modelcontextprotocol/client/stdio, and
StdioServerTransport to @modelcontextprotocol/server/stdio. The root entry
is now browser-safe; stdio consumers update one import path.
…stdio in quickstart tsconfig paths
- chunkImportsOf now BFS-walks the chunk import graph so the test name
('transitively imported') matches the implementation
- beforeAll builds the package if dist/ is missing (CI test job runs
pnpm test:all without a build step)
- examples/{client,server}-quickstart/tsconfig.json: add /stdio to paths
so tsc --noEmit resolves the new subpath without a prior build
…ntegration
CI test:all runs without building dist; vite-tsconfig-paths needs the
/stdio mapping to resolve to src/stdio.ts.
@felixweinberger

Copy link
Copy Markdown
ContributorAuthor

@claude review

Comment threadpackages/server/src/stdio.ts
…arify subpath comment
Server stdio uses only type-level node:stream imports (erased), so the
NODE_ONLY regex cannot detect re-export regressions. Add an explicit
symbol-absence check and correct the misleading comment in src/stdio.ts.
@felixweinberger

Copy link
Copy Markdown
ContributorAuthor

@claude review

1 similar comment
@felixweinberger

Copy link
Copy Markdown
ContributorAuthor

@claude review

Comment threaddocs/migration-SKILL.md Outdated

@KKonstantinovKKonstantinov left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Few comments, none of them blocking:

  • @types/cross-spawn in packages/server/package.json - could be cleaned-up, not used
  • packages/server/src/server/mcp.examples.ts - still imports from /stdio.js, but this file is synced via pnpm sync:snippets. Users who copy the example will get ./stdio.js, which is a path that only resolves inside the monorepo source tree, not from the published package
  • CLAUDE.md - probably would be good to add an explicit rule to the "Public API Exports" section as the PR establishes a clean invariant. E.g. something like

Exports whose module graph transitively touches unpolyfillable Node builtins (node:child_process, node:net, node:stream's runtime APIs, cross-spawn, etc.) must live at a named subpath export (e.g. ./stdio) and be covered by a barrelClean test in that package. The package root must stay runtime-neutral so browser and Cloudflare Workers bundlers can consume it.

  • Five tsconfigs now mirror thw ./stdio subpath by hand - it adds a drift risk, we could do some small follow-up lint/script that reads packages/{client,server}/package.jsonexports, derives the expected paths entries, and fails if any of the many tsconfigs are missing ones. Not needed for this PR, just an idea as the list grows longer

Comment threadpackages/server/test/server/barrelClean.test.ts Outdated
@felixweinberger
felixweinberger marked this pull request as ready for review April 15, 2026 19:39
@felixweinberger
felixweinberger requested a review from a team as a code ownerApril 15, 2026 19:39
@felixweinbergerfelixweinberger added the v2-bc v2 backwards-compatibility series label Apr 15, 2026
Comment threadpackages/server/src/index.ts Outdated
@felixweinbergerfelixweinberger added this to the v2.0.0-bc milestone Apr 15, 2026
…er index comment, align barrelClean NODE_ONLY regex
felixweinberger added a commit that referenced this pull request Apr 17, 2026
felixweinberger added a commit that referenced this pull request Apr 17, 2026
…ods+zod-schemas, take F1 callTool wording); SKILL.md --ours (preserves #1871/A1a/E-track content)
Comment threadpackages/server/src/index.ts
- README.md: split root-barrel StdioServerTransport import into /stdio subpath
- packages/server/package.json: drop unused @types/cross-spawn devDependency
- CLAUDE.md: document runtime-neutral root barrel rule under Public API Exports
@felixweinberger

Copy link
Copy Markdown
ContributorAuthor

@KKonstantinov thanks. Addressed in 9b2b5d3: dropped @types/cross-spawn from server, added the runtime-neutral root rule to CLAUDE.md, and fixed the README quickstart import while I was at it. Left mcp.examples.ts alone since sync:snippets only copies the #region body into JSDoc (the import line isn't synced) and all sibling .examples.ts files use the same relative pattern. Agree the tsconfig drift check would be a nice follow-up.

@claudeclaudeBot 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.

Thanks — 9b2b5d3 picks up the README quickstart import, and a repo-wide grep now finds zero remaining root-barrel Stdio*Transport imports. Everything I flagged is addressed; deferring to a maintainer for final sign-off on the breaking export-map change.

Extended reasoning...

Overview

This PR moves StdioClientTransport (plus getDefaultEnvironment, DEFAULT_INHERITED_ENV_VARS, StdioServerParameters) and StdioServerTransport from the package root barrels to new ./stdio subpath exports in @modelcontextprotocol/client and @modelcontextprotocol/server. It adds the subpath to both package.jsonexports maps and typesVersions, adds src/stdio.ts entry points to both tsdown configs, drops the unused @types/cross-spawn devDep from the server package, adds barrelClean.test.ts regression guards in both packages, and migrates every doc/example/integration-test callsite (36 files total). A CLAUDE.md rule codifies the runtime-neutral-root-entry constraint.

Since my last review (2026-04-27)

Commit 9b2b5d3 adds README.md to the diff with the split import, which was the last open item I'd raised. A fresh grep for Stdio(Server|Client)Transport.*from\s+['"]@modelcontextprotocol/(client|server)['"] across the repo now returns zero matches, and the moved helper symbols (getDefaultEnvironment, DEFAULT_INHERITED_ENV_VARS, StdioServerParameters) have no remaining root-barrel references outside the new subpath/tests. The changeset prose, server index.ts/stdio.ts comments, migration-SKILL.mdshared/stdio.js row, and server barrelClean symbol-absence assertion from earlier rounds are all in their corrected form.

Security risks

None. This is an export-map/packaging refactor; no auth, crypto, network, or input-handling code paths change. Runtime behavior of the transports themselves is untouched.

Level of scrutiny

Moderate-to-high. The implementation is mechanical, but it is an intentional breaking change to the public API surface of two published packages (removing four root exports and introducing a new subpath), wired through package.jsonexports, typesVersions, and the tsdown entry list. That falls under the repo's "every new export is intentional" / "removing from public API" bar and the v2-bc label, so a maintainer should give the final ack on the API shape rather than a bot.

Other factors

  • KKonstantinov reviewed an earlier revision and his NODE_ONLY-regex alignment request was applied (both barrelClean tests now share the same pattern plus the server symbol-absence check).
  • barrelClean tests give a build-output regression guard; integration tests and all type-checked *.examples.ts exercise the new import path.
  • Migration docs (migration.md, migration-SKILL.md) and the changeset cover the break per repo checklist.

@KKonstantinovKKonstantinov left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM!

@felixweinberger
felixweinberger merged commit 9fc9070 into mainApr 29, 2026
19 checks passed
@felixweinberger
felixweinberger deleted the fweinberger/browser-stdio-conditional-export branch April 29, 2026 13:48
@claudeclaudeBot mentioned this pull request May 5, 2026
6 tasks
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

v2-bcv2 backwards-compatibility series

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@felixweinberger@KKonstantinov
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' refactor(client,server): move stdio transports to ./stdio subpath export by felixweinberger · Pull Request #1871 · modelcontextprotocol/typescript-sdk · GitHub
Skip to content

refactor(client,server): move stdio transports to ./stdio subpath export - #1871

Merged
felixweinberger merged 10 commits into
mainfrom
fweinberger/browser-stdio-conditional-export
Apr 29, 2026
Merged

refactor(client,server): move stdio transports to ./stdio subpath export#1871
felixweinberger merged 10 commits into
mainfrom
fweinberger/browser-stdio-conditional-export

Conversation

@felixweinberger

@felixweinbergerfelixweinberger commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

Move stdio transports to a ./stdio subpath export so the package root no longer pulls in node:child_process, node:stream, or cross-spawn. Fixes bundling for browser and Cloudflare Workers consumers. Node.js, Bun, and Deno consumers update one import path.

// beforeimport{Client,StdioClientTransport}from'@modelcontextprotocol/client';import{McpServer,StdioServerTransport}from'@modelcontextprotocol/server';// afterimport{Client}from'@modelcontextprotocol/client';import{StdioClientTransport}from'@modelcontextprotocol/client/stdio';import{McpServer}from'@modelcontextprotocol/server';import{StdioServerTransport}from'@modelcontextprotocol/server/stdio';

Motivation and Context

The v2 root barrel exports StdioClientTransport, which top-level imports cross-spawn and node:child_process. Any bundler targeting browsers fails. v1 avoided this with deep subpath imports; this restores that boundary as a single ./stdio subpath.

How Has This Been Tested?

barrelClean.test.ts in both packages asserts the built dist/index.mjs (and its transitive chunks) contain zero process-spawning runtime imports, and that dist/stdio.mjs exports the moved symbols.

Breaking Changes

Yes — stdio symbols are no longer exported from the package root. Import from @modelcontextprotocol/{client,server}/stdio.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

Also moves getDefaultEnvironment, DEFAULT_INHERITED_ENV_VARS, and the StdioServerParameters type to the client ./stdio subpath.

@changeset-bot

changeset-botBot commented Apr 9, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 9b2b5d3

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

This PR includes changesets to release 6 packages
NameType
@modelcontextprotocol/clientMinor
@modelcontextprotocol/serverMinor
@modelcontextprotocol/expressMajor
@modelcontextprotocol/fastifyMajor
@modelcontextprotocol/honoMajor
@modelcontextprotocol/nodeMajor

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-newBot commented Apr 9, 2026

Copy link
Copy Markdown

Open in StackBlitz

@modelcontextprotocol/client

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

@modelcontextprotocol/server

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

@modelcontextprotocol/express

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

@modelcontextprotocol/fastify

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

@modelcontextprotocol/hono

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

@modelcontextprotocol/node

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

commit: 9b2b5d3

@felixweinberger

Copy link
Copy Markdown
ContributorAuthor

@claude review

@claudeclaudeBot 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.

LGTM — clean implementation following the existing _shims conditional-export pattern.

Extended reasoning...

Overview

The PR adds browser and workerd export conditions to the client and server package roots that swap the real stdio transports for throwing stubs. This prevents browser/CF Workers bundlers from pulling in node:child_process, node:stream, and cross-spawn. Changes span 14 files: two new index.browser.ts barrel files, two new stdioStub.ts files, updated package.json exports, build config updates, tests, a changeset, and a new SdkErrorCode.TransportNotSupported enum value in core.

Security Risks

None. The stubs throw a helpful error on use rather than silently failing. No auth, crypto, or permissions code is touched.

Level of Scrutiny

Moderate — package.json exports conditions are critical for correct module resolution across runtimes, but the change is additive (new conditions prepended before the existing default) and non-breaking. A diff of the browser vs. node barrel files shows the only difference is the stdio import source, confirming the mirrors are accurate.

Other Factors

The only reported bug is a cosmetic duplicate comment label in sdkErrors.ts, which has no functional impact. @modelcontextprotocol/core is a private (unpublished) workspace package, so the absence of a core entry in the changeset is correct. Tests verify the browser bundle excludes Node-only imports while still exporting the stub class. The approach follows the existing _shims conditional-export pattern already in the repo.

Comment threadpackages/core/src/errors/sdkErrors.ts
@felixweinberger
felixweinbergerforce-pushed the fweinberger/browser-stdio-conditional-export branch from 5350730 to 95b5ecaCompareApril 9, 2026 16:33
@felixweinbergerfelixweinberger changed the title fix(client,server): browser-conditional export for stdio transportsrefactor(client,server): move stdio transports to ./stdio subpath exportApr 9, 2026
Stdio transports require a process-spawning runtime (Node.js, Bun, Deno).
Exporting them from the package root meant browser and Cloudflare Workers
bundlers pulled in node:child_process, node:stream, and cross-spawn.
Move StdioClientTransport, getDefaultEnvironment, DEFAULT_INHERITED_ENV_VARS,
and StdioServerParameters to @modelcontextprotocol/client/stdio, and
StdioServerTransport to @modelcontextprotocol/server/stdio. The root entry
is now browser-safe; stdio consumers update one import path.
…stdio in quickstart tsconfig paths
- chunkImportsOf now BFS-walks the chunk import graph so the test name
('transitively imported') matches the implementation
- beforeAll builds the package if dist/ is missing (CI test job runs
pnpm test:all without a build step)
- examples/{client,server}-quickstart/tsconfig.json: add /stdio to paths
so tsc --noEmit resolves the new subpath without a prior build
…ntegration
CI test:all runs without building dist; vite-tsconfig-paths needs the
/stdio mapping to resolve to src/stdio.ts.
@felixweinberger

Copy link
Copy Markdown
ContributorAuthor

@claude review

Comment threadpackages/server/src/stdio.ts
…arify subpath comment
Server stdio uses only type-level node:stream imports (erased), so the
NODE_ONLY regex cannot detect re-export regressions. Add an explicit
symbol-absence check and correct the misleading comment in src/stdio.ts.
@felixweinberger

Copy link
Copy Markdown
ContributorAuthor

@claude review

1 similar comment
@felixweinberger

Copy link
Copy Markdown
ContributorAuthor

@claude review

Comment threaddocs/migration-SKILL.md Outdated

@KKonstantinovKKonstantinov left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Few comments, none of them blocking:

  • @types/cross-spawn in packages/server/package.json - could be cleaned-up, not used
  • packages/server/src/server/mcp.examples.ts - still imports from /stdio.js, but this file is synced via pnpm sync:snippets. Users who copy the example will get ./stdio.js, which is a path that only resolves inside the monorepo source tree, not from the published package
  • CLAUDE.md - probably would be good to add an explicit rule to the "Public API Exports" section as the PR establishes a clean invariant. E.g. something like

Exports whose module graph transitively touches unpolyfillable Node builtins (node:child_process, node:net, node:stream's runtime APIs, cross-spawn, etc.) must live at a named subpath export (e.g. ./stdio) and be covered by a barrelClean test in that package. The package root must stay runtime-neutral so browser and Cloudflare Workers bundlers can consume it.

  • Five tsconfigs now mirror thw ./stdio subpath by hand - it adds a drift risk, we could do some small follow-up lint/script that reads packages/{client,server}/package.jsonexports, derives the expected paths entries, and fails if any of the many tsconfigs are missing ones. Not needed for this PR, just an idea as the list grows longer

Comment threadpackages/server/test/server/barrelClean.test.ts Outdated
@felixweinberger
felixweinberger marked this pull request as ready for review April 15, 2026 19:39
@felixweinberger
felixweinberger requested a review from a team as a code ownerApril 15, 2026 19:39
@felixweinbergerfelixweinberger added the v2-bc v2 backwards-compatibility series label Apr 15, 2026
Comment threadpackages/server/src/index.ts Outdated
@felixweinbergerfelixweinberger added this to the v2.0.0-bc milestone Apr 15, 2026
…er index comment, align barrelClean NODE_ONLY regex
felixweinberger added a commit that referenced this pull request Apr 17, 2026
felixweinberger added a commit that referenced this pull request Apr 17, 2026
…ods+zod-schemas, take F1 callTool wording); SKILL.md --ours (preserves #1871/A1a/E-track content)
Comment threadpackages/server/src/index.ts
- README.md: split root-barrel StdioServerTransport import into /stdio subpath
- packages/server/package.json: drop unused @types/cross-spawn devDependency
- CLAUDE.md: document runtime-neutral root barrel rule under Public API Exports
@felixweinberger

Copy link
Copy Markdown
ContributorAuthor

@KKonstantinov thanks. Addressed in 9b2b5d3: dropped @types/cross-spawn from server, added the runtime-neutral root rule to CLAUDE.md, and fixed the README quickstart import while I was at it. Left mcp.examples.ts alone since sync:snippets only copies the #region body into JSDoc (the import line isn't synced) and all sibling .examples.ts files use the same relative pattern. Agree the tsconfig drift check would be a nice follow-up.

@claudeclaudeBot 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.

Thanks — 9b2b5d3 picks up the README quickstart import, and a repo-wide grep now finds zero remaining root-barrel Stdio*Transport imports. Everything I flagged is addressed; deferring to a maintainer for final sign-off on the breaking export-map change.

Extended reasoning...

Overview

This PR moves StdioClientTransport (plus getDefaultEnvironment, DEFAULT_INHERITED_ENV_VARS, StdioServerParameters) and StdioServerTransport from the package root barrels to new ./stdio subpath exports in @modelcontextprotocol/client and @modelcontextprotocol/server. It adds the subpath to both package.jsonexports maps and typesVersions, adds src/stdio.ts entry points to both tsdown configs, drops the unused @types/cross-spawn devDep from the server package, adds barrelClean.test.ts regression guards in both packages, and migrates every doc/example/integration-test callsite (36 files total). A CLAUDE.md rule codifies the runtime-neutral-root-entry constraint.

Since my last review (2026-04-27)

Commit 9b2b5d3 adds README.md to the diff with the split import, which was the last open item I'd raised. A fresh grep for Stdio(Server|Client)Transport.*from\s+['"]@modelcontextprotocol/(client|server)['"] across the repo now returns zero matches, and the moved helper symbols (getDefaultEnvironment, DEFAULT_INHERITED_ENV_VARS, StdioServerParameters) have no remaining root-barrel references outside the new subpath/tests. The changeset prose, server index.ts/stdio.ts comments, migration-SKILL.mdshared/stdio.js row, and server barrelClean symbol-absence assertion from earlier rounds are all in their corrected form.

Security risks

None. This is an export-map/packaging refactor; no auth, crypto, network, or input-handling code paths change. Runtime behavior of the transports themselves is untouched.

Level of scrutiny

Moderate-to-high. The implementation is mechanical, but it is an intentional breaking change to the public API surface of two published packages (removing four root exports and introducing a new subpath), wired through package.jsonexports, typesVersions, and the tsdown entry list. That falls under the repo's "every new export is intentional" / "removing from public API" bar and the v2-bc label, so a maintainer should give the final ack on the API shape rather than a bot.

Other factors

  • KKonstantinov reviewed an earlier revision and his NODE_ONLY-regex alignment request was applied (both barrelClean tests now share the same pattern plus the server symbol-absence check).
  • barrelClean tests give a build-output regression guard; integration tests and all type-checked *.examples.ts exercise the new import path.
  • Migration docs (migration.md, migration-SKILL.md) and the changeset cover the break per repo checklist.

@KKonstantinovKKonstantinov left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM!

@felixweinberger
felixweinberger merged commit 9fc9070 into mainApr 29, 2026
19 checks passed
@felixweinberger
felixweinberger deleted the fweinberger/browser-stdio-conditional-export branch April 29, 2026 13:48
@claudeclaudeBot mentioned this pull request May 5, 2026
6 tasks
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

v2-bcv2 backwards-compatibility series

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@felixweinberger@KKonstantinov
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' refactor(client,server): move stdio transports to ./stdio subpath export by felixweinberger · Pull Request #1871 · modelcontextprotocol/typescript-sdk · GitHub
Skip to content

refactor(client,server): move stdio transports to ./stdio subpath export - #1871

Merged
felixweinberger merged 10 commits into
mainfrom
fweinberger/browser-stdio-conditional-export
Apr 29, 2026
Merged

refactor(client,server): move stdio transports to ./stdio subpath export#1871
felixweinberger merged 10 commits into
mainfrom
fweinberger/browser-stdio-conditional-export

Conversation

@felixweinberger

@felixweinbergerfelixweinberger commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

Move stdio transports to a ./stdio subpath export so the package root no longer pulls in node:child_process, node:stream, or cross-spawn. Fixes bundling for browser and Cloudflare Workers consumers. Node.js, Bun, and Deno consumers update one import path.

// beforeimport{Client,StdioClientTransport}from'@modelcontextprotocol/client';import{McpServer,StdioServerTransport}from'@modelcontextprotocol/server';// afterimport{Client}from'@modelcontextprotocol/client';import{StdioClientTransport}from'@modelcontextprotocol/client/stdio';import{McpServer}from'@modelcontextprotocol/server';import{StdioServerTransport}from'@modelcontextprotocol/server/stdio';

Motivation and Context

The v2 root barrel exports StdioClientTransport, which top-level imports cross-spawn and node:child_process. Any bundler targeting browsers fails. v1 avoided this with deep subpath imports; this restores that boundary as a single ./stdio subpath.

How Has This Been Tested?

barrelClean.test.ts in both packages asserts the built dist/index.mjs (and its transitive chunks) contain zero process-spawning runtime imports, and that dist/stdio.mjs exports the moved symbols.

Breaking Changes

Yes — stdio symbols are no longer exported from the package root. Import from @modelcontextprotocol/{client,server}/stdio.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

Also moves getDefaultEnvironment, DEFAULT_INHERITED_ENV_VARS, and the StdioServerParameters type to the client ./stdio subpath.

@changeset-bot

changeset-botBot commented Apr 9, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 9b2b5d3

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

This PR includes changesets to release 6 packages
NameType
@modelcontextprotocol/clientMinor
@modelcontextprotocol/serverMinor
@modelcontextprotocol/expressMajor
@modelcontextprotocol/fastifyMajor
@modelcontextprotocol/honoMajor
@modelcontextprotocol/nodeMajor

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-newBot commented Apr 9, 2026

Copy link
Copy Markdown

Open in StackBlitz

@modelcontextprotocol/client

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

@modelcontextprotocol/server

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

@modelcontextprotocol/express

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

@modelcontextprotocol/fastify

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

@modelcontextprotocol/hono

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

@modelcontextprotocol/node

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

commit: 9b2b5d3

@felixweinberger

Copy link
Copy Markdown
ContributorAuthor

@claude review

@claudeclaudeBot 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.

LGTM — clean implementation following the existing _shims conditional-export pattern.

Extended reasoning...

Overview

The PR adds browser and workerd export conditions to the client and server package roots that swap the real stdio transports for throwing stubs. This prevents browser/CF Workers bundlers from pulling in node:child_process, node:stream, and cross-spawn. Changes span 14 files: two new index.browser.ts barrel files, two new stdioStub.ts files, updated package.json exports, build config updates, tests, a changeset, and a new SdkErrorCode.TransportNotSupported enum value in core.

Security Risks

None. The stubs throw a helpful error on use rather than silently failing. No auth, crypto, or permissions code is touched.

Level of Scrutiny

Moderate — package.json exports conditions are critical for correct module resolution across runtimes, but the change is additive (new conditions prepended before the existing default) and non-breaking. A diff of the browser vs. node barrel files shows the only difference is the stdio import source, confirming the mirrors are accurate.

Other Factors

The only reported bug is a cosmetic duplicate comment label in sdkErrors.ts, which has no functional impact. @modelcontextprotocol/core is a private (unpublished) workspace package, so the absence of a core entry in the changeset is correct. Tests verify the browser bundle excludes Node-only imports while still exporting the stub class. The approach follows the existing _shims conditional-export pattern already in the repo.

Comment threadpackages/core/src/errors/sdkErrors.ts
@felixweinberger
felixweinbergerforce-pushed the fweinberger/browser-stdio-conditional-export branch from 5350730 to 95b5ecaCompareApril 9, 2026 16:33
@felixweinbergerfelixweinberger changed the title fix(client,server): browser-conditional export for stdio transportsrefactor(client,server): move stdio transports to ./stdio subpath exportApr 9, 2026
Stdio transports require a process-spawning runtime (Node.js, Bun, Deno).
Exporting them from the package root meant browser and Cloudflare Workers
bundlers pulled in node:child_process, node:stream, and cross-spawn.
Move StdioClientTransport, getDefaultEnvironment, DEFAULT_INHERITED_ENV_VARS,
and StdioServerParameters to @modelcontextprotocol/client/stdio, and
StdioServerTransport to @modelcontextprotocol/server/stdio. The root entry
is now browser-safe; stdio consumers update one import path.
…stdio in quickstart tsconfig paths
- chunkImportsOf now BFS-walks the chunk import graph so the test name
('transitively imported') matches the implementation
- beforeAll builds the package if dist/ is missing (CI test job runs
pnpm test:all without a build step)
- examples/{client,server}-quickstart/tsconfig.json: add /stdio to paths
so tsc --noEmit resolves the new subpath without a prior build
…ntegration
CI test:all runs without building dist; vite-tsconfig-paths needs the
/stdio mapping to resolve to src/stdio.ts.
@felixweinberger

Copy link
Copy Markdown
ContributorAuthor

@claude review

Comment threadpackages/server/src/stdio.ts
…arify subpath comment
Server stdio uses only type-level node:stream imports (erased), so the
NODE_ONLY regex cannot detect re-export regressions. Add an explicit
symbol-absence check and correct the misleading comment in src/stdio.ts.
@felixweinberger

Copy link
Copy Markdown
ContributorAuthor

@claude review

1 similar comment
@felixweinberger

Copy link
Copy Markdown
ContributorAuthor

@claude review

Comment threaddocs/migration-SKILL.md Outdated

@KKonstantinovKKonstantinov left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Few comments, none of them blocking:

  • @types/cross-spawn in packages/server/package.json - could be cleaned-up, not used
  • packages/server/src/server/mcp.examples.ts - still imports from /stdio.js, but this file is synced via pnpm sync:snippets. Users who copy the example will get ./stdio.js, which is a path that only resolves inside the monorepo source tree, not from the published package
  • CLAUDE.md - probably would be good to add an explicit rule to the "Public API Exports" section as the PR establishes a clean invariant. E.g. something like

Exports whose module graph transitively touches unpolyfillable Node builtins (node:child_process, node:net, node:stream's runtime APIs, cross-spawn, etc.) must live at a named subpath export (e.g. ./stdio) and be covered by a barrelClean test in that package. The package root must stay runtime-neutral so browser and Cloudflare Workers bundlers can consume it.

  • Five tsconfigs now mirror thw ./stdio subpath by hand - it adds a drift risk, we could do some small follow-up lint/script that reads packages/{client,server}/package.jsonexports, derives the expected paths entries, and fails if any of the many tsconfigs are missing ones. Not needed for this PR, just an idea as the list grows longer

Comment threadpackages/server/test/server/barrelClean.test.ts Outdated
@felixweinberger
felixweinberger marked this pull request as ready for review April 15, 2026 19:39
@felixweinberger
felixweinberger requested a review from a team as a code ownerApril 15, 2026 19:39
@felixweinbergerfelixweinberger added the v2-bc v2 backwards-compatibility series label Apr 15, 2026
Comment threadpackages/server/src/index.ts Outdated
@felixweinbergerfelixweinberger added this to the v2.0.0-bc milestone Apr 15, 2026
…er index comment, align barrelClean NODE_ONLY regex
felixweinberger added a commit that referenced this pull request Apr 17, 2026
felixweinberger added a commit that referenced this pull request Apr 17, 2026
…ods+zod-schemas, take F1 callTool wording); SKILL.md --ours (preserves #1871/A1a/E-track content)
Comment threadpackages/server/src/index.ts
- README.md: split root-barrel StdioServerTransport import into /stdio subpath
- packages/server/package.json: drop unused @types/cross-spawn devDependency
- CLAUDE.md: document runtime-neutral root barrel rule under Public API Exports
@felixweinberger

Copy link
Copy Markdown
ContributorAuthor

@KKonstantinov thanks. Addressed in 9b2b5d3: dropped @types/cross-spawn from server, added the runtime-neutral root rule to CLAUDE.md, and fixed the README quickstart import while I was at it. Left mcp.examples.ts alone since sync:snippets only copies the #region body into JSDoc (the import line isn't synced) and all sibling .examples.ts files use the same relative pattern. Agree the tsconfig drift check would be a nice follow-up.

@claudeclaudeBot 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.

Thanks — 9b2b5d3 picks up the README quickstart import, and a repo-wide grep now finds zero remaining root-barrel Stdio*Transport imports. Everything I flagged is addressed; deferring to a maintainer for final sign-off on the breaking export-map change.

Extended reasoning...

Overview

This PR moves StdioClientTransport (plus getDefaultEnvironment, DEFAULT_INHERITED_ENV_VARS, StdioServerParameters) and StdioServerTransport from the package root barrels to new ./stdio subpath exports in @modelcontextprotocol/client and @modelcontextprotocol/server. It adds the subpath to both package.jsonexports maps and typesVersions, adds src/stdio.ts entry points to both tsdown configs, drops the unused @types/cross-spawn devDep from the server package, adds barrelClean.test.ts regression guards in both packages, and migrates every doc/example/integration-test callsite (36 files total). A CLAUDE.md rule codifies the runtime-neutral-root-entry constraint.

Since my last review (2026-04-27)

Commit 9b2b5d3 adds README.md to the diff with the split import, which was the last open item I'd raised. A fresh grep for Stdio(Server|Client)Transport.*from\s+['"]@modelcontextprotocol/(client|server)['"] across the repo now returns zero matches, and the moved helper symbols (getDefaultEnvironment, DEFAULT_INHERITED_ENV_VARS, StdioServerParameters) have no remaining root-barrel references outside the new subpath/tests. The changeset prose, server index.ts/stdio.ts comments, migration-SKILL.mdshared/stdio.js row, and server barrelClean symbol-absence assertion from earlier rounds are all in their corrected form.

Security risks

None. This is an export-map/packaging refactor; no auth, crypto, network, or input-handling code paths change. Runtime behavior of the transports themselves is untouched.

Level of scrutiny

Moderate-to-high. The implementation is mechanical, but it is an intentional breaking change to the public API surface of two published packages (removing four root exports and introducing a new subpath), wired through package.jsonexports, typesVersions, and the tsdown entry list. That falls under the repo's "every new export is intentional" / "removing from public API" bar and the v2-bc label, so a maintainer should give the final ack on the API shape rather than a bot.

Other factors

  • KKonstantinov reviewed an earlier revision and his NODE_ONLY-regex alignment request was applied (both barrelClean tests now share the same pattern plus the server symbol-absence check).
  • barrelClean tests give a build-output regression guard; integration tests and all type-checked *.examples.ts exercise the new import path.
  • Migration docs (migration.md, migration-SKILL.md) and the changeset cover the break per repo checklist.

@KKonstantinovKKonstantinov left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM!

@felixweinberger
felixweinberger merged commit 9fc9070 into mainApr 29, 2026
19 checks passed
@felixweinberger
felixweinberger deleted the fweinberger/browser-stdio-conditional-export branch April 29, 2026 13:48
@claudeclaudeBot mentioned this pull request May 5, 2026
6 tasks
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

v2-bcv2 backwards-compatibility series

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@felixweinberger@KKonstantinov
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' refactor(client,server): move stdio transports to ./stdio subpath export by felixweinberger · Pull Request #1871 · modelcontextprotocol/typescript-sdk · GitHub
Skip to content

refactor(client,server): move stdio transports to ./stdio subpath export - #1871

Merged
felixweinberger merged 10 commits into
mainfrom
fweinberger/browser-stdio-conditional-export
Apr 29, 2026
Merged

refactor(client,server): move stdio transports to ./stdio subpath export#1871
felixweinberger merged 10 commits into
mainfrom
fweinberger/browser-stdio-conditional-export

Conversation

@felixweinberger

@felixweinbergerfelixweinberger commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

Move stdio transports to a ./stdio subpath export so the package root no longer pulls in node:child_process, node:stream, or cross-spawn. Fixes bundling for browser and Cloudflare Workers consumers. Node.js, Bun, and Deno consumers update one import path.

// beforeimport{Client,StdioClientTransport}from'@modelcontextprotocol/client';import{McpServer,StdioServerTransport}from'@modelcontextprotocol/server';// afterimport{Client}from'@modelcontextprotocol/client';import{StdioClientTransport}from'@modelcontextprotocol/client/stdio';import{McpServer}from'@modelcontextprotocol/server';import{StdioServerTransport}from'@modelcontextprotocol/server/stdio';

Motivation and Context

The v2 root barrel exports StdioClientTransport, which top-level imports cross-spawn and node:child_process. Any bundler targeting browsers fails. v1 avoided this with deep subpath imports; this restores that boundary as a single ./stdio subpath.

How Has This Been Tested?

barrelClean.test.ts in both packages asserts the built dist/index.mjs (and its transitive chunks) contain zero process-spawning runtime imports, and that dist/stdio.mjs exports the moved symbols.

Breaking Changes

Yes — stdio symbols are no longer exported from the package root. Import from @modelcontextprotocol/{client,server}/stdio.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

Also moves getDefaultEnvironment, DEFAULT_INHERITED_ENV_VARS, and the StdioServerParameters type to the client ./stdio subpath.

@changeset-bot

changeset-botBot commented Apr 9, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 9b2b5d3

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

This PR includes changesets to release 6 packages
NameType
@modelcontextprotocol/clientMinor
@modelcontextprotocol/serverMinor
@modelcontextprotocol/expressMajor
@modelcontextprotocol/fastifyMajor
@modelcontextprotocol/honoMajor
@modelcontextprotocol/nodeMajor

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-newBot commented Apr 9, 2026

Copy link
Copy Markdown

Open in StackBlitz

@modelcontextprotocol/client

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

@modelcontextprotocol/server

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

@modelcontextprotocol/express

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

@modelcontextprotocol/fastify

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

@modelcontextprotocol/hono

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

@modelcontextprotocol/node

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

commit: 9b2b5d3

@felixweinberger

Copy link
Copy Markdown
ContributorAuthor

@claude review

@claudeclaudeBot 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.

LGTM — clean implementation following the existing _shims conditional-export pattern.

Extended reasoning...

Overview

The PR adds browser and workerd export conditions to the client and server package roots that swap the real stdio transports for throwing stubs. This prevents browser/CF Workers bundlers from pulling in node:child_process, node:stream, and cross-spawn. Changes span 14 files: two new index.browser.ts barrel files, two new stdioStub.ts files, updated package.json exports, build config updates, tests, a changeset, and a new SdkErrorCode.TransportNotSupported enum value in core.

Security Risks

None. The stubs throw a helpful error on use rather than silently failing. No auth, crypto, or permissions code is touched.

Level of Scrutiny

Moderate — package.json exports conditions are critical for correct module resolution across runtimes, but the change is additive (new conditions prepended before the existing default) and non-breaking. A diff of the browser vs. node barrel files shows the only difference is the stdio import source, confirming the mirrors are accurate.

Other Factors

The only reported bug is a cosmetic duplicate comment label in sdkErrors.ts, which has no functional impact. @modelcontextprotocol/core is a private (unpublished) workspace package, so the absence of a core entry in the changeset is correct. Tests verify the browser bundle excludes Node-only imports while still exporting the stub class. The approach follows the existing _shims conditional-export pattern already in the repo.

Comment threadpackages/core/src/errors/sdkErrors.ts
@felixweinberger
felixweinbergerforce-pushed the fweinberger/browser-stdio-conditional-export branch from 5350730 to 95b5ecaCompareApril 9, 2026 16:33
@felixweinbergerfelixweinberger changed the title fix(client,server): browser-conditional export for stdio transportsrefactor(client,server): move stdio transports to ./stdio subpath exportApr 9, 2026
Stdio transports require a process-spawning runtime (Node.js, Bun, Deno).
Exporting them from the package root meant browser and Cloudflare Workers
bundlers pulled in node:child_process, node:stream, and cross-spawn.
Move StdioClientTransport, getDefaultEnvironment, DEFAULT_INHERITED_ENV_VARS,
and StdioServerParameters to @modelcontextprotocol/client/stdio, and
StdioServerTransport to @modelcontextprotocol/server/stdio. The root entry
is now browser-safe; stdio consumers update one import path.
…stdio in quickstart tsconfig paths
- chunkImportsOf now BFS-walks the chunk import graph so the test name
('transitively imported') matches the implementation
- beforeAll builds the package if dist/ is missing (CI test job runs
pnpm test:all without a build step)
- examples/{client,server}-quickstart/tsconfig.json: add /stdio to paths
so tsc --noEmit resolves the new subpath without a prior build
…ntegration
CI test:all runs without building dist; vite-tsconfig-paths needs the
/stdio mapping to resolve to src/stdio.ts.
@felixweinberger

Copy link
Copy Markdown
ContributorAuthor

@claude review

Comment threadpackages/server/src/stdio.ts
…arify subpath comment
Server stdio uses only type-level node:stream imports (erased), so the
NODE_ONLY regex cannot detect re-export regressions. Add an explicit
symbol-absence check and correct the misleading comment in src/stdio.ts.
@felixweinberger

Copy link
Copy Markdown
ContributorAuthor

@claude review

1 similar comment
@felixweinberger

Copy link
Copy Markdown
ContributorAuthor

@claude review

Comment threaddocs/migration-SKILL.md Outdated

@KKonstantinovKKonstantinov left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Few comments, none of them blocking:

  • @types/cross-spawn in packages/server/package.json - could be cleaned-up, not used
  • packages/server/src/server/mcp.examples.ts - still imports from /stdio.js, but this file is synced via pnpm sync:snippets. Users who copy the example will get ./stdio.js, which is a path that only resolves inside the monorepo source tree, not from the published package
  • CLAUDE.md - probably would be good to add an explicit rule to the "Public API Exports" section as the PR establishes a clean invariant. E.g. something like

Exports whose module graph transitively touches unpolyfillable Node builtins (node:child_process, node:net, node:stream's runtime APIs, cross-spawn, etc.) must live at a named subpath export (e.g. ./stdio) and be covered by a barrelClean test in that package. The package root must stay runtime-neutral so browser and Cloudflare Workers bundlers can consume it.

  • Five tsconfigs now mirror thw ./stdio subpath by hand - it adds a drift risk, we could do some small follow-up lint/script that reads packages/{client,server}/package.jsonexports, derives the expected paths entries, and fails if any of the many tsconfigs are missing ones. Not needed for this PR, just an idea as the list grows longer

Comment threadpackages/server/test/server/barrelClean.test.ts Outdated
@felixweinberger
felixweinberger marked this pull request as ready for review April 15, 2026 19:39
@felixweinberger
felixweinberger requested a review from a team as a code ownerApril 15, 2026 19:39
@felixweinbergerfelixweinberger added the v2-bc v2 backwards-compatibility series label Apr 15, 2026
Comment threadpackages/server/src/index.ts Outdated
@felixweinbergerfelixweinberger added this to the v2.0.0-bc milestone Apr 15, 2026
…er index comment, align barrelClean NODE_ONLY regex
felixweinberger added a commit that referenced this pull request Apr 17, 2026
felixweinberger added a commit that referenced this pull request Apr 17, 2026
…ods+zod-schemas, take F1 callTool wording); SKILL.md --ours (preserves #1871/A1a/E-track content)
Comment threadpackages/server/src/index.ts
- README.md: split root-barrel StdioServerTransport import into /stdio subpath
- packages/server/package.json: drop unused @types/cross-spawn devDependency
- CLAUDE.md: document runtime-neutral root barrel rule under Public API Exports
@felixweinberger

Copy link
Copy Markdown
ContributorAuthor

@KKonstantinov thanks. Addressed in 9b2b5d3: dropped @types/cross-spawn from server, added the runtime-neutral root rule to CLAUDE.md, and fixed the README quickstart import while I was at it. Left mcp.examples.ts alone since sync:snippets only copies the #region body into JSDoc (the import line isn't synced) and all sibling .examples.ts files use the same relative pattern. Agree the tsconfig drift check would be a nice follow-up.

@claudeclaudeBot 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.

Thanks — 9b2b5d3 picks up the README quickstart import, and a repo-wide grep now finds zero remaining root-barrel Stdio*Transport imports. Everything I flagged is addressed; deferring to a maintainer for final sign-off on the breaking export-map change.

Extended reasoning...

Overview

This PR moves StdioClientTransport (plus getDefaultEnvironment, DEFAULT_INHERITED_ENV_VARS, StdioServerParameters) and StdioServerTransport from the package root barrels to new ./stdio subpath exports in @modelcontextprotocol/client and @modelcontextprotocol/server. It adds the subpath to both package.jsonexports maps and typesVersions, adds src/stdio.ts entry points to both tsdown configs, drops the unused @types/cross-spawn devDep from the server package, adds barrelClean.test.ts regression guards in both packages, and migrates every doc/example/integration-test callsite (36 files total). A CLAUDE.md rule codifies the runtime-neutral-root-entry constraint.

Since my last review (2026-04-27)

Commit 9b2b5d3 adds README.md to the diff with the split import, which was the last open item I'd raised. A fresh grep for Stdio(Server|Client)Transport.*from\s+['"]@modelcontextprotocol/(client|server)['"] across the repo now returns zero matches, and the moved helper symbols (getDefaultEnvironment, DEFAULT_INHERITED_ENV_VARS, StdioServerParameters) have no remaining root-barrel references outside the new subpath/tests. The changeset prose, server index.ts/stdio.ts comments, migration-SKILL.mdshared/stdio.js row, and server barrelClean symbol-absence assertion from earlier rounds are all in their corrected form.

Security risks

None. This is an export-map/packaging refactor; no auth, crypto, network, or input-handling code paths change. Runtime behavior of the transports themselves is untouched.

Level of scrutiny

Moderate-to-high. The implementation is mechanical, but it is an intentional breaking change to the public API surface of two published packages (removing four root exports and introducing a new subpath), wired through package.jsonexports, typesVersions, and the tsdown entry list. That falls under the repo's "every new export is intentional" / "removing from public API" bar and the v2-bc label, so a maintainer should give the final ack on the API shape rather than a bot.

Other factors

  • KKonstantinov reviewed an earlier revision and his NODE_ONLY-regex alignment request was applied (both barrelClean tests now share the same pattern plus the server symbol-absence check).
  • barrelClean tests give a build-output regression guard; integration tests and all type-checked *.examples.ts exercise the new import path.
  • Migration docs (migration.md, migration-SKILL.md) and the changeset cover the break per repo checklist.

@KKonstantinovKKonstantinov left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM!

@felixweinberger
felixweinberger merged commit 9fc9070 into mainApr 29, 2026
19 checks passed
@felixweinberger
felixweinberger deleted the fweinberger/browser-stdio-conditional-export branch April 29, 2026 13:48
@claudeclaudeBot mentioned this pull request May 5, 2026
6 tasks
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

v2-bcv2 backwards-compatibility series

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@felixweinberger@KKonstantinov
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); refactor(client,server): move stdio transports to ./stdio subpath export by felixweinberger · Pull Request #1871 · modelcontextprotocol/typescript-sdk · GitHub
Skip to content

refactor(client,server): move stdio transports to ./stdio subpath export - #1871

Merged
felixweinberger merged 10 commits into
mainfrom
fweinberger/browser-stdio-conditional-export
Apr 29, 2026
Merged

refactor(client,server): move stdio transports to ./stdio subpath export#1871
felixweinberger merged 10 commits into
mainfrom
fweinberger/browser-stdio-conditional-export

Conversation

@felixweinberger

@felixweinbergerfelixweinberger commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

Move stdio transports to a ./stdio subpath export so the package root no longer pulls in node:child_process, node:stream, or cross-spawn. Fixes bundling for browser and Cloudflare Workers consumers. Node.js, Bun, and Deno consumers update one import path.

// beforeimport{Client,StdioClientTransport}from'@modelcontextprotocol/client';import{McpServer,StdioServerTransport}from'@modelcontextprotocol/server';// afterimport{Client}from'@modelcontextprotocol/client';import{StdioClientTransport}from'@modelcontextprotocol/client/stdio';import{McpServer}from'@modelcontextprotocol/server';import{StdioServerTransport}from'@modelcontextprotocol/server/stdio';

Motivation and Context

The v2 root barrel exports StdioClientTransport, which top-level imports cross-spawn and node:child_process. Any bundler targeting browsers fails. v1 avoided this with deep subpath imports; this restores that boundary as a single ./stdio subpath.

How Has This Been Tested?

barrelClean.test.ts in both packages asserts the built dist/index.mjs (and its transitive chunks) contain zero process-spawning runtime imports, and that dist/stdio.mjs exports the moved symbols.

Breaking Changes

Yes — stdio symbols are no longer exported from the package root. Import from @modelcontextprotocol/{client,server}/stdio.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

Also moves getDefaultEnvironment, DEFAULT_INHERITED_ENV_VARS, and the StdioServerParameters type to the client ./stdio subpath.

@changeset-bot

changeset-botBot commented Apr 9, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 9b2b5d3

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

This PR includes changesets to release 6 packages
NameType
@modelcontextprotocol/clientMinor
@modelcontextprotocol/serverMinor
@modelcontextprotocol/expressMajor
@modelcontextprotocol/fastifyMajor
@modelcontextprotocol/honoMajor
@modelcontextprotocol/nodeMajor

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-newBot commented Apr 9, 2026

Copy link
Copy Markdown

Open in StackBlitz

@modelcontextprotocol/client

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

@modelcontextprotocol/server

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

@modelcontextprotocol/express

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

@modelcontextprotocol/fastify

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

@modelcontextprotocol/hono

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

@modelcontextprotocol/node

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

commit: 9b2b5d3

@felixweinberger

Copy link
Copy Markdown
ContributorAuthor

@claude review

@claudeclaudeBot 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.

LGTM — clean implementation following the existing _shims conditional-export pattern.

Extended reasoning...

Overview

The PR adds browser and workerd export conditions to the client and server package roots that swap the real stdio transports for throwing stubs. This prevents browser/CF Workers bundlers from pulling in node:child_process, node:stream, and cross-spawn. Changes span 14 files: two new index.browser.ts barrel files, two new stdioStub.ts files, updated package.json exports, build config updates, tests, a changeset, and a new SdkErrorCode.TransportNotSupported enum value in core.

Security Risks

None. The stubs throw a helpful error on use rather than silently failing. No auth, crypto, or permissions code is touched.

Level of Scrutiny

Moderate — package.json exports conditions are critical for correct module resolution across runtimes, but the change is additive (new conditions prepended before the existing default) and non-breaking. A diff of the browser vs. node barrel files shows the only difference is the stdio import source, confirming the mirrors are accurate.

Other Factors

The only reported bug is a cosmetic duplicate comment label in sdkErrors.ts, which has no functional impact. @modelcontextprotocol/core is a private (unpublished) workspace package, so the absence of a core entry in the changeset is correct. Tests verify the browser bundle excludes Node-only imports while still exporting the stub class. The approach follows the existing _shims conditional-export pattern already in the repo.

Comment threadpackages/core/src/errors/sdkErrors.ts
@felixweinberger
felixweinbergerforce-pushed the fweinberger/browser-stdio-conditional-export branch from 5350730 to 95b5ecaCompareApril 9, 2026 16:33
@felixweinbergerfelixweinberger changed the title fix(client,server): browser-conditional export for stdio transportsrefactor(client,server): move stdio transports to ./stdio subpath exportApr 9, 2026
Stdio transports require a process-spawning runtime (Node.js, Bun, Deno).
Exporting them from the package root meant browser and Cloudflare Workers
bundlers pulled in node:child_process, node:stream, and cross-spawn.
Move StdioClientTransport, getDefaultEnvironment, DEFAULT_INHERITED_ENV_VARS,
and StdioServerParameters to @modelcontextprotocol/client/stdio, and
StdioServerTransport to @modelcontextprotocol/server/stdio. The root entry
is now browser-safe; stdio consumers update one import path.
…stdio in quickstart tsconfig paths
- chunkImportsOf now BFS-walks the chunk import graph so the test name
('transitively imported') matches the implementation
- beforeAll builds the package if dist/ is missing (CI test job runs
pnpm test:all without a build step)
- examples/{client,server}-quickstart/tsconfig.json: add /stdio to paths
so tsc --noEmit resolves the new subpath without a prior build
…ntegration
CI test:all runs without building dist; vite-tsconfig-paths needs the
/stdio mapping to resolve to src/stdio.ts.
@felixweinberger

Copy link
Copy Markdown
ContributorAuthor

@claude review

Comment threadpackages/server/src/stdio.ts
…arify subpath comment
Server stdio uses only type-level node:stream imports (erased), so the
NODE_ONLY regex cannot detect re-export regressions. Add an explicit
symbol-absence check and correct the misleading comment in src/stdio.ts.
@felixweinberger

Copy link
Copy Markdown
ContributorAuthor

@claude review

1 similar comment
@felixweinberger

Copy link
Copy Markdown
ContributorAuthor

@claude review

Comment threaddocs/migration-SKILL.md Outdated

@KKonstantinovKKonstantinov left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Few comments, none of them blocking:

  • @types/cross-spawn in packages/server/package.json - could be cleaned-up, not used
  • packages/server/src/server/mcp.examples.ts - still imports from /stdio.js, but this file is synced via pnpm sync:snippets. Users who copy the example will get ./stdio.js, which is a path that only resolves inside the monorepo source tree, not from the published package
  • CLAUDE.md - probably would be good to add an explicit rule to the "Public API Exports" section as the PR establishes a clean invariant. E.g. something like

Exports whose module graph transitively touches unpolyfillable Node builtins (node:child_process, node:net, node:stream's runtime APIs, cross-spawn, etc.) must live at a named subpath export (e.g. ./stdio) and be covered by a barrelClean test in that package. The package root must stay runtime-neutral so browser and Cloudflare Workers bundlers can consume it.

  • Five tsconfigs now mirror thw ./stdio subpath by hand - it adds a drift risk, we could do some small follow-up lint/script that reads packages/{client,server}/package.jsonexports, derives the expected paths entries, and fails if any of the many tsconfigs are missing ones. Not needed for this PR, just an idea as the list grows longer

Comment threadpackages/server/test/server/barrelClean.test.ts Outdated
@felixweinberger
felixweinberger marked this pull request as ready for review April 15, 2026 19:39
@felixweinberger
felixweinberger requested a review from a team as a code ownerApril 15, 2026 19:39
@felixweinbergerfelixweinberger added the v2-bc v2 backwards-compatibility series label Apr 15, 2026
Comment threadpackages/server/src/index.ts Outdated
@felixweinbergerfelixweinberger added this to the v2.0.0-bc milestone Apr 15, 2026
…er index comment, align barrelClean NODE_ONLY regex
felixweinberger added a commit that referenced this pull request Apr 17, 2026
felixweinberger added a commit that referenced this pull request Apr 17, 2026
…ods+zod-schemas, take F1 callTool wording); SKILL.md --ours (preserves #1871/A1a/E-track content)
Comment threadpackages/server/src/index.ts
- README.md: split root-barrel StdioServerTransport import into /stdio subpath
- packages/server/package.json: drop unused @types/cross-spawn devDependency
- CLAUDE.md: document runtime-neutral root barrel rule under Public API Exports
@felixweinberger

Copy link
Copy Markdown
ContributorAuthor

@KKonstantinov thanks. Addressed in 9b2b5d3: dropped @types/cross-spawn from server, added the runtime-neutral root rule to CLAUDE.md, and fixed the README quickstart import while I was at it. Left mcp.examples.ts alone since sync:snippets only copies the #region body into JSDoc (the import line isn't synced) and all sibling .examples.ts files use the same relative pattern. Agree the tsconfig drift check would be a nice follow-up.

@claudeclaudeBot 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.

Thanks — 9b2b5d3 picks up the README quickstart import, and a repo-wide grep now finds zero remaining root-barrel Stdio*Transport imports. Everything I flagged is addressed; deferring to a maintainer for final sign-off on the breaking export-map change.

Extended reasoning...

Overview

This PR moves StdioClientTransport (plus getDefaultEnvironment, DEFAULT_INHERITED_ENV_VARS, StdioServerParameters) and StdioServerTransport from the package root barrels to new ./stdio subpath exports in @modelcontextprotocol/client and @modelcontextprotocol/server. It adds the subpath to both package.jsonexports maps and typesVersions, adds src/stdio.ts entry points to both tsdown configs, drops the unused @types/cross-spawn devDep from the server package, adds barrelClean.test.ts regression guards in both packages, and migrates every doc/example/integration-test callsite (36 files total). A CLAUDE.md rule codifies the runtime-neutral-root-entry constraint.

Since my last review (2026-04-27)

Commit 9b2b5d3 adds README.md to the diff with the split import, which was the last open item I'd raised. A fresh grep for Stdio(Server|Client)Transport.*from\s+['"]@modelcontextprotocol/(client|server)['"] across the repo now returns zero matches, and the moved helper symbols (getDefaultEnvironment, DEFAULT_INHERITED_ENV_VARS, StdioServerParameters) have no remaining root-barrel references outside the new subpath/tests. The changeset prose, server index.ts/stdio.ts comments, migration-SKILL.mdshared/stdio.js row, and server barrelClean symbol-absence assertion from earlier rounds are all in their corrected form.

Security risks

None. This is an export-map/packaging refactor; no auth, crypto, network, or input-handling code paths change. Runtime behavior of the transports themselves is untouched.

Level of scrutiny

Moderate-to-high. The implementation is mechanical, but it is an intentional breaking change to the public API surface of two published packages (removing four root exports and introducing a new subpath), wired through package.jsonexports, typesVersions, and the tsdown entry list. That falls under the repo's "every new export is intentional" / "removing from public API" bar and the v2-bc label, so a maintainer should give the final ack on the API shape rather than a bot.

Other factors

  • KKonstantinov reviewed an earlier revision and his NODE_ONLY-regex alignment request was applied (both barrelClean tests now share the same pattern plus the server symbol-absence check).
  • barrelClean tests give a build-output regression guard; integration tests and all type-checked *.examples.ts exercise the new import path.
  • Migration docs (migration.md, migration-SKILL.md) and the changeset cover the break per repo checklist.

@KKonstantinovKKonstantinov left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM!

@felixweinberger
felixweinberger merged commit 9fc9070 into mainApr 29, 2026
19 checks passed
@felixweinberger
felixweinberger deleted the fweinberger/browser-stdio-conditional-export branch April 29, 2026 13:48
@claudeclaudeBot mentioned this pull request May 5, 2026
6 tasks
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

v2-bcv2 backwards-compatibility series

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@felixweinberger@KKonstantinov