Skip to content

Commit 5350730

Browse files
fix(client,server): browser-conditional export for stdio transports
Adds browser/workerd conditions to the package root export that resolve to a build whose StdioClientTransport / StdioServerTransport are throwing stubs, so browser and Cloudflare Workers bundlers no longer pull node:child_process, node:stream, or cross-spawn. Node.js, Bun, and Deno continue to resolve the default condition and get the real implementation. Adds SdkErrorCode.TransportNotSupported for the stub error.
1 parent 7ba58da commit 5350730

File tree

14 files changed

+328
-4
lines changed

14 files changed

+328
-4
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@modelcontextprotocol/client': patch
3+
'@modelcontextprotocol/server': patch
4+
---
5+
6+
Add `browser`/`workerd` export conditions for the package root that swap the stdio transport for a throwing stub, so browser and Cloudflare Workers bundlers no longer pull `node:child_process`, `node:stream`, or `cross-spawn`. Node.js, Bun, and Deno continue to resolve the real `StdioClientTransport`/`StdioServerTransport`.

packages/client/package.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,14 @@
2121
],
2222
"exports": {
2323
".": {
24+
"workerd": {
25+
"types": "./dist/index.browser.d.mts",
26+
"import": "./dist/index.browser.mjs"
27+
},
28+
"browser": {
29+
"types": "./dist/index.browser.d.mts",
30+
"import": "./dist/index.browser.mjs"
31+
},
2432
"types": "./dist/index.d.mts",
2533
"import": "./dist/index.mjs"
2634
},
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// Public API for @modelcontextprotocol/client (browser / Cloudflare Workers entry).
2+
//
3+
// Mirrors ./index.ts exactly, except stdio symbols come from ./stdioStub.js so
4+
// the bundle does not pull `node:child_process`, `node:stream`, or `cross-spawn`.
5+
// Selected via the `browser` and `workerd` export conditions.
6+
7+
export type {
8+
AddClientAuthentication,
9+
AuthProvider,
10+
AuthResult,
11+
ClientAuthMethod,
12+
OAuthClientProvider,
13+
OAuthDiscoveryState,
14+
OAuthServerInfo
15+
} from './client/auth.js';
16+
export {
17+
auth,
18+
buildDiscoveryUrls,
19+
discoverAuthorizationServerMetadata,
20+
discoverOAuthMetadata,
21+
discoverOAuthProtectedResourceMetadata,
22+
discoverOAuthServerInfo,
23+
exchangeAuthorization,
24+
extractResourceMetadataUrl,
25+
extractWWWAuthenticateParams,
26+
fetchToken,
27+
isHttpsUrl,
28+
parseErrorResponse,
29+
prepareAuthorizationCodeRequest,
30+
refreshAuthorization,
31+
registerClient,
32+
selectClientAuthMethod,
33+
selectResourceURL,
34+
startAuthorization,
35+
UnauthorizedError
36+
} from './client/auth.js';
37+
export type {
38+
AssertionCallback,
39+
ClientCredentialsProviderOptions,
40+
CrossAppAccessContext,
41+
CrossAppAccessProviderOptions,
42+
PrivateKeyJwtProviderOptions,
43+
StaticPrivateKeyJwtProviderOptions
44+
} from './client/authExtensions.js';
45+
export {
46+
ClientCredentialsProvider,
47+
createPrivateKeyJwtAuth,
48+
CrossAppAccessProvider,
49+
PrivateKeyJwtProvider,
50+
StaticPrivateKeyJwtProvider
51+
} from './client/authExtensions.js';
52+
export type { ClientOptions } from './client/client.js';
53+
export { Client } from './client/client.js';
54+
export { getSupportedElicitationModes } from './client/client.js';
55+
export type { DiscoverAndRequestJwtAuthGrantOptions, JwtAuthGrantResult, RequestJwtAuthGrantOptions } from './client/crossAppAccess.js';
56+
export { discoverAndRequestJwtAuthGrant, exchangeJwtAuthGrant, requestJwtAuthorizationGrant } from './client/crossAppAccess.js';
57+
export type { LoggingOptions, Middleware, RequestLogger } from './client/middleware.js';
58+
export { applyMiddlewares, createMiddleware, withLogging, withOAuth } from './client/middleware.js';
59+
export type { SSEClientTransportOptions } from './client/sse.js';
60+
export { SSEClientTransport, SseError } from './client/sse.js';
61+
export type {
62+
ReconnectionScheduler,
63+
StartSSEOptions,
64+
StreamableHTTPClientTransportOptions,
65+
StreamableHTTPReconnectionOptions
66+
} from './client/streamableHttp.js';
67+
export { StreamableHTTPClientTransport } from './client/streamableHttp.js';
68+
69+
// stdio: stubbed for browser / Cloudflare Workers — throws on use
70+
export type { StdioServerParameters } from './stdioStub.js';
71+
export { DEFAULT_INHERITED_ENV_VARS, getDefaultEnvironment, StdioClientTransport } from './stdioStub.js';
72+
73+
// experimental exports
74+
export { ExperimentalClientTasks } from './experimental/tasks/client.js';
75+
76+
// runtime-aware wrapper (shadows core/public's fromJsonSchema with optional validator)
77+
export { fromJsonSchema } from './fromJsonSchema.js';
78+
79+
// re-export curated public API from core
80+
export * from '@modelcontextprotocol/core/public';

packages/client/src/stdioStub.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/**
2+
* Browser/Workers stub for {@linkcode StdioClientTransport}.
3+
*
4+
* Selected via the `browser` / `workerd` export conditions on the package root.
5+
* The real implementation in `./client/stdio.ts` imports `node:child_process`,
6+
* `node:stream`, and `cross-spawn`, which are unavailable in browsers and
7+
* Cloudflare Workers. Node.js, Bun, and Deno resolve the default condition and
8+
* get the real implementation.
9+
*/
10+
import type { JSONRPCMessage, Transport } from '@modelcontextprotocol/core';
11+
import { SdkError, SdkErrorCode } from '@modelcontextprotocol/core';
12+
13+
/**
14+
* Parameters for launching an MCP server over stdio.
15+
*
16+
* Browser stub: `stderr` is loosely typed to avoid pulling Node-only types.
17+
*/
18+
export type StdioServerParameters = {
19+
command: string;
20+
args?: string[];
21+
env?: Record<string, string>;
22+
stderr?: unknown;
23+
cwd?: string;
24+
};
25+
26+
const UNSUPPORTED_MESSAGE =
27+
'StdioClientTransport requires a process-spawning runtime (Node.js, Bun, or Deno) and is not available in browsers or Cloudflare Workers. Use StreamableHTTPClientTransport instead.';
28+
29+
function unsupported(): never {
30+
throw new SdkError(SdkErrorCode.TransportNotSupported, UNSUPPORTED_MESSAGE);
31+
}
32+
33+
/** Browser stub: empty in environments without a process-level env. */
34+
export const DEFAULT_INHERITED_ENV_VARS: string[] = [];
35+
36+
/** Browser stub: throws — there is no process environment to inherit. */
37+
export function getDefaultEnvironment(): Record<string, string> {
38+
return unsupported();
39+
}
40+
41+
/**
42+
* Browser stub for `StdioClientTransport`. Throws on construction with a message
43+
* directing the caller to `StreamableHTTPClientTransport`.
44+
*/
45+
export class StdioClientTransport implements Transport {
46+
onclose?: () => void;
47+
onerror?: (error: Error) => void;
48+
onmessage?: (message: JSONRPCMessage) => void;
49+
50+
constructor(_server: StdioServerParameters) {
51+
unsupported();
52+
}
53+
54+
start(): Promise<void> {
55+
return unsupported();
56+
}
57+
send(_message: JSONRPCMessage): Promise<void> {
58+
return unsupported();
59+
}
60+
close(): Promise<void> {
61+
return Promise.resolve();
62+
}
63+
}

packages/client/src/validators/cfWorker.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@
66
* import { CfWorkerJsonSchemaValidator } from '@modelcontextprotocol/client/validators/cf-worker';
77
* ```
88
*/
9-
export { CfWorkerJsonSchemaValidator } from '@modelcontextprotocol/core';
109
export type { CfWorkerSchemaDraft } from '@modelcontextprotocol/core';
10+
export { CfWorkerJsonSchemaValidator } from '@modelcontextprotocol/core';
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { existsSync, readFileSync } from 'node:fs';
2+
import { dirname, join } from 'node:path';
3+
import { fileURLToPath } from 'node:url';
4+
5+
import { describe, expect, test } from 'vitest';
6+
7+
const distDir = join(dirname(fileURLToPath(import.meta.url)), '../../dist');
8+
const browserBundle = join(distDir, 'index.browser.mjs');
9+
const nodeBundle = join(distDir, 'index.mjs');
10+
11+
const built = existsSync(browserBundle) && existsSync(nodeBundle);
12+
13+
describe.skipIf(!built)('browser-conditional bundle (requires `pnpm build`)', () => {
14+
test('index.browser.mjs has no Node-only stdio dependencies', () => {
15+
const src = readFileSync(browserBundle, 'utf8');
16+
expect(src).not.toMatch(/\bchild_process\b/);
17+
expect(src).not.toMatch(/\bcross-spawn\b/);
18+
expect(src).not.toMatch(/\bnode:stream\b/);
19+
});
20+
21+
test('index.browser.mjs still exports StdioClientTransport (stubbed)', () => {
22+
const src = readFileSync(browserBundle, 'utf8');
23+
expect(src).toContain('StdioClientTransport');
24+
});
25+
26+
test('default index.mjs still includes the real stdio transport', () => {
27+
const src = readFileSync(nodeBundle, 'utf8');
28+
expect(src).toContain('cross-spawn');
29+
});
30+
});

packages/client/tsdown.config.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,14 @@ export default defineConfig({
44
failOnWarn: 'ci-only',
55
// 1. Entry Points
66
// Directly matches package.json include/exclude globs
7-
entry: ['src/index.ts', 'src/shimsNode.ts', 'src/shimsWorkerd.ts', 'src/shimsBrowser.ts', 'src/validators/cfWorker.ts'],
7+
entry: [
8+
'src/index.ts',
9+
'src/index.browser.ts',
10+
'src/shimsNode.ts',
11+
'src/shimsWorkerd.ts',
12+
'src/shimsBrowser.ts',
13+
'src/validators/cfWorker.ts'
14+
],
815

916
// 2. Output Configuration
1017
format: ['esm'],

packages/core/src/errors/sdkErrors.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ export enum SdkErrorCode {
2020
CapabilityNotSupported = 'CAPABILITY_NOT_SUPPORTED',
2121

2222
// Transport errors
23+
/** Transport is not available in the current runtime (e.g. stdio in a browser) */
24+
TransportNotSupported = 'TRANSPORT_NOT_SUPPORTED',
2325
/** Request timed out waiting for response */
2426
RequestTimeout = 'REQUEST_TIMEOUT',
2527
/** Connection was closed */

packages/server/package.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,14 @@
2121
],
2222
"exports": {
2323
".": {
24+
"workerd": {
25+
"types": "./dist/index.browser.d.mts",
26+
"import": "./dist/index.browser.mjs"
27+
},
28+
"browser": {
29+
"types": "./dist/index.browser.d.mts",
30+
"import": "./dist/index.browser.mjs"
31+
},
2432
"types": "./dist/index.d.mts",
2533
"import": "./dist/index.mjs"
2634
},
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Public API for @modelcontextprotocol/server (browser / Cloudflare Workers entry).
2+
//
3+
// Mirrors ./index.ts exactly, except stdio symbols come from ./stdioStub.js so
4+
// the bundle does not pull `node:stream`.
5+
// Selected via the `browser` and `workerd` export conditions.
6+
7+
export type { CompletableSchema, CompleteCallback } from './server/completable.js';
8+
export { completable, isCompletable } from './server/completable.js';
9+
export type {
10+
AnyToolHandler,
11+
BaseToolCallback,
12+
CompleteResourceTemplateCallback,
13+
ListResourcesCallback,
14+
PromptCallback,
15+
ReadResourceCallback,
16+
ReadResourceTemplateCallback,
17+
RegisteredPrompt,
18+
RegisteredResource,
19+
RegisteredResourceTemplate,
20+
RegisteredTool,
21+
ResourceMetadata,
22+
ToolCallback
23+
} from './server/mcp.js';
24+
export { McpServer, ResourceTemplate } from './server/mcp.js';
25+
export type { HostHeaderValidationResult } from './server/middleware/hostHeaderValidation.js';
26+
export { hostHeaderValidationResponse, localhostAllowedHostnames, validateHostHeader } from './server/middleware/hostHeaderValidation.js';
27+
export type { ServerOptions } from './server/server.js';
28+
export { Server } from './server/server.js';
29+
export type {
30+
EventId,
31+
EventStore,
32+
HandleRequestOptions,
33+
StreamId,
34+
WebStandardStreamableHTTPServerTransportOptions
35+
} from './server/streamableHttp.js';
36+
export { WebStandardStreamableHTTPServerTransport } from './server/streamableHttp.js';
37+
38+
// stdio: stubbed for browser / Cloudflare Workers — throws on use
39+
export { StdioServerTransport } from './stdioStub.js';
40+
41+
// experimental exports
42+
export type { CreateTaskRequestHandler, TaskRequestHandler, ToolTaskHandler } from './experimental/tasks/interfaces.js';
43+
export { ExperimentalMcpServerTasks } from './experimental/tasks/mcpServer.js';
44+
export { ExperimentalServerTasks } from './experimental/tasks/server.js';
45+
46+
// runtime-aware wrapper (shadows core/public's fromJsonSchema with optional validator)
47+
export { fromJsonSchema } from './fromJsonSchema.js';
48+
49+
// re-export curated public API from core
50+
export * from '@modelcontextprotocol/core/public';

0 commit comments

Comments
 (0)