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 c2f42b5f5..fb5ffb693 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 5d7763cbe..3c00fc185 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/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/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 d1af95103..a888bf8b7 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -69,7 +69,6 @@ export type { 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/pnpm-lock.yaml b/pnpm-lock.yaml index 20ed01f15..a298f5415 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.20260327.2 @@ -84,9 +81,6 @@ catalogs: wrangler: specifier: ^4.14.4 version: 4.78.0 - ws: - specifier: ^8.18.0 - version: 8.20.0 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.20260327.2 @@ -233,9 +224,6 @@ importers: vitest: specifier: catalog:devTools version: 4.1.2(@opentelemetry/api@1.9.1)(@types/node@24.12.0)(vite@7.3.0(@types/node@24.12.0)(tsx@4.21.0)(yaml@2.8.3)) - ws: - specifier: catalog:devTools - version: 8.20.0 zod: specifier: catalog:runtimeShared version: 4.3.6 @@ -2362,9 +2350,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.57.2': resolution: {integrity: sha512-NZZgp0Fm2IkD+La5PR81sd+g+8oS6JwJje+aRWsDocxHkjyRw0J5L5ZTlN3LI1LlOcGL7ph3eaIUmTXMIjLk0w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -4866,18 +4851,6 @@ packages: utf-8-validate: optional: true - ws@8.20.0: - resolution: {integrity: sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==} - 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'} @@ -6022,10 +5995,6 @@ snapshots: '@types/unist@3.0.3': {} - '@types/ws@8.18.1': - dependencies: - '@types/node': 24.12.0 - '@typescript-eslint/eslint-plugin@8.57.2(@typescript-eslint/parser@8.57.2(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 @@ -8762,8 +8731,6 @@ snapshots: ws@8.18.0: {} - ws@8.20.0: {} - wsl-utils@0.3.1: dependencies: is-wsl: 3.1.1 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 0bfd2c9a1..809a23efe 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'; @@ -38,7 +32,7 @@ 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)); + throw new Error('WebSocket URLs are no longer supported. Use http(s) or stdio instead.'); } else { clientTransport = new StdioClientTransport({ command: url_or_command,