diff --git a/.changeset/stdio-subpath-export.md b/.changeset/stdio-subpath-export.md new file mode 100644 index 000000000..43cb6f1fe --- /dev/null +++ b/.changeset/stdio-subpath-export.md @@ -0,0 +1,6 @@ +--- +'@modelcontextprotocol/client': minor +'@modelcontextprotocol/server': minor +--- + +Move stdio transports to a `./stdio` subpath export. Import `StdioClientTransport`, `getDefaultEnvironment`, `DEFAULT_INHERITED_ENV_VARS`, and `StdioServerParameters` from `@modelcontextprotocol/client/stdio`, and `StdioServerTransport` from `@modelcontextprotocol/server/stdio`. The package root entries no longer pull in `node:child_process`, `node:stream`, or `cross-spawn`, fixing bundling for browser and Cloudflare Workers targets. Node.js, Bun, and Deno consumers update the import path; runtime behavior is unchanged. diff --git a/docs/client-quickstart.md b/docs/client-quickstart.md index 140afbb5d..71b8a9e12 100644 --- a/docs/client-quickstart.md +++ b/docs/client-quickstart.md @@ -112,7 +112,8 @@ First, let's set up our imports and create the basic client class in `src/index. ```ts source="../examples/client-quickstart/src/index.ts#prelude" import Anthropic from '@anthropic-ai/sdk'; -import { Client, StdioClientTransport } from '@modelcontextprotocol/client'; +import { Client } from '@modelcontextprotocol/client'; +import { StdioClientTransport } from '@modelcontextprotocol/client/stdio'; import readline from 'readline/promises'; const ANTHROPIC_MODEL = 'claude-sonnet-4-5'; diff --git a/docs/client.md b/docs/client.md index b5086f531..0946eeec9 100644 --- a/docs/client.md +++ b/docs/client.md @@ -26,9 +26,9 @@ import { SdkError, SdkErrorCode, SSEClientTransport, - StdioClientTransport, StreamableHTTPClientTransport } from '@modelcontextprotocol/client'; +import { StdioClientTransport } from '@modelcontextprotocol/client/stdio'; ``` ## Connecting to a server diff --git a/docs/migration-SKILL.md b/docs/migration-SKILL.md index f581c0cb6..d34b1fa17 100644 --- a/docs/migration-SKILL.md +++ b/docs/migration-SKILL.md @@ -42,7 +42,7 @@ Replace all `@modelcontextprotocol/sdk/...` imports using this table. | `@modelcontextprotocol/sdk/client/auth.js` | `@modelcontextprotocol/client` | | `@modelcontextprotocol/sdk/client/streamableHttp.js` | `@modelcontextprotocol/client` | | `@modelcontextprotocol/sdk/client/sse.js` | `@modelcontextprotocol/client` | -| `@modelcontextprotocol/sdk/client/stdio.js` | `@modelcontextprotocol/client` | +| `@modelcontextprotocol/sdk/client/stdio.js` | `@modelcontextprotocol/client/stdio` | | `@modelcontextprotocol/sdk/client/websocket.js` | REMOVED (use Streamable HTTP or stdio; implement `Transport` for custom needs) | ### Server imports @@ -51,7 +51,7 @@ Replace all `@modelcontextprotocol/sdk/...` imports using this table. | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `@modelcontextprotocol/sdk/server/mcp.js` | `@modelcontextprotocol/server` | | `@modelcontextprotocol/sdk/server/index.js` | `@modelcontextprotocol/server` | -| `@modelcontextprotocol/sdk/server/stdio.js` | `@modelcontextprotocol/server` | +| `@modelcontextprotocol/sdk/server/stdio.js` | `@modelcontextprotocol/server/stdio` | | `@modelcontextprotocol/sdk/server/streamableHttp.js` | `@modelcontextprotocol/node` (class renamed to `NodeStreamableHTTPServerTransport`) OR `@modelcontextprotocol/server` (web-standard `WebStandardStreamableHTTPServerTransport` for Cloudflare Workers, Deno, etc.) | | `@modelcontextprotocol/sdk/server/sse.js` | REMOVED (migrate to Streamable HTTP) | | `@modelcontextprotocol/sdk/server/auth/*` | REMOVED (use external auth library) | @@ -66,7 +66,7 @@ Replace all `@modelcontextprotocol/sdk/...` imports using this table. | `@modelcontextprotocol/sdk/shared/transport.js` | `@modelcontextprotocol/client` or `@modelcontextprotocol/server` | | `@modelcontextprotocol/sdk/shared/uriTemplate.js` | `@modelcontextprotocol/client` or `@modelcontextprotocol/server` | | `@modelcontextprotocol/sdk/shared/auth.js` | `@modelcontextprotocol/client` or `@modelcontextprotocol/server` | -| `@modelcontextprotocol/sdk/shared/stdio.js` | `@modelcontextprotocol/client` or `@modelcontextprotocol/server` | +| `@modelcontextprotocol/sdk/shared/stdio.js` | `@modelcontextprotocol/client/stdio` or `@modelcontextprotocol/server/stdio` | Notes: diff --git a/docs/migration.md b/docs/migration.md index 14fc719db..6e6b739f5 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -50,8 +50,10 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js' **After (v2):** ```typescript -import { Client, StreamableHTTPClientTransport, StdioClientTransport } from '@modelcontextprotocol/client'; -import { McpServer, StdioServerTransport, WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/server'; +import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client'; +import { StdioClientTransport } from '@modelcontextprotocol/client/stdio'; +import { McpServer, WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/server'; +import { StdioServerTransport } from '@modelcontextprotocol/server/stdio'; // Node.js HTTP server transport is in the @modelcontextprotocol/node package import { NodeStreamableHTTPServerTransport } from '@modelcontextprotocol/node'; diff --git a/docs/server-quickstart.md b/docs/server-quickstart.md index 4ee04fd9c..b8d19e7e1 100644 --- a/docs/server-quickstart.md +++ b/docs/server-quickstart.md @@ -127,7 +127,8 @@ Now let's dive into building your server. Add these to the top of your `src/index.ts`: ```ts source="../examples/server-quickstart/src/index.ts#prelude" -import { McpServer, StdioServerTransport } from '@modelcontextprotocol/server'; +import { McpServer } from '@modelcontextprotocol/server'; +import { StdioServerTransport } from '@modelcontextprotocol/server/stdio'; import * as z from 'zod/v4'; const NWS_API_BASE = 'https://api.weather.gov'; diff --git a/docs/server.md b/docs/server.md index 1848a820e..3b173af4e 100644 --- a/docs/server.md +++ b/docs/server.md @@ -22,7 +22,8 @@ import { randomUUID } from 'node:crypto'; import { createMcpExpressApp } from '@modelcontextprotocol/express'; import { NodeStreamableHTTPServerTransport } from '@modelcontextprotocol/node'; import type { CallToolResult, ResourceLink } from '@modelcontextprotocol/server'; -import { completable, McpServer, ResourceTemplate, StdioServerTransport } from '@modelcontextprotocol/server'; +import { completable, McpServer, ResourceTemplate } from '@modelcontextprotocol/server'; +import { StdioServerTransport } from '@modelcontextprotocol/server/stdio'; import * as z from 'zod/v4'; ``` diff --git a/examples/client-quickstart/src/index.ts b/examples/client-quickstart/src/index.ts index 95b6057bd..f677834c0 100644 --- a/examples/client-quickstart/src/index.ts +++ b/examples/client-quickstart/src/index.ts @@ -1,6 +1,7 @@ //#region prelude import Anthropic from '@anthropic-ai/sdk'; -import { Client, StdioClientTransport } from '@modelcontextprotocol/client'; +import { Client } from '@modelcontextprotocol/client'; +import { StdioClientTransport } from '@modelcontextprotocol/client/stdio'; import readline from 'readline/promises'; const ANTHROPIC_MODEL = 'claude-sonnet-4-5'; diff --git a/examples/client/src/clientGuide.examples.ts b/examples/client/src/clientGuide.examples.ts index f07d272db..9704ed8a5 100644 --- a/examples/client/src/clientGuide.examples.ts +++ b/examples/client/src/clientGuide.examples.ts @@ -21,9 +21,9 @@ import { SdkError, SdkErrorCode, SSEClientTransport, - StdioClientTransport, StreamableHTTPClientTransport } from '@modelcontextprotocol/client'; +import { StdioClientTransport } from '@modelcontextprotocol/client/stdio'; //#endregion imports // --------------------------------------------------------------------------- diff --git a/examples/server-quickstart/src/index.ts b/examples/server-quickstart/src/index.ts index dd42901fd..22d459173 100644 --- a/examples/server-quickstart/src/index.ts +++ b/examples/server-quickstart/src/index.ts @@ -1,5 +1,6 @@ //#region prelude -import { McpServer, StdioServerTransport } from '@modelcontextprotocol/server'; +import { McpServer } from '@modelcontextprotocol/server'; +import { StdioServerTransport } from '@modelcontextprotocol/server/stdio'; import * as z from 'zod/v4'; const NWS_API_BASE = 'https://api.weather.gov'; diff --git a/examples/server/src/arktypeExample.ts b/examples/server/src/arktypeExample.ts index ff96a334a..4a470532e 100644 --- a/examples/server/src/arktypeExample.ts +++ b/examples/server/src/arktypeExample.ts @@ -4,7 +4,8 @@ * ArkType implements the Standard Schema spec with built-in JSON Schema conversion. */ -import { McpServer, StdioServerTransport } from '@modelcontextprotocol/server'; +import { McpServer } from '@modelcontextprotocol/server'; +import { StdioServerTransport } from '@modelcontextprotocol/server/stdio'; import { type } from 'arktype'; const server = new McpServer({ diff --git a/examples/server/src/mcpServerOutputSchema.ts b/examples/server/src/mcpServerOutputSchema.ts index ab5435c12..955855c41 100644 --- a/examples/server/src/mcpServerOutputSchema.ts +++ b/examples/server/src/mcpServerOutputSchema.ts @@ -4,7 +4,8 @@ * This demonstrates how to easily create tools with structured output */ -import { McpServer, StdioServerTransport } from '@modelcontextprotocol/server'; +import { McpServer } from '@modelcontextprotocol/server'; +import { StdioServerTransport } from '@modelcontextprotocol/server/stdio'; import * as z from 'zod/v4'; const server = new McpServer({ diff --git a/examples/server/src/serverGuide.examples.ts b/examples/server/src/serverGuide.examples.ts index 70cd002d1..5a4712f83 100644 --- a/examples/server/src/serverGuide.examples.ts +++ b/examples/server/src/serverGuide.examples.ts @@ -13,7 +13,8 @@ import { randomUUID } from 'node:crypto'; import { createMcpExpressApp } from '@modelcontextprotocol/express'; import { NodeStreamableHTTPServerTransport } from '@modelcontextprotocol/node'; import type { CallToolResult, ResourceLink } from '@modelcontextprotocol/server'; -import { completable, McpServer, ResourceTemplate, StdioServerTransport } from '@modelcontextprotocol/server'; +import { completable, McpServer, ResourceTemplate } from '@modelcontextprotocol/server'; +import { StdioServerTransport } from '@modelcontextprotocol/server/stdio'; import * as z from 'zod/v4'; //#endregion imports diff --git a/examples/server/src/toolWithSampleServer.ts b/examples/server/src/toolWithSampleServer.ts index fa41348c6..f6b053cf2 100644 --- a/examples/server/src/toolWithSampleServer.ts +++ b/examples/server/src/toolWithSampleServer.ts @@ -1,6 +1,7 @@ // Run with: pnpm tsx src/toolWithSampleServer.ts -import { McpServer, StdioServerTransport } from '@modelcontextprotocol/server'; +import { McpServer } from '@modelcontextprotocol/server'; +import { StdioServerTransport } from '@modelcontextprotocol/server/stdio'; import * as z from 'zod/v4'; const mcpServer = new McpServer({ diff --git a/examples/server/src/valibotExample.ts b/examples/server/src/valibotExample.ts index 46ab793c0..8d92bf199 100644 --- a/examples/server/src/valibotExample.ts +++ b/examples/server/src/valibotExample.ts @@ -5,7 +5,8 @@ * StandardJSONSchemaV1-compliant schemas. */ -import { McpServer, StdioServerTransport } from '@modelcontextprotocol/server'; +import { McpServer } from '@modelcontextprotocol/server'; +import { StdioServerTransport } from '@modelcontextprotocol/server/stdio'; import { toStandardJsonSchema } from '@valibot/to-json-schema'; import * as v from 'valibot'; diff --git a/packages/client/package.json b/packages/client/package.json index cf9dbff6b..6e6cda18a 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -24,6 +24,10 @@ "types": "./dist/index.d.mts", "import": "./dist/index.mjs" }, + "./stdio": { + "types": "./dist/stdio.d.mts", + "import": "./dist/stdio.mjs" + }, "./validators/cf-worker": { "types": "./dist/validators/cfWorker.d.mts", "import": "./dist/validators/cfWorker.mjs" diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index be30382a7..9260838b1 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -60,8 +60,8 @@ export type { LoggingOptions, Middleware, RequestLogger } from './client/middlew export { applyMiddlewares, createMiddleware, withLogging, withOAuth } from './client/middleware.js'; export type { SSEClientTransportOptions } from './client/sse.js'; export { SSEClientTransport, SseError } from './client/sse.js'; -export type { StdioServerParameters } from './client/stdio.js'; -export { DEFAULT_INHERITED_ENV_VARS, getDefaultEnvironment, StdioClientTransport } from './client/stdio.js'; +// StdioClientTransport, getDefaultEnvironment, DEFAULT_INHERITED_ENV_VARS, StdioServerParameters are exported from +// the './stdio' subpath to keep the root entry free of process-spawning runtime dependencies (child_process, cross-spawn). export type { ReconnectionScheduler, StartSSEOptions, diff --git a/packages/client/src/stdio.ts b/packages/client/src/stdio.ts new file mode 100644 index 000000000..a6ecd1697 --- /dev/null +++ b/packages/client/src/stdio.ts @@ -0,0 +1,8 @@ +// Subpath entry for the stdio client transport. +// +// Exported separately from the root entry so that bundling `@modelcontextprotocol/client` for browser or +// Cloudflare Workers targets does not pull in `node:child_process`, `node:stream`, or `cross-spawn`. Import +// from `@modelcontextprotocol/client/stdio` only in process-spawning runtimes (Node.js, Bun, Deno). + +export type { StdioServerParameters } from './client/stdio.js'; +export { DEFAULT_INHERITED_ENV_VARS, getDefaultEnvironment, StdioClientTransport } from './client/stdio.js'; diff --git a/packages/client/test/client/barrelClean.test.ts b/packages/client/test/client/barrelClean.test.ts new file mode 100644 index 000000000..d26ee3e73 --- /dev/null +++ b/packages/client/test/client/barrelClean.test.ts @@ -0,0 +1,36 @@ +import { readFileSync } from 'node:fs'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import { describe, expect, test } from 'vitest'; + +const distDir = join(dirname(fileURLToPath(import.meta.url)), '../../dist'); +const NODE_ONLY = /\b(child_process|cross-spawn|node:stream|node:child_process)\b/; + +function chunkImportsOf(entryPath: string): string[] { + const src = readFileSync(entryPath, 'utf8'); + return [...src.matchAll(/from\s+["']\.\/(.+?\.mjs)["']/g)].map(m => join(distDir, m[1]!)); +} + +describe('@modelcontextprotocol/client root entry is browser-safe', () => { + test('dist/index.mjs contains no process-spawning runtime imports', () => { + const entry = join(distDir, 'index.mjs'); + expect(readFileSync(entry, 'utf8')).not.toMatch(NODE_ONLY); + }); + + test('chunks transitively imported by dist/index.mjs contain no process-spawning runtime imports', () => { + const entry = join(distDir, 'index.mjs'); + for (const chunk of chunkImportsOf(entry)) { + expect({ chunk, content: readFileSync(chunk, 'utf8') }).not.toEqual( + expect.objectContaining({ content: expect.stringMatching(NODE_ONLY) }) + ); + } + }); + + test('dist/stdio.mjs exists and exports StdioClientTransport', () => { + const stdio = readFileSync(join(distDir, 'stdio.mjs'), 'utf8'); + expect(stdio).toMatch(/\bStdioClientTransport\b/); + expect(stdio).toMatch(/\bgetDefaultEnvironment\b/); + expect(stdio).toMatch(/\bDEFAULT_INHERITED_ENV_VARS\b/); + }); +}); diff --git a/packages/client/tsdown.config.ts b/packages/client/tsdown.config.ts index 6afda65d1..356a16b47 100644 --- a/packages/client/tsdown.config.ts +++ b/packages/client/tsdown.config.ts @@ -4,7 +4,7 @@ export default defineConfig({ failOnWarn: 'ci-only', // 1. Entry Points // Directly matches package.json include/exclude globs - entry: ['src/index.ts', 'src/shimsNode.ts', 'src/shimsWorkerd.ts', 'src/shimsBrowser.ts', 'src/validators/cfWorker.ts'], + entry: ['src/index.ts', 'src/stdio.ts', 'src/shimsNode.ts', 'src/shimsWorkerd.ts', 'src/shimsBrowser.ts', 'src/validators/cfWorker.ts'], // 2. Output Configuration format: ['esm'], diff --git a/packages/server/package.json b/packages/server/package.json index b40135ec9..1786d493a 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -24,6 +24,10 @@ "types": "./dist/index.d.mts", "import": "./dist/index.mjs" }, + "./stdio": { + "types": "./dist/stdio.d.mts", + "import": "./dist/stdio.mjs" + }, "./validators/cf-worker": { "types": "./dist/validators/cfWorker.d.mts", "import": "./dist/validators/cfWorker.mjs" diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 6e1bba28d..f4ebc3b20 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -28,7 +28,8 @@ export type { HostHeaderValidationResult } from './server/middleware/hostHeaderV export { hostHeaderValidationResponse, localhostAllowedHostnames, validateHostHeader } from './server/middleware/hostHeaderValidation.js'; export type { ServerOptions } from './server/server.js'; export { Server } from './server/server.js'; -export { StdioServerTransport } from './server/stdio.js'; +// StdioServerTransport is exported from the './stdio' subpath to keep the root entry free of process-stdio +// runtime dependencies (node:stream). export type { EventId, EventStore, diff --git a/packages/server/src/stdio.ts b/packages/server/src/stdio.ts new file mode 100644 index 000000000..ce34a3520 --- /dev/null +++ b/packages/server/src/stdio.ts @@ -0,0 +1,7 @@ +// Subpath entry for the stdio server transport. +// +// Exported separately from the root entry so that bundling `@modelcontextprotocol/server` for browser or +// Cloudflare Workers targets does not pull in `node:stream`. Import from `@modelcontextprotocol/server/stdio` +// only in process-stdio runtimes (Node.js, Bun, Deno). + +export { StdioServerTransport } from './server/stdio.js'; diff --git a/packages/server/test/server/barrelClean.test.ts b/packages/server/test/server/barrelClean.test.ts new file mode 100644 index 000000000..6edef5f3e --- /dev/null +++ b/packages/server/test/server/barrelClean.test.ts @@ -0,0 +1,34 @@ +import { readFileSync } from 'node:fs'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import { describe, expect, test } from 'vitest'; + +const distDir = join(dirname(fileURLToPath(import.meta.url)), '../../dist'); +const NODE_ONLY = /\b(node:stream|node:child_process)\b/; + +function chunkImportsOf(entryPath: string): string[] { + const src = readFileSync(entryPath, 'utf8'); + return [...src.matchAll(/from\s+["']\.\/(.+?\.mjs)["']/g)].map(m => join(distDir, m[1]!)); +} + +describe('@modelcontextprotocol/server root entry is browser-safe', () => { + test('dist/index.mjs contains no process-stdio runtime imports', () => { + const entry = join(distDir, 'index.mjs'); + expect(readFileSync(entry, 'utf8')).not.toMatch(NODE_ONLY); + }); + + test('chunks transitively imported by dist/index.mjs contain no process-stdio runtime imports', () => { + const entry = join(distDir, 'index.mjs'); + for (const chunk of chunkImportsOf(entry)) { + expect({ chunk, content: readFileSync(chunk, 'utf8') }).not.toEqual( + expect.objectContaining({ content: expect.stringMatching(NODE_ONLY) }) + ); + } + }); + + test('dist/stdio.mjs exists and exports StdioServerTransport', () => { + const stdio = readFileSync(join(distDir, 'stdio.mjs'), 'utf8'); + expect(stdio).toMatch(/\bStdioServerTransport\b/); + }); +}); diff --git a/packages/server/tsdown.config.ts b/packages/server/tsdown.config.ts index fb0cd8a93..02055bd1f 100644 --- a/packages/server/tsdown.config.ts +++ b/packages/server/tsdown.config.ts @@ -4,7 +4,7 @@ export default defineConfig({ failOnWarn: 'ci-only', // 1. Entry Points // Directly matches package.json include/exclude globs - entry: ['src/index.ts', 'src/shimsNode.ts', 'src/shimsWorkerd.ts', 'src/validators/cfWorker.ts'], + entry: ['src/index.ts', 'src/stdio.ts', 'src/shimsNode.ts', 'src/shimsWorkerd.ts', 'src/validators/cfWorker.ts'], // 2. Output Configuration format: ['esm'], diff --git a/test/integration/test/__fixtures__/serverThatHangs.ts b/test/integration/test/__fixtures__/serverThatHangs.ts index 861c20687..dbaf19897 100644 --- a/test/integration/test/__fixtures__/serverThatHangs.ts +++ b/test/integration/test/__fixtures__/serverThatHangs.ts @@ -1,7 +1,8 @@ import process from 'node:process'; import { setInterval } from 'node:timers'; -import { McpServer, StdioServerTransport } from '@modelcontextprotocol/server'; +import { McpServer } from '@modelcontextprotocol/server'; +import { StdioServerTransport } from '@modelcontextprotocol/server/stdio'; const transport = new StdioServerTransport(); diff --git a/test/integration/test/__fixtures__/testServer.ts b/test/integration/test/__fixtures__/testServer.ts index 613144042..407c1c968 100644 --- a/test/integration/test/__fixtures__/testServer.ts +++ b/test/integration/test/__fixtures__/testServer.ts @@ -1,4 +1,5 @@ -import { McpServer, StdioServerTransport } from '@modelcontextprotocol/server'; +import { McpServer } from '@modelcontextprotocol/server'; +import { StdioServerTransport } from '@modelcontextprotocol/server/stdio'; const transport = new StdioServerTransport(); diff --git a/test/integration/test/processCleanup.test.ts b/test/integration/test/processCleanup.test.ts index 780c920f8..3554d9936 100644 --- a/test/integration/test/processCleanup.test.ts +++ b/test/integration/test/processCleanup.test.ts @@ -1,8 +1,10 @@ import path from 'node:path'; import { Readable, Writable } from 'node:stream'; -import { Client, StdioClientTransport } from '@modelcontextprotocol/client'; -import { Server, StdioServerTransport } from '@modelcontextprotocol/server'; +import { Client } from '@modelcontextprotocol/client'; +import { StdioClientTransport } from '@modelcontextprotocol/client/stdio'; +import { Server } from '@modelcontextprotocol/server'; +import { StdioServerTransport } from '@modelcontextprotocol/server/stdio'; // Use the local fixtures directory alongside this test file const FIXTURES_DIR = path.resolve(__dirname, './__fixtures__');