From e1ce74a2bdb4f30604875ea67e5422aa4de95915 Mon Sep 17 00:00:00 2001 From: Felix Weinberger Date: Fri, 27 Mar 2026 11:12:39 +0000 Subject: [PATCH 1/2] feat!: remove WebSocketClientTransport --- .changeset/remove-websocket-transport.md | 5 ++ docs/migration-SKILL.md | 3 +- docs/migration.md | 20 +++++++ packages/client/src/client/websocket.ts | 74 ------------------------ packages/client/src/index.ts | 1 - scripts/cli.ts | 8 --- 6 files changed, 27 insertions(+), 84 deletions(-) create mode 100644 .changeset/remove-websocket-transport.md delete mode 100644 packages/client/src/client/websocket.ts diff --git a/.changeset/remove-websocket-transport.md b/.changeset/remove-websocket-transport.md new file mode 100644 index 000000000..a36102da4 --- /dev/null +++ b/.changeset/remove-websocket-transport.md @@ -0,0 +1,5 @@ +--- +'@modelcontextprotocol/client': major +--- + +Remove `WebSocketClientTransport`. WebSocket is not a spec-defined transport; use stdio or Streamable HTTP. The `Transport` interface remains exported for custom implementations. See #142. diff --git a/docs/migration-SKILL.md b/docs/migration-SKILL.md index 0056795c3..628f8e61e 100644 --- a/docs/migration-SKILL.md +++ b/docs/migration-SKILL.md @@ -43,7 +43,7 @@ Replace all `@modelcontextprotocol/sdk/...` imports using this table. | `@modelcontextprotocol/sdk/client/streamableHttp.js` | `@modelcontextprotocol/client` | | `@modelcontextprotocol/sdk/client/sse.js` | `@modelcontextprotocol/client` | | `@modelcontextprotocol/sdk/client/stdio.js` | `@modelcontextprotocol/client` | -| `@modelcontextprotocol/sdk/client/websocket.js` | `@modelcontextprotocol/client` | +| `@modelcontextprotocol/sdk/client/websocket.js` | REMOVED (use Streamable HTTP or stdio; implement `Transport` for custom needs) | ### Server imports @@ -96,6 +96,7 @@ Notes: | `ErrorCode.RequestTimeout` | `SdkErrorCode.RequestTimeout` | | `ErrorCode.ConnectionClosed` | `SdkErrorCode.ConnectionClosed` | | `StreamableHTTPError` | REMOVED (use `SdkError` with `SdkErrorCode.ClientHttp*`) | +| `WebSocketClientTransport` | REMOVED (use `StreamableHTTPClientTransport` or `StdioClientTransport`) | All other symbols from `@modelcontextprotocol/sdk/types.js` retain their original names (e.g., `CallToolResultSchema`, `ListToolsResultSchema`, etc.). diff --git a/docs/migration.md b/docs/migration.md index 21f8b67c9..9634b8140 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -110,6 +110,26 @@ const transport = new NodeStreamableHTTPServerTransport({ sessionIdGenerator: () The SSE transport has been removed from the server. Servers should migrate to Streamable HTTP. The client-side SSE transport remains available for connecting to legacy SSE servers. +### `WebSocketClientTransport` removed + +`WebSocketClientTransport` has been removed. WebSocket is not a spec-defined MCP transport, and keeping it in the SDK encouraged transport proliferation without a conformance baseline. + +Use `StdioClientTransport` for local servers or `StreamableHTTPClientTransport` for remote servers. If you need WebSocket for a custom deployment, implement the `Transport` interface directly — it remains exported from `@modelcontextprotocol/client`. + +**Before (v1):** + +```typescript +import { WebSocketClientTransport } from '@modelcontextprotocol/sdk/client/websocket.js'; +const transport = new WebSocketClientTransport(new URL('ws://localhost:3000')); +``` + +**After (v2):** + +```typescript +import { StreamableHTTPClientTransport } from '@modelcontextprotocol/client'; +const transport = new StreamableHTTPClientTransport(new URL('http://localhost:3000/mcp')); +``` + ### Server auth removed Server-side OAuth/auth has been removed entirely from the SDK. This includes `mcpAuthRouter`, `OAuthServerProvider`, `OAuthTokenVerifier`, `requireBearerAuth`, `authenticateClient`, `ProxyOAuthServerProvider`, `allowedMethods`, and all associated types. diff --git a/packages/client/src/client/websocket.ts b/packages/client/src/client/websocket.ts deleted file mode 100644 index cb0c34687..000000000 --- a/packages/client/src/client/websocket.ts +++ /dev/null @@ -1,74 +0,0 @@ -import type { JSONRPCMessage, Transport } from '@modelcontextprotocol/core'; -import { JSONRPCMessageSchema } from '@modelcontextprotocol/core'; - -const SUBPROTOCOL = 'mcp'; - -/** - * Client transport for WebSocket: this will connect to a server over the WebSocket protocol. - */ -export class WebSocketClientTransport implements Transport { - private _socket?: WebSocket; - private _url: URL; - - onclose?: () => void; - onerror?: (error: Error) => void; - onmessage?: (message: JSONRPCMessage) => void; - - constructor(url: URL) { - this._url = url; - } - - start(): Promise { - if (this._socket) { - throw new Error( - 'WebSocketClientTransport already started! If using Client class, note that connect() calls start() automatically.' - ); - } - - return new Promise((resolve, reject) => { - this._socket = new WebSocket(this._url, SUBPROTOCOL); - - this._socket.onerror = event => { - const error = 'error' in event ? (event.error as Error) : new Error(`WebSocket error: ${JSON.stringify(event)}`); - reject(error); - this.onerror?.(error); - }; - - this._socket.onopen = () => { - resolve(); - }; - - this._socket.onclose = () => { - this.onclose?.(); - }; - - this._socket.onmessage = (event: MessageEvent) => { - let message: JSONRPCMessage; - try { - message = JSONRPCMessageSchema.parse(JSON.parse(event.data)); - } catch (error) { - this.onerror?.(error as Error); - return; - } - - this.onmessage?.(message); - }; - }); - } - - async close(): Promise { - this._socket?.close(); - } - - send(message: JSONRPCMessage): Promise { - return new Promise((resolve, reject) => { - if (!this._socket) { - reject(new Error('Not connected')); - return; - } - - this._socket?.send(JSON.stringify(message)); - resolve(); - }); - } -} diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 3abbd628c..30efaddb3 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -64,7 +64,6 @@ export type { StdioServerParameters } from './client/stdio.js'; export { DEFAULT_INHERITED_ENV_VARS, getDefaultEnvironment, StdioClientTransport } from './client/stdio.js'; export type { StartSSEOptions, StreamableHTTPClientTransportOptions, StreamableHTTPReconnectionOptions } from './client/streamableHttp.js'; export { StreamableHTTPClientTransport } from './client/streamableHttp.js'; -export { WebSocketClientTransport } from './client/websocket.js'; // experimental exports export { ExperimentalClientTasks } from './experimental/tasks/client.js'; diff --git a/scripts/cli.ts b/scripts/cli.ts index 0bfd2c9a1..0d1d2c75c 100644 --- a/scripts/cli.ts +++ b/scripts/cli.ts @@ -1,13 +1,7 @@ -import WebSocket from 'ws'; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -(global as any).WebSocket = WebSocket; - import express from 'express'; import { Client } from '../src/client/index.js'; import { SSEClientTransport } from '../src/client/sse.js'; import { StdioClientTransport } from '../src/client/stdio.js'; -import { WebSocketClientTransport } from '../src/client/websocket.js'; import { Server } from '../src/server/index.js'; import { SSEServerTransport } from '../src/server/sse.js'; import { StdioServerTransport } from '../src/server/stdio.js'; @@ -37,8 +31,6 @@ async function runClient(url_or_command: string, args: string[]) { if (url?.protocol === 'http:' || url?.protocol === 'https:') { clientTransport = new SSEClientTransport(new URL(url_or_command)); - } else if (url?.protocol === 'ws:' || url?.protocol === 'wss:') { - clientTransport = new WebSocketClientTransport(new URL(url_or_command)); } else { clientTransport = new StdioClientTransport({ command: url_or_command, From 3baf223b7ae4ec3c3a0e2e1b4b3e7e36a707c361 Mon Sep 17 00:00:00 2001 From: Felix Weinberger Date: Fri, 27 Mar 2026 14:25:32 +0000 Subject: [PATCH 2/2] chore: remove dead ws dependencies and add helpful error for ws:// URLs --- package.json | 2 -- pnpm-lock.yaml | 33 --------------------------------- pnpm-workspace.yaml | 2 -- scripts/cli.ts | 2 ++ 4 files changed, 2 insertions(+), 37 deletions(-) diff --git a/package.json b/package.json index 025709b2e..e38d12ba0 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,6 @@ "@types/express-serve-static-core": "catalog:devTools", "@types/node": "^24.10.1", "@types/supertest": "catalog:devTools", - "@types/ws": "catalog:devTools", "@typescript/native-preview": "catalog:devTools", "eslint": "catalog:devTools", "eslint-config-prettier": "catalog:devTools", @@ -75,7 +74,6 @@ "typescript": "catalog:devTools", "typescript-eslint": "catalog:devTools", "vitest": "catalog:devTools", - "ws": "catalog:devTools", "zod": "catalog:runtimeShared" }, "resolutions": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 06e705f91..b3da08723 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -30,9 +30,6 @@ catalogs: '@types/supertest': specifier: ^6.0.2 version: 6.0.3 - '@types/ws': - specifier: ^8.5.12 - version: 8.18.1 '@typescript/native-preview': specifier: ^7.0.0-dev.20251217.1 version: 7.0.0-dev.20260105.1 @@ -84,9 +81,6 @@ catalogs: wrangler: specifier: ^4.14.4 version: 4.60.0 - ws: - specifier: ^8.18.0 - version: 8.18.3 runtimeClientOnly: cross-spawn: specifier: ^7.0.5 @@ -185,9 +179,6 @@ importers: '@types/supertest': specifier: catalog:devTools version: 6.0.3 - '@types/ws': - specifier: catalog:devTools - version: 8.18.1 '@typescript/native-preview': specifier: catalog:devTools version: 7.0.0-dev.20260105.1 @@ -233,9 +224,6 @@ importers: vitest: specifier: catalog:devTools version: 4.0.16(@types/node@24.10.4)(tsx@4.21.0)(yaml@2.8.2) - ws: - specifier: catalog:devTools - version: 8.18.3 zod: specifier: catalog:runtimeShared version: 4.3.5 @@ -2283,9 +2271,6 @@ packages: '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} - '@types/ws@8.18.1': - resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} - '@typescript-eslint/eslint-plugin@8.51.0': resolution: {integrity: sha512-XtssGWJvypyM2ytBnSnKtHYOGT+4ZwTnBVl36TA4nRO2f4PRNGz5/1OszHzcZCvcBMh+qb7I06uoCmLTRdR9og==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -4773,18 +4758,6 @@ packages: utf-8-validate: optional: true - ws@8.18.3: - resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - wsl-utils@0.3.1: resolution: {integrity: sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==} engines: {node: '>=20'} @@ -5886,10 +5859,6 @@ snapshots: '@types/unist@3.0.3': {} - '@types/ws@8.18.1': - dependencies: - '@types/node': 24.10.4 - '@typescript-eslint/eslint-plugin@8.51.0(@typescript-eslint/parser@8.51.0(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2)(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 @@ -8566,8 +8535,6 @@ snapshots: ws@8.18.0: {} - ws@8.18.3: {} - wsl-utils@0.3.1: dependencies: is-wsl: 3.1.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 0afccd5b7..e28823591 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -18,7 +18,6 @@ catalogs: '@types/express': ^5.0.6 '@types/express-serve-static-core': ^5.1.0 '@types/supertest': ^6.0.2 - '@types/ws': ^8.5.12 '@typescript/native-preview': ^7.0.0-dev.20251217.1 eslint: ^9.39.2 eslint-config-prettier: ^10.1.8 @@ -32,7 +31,6 @@ catalogs: typescript-eslint: ^8.48.1 vite-tsconfig-paths: ^5.1.4 vitest: ^4.0.15 - ws: ^8.18.0 runtimeClientOnly: cross-spawn: ^7.0.5 eventsource: ^3.0.2 diff --git a/scripts/cli.ts b/scripts/cli.ts index 0d1d2c75c..809a23efe 100644 --- a/scripts/cli.ts +++ b/scripts/cli.ts @@ -31,6 +31,8 @@ async function runClient(url_or_command: string, args: string[]) { if (url?.protocol === 'http:' || url?.protocol === 'https:') { clientTransport = new SSEClientTransport(new URL(url_or_command)); + } else if (url?.protocol === 'ws:' || url?.protocol === 'wss:') { + throw new Error('WebSocket URLs are no longer supported. Use http(s) or stdio instead.'); } else { clientTransport = new StdioClientTransport({ command: url_or_command,