同步完整源码 - 2026-05-25

This commit is contained in:
xiaoxue 2026-05-25 00:25:13 +08:00
commit 7e65fec24a
479 changed files with 112449 additions and 0 deletions

6
.changeset/README.md Normal file
View File

@ -0,0 +1,6 @@
# Changesets
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works with multi-package repos, or single-package repos to help you version and publish your code. You can find the full documentation for it
[in our repository](https://github.com/changesets/changesets)
We have a quick list of common questions to get you started engaging with this project in [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)

View File

@ -0,0 +1,5 @@
---
'@modelcontextprotocol/core': patch
---
Abort in-flight request handlers when the connection closes. Previously, request handlers would continue running after the transport disconnected, wasting resources and preventing proper cleanup. Also fixes `InMemoryTransport.close()` firing `onclose` twice on the initiating side.

View File

@ -0,0 +1,5 @@
---
'@modelcontextprotocol/fastify': minor
---
Add Fastify middleware adapter for MCP servers, following the same pattern as the Express and Hono adapters.

View File

@ -0,0 +1,5 @@
---
'@modelcontextprotocol/node': patch
---
Add missing `hono` peer dependency to `@modelcontextprotocol/node`. The package already depends on `@hono/node-server` which requires `hono` at runtime, but `hono` was only listed in the workspace root, not as a peer dependency of the package itself.

View File

@ -0,0 +1,5 @@
---
'@modelcontextprotocol/core': patch
---
Add missing `size` field to `ResourceSchema` to match the MCP specification

View File

@ -0,0 +1,6 @@
---
"@modelcontextprotocol/core": minor
"@modelcontextprotocol/client": minor
---
Add `SdkHttpError` subclass with typed `.status` / `.statusText` accessors for HTTP transport failures. `StreamableHTTPClientTransport` now throws `SdkHttpError` (which extends `SdkError`) for non-OK HTTP responses; `SSEClientTransport` throws `SdkHttpError` for 401-after-reauth (circuit breaker).

View File

@ -0,0 +1,5 @@
---
'@modelcontextprotocol/node': patch
---
Prevent Hono from overriding global Response object by passing `overrideGlobalObjects: false` to `getRequestListener()`. This fixes compatibility with frameworks like Next.js whose response classes extend the native Response.

View File

@ -0,0 +1,6 @@
---
'@modelcontextprotocol/client': patch
'@modelcontextprotocol/server': patch
---
tasks - disallow requesting a null TTL

View File

@ -0,0 +1,6 @@
---
'@modelcontextprotocol/core': patch
'@modelcontextprotocol/server': patch
---
Fix ReDoS vulnerability in UriTemplate regex patterns (CVE-2026-0621)

View File

@ -0,0 +1,6 @@
---
'@modelcontextprotocol/server': patch
'@modelcontextprotocol/client': patch
---
Stop bundling `@cfworker/json-schema` into the main package barrel. Previously `CfWorkerJsonSchemaValidator` was re-exported from the core internal barrel, so tsdown inlined the `@cfworker/json-schema` dev dependency into every consumer's bundle even when it was never used. The validator is now reachable only via the `_shims` conditional (workerd/browser) and the explicit `@modelcontextprotocol/{server,client}/validators/cf-worker` subpath, so consumers that don't opt into it no longer ship that code. No public API change.

17
.changeset/config.json Normal file
View File

@ -0,0 +1,17 @@
{
"$schema": "https://unpkg.com/@changesets/config@3.1.2/schema.json",
"changelog": ["@changesets/changelog-github", { "repo": "modelcontextprotocol/typescript-sdk" }],
"commit": false,
"fixed": [],
"linked": [],
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": [
"@modelcontextprotocol/examples-client",
"@modelcontextprotocol/examples-client-quickstart",
"@modelcontextprotocol/examples-server",
"@modelcontextprotocol/examples-server-quickstart",
"@modelcontextprotocol/examples-shared"
]
}

View File

@ -0,0 +1,9 @@
---
'@modelcontextprotocol/core': minor
'@modelcontextprotocol/client': minor
'@modelcontextprotocol/server': minor
---
Add custom (non-spec) method support: a 3-arg `setRequestHandler(method, schemas, handler)` / `setNotificationHandler(method, schemas, handler)` form for vendor-prefixed methods, and a `request(req, resultSchema)` overload (also on `ctx.mcpReq.send`) for typed custom-method results. Spec-method calls are unchanged.
Response result-schema validation failure now rejects with `SdkError(InvalidResult)` instead of a raw `ZodError`. Adds `SdkErrorCode.InvalidResult`.

View File

@ -0,0 +1,5 @@
---
'@modelcontextprotocol/server': patch
---
missing change for fix(client): replace body.cancel() with text() to prevent hanging

View File

@ -0,0 +1,9 @@
---
'@modelcontextprotocol/client': patch
'@modelcontextprotocol/server': patch
---
Drop `zod` from `peerDependencies` (kept as direct dependency)
Since Standard Schema support landed, `zod` is purely an internal runtime dependency used for protocol message parsing. User-facing schemas (`registerTool`, `registerPrompt`) accept any Standard Schema library. `zod` remains in `dependencies` and auto-installs; users no longer
need to install it alongside the SDK.

View File

@ -0,0 +1,6 @@
---
'@modelcontextprotocol/server': patch
'@modelcontextprotocol/client': patch
---
Export `InMemoryTransport` for in-process testing.

View File

@ -0,0 +1,10 @@
---
'@modelcontextprotocol/client': minor
---
Add `discoverOAuthServerInfo()` function and unified discovery state caching for OAuth
- New `discoverOAuthServerInfo(serverUrl)` export that performs RFC 9728 protected resource metadata discovery followed by authorization server metadata discovery in a single call. Use this for operations like token refresh and revocation that need the authorization server URL outside of `auth()`.
- New `OAuthDiscoveryState` type and optional `OAuthClientProvider` methods `saveDiscoveryState()` / `discoveryState()` allow providers to persist all discovery results (auth server URL, resource metadata URL, resource metadata, auth server metadata) across sessions. This avoids redundant discovery requests and handles browser redirect scenarios where discovery state would otherwise be lost.
- New `'discovery'` scope for `invalidateCredentials()` to clear cached discovery state.
- New `OAuthServerInfo` type exported for the return value of `discoverOAuthServerInfo()`.

View File

@ -0,0 +1,5 @@
---
'@modelcontextprotocol/express': minor
---
Add OAuth Resource-Server glue to the Express adapter: `requireBearerAuth` middleware (token verification + RFC 6750 `WWW-Authenticate` challenges), `mcpAuthMetadataRouter` (serves RFC 9728 Protected Resource Metadata and mirrors RFC 8414 AS metadata at the resource origin), the `getOAuthProtectedResourceMetadataUrl` helper, and the `OAuthTokenVerifier` interface. These restore the v1 `src/server/auth` Resource-Server pieces as first-class v2 API so MCP servers can plug into an external Authorization Server with a few lines of Express wiring.

View File

@ -0,0 +1,10 @@
---
"@modelcontextprotocol/core": minor
"@modelcontextprotocol/client": minor
"@modelcontextprotocol/server": minor
---
refactor: extract task orchestration from Protocol into TaskManager
**Breaking changes:**
- `taskStore`, `taskMessageQueue`, `defaultTaskPollInterval`, and `maxTaskQueueSize` moved from `ProtocolOptions` to `capabilities.tasks` on `ClientOptions`/`ServerOptions`

View File

@ -0,0 +1,10 @@
---
'@modelcontextprotocol/express': patch
'@modelcontextprotocol/fastify': patch
'@modelcontextprotocol/hono': patch
'@modelcontextprotocol/node': patch
'@modelcontextprotocol/client': patch
'@modelcontextprotocol/server': patch
---
tsdown exports resolution fix

View File

@ -0,0 +1,7 @@
---
'@modelcontextprotocol/core': patch
'@modelcontextprotocol/client': patch
'@modelcontextprotocol/server': patch
---
Convert remaining capability-assertion throws to `SdkError(SdkErrorCode.CapabilityNotSupported, ...)`. Follow-up to #1454 which missed `Client.assertCapability()`, the task capability helpers in `experimental/tasks/helpers.ts`, and the sampling/elicitation capability checks in `experimental/tasks/server.ts`.

View File

@ -0,0 +1,5 @@
---
'@modelcontextprotocol/core': patch
---
Consolidate per-request cleanup in `_requestWithSchema` into a single `.finally()` block. This fixes an abort signal listener leak (listeners accumulated when a caller reused one `AbortSignal` across requests) and two cases where `_responseHandlers` entries leaked on send-failure paths.

View File

@ -0,0 +1,5 @@
---
'@modelcontextprotocol/core': patch
---
Fix `requestStream` to call `tasks/result` for failed tasks instead of yielding a hardcoded `ProtocolError`. When a task reaches the `failed` terminal status, the stream now retrieves and yields the actual stored result (matching the behavior for `completed` tasks), as required by the spec.

View File

@ -0,0 +1,5 @@
---
'@modelcontextprotocol/client': patch
---
Continue OAuth metadata discovery on 502 (Bad Gateway) responses, matching the existing behavior for 4xx. This fixes MCP servers behind reverse proxies that return 502 for path-aware metadata URLs. Other 5xx errors still throw to avoid retrying against overloaded servers.

View File

@ -0,0 +1,5 @@
---
'@modelcontextprotocol/server': patch
---
Fix transport errors being silently swallowed by adding missing `onerror` callback invocations before all `createJsonErrorResponse` calls in `WebStandardStreamableHTTPServerTransport`. This ensures errors like parse failures, invalid headers, and session validation errors are properly reported via the `onerror` callback.

View File

@ -0,0 +1,5 @@
---
'@modelcontextprotocol/server': patch
---
fix(server): propagate negotiated protocol version to transport in _oninitialize

View File

@ -0,0 +1,5 @@
---
'@modelcontextprotocol/examples-server': patch
---
Example servers now return HTTP 404 (not 400) when a request includes an unknown session ID, so clients can correctly detect they need to start a new session. Requests missing a session ID entirely still return 400.

View File

@ -0,0 +1,5 @@
---
'@modelcontextprotocol/server': patch
---
Handle stdout errors (e.g. EPIPE) in `StdioServerTransport` gracefully instead of crashing. When the client disconnects abruptly, the transport now catches the stdout error, surfaces it via `onerror`, and closes.

View File

@ -0,0 +1,5 @@
---
'@modelcontextprotocol/client': patch
---
Always set `windowsHide` when spawning stdio server processes on Windows, not just in Electron environments. Prevents unwanted console windows in non-Electron Windows applications.

View File

@ -0,0 +1,5 @@
---
'@modelcontextprotocol/server': patch
---
Prevent stack overflow in StreamableHTTPServerTransport.close() with re-entrant guard

View File

@ -0,0 +1,5 @@
---
'@modelcontextprotocol/client': patch
---
Fix StreamableHTTPClientTransport to handle error responses in SSE streams

View File

@ -0,0 +1,5 @@
---
'@modelcontextprotocol/core': patch
---
Fix InMemoryTaskStore to enforce session isolation. Previously, sessionId was accepted but ignored on all TaskStore methods, allowing any session to enumerate, read, and mutate tasks created by other sessions. The store now persists sessionId at creation time and enforces ownership on all reads and writes.

View File

@ -0,0 +1,9 @@
---
'@modelcontextprotocol/core': patch
---
Add explicit `| undefined` to optional properties on the `Transport` interface and `TransportSendOptions` (`onclose`, `onerror`, `onmessage`, `sessionId`, `setProtocolVersion`, `setSupportedProtocolVersions`, `onresumptiontoken`).
This fixes TS2420 errors for consumers using `exactOptionalPropertyTypes: true` without `skipLibCheck`, where the emitted `.d.ts` for implementing classes included `| undefined` but the interface did not.
Workaround for older SDK versions: enable `skipLibCheck: true` in your tsconfig.

View File

@ -0,0 +1,15 @@
---
"@modelcontextprotocol/core": minor
"@modelcontextprotocol/server": major
---
Fix error handling for unknown tools and resources per MCP spec.
**Tools:** Unknown or disabled tool calls now return JSON-RPC protocol errors with
code `-32602` (InvalidParams) instead of `CallToolResult` with `isError: true`.
Callers who checked `result.isError` for unknown tools should catch rejected promises instead.
**Resources:** Unknown resource reads now return error code `-32002` (ResourceNotFound)
instead of `-32602` (InvalidParams).
Added `ProtocolErrorCode.ResourceNotFound`.

View File

@ -0,0 +1,9 @@
---
'@modelcontextprotocol/client': minor
---
Add `validateClientMetadataUrl()` utility for early validation of `clientMetadataUrl`
Exports a `validateClientMetadataUrl()` function that `OAuthClientProvider` implementations
can call in their constructors to fail fast on invalid URL-based client IDs, instead of
discovering the error deep in the auth flow.

View File

@ -0,0 +1,8 @@
---
'@modelcontextprotocol/node': patch
'@modelcontextprotocol/test-integration': patch
'@modelcontextprotocol/server': patch
'@modelcontextprotocol/core': patch
---
remove deprecated .tool, .prompt, .resource method signatures

View File

@ -0,0 +1,7 @@
---
'@modelcontextprotocol/server': patch
---
Add `| undefined` to optional callback and function properties on `WebStandardStreamableHTTPServerTransportOptions` (`sessionIdGenerator`, `onsessioninitialized`, `onsessionclosed`) and corresponding private fields.
This fixes TS2430 errors for consumers using `exactOptionalPropertyTypes: true` without `skipLibCheck`, where optional properties with function types need explicit `| undefined` to match their emitted declarations.

View File

@ -0,0 +1,5 @@
---
'@modelcontextprotocol/server': patch
---
reverting application/json in notifications

View File

@ -0,0 +1,5 @@
---
'@modelcontextprotocol/node': patch
---
Mark `hono` peer dependency as optional. `@modelcontextprotocol/node` only uses `getRequestListener` from `@hono/node-server` (Node HTTP ↔ Web Standard conversion), which does not require the `hono` framework at runtime. Consumers no longer need to install `hono` to use `NodeStreamableHTTPServerTransport`. Note: `@hono/node-server` itself still declares `hono` as a hard peer, so package managers may emit a warning; this is upstream and harmless for `getRequestListener`-only usage.

View File

@ -0,0 +1,10 @@
---
'@modelcontextprotocol/client': patch
'@modelcontextprotocol/server': patch
'@modelcontextprotocol/node': patch
'@modelcontextprotocol/express': patch
'@modelcontextprotocol/fastify': patch
'@modelcontextprotocol/hono': patch
---
Add top-level `types` field (and `typesVersions` on client/server for their subpath exports) so consumers on legacy `moduleResolution: "node"` can resolve type declarations. The `exports` map remains the source of truth for `nodenext`/`bundler` resolution. The `typesVersions` map includes entries for subpaths added by sibling PRs in this series (`zod-schemas`, `stdio`); those entries are no-ops until the corresponding `dist/*.d.mts` files exist.

View File

@ -0,0 +1,7 @@
---
'@modelcontextprotocol/client': patch
---
Fix OAuth error handling for servers returning errors with HTTP 200 status
Some OAuth servers (e.g., GitHub) return error responses with HTTP 200 status instead of 4xx. The SDK now checks for an `error` field in the JSON response before attempting to parse it as tokens, providing users with meaningful error messages.

View File

@ -0,0 +1,7 @@
---
"@modelcontextprotocol/client": patch
---
fix(client): append custom Accept headers to spec-required defaults in StreamableHTTPClientTransport
Custom Accept headers provided via `requestInit.headers` are now appended to the spec-mandated Accept types instead of being overwritten. This ensures the required media types (`application/json, text/event-stream` for POST; `text/event-stream` for GET SSE) are always present while allowing users to include additional types for proxy/gateway routing.

68
.changeset/pre.json Normal file
View File

@ -0,0 +1,68 @@
{
"mode": "pre",
"tag": "alpha",
"initialVersions": {
"@modelcontextprotocol/eslint-config": "2.0.0",
"@modelcontextprotocol/tsconfig": "2.0.0",
"@modelcontextprotocol/vitest-config": "2.0.0",
"@modelcontextprotocol/examples-client": "2.0.0-alpha.0",
"@modelcontextprotocol/examples-client-quickstart": "2.0.0-alpha.0",
"@modelcontextprotocol/examples-server": "2.0.0-alpha.0",
"@modelcontextprotocol/examples-server-quickstart": "2.0.0-alpha.0",
"@modelcontextprotocol/examples-shared": "2.0.0-alpha.0",
"@modelcontextprotocol/client": "2.0.0-alpha.0",
"@modelcontextprotocol/core": "2.0.0-alpha.0",
"@modelcontextprotocol/express": "2.0.0-alpha.0",
"@modelcontextprotocol/fastify": "2.0.0-alpha.0",
"@modelcontextprotocol/hono": "2.0.0-alpha.0",
"@modelcontextprotocol/node": "2.0.0-alpha.0",
"@modelcontextprotocol/server": "2.0.0-alpha.0",
"@modelcontextprotocol/test-conformance": "2.0.0-alpha.0",
"@modelcontextprotocol/test-helpers": "2.0.0-alpha.0",
"@modelcontextprotocol/test-integration": "2.0.0-alpha.0"
},
"changesets": [
"abort-handlers-on-close",
"add-fastify-middleware",
"add-hono-peer-dep",
"add-resource-size-field",
"brave-lions-glow",
"busy-rice-smoke",
"busy-weeks-hang",
"cyan-cycles-pump",
"drop-zod-peer-dep",
"expose-auth-server-discovery",
"extract-task-manager",
"fast-dragons-lead",
"finish-sdkerror-capability",
"fix-abort-listener-leak",
"fix-oauth-5xx-discovery",
"fix-onerror-callbacks",
"fix-server-protocol-version",
"fix-session-status-codes",
"fix-stdio-epipe-crash",
"fix-stdio-windows-hide",
"fix-streamable-http-error-response",
"fix-task-session-isolation",
"fix-transport-exact-optional-property-types",
"fix-unknown-tool-protocol-error",
"funky-baths-attack",
"heavy-walls-swim",
"oauth-error-http200",
"quick-islands-occur",
"reconnection-scheduler",
"remove-websocket-transport",
"respect-capability-negotiation",
"rich-hounds-report",
"schema-object-type-for-unions",
"shy-times-learn",
"spotty-cats-tickle",
"stdio-skip-non-json",
"support-standard-json-schema",
"tame-camels-greet",
"tender-snails-fold",
"token-provider-composable-auth",
"twelve-dodos-taste",
"use-scopes-supported-in-dcr"
]
}

View File

@ -0,0 +1,10 @@
---
'@modelcontextprotocol/express': patch
'@modelcontextprotocol/hono': patch
'@modelcontextprotocol/node': patch
'@modelcontextprotocol/client': patch
'@modelcontextprotocol/server': patch
'@modelcontextprotocol/core': patch
---
remove npm references, use pnpm

View File

@ -0,0 +1,5 @@
---
'@modelcontextprotocol/client': minor
---
Add `reconnectionScheduler` option to `StreamableHTTPClientTransport`. Lets non-persistent environments (serverless, mobile, desktop sleep/wake) override the default `setTimeout`-based SSE reconnection scheduling. The scheduler may return a cancel function that is invoked on `transport.close()`.

View File

@ -0,0 +1,8 @@
---
'@modelcontextprotocol/core': patch
'@modelcontextprotocol/server': patch
---
`registerTool`/`registerPrompt` accept a raw Zod shape (`{ field: z.string() }`) for `inputSchema`/`outputSchema`/`argsSchema` in addition to a wrapped Standard Schema. Raw shapes are auto-wrapped with `z.object()`. The raw-shape overloads are `@deprecated`; prefer wrapping with `z.object()`.
Also widens the `completable()` constraint from `StandardSchemaWithJSON` to `StandardSchemaV1` so v1's `completable(z.string(), fn)` continues to work.

View File

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

View File

@ -0,0 +1,14 @@
---
'@modelcontextprotocol/client': patch
---
Respect capability negotiation in list methods by returning empty lists when server lacks capability
The Client now returns empty lists instead of sending requests to servers that don't advertise the corresponding capability:
- `listPrompts()` returns `{ prompts: [] }` if server lacks prompts capability
- `listResources()` returns `{ resources: [] }` if server lacks resources capability
- `listResourceTemplates()` returns `{ resourceTemplates: [] }` if server lacks resources capability
- `listTools()` returns `{ tools: [] }` if server lacks tools capability
This respects the MCP spec requirement that "Both parties SHOULD respect capability negotiation" and avoids unnecessary server warnings and traffic. The existing `enforceStrictCapabilities` option continues to throw errors when set to `true`.

View File

@ -0,0 +1,10 @@
---
'@modelcontextprotocol/express': patch
'@modelcontextprotocol/hono': patch
'@modelcontextprotocol/node': patch
'@modelcontextprotocol/client': patch
'@modelcontextprotocol/server': patch
'@modelcontextprotocol/core': patch
---
clean up package manager usage, all pnpm

View File

@ -0,0 +1,5 @@
---
'@modelcontextprotocol/core': patch
---
Ensure `standardSchemaToJsonSchema` emits `type: "object"` at the root, fixing discriminated-union tool/prompt schemas that previously produced `{oneOf: [...]}` without the MCP-required top-level type. Also throws a clear error when given an explicitly non-object schema (e.g. `z.string()`). Fixes #1643.

View File

@ -0,0 +1,8 @@
---
'@modelcontextprotocol/node': patch
'@modelcontextprotocol/test-integration': patch
'@modelcontextprotocol/server': patch
'@modelcontextprotocol/core': patch
---
deprecated .tool, .prompt, .resource method removal

View File

@ -0,0 +1,6 @@
---
'@modelcontextprotocol/client': minor
'@modelcontextprotocol/server': minor
---
Export `isSpecType` and `specTypeSchemas` records for runtime validation of any MCP spec type by name. `isSpecType.ContentBlock(value)` is a type predicate; `specTypeSchemas.ContentBlock` is a `StandardSchemaV1Sync<ContentBlock>` validator — `validate()` returns the result synchronously. Guards are standalone functions, so `arr.filter(isSpecType.ContentBlock)` works. Also export the `SpecTypeName`, `SpecTypes`, and `StandardSchemaV1Sync` types.

View File

@ -0,0 +1,5 @@
---
'@modelcontextprotocol/client': minor
---
The client credentials providers now support scopes being added to the token request.

View File

@ -0,0 +1,5 @@
---
'@modelcontextprotocol/core': patch
---
`ReadBuffer.readMessage()` now silently skips non-JSON lines instead of throwing `SyntaxError`. This prevents noisy `onerror` callbacks when hot-reload tools (tsx, nodemon) write debug output like "Gracefully restarting..." to stdout. Lines that parse as JSON but fail JSONRPC schema validation still throw.

View File

@ -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 `@modelcontextprotocol/client` root entry no longer pulls in `node:child_process`, `node:stream`, or `cross-spawn`, fixing bundling for browser and Cloudflare Workers targets; the `@modelcontextprotocol/server` root entry drops its `node:stream` reference. Node.js, Bun, and Deno consumers update the import path; runtime behavior is unchanged.

View File

@ -0,0 +1,34 @@
---
'@modelcontextprotocol/core': minor
'@modelcontextprotocol/server': minor
'@modelcontextprotocol/client': minor
---
Support Standard Schema for tool and prompt schemas
Tool and prompt registration now accepts any schema library that implements the [Standard Schema spec](https://standardschema.dev/): Zod v4, Valibot, ArkType, and others. `RegisteredTool.inputSchema`, `RegisteredTool.outputSchema`, and `RegisteredPrompt.argsSchema` now use `StandardSchemaWithJSON` (requires both `~standard.validate` and `~standard.jsonSchema`) instead of the Zod-specific `AnySchema` type.
**Zod v4 schemas continue to work unchanged** — Zod v4 implements the required interfaces natively.
```typescript
import { type } from 'arktype';
server.registerTool('greet', {
inputSchema: type({ name: 'string' })
}, async ({ name }) => ({ content: [{ type: 'text', text: `Hello, ${name}!` }] }));
```
For raw JSON Schema (e.g. TypeBox output), use the new `fromJsonSchema` adapter:
```typescript
import { fromJsonSchema, AjvJsonSchemaValidator } from '@modelcontextprotocol/core';
server.registerTool('greet', {
inputSchema: fromJsonSchema({ type: 'object', properties: { name: { type: 'string' } } }, new AjvJsonSchemaValidator())
}, handler);
```
**Breaking changes:**
- `experimental.tasks.getTaskResult()` no longer accepts a `resultSchema` parameter. Returns `GetTaskPayloadResult` (a loose `Result`); cast to the expected type at the call site.
- Removed unused exports from `@modelcontextprotocol/core`: `SchemaInput`, `schemaToJson`, `parseSchemaAsync`, `getSchemaShape`, `getSchemaDescription`, `isOptionalSchema`, `unwrapOptionalSchema`. Use the new `standardSchemaToJsonSchema` and `validateStandardSchema` instead.
- `completable()` remains Zod-specific (it relies on Zod's `.shape` introspection).

View File

@ -0,0 +1,9 @@
---
'@modelcontextprotocol/client': patch
---
Don't swallow fetch `TypeError` as CORS in non-browser environments. Network errors
(DNS resolution failure, connection refused, invalid URL) in Node.js and Cloudflare
Workers now propagate from OAuth discovery instead of being silently misattributed
to CORS and returning `undefined`. This surfaces the real error to callers rather
than masking it as "metadata not found."

View File

@ -0,0 +1,6 @@
---
'@modelcontextprotocol/client': patch
'@modelcontextprotocol/server': patch
---
Initial 2.0.0-alpha.0 client and server package

View File

@ -0,0 +1,16 @@
---
'@modelcontextprotocol/client': minor
---
Add `AuthProvider` for composable bearer-token auth; transports adapt `OAuthClientProvider` automatically
- New `AuthProvider` interface: `{ token(): Promise<string | undefined>; onUnauthorized?(ctx): Promise<void> }`. Transports call `token()` before every request and `onUnauthorized()` on 401 (then retry once).
- Transport `authProvider` option now accepts `AuthProvider | OAuthClientProvider`. OAuth providers are adapted internally via `adaptOAuthProvider()` — no changes needed to existing `OAuthClientProvider` implementations.
- For simple bearer tokens (API keys, gateway-managed tokens, service accounts): `{ authProvider: { token: async () => myKey } }` — one-line object literal, no class.
- New `adaptOAuthProvider(provider)` export for explicit adaptation.
- New `handleOAuthUnauthorized(provider, ctx)` helper — the standard OAuth `onUnauthorized` behavior.
- New `isOAuthClientProvider()` type guard.
- New `UnauthorizedContext` type.
- Exported previously-internal auth helpers for building custom flows: `applyBasicAuth`, `applyPostAuth`, `applyPublicAuth`, `executeTokenRequest`.
Transports are simplified internally — ~50 lines of inline OAuth orchestration (auth() calls, WWW-Authenticate parsing, circuit-breaker state) moved into the adapter's `onUnauthorized()` implementation. `OAuthClientProvider` itself is unchanged.

View File

@ -0,0 +1,5 @@
---
"@modelcontextprotocol/express": patch
---
Add jsonLimit option to createMcpExpressApp

View File

@ -0,0 +1,10 @@
---
'@modelcontextprotocol/client': minor
---
Apply resolved scope consistently to both DCR and the authorization URL (SEP-835)
When `scopes_supported` is present in the protected resource metadata (`/.well-known/oauth-protected-resource`), the SDK already uses it as the default scope for the authorization URL. This change applies the same resolved scope to the dynamic client registration request body, ensuring both use a consistent value.
- `registerClient()` now accepts an optional `scope` parameter that overrides `clientMetadata.scope` in the registration body.
- `auth()` now computes the resolved scope once (WWW-Authenticate → PRM `scopes_supported``clientMetadata.scope`) and passes it to both DCR and the authorization request.

View File

@ -0,0 +1,7 @@
---
'@modelcontextprotocol/core': patch
'@modelcontextprotocol/client': patch
'@modelcontextprotocol/server': patch
---
refactor: subclasses override `_wrapHandler` hook instead of redeclaring `setRequestHandler`.

View File

@ -0,0 +1,5 @@
---
'@modelcontextprotocol/core': patch
---
Allow additional JSON Schema properties in elicitInput's requestedSchema type by adding .catchall(z.unknown()), matching the pattern used by inputSchema. This fixes type incompatibility when using Zod v4's .toJSONSchema() output which includes extra properties like $schema and additionalProperties.

View File

@ -0,0 +1,7 @@
---
'@modelcontextprotocol/core': patch
'@modelcontextprotocol/server': patch
'@modelcontextprotocol/client': patch
---
Fix runtime crash on `tools/list` when a tool's `inputSchema` comes from zod 4.04.1. The SDK requires `~standard.jsonSchema` (StandardJSONSchemaV1, added in zod 4.2.0); previously a missing `jsonSchema` crashed at `undefined[io]`. `standardSchemaToJsonSchema` now detects zod 4 schemas lacking `jsonSchema` and falls back to the SDK-bundled `z.toJSONSchema()`, emitting a one-time console warning. zod 3 schemas (which the bundled zod 4 converter cannot introspect) and non-zod schema libraries without `jsonSchema` get a clear error pointing to `fromJsonSchema()`. The workspace zod catalog is also bumped to `^4.2.0`.

0
.git-blame-ignore-revs Normal file
View File

11
.github/CODEOWNERS vendored Normal file
View File

@ -0,0 +1,11 @@
# TypeScript SDK Code Owners
# Default owners for everything in the repo
* @modelcontextprotocol/typescript-sdk
# Auth team owns all auth-related code
/src/server/auth/ @modelcontextprotocol/typescript-sdk-auth
/src/client/auth* @modelcontextprotocol/typescript-sdk-auth
/src/shared/auth* @modelcontextprotocol/typescript-sdk-auth
/src/examples/client/simpleOAuthClient.ts @modelcontextprotocol/typescript-sdk-auth
/src/examples/server/demoInMemoryOAuthProvider.ts @modelcontextprotocol/typescript-sdk-auth

6
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: 'github-actions'
directory: '/'
schedule:
interval: 'weekly'

41
.github/workflows/claude.yml vendored Normal file
View File

@ -0,0 +1,41 @@
# Source: https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
name: Claude Code
on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
issues:
types: [opened, assigned]
pull_request_review:
types: [submitted]
jobs:
claude:
if: |
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude') && !startsWith(github.event.comment.body, '@claude review')) ||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
issues: read
id-token: write
actions: read
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 1
- name: Run Claude Code
id: claude
uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
use_commit_signing: true
additional_permissions: |
actions: read

51
.github/workflows/conformance.yml vendored Normal file
View File

@ -0,0 +1,51 @@
name: Conformance Tests
on:
push:
branches: [main]
pull_request:
workflow_dispatch:
concurrency:
group: conformance-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
client-conformance:
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@v6
- name: Install pnpm
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
with:
run_install: false
- uses: actions/setup-node@v6
with:
node-version: 24
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- run: pnpm install
- run: pnpm run build:all
- run: pnpm run test:conformance:client:all
server-conformance:
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@v6
- name: Install pnpm
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
with:
run_install: false
- uses: actions/setup-node@v6
with:
node-version: 24
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- run: pnpm install
- run: pnpm run build:all
- run: pnpm run test:conformance:server

53
.github/workflows/deploy-docs.yml vendored Normal file
View File

@ -0,0 +1,53 @@
name: Deploy Docs
on:
push:
branches:
- main
- v1.x
workflow_dispatch:
concurrency:
group: deploy-docs
cancel-in-progress: true
jobs:
deploy-docs:
runs-on: ubuntu-latest
permissions:
contents: read
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- uses: actions/checkout@v6
- name: Install pnpm
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
with:
run_install: false
- uses: actions/setup-node@v6
with:
node-version: 24
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- name: Generate multi-version docs
run: bash scripts/generate-multidoc.sh tmp/docs-combined
- name: Configure Pages
uses: actions/configure-pages@v6
- name: Upload Pages artifact
uses: actions/upload-pages-artifact@v4
with:
path: tmp/docs-combined
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v5

94
.github/workflows/main.yml vendored Normal file
View File

@ -0,0 +1,94 @@
on:
push:
branches:
- main
pull_request:
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Install pnpm
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
id: pnpm-install
with:
run_install: false
- uses: actions/setup-node@v6
with:
node-version: 24
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- run: pnpm install
- run: pnpm run check:all
- run: pnpm run build:all
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
node-version: [20, 22, 24]
steps:
- uses: actions/checkout@v6
- name: Install pnpm
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
id: pnpm-install
with:
run_install: false
- uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node-version }}
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- run: pnpm install
- run: pnpm test:all
test-runtimes:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- runtime: bun
version: "1.x"
- runtime: deno
version: v2.x
steps:
- uses: actions/checkout@v6
- name: Install pnpm
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
with:
run_install: false
- uses: actions/setup-node@v6
with:
node-version: 24
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- name: Set up Bun
if: matrix.runtime == 'bun'
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: ${{ matrix.version }}
- name: Set up Deno
if: matrix.runtime == 'deno'
uses: denoland/setup-deno@667a34cdef165d8d2b2e98dde39547c9daac7282 # v2
with:
deno-version: ${{ matrix.version }}
- run: pnpm install
- run: pnpm build:all
- name: Run ${{ matrix.runtime }} integration tests
run: pnpm --filter @modelcontextprotocol/test-integration test:integration:${{ matrix.runtime }}

43
.github/workflows/publish.yml vendored Normal file
View File

@ -0,0 +1,43 @@
name: Publish Any Commit
permissions:
contents: read
on:
pull_request:
push:
branches:
- '**'
tags:
- '!**'
jobs:
pkg-publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Install pnpm
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
with:
run_install: false
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 24
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
registry-url: 'https://registry.npmjs.org'
- name: Install dependencies
run: pnpm install
- name: Build packages
run: pnpm run build:all
- name: Publish preview packages
run:
pnpm dlx pkg-pr-new publish --packageManager=npm --pnpm './packages/server' './packages/client'
'./packages/codemod' './packages/middleware/express' './packages/middleware/fastify' './packages/middleware/hono' './packages/middleware/node'

82
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,82 @@
name: Release
on:
push:
branches:
- main
concurrency: ${{ github.workflow }}-${{ github.ref }}
jobs:
version:
name: Version
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
outputs:
hasChangesets: ${{ steps.changesets.outputs.hasChangesets }}
steps:
- uses: actions/checkout@v6
- name: Install pnpm
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
with:
run_install: false
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 24
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- name: Install dependencies
run: pnpm install
- name: Create or update Version Packages PR
id: changesets
uses: changesets/action@6a0a831ff30acef54f2c6aa1cbbc1096b066edaf # v1
env:
GITHUB_TOKEN: ${{ github.token }}
publish:
name: Publish
needs: version
if: needs.version.outputs.hasChangesets == 'false'
runs-on: ubuntu-latest
environment: release
permissions:
contents: write
id-token: write
steps:
- uses: actions/checkout@v6
- name: Install pnpm
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
with:
run_install: false
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 24
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- name: Install dependencies
run: pnpm install
# pnpm@10 delegates `pnpm publish` to the npm CLI; OIDC trusted publishing
# requires npm >=11.5.1, which Node 24's bundled npm only satisfies from
# ~24.6 onward. Install a recent-enough npm so we don't depend on which Node patch resolves.
- name: Ensure npm CLI supports OIDC trusted publishing
run: npm install -g npm@11.5.1
- name: Publish to npm
uses: changesets/action@6a0a831ff30acef54f2c6aa1cbbc1096b066edaf # v1
with:
publish: pnpm run ci:publish
env:
GITHUB_TOKEN: ${{ github.token }}
NPM_CONFIG_PROVENANCE: 'true'

84
.github/workflows/update-spec-types.yml vendored Normal file
View File

@ -0,0 +1,84 @@
name: Update Spec Types
on:
schedule:
# Run nightly at 4 AM UTC
- cron: '0 4 * * *'
workflow_dispatch:
permissions:
contents: write
pull-requests: write
jobs:
update-spec-types:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Install pnpm
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
id: pnpm-install
with:
run_install: false
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 24
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- name: Install dependencies
run: pnpm install
- name: Fetch latest spec types
run: pnpm run fetch:spec-types
- name: Check for changes
id: check_changes
run: |
if git diff --quiet packages/core/src/types/spec.types.ts; then
echo "has_changes=false" >> $GITHUB_OUTPUT
else
echo "has_changes=true" >> $GITHUB_OUTPUT
LATEST_SHA=$(grep "Last updated from commit:" packages/core/src/types/spec.types.ts | cut -d: -f2 | tr -d ' ')
echo "sha=$LATEST_SHA" >> $GITHUB_OUTPUT
fi
- name: Create Pull Request
if: steps.check_changes.outputs.has_changes == 'true'
env:
GH_TOKEN: ${{ github.token }}
# Skip lefthook pre-push (typecheck/lint/build); spec drift that breaks
# typecheck should still open a PR so it can be fixed there.
LEFTHOOK: 0
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git checkout -B update-spec-types
git add packages/core/src/types/spec.types.ts
git commit -m "chore: update spec.types.ts from upstream"
git push -f --no-verify origin update-spec-types
# Create PR if it doesn't exist, or update if it does
PR_BODY="This PR updates \`packages/core/src/types/spec.types.ts\` from the Model Context Protocol specification.
Source file: https://github.com/modelcontextprotocol/modelcontextprotocol/blob/${{ steps.check_changes.outputs.sha }}/schema/draft/schema.ts
This is an automated update triggered by the nightly cron job."
# `gh pr view <branch>` matches closed PRs too, so check for an *open* PR explicitly.
EXISTING_PR=$(gh pr list --head update-spec-types --state open --json number --jq '.[0].number // empty')
if [ -n "$EXISTING_PR" ]; then
echo "PR #$EXISTING_PR already exists, updating description..."
gh pr edit "$EXISTING_PR" --body "$PR_BODY"
else
gh pr create \
--title "chore: update spec.types.ts from upstream" \
--body "$PR_BODY" \
--base main \
--head update-spec-types
fi

57
.gitignore vendored Normal file
View File

@ -0,0 +1,57 @@
# Temporary files
tmp/
# Logs
logs
*.log
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
# TypeScript cache
*.tsbuildinfo
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
.DS_Store
dist/
# IDE
.idea/
.cursor/
# Git worktrees for local doc generation
.worktrees/
# Conformance test results
results/
# Ignore local lefthook configuration
lefthook-local.yml

1
.npmrc Normal file
View File

@ -0,0 +1 @@
registry = "https://registry.npmjs.org/"

21
.prettierignore Normal file
View File

@ -0,0 +1,21 @@
# Ignore artifacts:
build
dist
coverage
*-lock.*
node_modules
**/build
**/dist
.github/CODEOWNERS
pnpm-lock.yaml
# Ignore generated files
src/spec.types.ts
# Batch test cloned repos and results
packages/codemod/batch-test/repos
packages/codemod/batch-test/results
# Quickstart examples uses 2-space indent to match ecosystem conventions
examples/client-quickstart/
examples/server-quickstart/

20
.prettierrc.json Normal file
View File

@ -0,0 +1,20 @@
{
"printWidth": 140,
"tabWidth": 4,
"useTabs": false,
"semi": true,
"singleQuote": true,
"trailingComma": "none",
"bracketSpacing": true,
"bracketSameLine": false,
"proseWrap": "always",
"arrowParens": "avoid",
"overrides": [
{
"files": "**/*.md",
"options": {
"printWidth": 280
}
}
]
}

280
CLAUDE.md Normal file
View File

@ -0,0 +1,280 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Build & Test Commands
```sh
pnpm install # Install all workspace dependencies
pnpm build:all # Build all packages
pnpm lint:all # Run ESLint + Prettier checks across all packages
pnpm lint:fix:all # Auto-fix lint and formatting issues across all packages
pnpm typecheck:all # Type-check all packages
pnpm test:all # Run all tests (vitest) across all packages
pnpm check:all # typecheck + lint across all packages
# Run a single package script (examples)
# Run a single package script from the repo root with pnpm filter
pnpm --filter @modelcontextprotocol/core test # vitest run (core)
pnpm --filter @modelcontextprotocol/core test:watch # vitest (watch)
pnpm --filter @modelcontextprotocol/core test -- path/to/file.test.ts
pnpm --filter @modelcontextprotocol/core test -- -t "test name"
```
## Breaking Changes
When making breaking changes, document them in **both**:
- `docs/migration.md` — human-readable guide with before/after code examples
- `docs/migration-SKILL.md` — LLM-optimized mapping tables for mechanical migration
Include what changed, why, and how to migrate. Search for related sections and group related changes together rather than adding new standalone sections.
## Code Style Guidelines
- **TypeScript**: Strict type checking, ES modules, explicit return types
- **Naming**: PascalCase for classes/types, camelCase for functions/variables
- **Files**: Lowercase with hyphens, test files with `.test.ts` suffix
- **Imports**: ES module style, include `.js` extension, group imports logically
- **Formatting**: 2-space indentation, semicolons required, single quotes preferred
- **Testing**: Place tests under each package's `test/` directory (vitest only includes `test/**/*.test.ts`), use descriptive test names
- **Comments**: JSDoc for public APIs, inline comments for complex logic
### JSDoc `@example` Code Snippets
JSDoc `@example` tags should pull type-checked code from companion `.examples.ts` files (e.g., `client.ts``client.examples.ts`). Use `` ```ts source="./file.examples.ts#regionName" `` fences referencing `//#region regionName` blocks; region names follow `exportedName_variant` or `ClassName_methodName_variant` pattern (e.g., `applyMiddlewares_basicUsage`, `Client_connect_basicUsage`). For whole-file inclusion (any file type), omit the `#regionName`.
Run `pnpm sync:snippets` to sync example content into JSDoc comments and markdown files.
## Architecture Overview
### Core Layers
The SDK is organized into three main layers:
1. **Types Layer** (`packages/core/src/types/types.ts`) - Protocol types generated from the MCP specification. All JSON-RPC message types, schemas, and protocol constants are defined here using Zod v4.
2. **Protocol Layer** (`packages/core/src/shared/protocol.ts`) - The abstract `Protocol` class that handles JSON-RPC message routing, request/response correlation, capability negotiation, and transport management. Both `Client` and `Server` extend this class.
3. **High-Level APIs**:
- `Client` (`packages/client/src/client/client.ts`) - Client implementation extending Protocol with typed methods for MCP operations
- `Server` (`packages/server/src/server/server.ts`) - Server implementation extending Protocol with request handler registration
- `McpServer` (`packages/server/src/server/mcp.ts`) - High-level server API with simplified resource/tool/prompt registration
### Public API Exports
The SDK has a two-layer export structure to separate internal code from the public API:
- **`@modelcontextprotocol/core`** (main entry, `packages/core/src/index.ts`) — Internal barrel. Exports everything (including Zod schemas, Protocol class, stdio utils). Only consumed by sibling packages within the monorepo (`private: true`).
- **`@modelcontextprotocol/core/public`** (`packages/core/src/exports/public/index.ts`) — Curated public API. Exports only TypeScript types, error classes, constants, and guards. Re-exported by client and server packages.
- **`@modelcontextprotocol/client`** and **`@modelcontextprotocol/server`** (`packages/*/src/index.ts`) — Final public surface. Package-specific exports (named explicitly) plus re-exports from `core/public`.
When modifying exports:
- Use explicit named exports, not `export *`, in package `index.ts` files and `core/public`.
- Adding a symbol to a package `index.ts` makes it public API — do so intentionally.
- Internal helpers should stay in the core internal barrel and not be added to `core/public` or package index files.
- The package root entry must stay runtime-neutral so browser and Cloudflare Workers bundlers can consume it. Exports whose module graph transitively touches unpolyfillable Node builtins (`node:child_process`, `node:net`, `cross-spawn`, etc.) must live at a named subpath export (e.g. `./stdio`) and be covered by a `barrelClean` test in that package.
### Transport System
Transports (`packages/core/src/shared/transport.ts`) provide the communication layer:
- **Streamable HTTP** (`packages/server/src/server/streamableHttp.ts`, `packages/client/src/client/streamableHttp.ts`) - Recommended transport for remote servers, supports SSE for streaming
- **SSE** (`packages/server/src/server/sse.ts`, `packages/client/src/client/sse.ts`) - Legacy HTTP+SSE transport for backwards compatibility
- **stdio** (`packages/server/src/server/stdio.ts`, `packages/client/src/client/stdio.ts`) - For local process-spawned integrations
### Server-Side Features
- **Tools/Resources/Prompts**: Registered via `McpServer.tool()`, `.resource()`, `.prompt()` methods
- **OAuth/Auth**: Full OAuth 2.0 server implementation in `packages/server/src/server/auth/`
- **Completions**: Auto-completion support via `packages/server/src/server/completable.ts`
### Client-Side Features
- **Auth**: OAuth client support in `packages/client/src/client/auth.ts` and `packages/client/src/client/auth-extensions.ts`
- **Client middleware**: Request middleware in `packages/client/src/client/middleware.ts` (unrelated to the framework adapter packages below)
- **Sampling**: Clients can handle `sampling/createMessage` requests from servers (LLM completions)
- **Elicitation**: Clients can handle `elicitation/create` requests for user input (form or URL mode)
- **Roots**: Clients can expose filesystem roots to servers via `roots/list`
### Middleware packages (framework/runtime adapters)
The repo also ships “middleware” packages under `packages/middleware/` (e.g. `@modelcontextprotocol/express`, `@modelcontextprotocol/hono`, `@modelcontextprotocol/node`). These are thin integration layers for specific frameworks/runtimes and should not add new MCP functionality.
### Experimental Features
Located in `packages/*/src/experimental/`:
- **Tasks**: Long-running task support with polling/resumption (`packages/core/src/experimental/tasks/`)
### Zod Schemas
The SDK uses `zod/v4` internally. Schema utilities live in:
- `packages/core/src/util/schema.ts` - AnySchema alias and helpers for inspecting Zod objects
### Validation
Pluggable JSON Schema validation (`packages/core/src/validators/`):
- `ajvProvider.ts` - Default Ajv-based validator
- `cfWorkerProvider.ts` - Cloudflare Workers-compatible alternative
### Examples
Runnable examples in `examples/`:
- `examples/server/src/` - Various server configurations (stateful, stateless, OAuth, etc.)
- `examples/client/src/` - Client examples (basic, OAuth, parallel calls, etc.)
- `examples/shared/src/` - Shared utilities (OAuth demo provider, etc.)
## Message Flow (Bidirectional Protocol)
MCP is bidirectional: both client and server can send requests. Understanding this flow is essential when implementing new request types.
### Class Hierarchy
```
Protocol (abstract base)
├── Client (packages/client/src/client/client.ts) - can send requests TO server, handle requests FROM server
└── Server (packages/server/src/server/server.ts) - can send requests TO client, handle requests FROM client
└── McpServer (packages/server/src/server/mcp.ts) - high-level wrapper around Server
```
### Outbound Flow: Sending Requests
When code calls `client.callTool()` or `server.createMessage()`:
1. **High-level method** (e.g., `Client.callTool()`) calls `this.request()`
2. **`Protocol.request()`**:
- Assigns unique message ID
- Checks capabilities via `assertCapabilityForMethod()` (abstract, implemented by Client/Server)
- Creates response handler promise
- Calls `transport.send()` with JSON-RPC request
- Waits for response handler to resolve
3. **Transport** serializes and sends over wire (HTTP, stdio, etc.)
4. **`Protocol._onresponse()`** resolves the promise when response arrives
### Inbound Flow: Handling Requests
When a request arrives from the remote side:
1. **Transport** receives message, calls `transport.onmessage()`
2. **`Protocol.connect()`** routes to `_onrequest()`, `_onresponse()`, or `_onnotification()`
3. **`Protocol._onrequest()`**:
- Looks up handler in `_requestHandlers` map (keyed by method name)
- Creates `BaseContext` with `signal`, `sessionId`, `sendNotification`, `sendRequest`, etc.
- Calls `buildContext()` to let subclasses enrich the context (e.g., Server adds HTTP request info)
- Invokes handler, sends JSON-RPC response back via transport
4. **Handler** was registered via `setRequestHandler('method', handler)`
### Handler Registration
```typescript
// In Client (for server→client requests like sampling, elicitation)
client.setRequestHandler('sampling/createMessage', async (request, ctx) => {
// Handle sampling request from server
return { role: "assistant", content: {...}, model: "..." };
});
// In Server (for client→server requests like tools/call)
server.setRequestHandler('tools/call', async (request, ctx) => {
// Handle tool call from client
return { content: [...] };
});
```
### Request Handler Context
The `ctx` parameter in handlers provides a structured context:
**`BaseContext`** (common to both Server and Client), fields organized into nested groups:
- `sessionId?`: Transport session identifier
- `mcpReq`: Request-level concerns
- `id`: JSON-RPC message ID
- `method`: Request method string (e.g., 'tools/call')
- `_meta?`: Request metadata
- `signal`: AbortSignal for cancellation
- `send(request, schema, options?)`: Send related request (for bidirectional flows)
- `notify(notification)`: Send related notification back
- `http?`: HTTP transport info (undefined for stdio)
- `authInfo?`: Validated auth token info
- `task?`: Task context (`{ id?, store, requestedTtl? }`) when task storage is configured
**`ServerContext`** extends `BaseContext.mcpReq` and `BaseContext.http?` via type intersection:
- `mcpReq` adds: `log(level, data, logger?)`, `elicitInput(params, options?)`, `requestSampling(params, options?)`
- `http?` adds: `req?` (HTTP request info), `closeSSE?`, `closeStandaloneSSE?`
**`ClientContext`** is currently identical to `BaseContext`.
### Capability Checking
Both sides declare capabilities during initialization. The SDK enforces these:
- **Client→Server**: `Client.assertCapabilityForMethod()` checks `_serverCapabilities`
- **Server→Client**: `Server.assertCapabilityForMethod()` checks `_clientCapabilities`
- **Handler registration**: `assertRequestHandlerCapability()` validates local capabilities
### Adding a New Request Type
1. **Define schema** in `src/types.ts` (request params, result schema)
2. **Add capability** to `ClientCapabilities` or `ServerCapabilities` in types
3. **Implement sender** method in Client or Server class
4. **Add capability check** in the appropriate `assertCapabilityForMethod()`
5. **Register handler** on the receiving side with `setRequestHandler()`
6. **For McpServer**: Add high-level wrapper method if needed
### Server-Initiated Requests (Sampling, Elicitation)
Server can request actions from client (requires client capability):
```typescript
// Server sends sampling request to client
const result = await server.createMessage({
messages: [...],
maxTokens: 100
});
// Client must have registered handler:
client.setRequestHandler('sampling/createMessage', async (request, extra) => {
// Client-side LLM call
return { role: "assistant", content: {...} };
});
```
## Key Patterns
### Request Handler Registration (Low-Level Server)
```typescript
server.setRequestHandler('tools/call', async (request, extra) => {
// extra contains sessionId, authInfo, sendNotification, etc.
return {
/* result */
};
});
```
### Tool Registration (High-Level McpServer)
```typescript
mcpServer.tool('tool-name', { param: z.string() }, async ({ param }, extra) => {
return { content: [{ type: 'text', text: 'result' }] };
});
```
### Transport Connection
```typescript
// Server
// (Node.js IncomingMessage/ServerResponse wrapper; exported by @modelcontextprotocol/node)
const transport = new NodeStreamableHTTPServerTransport({ sessionIdGenerator: () => randomUUID() });
await server.connect(transport);
// Client
const transport = new StreamableHTTPClientTransport(new URL('http://localhost:3000/mcp'));
await client.connect(transport);
```

83
CODE_OF_CONDUCT.md Normal file
View File

@ -0,0 +1,83 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience,
education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our community include:
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
- Focusing on what is best not just for us as individuals, but for the overall community
Examples of unacceptable behavior include:
- The use of sexualized language or imagery, and sexual attention or advances of any kind
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email address, without their explicit permission
- Other conduct which could reasonably be considered inappropriate in a professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account,
or acting as an appointed representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at <mcp-coc@anthropic.com>. All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series of actions.
**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as
well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is
allowed during this period. Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at <https://www.contributor-covenant.org/version/2/0/code_of_conduct.html>.
Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at <https://www.contributor-covenant.org/faq>. Translations are available at <https://www.contributor-covenant.org/translations>.

187
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,187 @@
# Contributing to MCP TypeScript SDK
Welcome, and thanks for your interest in contributing! We're glad you're here.
This document outlines how to contribute effectively to the TypeScript SDK.
## Issues
### Discuss Before You Code
**Please open an issue before starting work on new features or significant changes.** This gives us a chance to align on approach and save you time if we see potential issues.
We'll close PRs for undiscussed features—not because we don't appreciate the effort, but because every merged feature becomes an ongoing maintenance burden for our small team of maintainers. Talking first helps us figure out together whether something belongs in the SDK.
Straightforward bug fixes (a few lines of code with tests demonstrating the fix) can skip this step. For complex bugs that need significant changes, consider opening an issue first.
### What Counts as "Significant"?
- New public APIs or classes
- Architectural changes or refactoring
- Changes that touch multiple modules
- Features that might require spec changes (these need a [SEP](https://modelcontextprotocol.io/community/sep-guidelines) first)
### Writing Good Issues
Help us help you:
- Lead with what's broken or what you need
- Include code we can run to see the problem
- Keep it focused—a clear problem statement goes a long way
We're a small team, so issues that include some upfront debugging help us move faster. Low-effort or obviously AI-generated issues will be closed.
### Finding Issues to Work On
| Label | For | Description |
| ----------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | --------------------------------------------- |
| [`good first issue`](https://github.com/modelcontextprotocol/typescript-sdk/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22) | Newcomers | Can tackle without deep codebase knowledge |
| [`help wanted`](https://github.com/modelcontextprotocol/typescript-sdk/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22) | Experienced contributors | Maintainers probably won't get to this |
| [`ready for work`](https://github.com/modelcontextprotocol/typescript-sdk/issues?q=is%3Aopen+is%3Aissue+label%3A%22ready+for+work%22) | Maintainers | Triaged and ready for a maintainer to pick up |
Issues labeled `needs confirmation`, `needs repro`, or `needs design` are **not** ready for work—wait for maintainer input before starting.
Before starting work, comment on the issue so we can assign it to you. This lets others know and avoids duplicate effort.
## Pull Requests
By the time you open a PR, the "what" and "why" should already be settled in an issue. This keeps PR reviews focused on implementation rather than revisiting whether we should do it at all.
### Branches
This repository has two main branches:
- **`main`** v2 of the SDK (currently in development). This is a monorepo with split packages.
- **`v1.x`** stable v1 release. Bug fixes and patches for v1 should target this branch.
**Which branch should I use as a base?**
- For **new features** or **v2-related work**: base your PR on `main`
- For **v1 bug fixes** or **patches**: base your PR on `v1.x`
### Scope
Small PRs get reviewed fast. Large PRs sit in the queue.
We can review a few dozen lines in a few minutes. But a PR touching hundreds of lines across many files takes real effort to verify—and things inevitably slip through. If your change is big, break it into a stack of smaller PRs or get clear alignment from a maintainer on your
approach in an issue before submitting a large PR.
### What Gets Rejected
PRs may be rejected for:
- **Lack of prior discussion** — Features or significant changes without an approved issue
- **Scope creep** — Changes that go beyond what was discussed or add unrequested features
- **Misalignment with SDK direction** — Even well-implemented features may be rejected if they don't fit the SDK's goals
- **Insufficient quality** — Code that doesn't meet clarity, maintainability, or style standards
- **Overengineering** — Unnecessary complexity or abstraction for simple problems
### Submitting Your PR
1. Follow the existing code style
2. Include tests for new functionality
3. Update documentation as needed
4. Keep changes focused and atomic
5. Provide a clear description of changes
## Development
### Getting Started
This project uses [pnpm](https://pnpm.io/) as its package manager. If you don't have pnpm installed, enable it via [corepack](https://nodejs.org/api/corepack.html) (included with Node.js 16.9+):
```bash
corepack enable
```
Then:
1. Fork the repository
2. Clone your fork: `git clone https://github.com/YOUR-USERNAME/typescript-sdk.git`
3. Install dependencies: `pnpm install`
4. Build the project: `pnpm build:all`
5. Run tests: `pnpm test:all`
### Workflow
1. Create a new branch for your changes (based on `main` or `v1.x` as appropriate)
2. Make your changes
3. Run `pnpm lint:all` to ensure code style compliance
4. Run `pnpm test:all` to verify all tests pass
5. Submit a pull request
### Running Examples
See [`examples/server/README.md`](examples/server/README.md) and [`examples/client/README.md`](examples/client/README.md) for a full list of runnable examples.
Quick start:
```bash
# Run a server example
pnpm --filter @modelcontextprotocol/examples-server exec tsx src/simpleStreamableHttp.ts
# Run a client example (in another terminal)
pnpm --filter @modelcontextprotocol/examples-client exec tsx src/simpleStreamableHttp.ts
```
## Releasing v1.x Patches
The `v1.x` branch contains the stable v1 release. To release a patch:
### Latest v1.x (e.g., v1.25.3)
```bash
git checkout v1.x
git pull origin v1.x
# Apply your fix or cherry-pick commits
npm version patch # Bumps version and creates tag (e.g., v1.25.3)
git push origin v1.x --tags
```
The tag push automatically triggers the release workflow.
### Older minor versions (e.g., v1.23.2)
For patching older minor versions that aren't on the `v1.x` branch:
```bash
# 1. Create a release branch from the last release tag
git checkout -b release/1.23 v1.23.1
# 2. Apply your fixes (cherry-pick or manual)
git cherry-pick <commit-hash>
# 3. Bump version and push
npm version patch # Creates v1.23.2 tag
git push origin release/1.23 --tags
```
Then manually trigger the "Publish v1.x" workflow from [GitHub Actions](https://github.com/modelcontextprotocol/typescript-sdk/actions/workflows/release-v1x.yml), specifying the tag (e.g., `v1.23.2`).
### npm Tags
v1.x releases are published with `release-X.Y` npm tags (e.g., `release-1.25`), not `latest`. To install a specific minor version:
```bash
npm install @modelcontextprotocol/sdk@release-1.25
```
## Policies
### Code of Conduct
This project follows our [Code of Conduct](CODE_OF_CONDUCT.md). Please review it before contributing.
### Reporting Issues
- Use the [GitHub issue tracker](https://github.com/modelcontextprotocol/typescript-sdk/issues)
- Search existing issues before creating a new one
- Provide clear reproduction steps
### Security Issues
Please review our [Security Policy](SECURITY.md) for reporting security vulnerabilities.
### License
By contributing, you agree that your code contributions will be licensed under the Apache License 2.0. Documentation contributions (excluding specifications) are licensed under CC-BY 4.0. See the [LICENSE](LICENSE) file for details.

216
LICENSE Normal file
View File

@ -0,0 +1,216 @@
The MCP project is undergoing a licensing transition from the MIT License to the Apache License, Version 2.0 ("Apache-2.0"). All new code and specification contributions to the project are licensed under Apache-2.0. Documentation contributions (excluding specifications) are licensed under CC-BY-4.0.
Contributions for which relicensing consent has been obtained are licensed under Apache-2.0. Contributions made by authors who originally licensed their work under the MIT License and who have not yet granted explicit permission to relicense remain licensed under the MIT License.
No rights beyond those granted by the applicable original license are conveyed for such contributions.
---
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to the Licensor for inclusion in the Work by the copyright
owner or by an individual or Legal Entity authorized to submit on behalf
of the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
---
MIT License
Copyright (c) 2024-2025 Model Context Protocol a Series of LF Projects, LLC.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
---
Creative Commons Attribution 4.0 International (CC-BY-4.0)
Documentation in this project (excluding specifications) is licensed under
CC-BY-4.0. See https://creativecommons.org/licenses/by/4.0/legalcode for
the full license text.

168
README.md Normal file
View File

@ -0,0 +1,168 @@
# MCP TypeScript SDK
<!-- prettier-ignore -->
> [!IMPORTANT]
> **This is the `main` branch which contains v2 of the SDK (currently in development, pre-alpha).**
>
> We anticipate a stable v2 release in Q1 2026. Until then, **v1.x remains the recommended version** for production use. v1.x will continue to receive bug fixes and security updates for at least 6 months after v2 ships to give people time to upgrade.
>
> For v1 documentation, see the [V1 API docs](https://ts.sdk.modelcontextprotocol.io/). For v2 API docs, see [`/v2/`](https://ts.sdk.modelcontextprotocol.io/v2/).
[![NPM Version - Server](https://img.shields.io/npm/v/%40modelcontextprotocol%2Fserver?label=%40modelcontextprotocol%2Fserver)](https://www.npmjs.com/package/@modelcontextprotocol/server)
[![NPM Version - Client](https://img.shields.io/npm/v/%40modelcontextprotocol%2Fclient?label=%40modelcontextprotocol%2Fclient)](https://www.npmjs.com/package/@modelcontextprotocol/client) ![MIT licensed](https://img.shields.io/npm/l/%40modelcontextprotocol%2Fserver)
<details>
<summary>Table of Contents</summary>
- [Overview](#overview)
- [Packages](#packages)
- [Installation](#installation)
- [Getting Started](#getting-started)
- [Documentation](#documentation)
- [Contributing](#contributing)
- [License](#license)
</details>
## Overview
The Model Context Protocol (MCP) allows applications to provide context for LLMs in a standardized way, separating the concerns of providing context from the actual LLM interaction.
This repository contains the TypeScript SDK implementation of the MCP specification. It runs on **Node.js**, **Bun**, and **Deno**, and ships:
- MCP **server** libraries (tools/resources/prompts, Streamable HTTP, stdio, auth helpers)
- MCP **client** libraries (transports, high-level helpers, OAuth helpers)
- Optional **middleware packages** for specific runtimes/frameworks (Express, Hono, Node.js HTTP)
- Runnable **examples** (under [`examples/`](https://github.com/modelcontextprotocol/typescript-sdk/tree/main/examples))
## Packages
This monorepo publishes split packages:
- **`@modelcontextprotocol/server`**: build MCP servers
- **`@modelcontextprotocol/client`**: build MCP clients
Tool and prompt schemas use [Standard Schema](https://standardschema.dev/) — bring Zod v4, Valibot, ArkType, or any compatible library.
### Middleware packages (optional)
The SDK also publishes small "middleware" packages under [`packages/middleware/`](https://github.com/modelcontextprotocol/typescript-sdk/tree/main/packages/middleware) that help you **wire MCP into a specific runtime or web framework**.
They are intentionally thin adapters: they should not introduce new MCP functionality or business logic. See [`packages/middleware/README.md`](packages/middleware/README.md) for details.
- **`@modelcontextprotocol/node`**: Node.js Streamable HTTP transport wrapper for `IncomingMessage` / `ServerResponse`
- **`@modelcontextprotocol/express`**: Express helpers (app defaults + Host header validation)
- **`@modelcontextprotocol/hono`**: Hono helpers (app defaults + JSON body parsing hook + Host header validation)
## Installation
### Server
```bash
npm install @modelcontextprotocol/server
# or
bun add @modelcontextprotocol/server
# or
deno add npm:@modelcontextprotocol/server
```
### Client
```bash
npm install @modelcontextprotocol/client
# or
bun add @modelcontextprotocol/client
# or
deno add npm:@modelcontextprotocol/client
```
### Optional middleware packages
The SDK also publishes optional “middleware” packages that help you **wire MCP into a specific runtime or web framework** (for example Express, Hono, or Node.js `http`).
These packages are intentionally thin adapters and should not introduce additional MCP features or business logic. See [`packages/middleware/README.md`](packages/middleware/README.md) for details.
```bash
# Node.js HTTP (IncomingMessage/ServerResponse) Streamable HTTP transport:
npm install @modelcontextprotocol/node
# Express integration:
npm install @modelcontextprotocol/express express
# Hono integration:
npm install @modelcontextprotocol/hono hono
```
## Getting Started
Here is what an MCP server looks like. This minimal example exposes a single `greet` tool over stdio:
```typescript
import { McpServer } from '@modelcontextprotocol/server';
import { StdioServerTransport } from '@modelcontextprotocol/server/stdio';
import * as z from 'zod/v4';
const server = new McpServer({ name: 'greeting-server', version: '1.0.0' });
server.registerTool(
'greet',
{
description: 'Greet someone by name',
inputSchema: z.object({ name: z.string() })
},
async ({ name }) => ({
content: [{ type: 'text', text: `Hello, ${name}!` }]
})
);
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
}
main();
```
Ready to build something real? Follow the step-by-step quickstart tutorials:
- [Build a weather server](docs/server-quickstart.md) — server quickstart
- [Build an LLM-powered chatbot](docs/client-quickstart.md) — client quickstart
The complete code for each tutorial is in [`examples/server-quickstart/`](https://github.com/modelcontextprotocol/typescript-sdk/tree/main/examples/server-quickstart/) and
[`examples/client-quickstart/`](https://github.com/modelcontextprotocol/typescript-sdk/tree/main/examples/client-quickstart/). For more advanced runnable examples, see:
- [`examples/server/README.md`](examples/server/README.md) — server examples index
- [`examples/client/README.md`](examples/client/README.md) — client examples index
## Documentation
- [Server Guide](docs/server.md) — building MCP servers: transports, tools, resources, prompts, server-initiated requests, and deployment
- [Client Guide](docs/client.md) — building MCP clients: connecting, tools, resources, prompts, server-initiated requests, and error handling
- [FAQ](docs/faq.md) — frequently asked questions and troubleshooting
- [API docs](https://modelcontextprotocol.github.io/typescript-sdk/)
- [MCP documentation](https://modelcontextprotocol.io/docs)
- [MCP specification](https://modelcontextprotocol.io/specification/latest)
### Building docs locally
To generate the API reference documentation locally:
```bash
pnpm docs # Generate V2 docs only (output: tmp/docs/)
pnpm docs:multi # Generate combined V1 + V2 docs (output: tmp/docs-combined/)
```
The `docs:multi` script checks out both the `v1.x` and `main` branches via git worktrees, builds each, and produces a combined site with V1 docs at the root and V2 docs under `/v2/`.
## v1 (legacy) documentation and fixes
If you are using the **v1** generation of the SDK, the **v1 API documentation** is available at [`https://ts.sdk.modelcontextprotocol.io/`](https://ts.sdk.modelcontextprotocol.io/). The v1 source code and any v1-specific fixes live on the long-lived
[`v1.x` branch](https://github.com/modelcontextprotocol/typescript-sdk/tree/v1.x). V2 API docs are at [`/v2/`](https://ts.sdk.modelcontextprotocol.io/v2/).
## Contributing
Issues and pull requests are welcome on GitHub at <https://github.com/modelcontextprotocol/typescript-sdk>.
## License
This project is licensed under the Apache License 2.0 for new contributions, with existing code under MIT. See the [LICENSE](LICENSE) file for details.

95
REVIEW.md Normal file
View File

@ -0,0 +1,95 @@
# typescript-sdk Review Conventions
Guidance for reviewing pull requests on this repository. The first three sections are
stable principles; the **Recurring Catches** section is auto-maintained from past human
review rounds and grows over time.
## Guiding Principles
1. **Minimalism** — The SDK should do less, not more. Protocol correctness, transport
lifecycle, types, and clean handler context belong in the SDK. Middleware engines,
registry managers, builder patterns, and content helpers belong in userland.
2. **Burden of proof is on addition** — The default answer to "should we add this?" is
no. Removing something from the public API is far harder than not adding it.
3. **Justify with concrete evidence** — Every new abstraction needs a concrete consumer
today. Ask for real issues, benchmarks, real-world examples; apply the same standard
to your own review (link spec sections, link code, show the simpler alternative).
4. **Spec is the anchor** — The SDK implements the protocol spec. The further a feature
drifts from the spec, the stronger the justification needs to be.
5. **Kill at the highest level** — If the design is wrong, don't review the
implementation. Lead with the highest-level concern; specific bugs are supporting
detail.
6. **Decompose by default** — A PR doing multiple things should be multiple PRs unless
there's a strong reason to bundle.
## Review Ordering
1. **Design justification** — Is the overall approach sound? Is the complexity warranted?
2. **Structural concerns** — Is the architecture right? Are abstractions justified?
3. **Correctness** — Bugs, regressions, missing functionality.
4. **Style and naming** — Nits, conventions, documentation.
## Checklist
**Protocol & spec**
- Types match [`schema.ts`](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/schema/draft/schema.ts) exactly (optional vs required fields)
- Correct `ProtocolError` codes (enum `ProtocolErrorCode`); HTTP status codes match spec (e.g., 404 vs 410)
- Works for both stdio and Streamable HTTP transports — no transport-specific assumptions
- Cross-SDK consistency: check what `python-sdk` does for the same feature
**API surface**
- Every new export is intentional (see CLAUDE.md § Public API Exports); helpers users can write themselves belong in a cookbook, not the SDK
- New abstractions have at least one concrete callsite in the PR
- One way to do things — improving an existing API beats adding a parallel one
**Correctness**
- Async: race conditions, cleanup on cancellation, unhandled rejections, missing `await`
- Error propagation: caught/rethrown properly, resources cleaned up on error paths
- Type safety: no unjustified `any`, no unsafe `as` assertions
- Backwards compat: public-interface changes, default changes, removed exports — flagged and justified
**Tests & docs**
- New behavior has vitest coverage including error paths
- Breaking changes documented in `docs/migration.md` and `docs/migration-SKILL.md`
- Bugfix or behavior change: check whether `docs/**/*.md` describes the old behavior and needs updating; flag prose that now contradicts the implementation
- New feature: verify prose documentation is added (not just JSDoc), and assess whether `examples/` needs a new or updated example
- Behavior change: assess whether existing `examples/` still compile and demonstrate the current API
## Reference
When verifying spec compliance, consult the spec directly rather than relying on memory:
- MCP documentation server: `https://modelcontextprotocol.io/mcp`
- Full spec text (single file, LLM-friendly): `https://modelcontextprotocol.io/llms-full.txt` — fetch to a temp file and grep for the relevant section
- Schema source of truth: [`schema.ts`](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/schema/draft/schema.ts)
## Recurring Catches
### HTTP Transport
- When validating `Mcp-Session-Id`, return **400** for a missing header and **404** for an unknown/expired session — never conflate `!sessionId || !transports[sessionId]` into one status, because the client needs to distinguish "fix your request" from "start a new session". Flag any diff that branches on session-id presence/lookup with a single 4xx. (#1707, #1770)
### Error Handling
- Broad `catch` blocks must not emit client-fault JSON-RPC codes (`-32700` ParseError, `-32602` InvalidParams) for server-internal failures like stream setup, task-store misses, or polling errors — map those to `-32603` InternalError so clients don't retry/reformat pointlessly. Flag any catch-all that hard-codes ParseError/InvalidParams without discriminating the thrown cause. (#1752, #1769)
### Schema Compliance
- When editing Zod protocol schemas in `schemas.ts`, verify unknown-key handling matches the spec `schema.ts`: if the spec type has no `additionalProperties: false`, the SDK schema must use `z.looseObject()` / `.catchall(z.unknown())` rather than implicit strict — over-strict Zod (incl. `z.literal('object')` on `type`) rejects spec-valid payloads from other SDKs. Also confirm `spec.types.test.ts` still passes bidirectionally. (#1768, #1849, #1169)
### Async / Lifecycle
- In `close()` / shutdown paths, wrap user-supplied or chained callbacks (`onclose?.()`, cancel fns) in `try/finally` so a throw can't skip the remaining teardown (`abort()`, `_onclose()`, map clears) — otherwise the transport is left half-open. (#1735, #1763)
- Deferred callbacks (`setTimeout`, `.finally()`, reconnect closures) must check closed/aborted state before mutating `this._*` or starting I/O — a callback scheduled pre-close can fire after close/reconnect and corrupt the new connection's state (e.g., delete the new request's `AbortController`). (#1735, #1763)
### Completeness
- When a PR replaces a pattern (error class, auth-flow step, catch shape), grep the package for surviving instances of the old form — partial migrations leave sibling code paths with the very bug the PR claims to fix. Flag every leftover site. (#1657, #1761, #1595)
### Documentation & Changesets
- Read added `.changeset/*.md` text and new inline comments against the implementation in the same diff — prose that promises behavior the code no longer ships misleads consumers and contradicts stated intent. Flag any claim the diff doesn't back. (#1718, #1838)
### CI & GitHub Actions
- Do **not** assert that a third-party GitHub Action or publish toolchain will fail or needs extra permissions/tokens without verifying its docs or source — `pnpm publish` delegates to the system npm CLI (so npm OIDC works), and `changesets/action` in publish mode has no PR-comment step requiring `pull-requests: write`. For diffs under `.github/workflows/`, confirm claimed behavior in the action's README/source before flagging. (#1838, #1836)

21
SECURITY.md Normal file
View File

@ -0,0 +1,21 @@
# Security Policy
Thank you for helping keep the Model Context Protocol and its ecosystem secure.
## Reporting Security Issues
If you discover a security vulnerability in this repository, please report it through
the [GitHub Security Advisory process](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability)
for this repository.
Please **do not** report security vulnerabilities through public GitHub issues, discussions,
or pull requests.
## What to Include
To help us triage and respond quickly, please include:
- A description of the vulnerability
- Steps to reproduce the issue
- The potential impact
- Any suggested fixes (optional)

View File

@ -0,0 +1,109 @@
// @ts-check
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import eslint from '@eslint/js';
import { defineConfig } from 'eslint/config';
import eslintConfigPrettier from 'eslint-config-prettier/flat';
import importPlugin from 'eslint-plugin-import';
import nodePlugin from 'eslint-plugin-n';
import simpleImportSortPlugin from 'eslint-plugin-simple-import-sort';
import eslintPluginUnicorn from 'eslint-plugin-unicorn';
import { configs } from 'typescript-eslint';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
export default defineConfig(
eslint.configs.recommended,
...configs.recommended,
importPlugin.flatConfigs.recommended,
importPlugin.flatConfigs.typescript,
eslintPluginUnicorn.configs.recommended,
{
languageOptions: {
parserOptions: {
// Ensure consumers of this shared config get a stable tsconfig root
tsconfigRootDir: __dirname
}
},
linterOptions: {
reportUnusedDisableDirectives: false
},
plugins: {
n: nodePlugin,
'simple-import-sort': simpleImportSortPlugin
},
settings: {
'import/resolver': {
typescript: {
// Let the TS resolver handle NodeNext-style imports like "./foo.js"
extensions: ['.js', '.jsx', '.ts', '.tsx', '.d.ts'],
// Use the tsconfig in each package root (when running ESLint from that package)
project: 'tsconfig.json'
}
}
},
rules: {
'unicorn/prevent-abbreviations': 'off',
'unicorn/no-null': 'off',
'unicorn/prefer-add-event-listener': 'off',
'unicorn/no-useless-undefined': ['error', { checkArguments: false }],
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'n/prefer-node-protocol': 'error',
'@typescript-eslint/consistent-type-imports': ['error', { disallowTypeAnnotations: false }],
'simple-import-sort/imports': 'warn',
'simple-import-sort/exports': 'warn',
'import/consistent-type-specifier-style': ['error', 'prefer-top-level'],
'import/no-extraneous-dependencies': [
'error',
{
devDependencies: [
'**/test/**',
'**/*.test.ts',
'**/*.test.tsx',
'**/scripts/**',
'**/vitest.config.*',
'**/tsdown.config.*',
'**/eslint.config.*',
'**/vitest.setup.*'
],
optionalDependencies: false,
peerDependencies: true
}
],
'unicorn/filename-case': [
'error',
{
case: 'camelCase'
}
]
}
},
{
// Disable consistent-function-scoping in test files where helper functions are common
files: ['**/*.test.ts', '**/*.test.tsx', '**/test/**'],
rules: {
'unicorn/consistent-function-scoping': 'off'
}
},
{
// Example files contain intentionally unused functions (one per region)
files: ['**/*.examples.ts'],
rules: {
'@typescript-eslint/no-unused-vars': 'off',
'no-console': 'off'
}
},
{
// Ignore generated protocol types everywhere
ignores: ['**/spec.types.ts']
},
{
files: ['packages/client/**/*.ts', 'packages/server/**/*.ts'],
ignores: ['**/*.test.ts'],
rules: {
'no-console': 'error'
}
},
eslintConfigPrettier
);

View File

@ -0,0 +1,37 @@
{
"name": "@modelcontextprotocol/eslint-config",
"private": true,
"main": "eslint.config.mjs",
"type": "module",
"exports": {
".": "./eslint.config.mjs"
},
"dependencies": {
"typescript": "catalog:devTools"
},
"repository": {
"type": "git",
"url": "https://github.com/modelcontextprotocol/typescript-sdk.git"
},
"bugs": {
"url": "https://github.com/modelcontextprotocol/typescript-sdk/issues"
},
"homepage": "https://github.com/modelcontextprotocol/typescript-sdk/tree/develop/common/eslint-config",
"publishConfig": {
"registry": "https://npm.pkg.github.com/"
},
"version": "2.0.0",
"devDependencies": {
"@eslint/js": "catalog:devTools",
"eslint": "catalog:devTools",
"eslint-config-prettier": "catalog:devTools",
"eslint-import-resolver-typescript": "^4.4.4",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-n": "catalog:devTools",
"eslint-plugin-simple-import-sort": "^12.1.1",
"eslint-plugin-unicorn": "^62.0.0",
"prettier": "catalog:devTools",
"typescript": "catalog:devTools",
"typescript-eslint": "catalog:devTools"
}
}

View File

@ -0,0 +1,21 @@
{
"name": "@modelcontextprotocol/tsconfig",
"private": true,
"main": "tsconfig.json",
"type": "module",
"dependencies": {
"typescript": "catalog:devTools"
},
"repository": {
"type": "git",
"url": "https://github.com/modelcontextprotocol/typescript-sdk.git"
},
"bugs": {
"url": "https://github.com/modelcontextprotocol/typescript-sdk/issues"
},
"homepage": "https://github.com/modelcontextprotocol/typescript-sdk/tree/develop/common/ts-config",
"publishConfig": {
"registry": "https://npm.pkg.github.com/"
},
"version": "2.0.0"
}

View File

@ -0,0 +1,31 @@
{
"compilerOptions": {
"target": "esnext",
"lib": ["esnext"],
"module": "NodeNext",
"moduleResolution": "NodeNext",
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"libReplacement": false,
"noImplicitReturns": true,
"incremental": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "./dist",
"strict": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"isolatedModules": true,
"skipLibCheck": true,
"paths": {
"pkce-challenge": ["./node_modules/pkce-challenge/dist/index.node"]
},
"types": ["node", "vitest/globals"]
}
}

View File

@ -0,0 +1,28 @@
{
"name": "@modelcontextprotocol/vitest-config",
"private": true,
"main": "vitest.config.mjs",
"type": "module",
"exports": {
".": "./vitest.config.js"
},
"dependencies": {
"typescript": "catalog:devTools"
},
"repository": {
"type": "git",
"url": "https://github.com/modelcontextprotocol/typescript-sdk.git"
},
"bugs": {
"url": "https://github.com/modelcontextprotocol/typescript-sdk/issues"
},
"homepage": "https://github.com/modelcontextprotocol/typescript-sdk/tree/develop/common/vitest-config",
"publishConfig": {
"registry": "https://npm.pkg.github.com/"
},
"version": "2.0.0",
"devDependencies": {
"@modelcontextprotocol/tsconfig": "workspace:^",
"vite-tsconfig-paths": "catalog:devTools"
}
}

View File

@ -0,0 +1,8 @@
{
"include": ["./"],
"extends": "@modelcontextprotocol/tsconfig",
"compilerOptions": {
"noEmit": true,
"allowJs": true
}
}

View File

@ -0,0 +1,25 @@
import { defineConfig } from 'vitest/config';
import tsconfigPaths from 'vite-tsconfig-paths';
import path from 'node:path';
import url from 'node:url';
const ignorePatterns = ['**/dist/**'];
const __dirname = path.dirname(url.fileURLToPath(import.meta.url));
export default defineConfig({
test: {
globals: true,
environment: 'node',
include: ['test/**/*.test.ts'],
exclude: ignorePatterns,
deps: {
moduleDirectories: ['node_modules', path.resolve(__dirname, '../../packages'), path.resolve(__dirname, '../../common')]
}
},
poolOptions: {
threads: {
useAtomics: true
}
},
plugins: [tsconfigPaths()]
});

424
docs/client-quickstart.md Normal file
View File

@ -0,0 +1,424 @@
---
title: Client Quickstart
---
# Quickstart: Build an LLM-powered chatbot
In this tutorial, we'll build an LLM-powered chatbot that connects to an MCP server, discovers its tools, and uses Claude to call them.
Before you begin, it helps to have gone through the [server quickstart](./server-quickstart.md) so you understand how clients and servers communicate.
[You can find the complete code for this tutorial here.](https://github.com/modelcontextprotocol/typescript-sdk/tree/main/examples/client-quickstart)
## Prerequisites
This quickstart assumes you have familiarity with:
- TypeScript
- LLMs like Claude
Before starting, ensure your system meets these requirements:
- Node.js 20 or higher installed (or **Bun** / **Deno** — the SDK supports all three runtimes)
- Latest version of `npm` installed
- An Anthropic API key from the [Anthropic Console](https://console.anthropic.com/settings/keys)
> [!TIP]
> This tutorial uses Node.js and npm, but you can substitute `bun` or `deno` commands where appropriate. For example, use `bun add` instead of `npm install`, or run the client with `bun run` / `deno run`.
## Set up your environment
First, let's create and set up our project:
**macOS/Linux:**
```bash
# Create project directory
mkdir mcp-client
cd mcp-client
# Initialize npm project
npm init -y
# Install dependencies
npm install @anthropic-ai/sdk @modelcontextprotocol/client
# Install dev dependencies
npm install -D @types/node typescript
# Create source file
mkdir src
touch src/index.ts
```
**Windows:**
```powershell
# Create project directory
md mcp-client
cd mcp-client
# Initialize npm project
npm init -y
# Install dependencies
npm install @anthropic-ai/sdk @modelcontextprotocol/client
# Install dev dependencies
npm install -D @types/node typescript
# Create source file
md src
new-item src\index.ts
```
Update your `package.json` to set `type: "module"` and a build script:
```json
{
"type": "module",
"scripts": {
"build": "tsc"
}
}
```
Create a `tsconfig.json` in the root of your project:
```json
{
"compilerOptions": {
"target": "ES2023",
"lib": ["ES2023"],
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./build",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
```
## Creating the client
### Basic client structure
First, let's set up our imports and create the basic client class in `src/index.ts`:
```ts source="../examples/client-quickstart/src/index.ts#prelude"
import Anthropic from '@anthropic-ai/sdk';
import { Client } from '@modelcontextprotocol/client';
import { StdioClientTransport } from '@modelcontextprotocol/client/stdio';
import readline from 'readline/promises';
const ANTHROPIC_MODEL = 'claude-sonnet-4-5';
class MCPClient {
private mcp: Client;
private _anthropic: Anthropic | null = null;
private transport: StdioClientTransport | null = null;
private tools: Anthropic.Tool[] = [];
constructor() {
// Initialize MCP client
this.mcp = new Client({ name: 'mcp-client-cli', version: '1.0.0' });
}
private get anthropic(): Anthropic {
// Lazy-initialize Anthropic client when needed
return this._anthropic ??= new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
}
```
### Server connection management
Next, we'll implement the method to connect to an MCP server:
```ts source="../examples/client-quickstart/src/index.ts#connectToServer"
async connectToServer(serverScriptPath: string) {
try {
// Determine script type and appropriate command
const isJs = serverScriptPath.endsWith('.js');
const isPy = serverScriptPath.endsWith('.py');
if (!isJs && !isPy) {
throw new Error('Server script must be a .js or .py file');
}
const command = isPy
? (process.platform === 'win32' ? 'python' : 'python3')
: process.execPath;
// Initialize transport and connect to server
this.transport = new StdioClientTransport({ command, args: [serverScriptPath] });
await this.mcp.connect(this.transport);
// List available tools
const toolsResult = await this.mcp.listTools();
this.tools = toolsResult.tools.map((tool) => ({
name: tool.name,
description: tool.description ?? '',
input_schema: tool.inputSchema as Anthropic.Tool.InputSchema,
}));
console.log('Connected to server with tools:', this.tools.map(({ name }) => name));
} catch (e) {
console.log('Failed to connect to MCP server: ', e);
throw e;
}
}
```
### Query processing logic
Now let's add the core functionality for processing queries and handling tool calls:
```ts source="../examples/client-quickstart/src/index.ts#processQuery"
async processQuery(query: string) {
const messages: Anthropic.MessageParam[] = [
{
role: 'user',
content: query,
},
];
// Initial Claude API call
const response = await this.anthropic.messages.create({
model: ANTHROPIC_MODEL,
max_tokens: 1000,
messages,
tools: this.tools,
});
// Process response and handle tool calls
const finalText = [];
for (const content of response.content) {
if (content.type === 'text') {
finalText.push(content.text);
} else if (content.type === 'tool_use') {
// Execute tool call
const toolName = content.name;
const toolArgs = content.input as Record<string, unknown> | undefined;
const result = await this.mcp.callTool({
name: toolName,
arguments: toolArgs,
});
finalText.push(`[Calling tool ${toolName} with args ${JSON.stringify(toolArgs)}]`);
// Extract text from tool result content blocks
const toolResultText = result.content
.filter((block) => block.type === 'text')
.map((block) => block.text)
.join('\n');
// Continue conversation with tool results
messages.push({
role: 'assistant',
content: response.content,
});
messages.push({
role: 'user',
content: [{
type: 'tool_result',
tool_use_id: content.id,
content: toolResultText,
}],
});
// Get next response from Claude
const followUp = await this.anthropic.messages.create({
model: ANTHROPIC_MODEL,
max_tokens: 1000,
messages,
});
finalText.push(followUp.content[0].type === 'text' ? followUp.content[0].text : '');
}
}
return finalText.join('\n');
}
```
### Interactive chat interface
Now we'll add the chat loop and cleanup functionality:
```ts source="../examples/client-quickstart/src/index.ts#chatLoop"
async chatLoop() {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
try {
console.log('\nMCP Client Started!');
console.log('Type your queries or "quit" to exit.');
while (true) {
const message = await rl.question('\nQuery: ');
if (message.toLowerCase() === 'quit') {
break;
}
const response = await this.processQuery(message);
console.log('\n' + response);
}
} finally {
rl.close();
}
}
async cleanup() {
await this.mcp.close();
}
}
```
### Main entry point
Finally, we'll add the main execution logic:
```ts source="../examples/client-quickstart/src/index.ts#main"
async function main() {
if (process.argv.length < 3) {
console.log('Usage: node build/index.js <path_to_server_script>');
return;
}
const mcpClient = new MCPClient();
try {
await mcpClient.connectToServer(process.argv[2]);
// Check if we have a valid API key to continue
const apiKey = process.env.ANTHROPIC_API_KEY;
if (!apiKey) {
console.log(
'\nNo ANTHROPIC_API_KEY found. To query these tools with Claude, set your API key:'
+ '\n export ANTHROPIC_API_KEY=your-api-key-here'
);
return;
}
await mcpClient.chatLoop();
} catch (e) {
console.error('Error:', e);
process.exit(1);
} finally {
await mcpClient.cleanup();
process.exit(0);
}
}
main();
```
## Running the client
To run your client with any MCP server:
**macOS/Linux:**
```bash
# Build TypeScript
npm run build
# Run the client with a Node.js MCP server
ANTHROPIC_API_KEY=your-key-here node build/index.js path/to/server/build/index.js
# Example: connect to the weather server from the server quickstart
ANTHROPIC_API_KEY=your-key-here node build/index.js /absolute/path/to/weather/build/index.js
```
**Windows:**
```powershell
# Build TypeScript
npm run build
# Run the client with a Node.js MCP server
$env:ANTHROPIC_API_KEY="your-key-here"; node build/index.js path\to\server\build\index.js
```
**The client will:**
1. Connect to the specified server
2. List available tools
3. Start an interactive chat session where you can:
- Enter queries
- See tool executions
- Get responses from Claude
## What's happening under the hood
When you submit a query:
1. Your query is sent to Claude along with the tool descriptions discovered during connection
2. Claude decides which tools (if any) to use
3. The client executes any requested tool calls through the server
4. Results are sent back to Claude
5. Claude provides a natural language response
6. The response is displayed to you
## Troubleshooting
### Server Path Issues
- Double-check the path to your server script is correct
- Use the absolute path if the relative path isn't working
- For Windows users, make sure to use forward slashes (`/`) or escaped backslashes (`\\`) in the path
- Verify the server file has the correct extension (`.js` for Node.js or `.py` for Python)
Example of correct path usage:
**macOS/Linux:**
```bash
# Relative path
node build/index.js ./server/build/index.js
# Absolute path
node build/index.js /Users/username/projects/mcp-server/build/index.js
```
**Windows:**
```powershell
# Relative path
node build/index.js .\server\build\index.js
# Absolute path (either format works)
node build/index.js C:\projects\mcp-server\build\index.js
node build/index.js C:/projects/mcp-server/build/index.js
```
### Response Timing
- The first response might take up to 30 seconds to return
- This is normal and happens while:
- The server initializes
- Claude processes the query
- Tools are being executed
- Subsequent responses are typically faster
- Don't interrupt the process during this initial waiting period
### Common Error Messages
If you see:
- `Error: Cannot find module`: Check your build folder and ensure TypeScript compilation succeeded
- `Connection refused`: Ensure the server is running and the path is correct
- `Tool execution failed`: Verify the tool's required environment variables are set
- `ANTHROPIC_API_KEY is not set`: Check your environment variables (e.g., `export ANTHROPIC_API_KEY=...`)
- `TypeError`: Ensure you're using the correct types for tool arguments
- `BadRequestError`: Ensure you have enough credits to access the Anthropic API
## Next steps
Now that you have a working client, here are some ways to go further:
- [**Client guide**](./client.md) — Add OAuth, middleware, sampling, and more to your client.
- [**Example clients**](https://github.com/modelcontextprotocol/typescript-sdk/tree/main/examples/client) — Browse runnable client examples.
- [**FAQ**](./faq.md) — Troubleshoot common errors.

626
docs/client.md Normal file
View File

@ -0,0 +1,626 @@
---
title: Client Guide
---
# Building MCP clients
This guide covers the TypeScript SDK APIs for building MCP clients. For protocol-level concepts, see the [MCP overview](https://modelcontextprotocol.io/docs/learn/architecture).
A client connects to a server, discovers what it offers — tools, resources, prompts — and invokes them. Beyond that core loop, this guide covers authentication, error handling, and responding to server-initiated requests like sampling and elicitation.
## Imports
The examples below use these imports. Adjust based on which features and transport you need:
```ts source="../examples/client/src/clientGuide.examples.ts#imports"
import type { AuthProvider, Prompt, Resource, Tool } from '@modelcontextprotocol/client';
import {
applyMiddlewares,
Client,
ClientCredentialsProvider,
createMiddleware,
CrossAppAccessProvider,
discoverAndRequestJwtAuthGrant,
PrivateKeyJwtProvider,
ProtocolError,
SdkError,
SdkErrorCode,
SSEClientTransport,
StreamableHTTPClientTransport
} from '@modelcontextprotocol/client';
import { StdioClientTransport } from '@modelcontextprotocol/client/stdio';
```
## Connecting to a server
### Streamable HTTP
For remote HTTP servers, use {@linkcode @modelcontextprotocol/client!client/streamableHttp.StreamableHTTPClientTransport | StreamableHTTPClientTransport}:
```ts source="../examples/client/src/clientGuide.examples.ts#connect_streamableHttp"
const client = new Client({ name: 'my-client', version: '1.0.0' });
const transport = new StreamableHTTPClientTransport(new URL('http://localhost:3000/mcp'));
await client.connect(transport);
```
For a full interactive client over Streamable HTTP, see [`simpleStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/client/src/simpleStreamableHttp.ts).
### stdio
For local, process-spawned servers (Claude Desktop, CLI tools), use {@linkcode @modelcontextprotocol/client!client/stdio.StdioClientTransport | StdioClientTransport}. The transport spawns the server process and communicates over stdin/stdout:
```ts source="../examples/client/src/clientGuide.examples.ts#connect_stdio"
const client = new Client({ name: 'my-client', version: '1.0.0' });
const transport = new StdioClientTransport({
command: 'node',
args: ['server.js']
});
await client.connect(transport);
```
### SSE fallback for legacy servers
To support both modern Streamable HTTP and legacy SSE servers, try {@linkcode @modelcontextprotocol/client!client/streamableHttp.StreamableHTTPClientTransport | StreamableHTTPClientTransport} first and fall back to {@linkcode @modelcontextprotocol/client!client/sse.SSEClientTransport | SSEClientTransport} on failure:
```ts source="../examples/client/src/clientGuide.examples.ts#connect_sseFallback"
const baseUrl = new URL(url);
try {
// Try modern Streamable HTTP transport first
const client = new Client({ name: 'my-client', version: '1.0.0' });
const transport = new StreamableHTTPClientTransport(baseUrl);
await client.connect(transport);
return { client, transport };
} catch {
// Fall back to legacy SSE transport
const client = new Client({ name: 'my-client', version: '1.0.0' });
const transport = new SSEClientTransport(baseUrl);
await client.connect(transport);
return { client, transport };
}
```
For a complete example with error reporting, see [`streamableHttpWithSseFallbackClient.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/client/src/streamableHttpWithSseFallbackClient.ts).
### Disconnecting
Call {@linkcode @modelcontextprotocol/client!client/client.Client#close | await client.close() } to disconnect. Pending requests are rejected with a {@linkcode @modelcontextprotocol/client!index.SdkErrorCode.ConnectionClosed | CONNECTION_CLOSED} error.
For Streamable HTTP, terminate the server-side session first (per the MCP specification):
```ts source="../examples/client/src/clientGuide.examples.ts#disconnect_streamableHttp"
await transport.terminateSession(); // notify the server (recommended)
await client.close();
```
For stdio, `client.close()` handles graceful process shutdown (closes stdin, then SIGTERM, then SIGKILL if needed).
### Server instructions
Servers can provide an `instructions` string during initialization that describes how to use them — cross-tool relationships, workflow patterns, and constraints (see [Instructions](https://modelcontextprotocol.io/specification/latest/basic/lifecycle#instructions) in the MCP specification). Retrieve it after connecting and include it in the model's system prompt:
```ts source="../examples/client/src/clientGuide.examples.ts#serverInstructions_basic"
const instructions = client.getInstructions();
const systemPrompt = ['You are a helpful assistant.', instructions].filter(Boolean).join('\n\n');
console.log(systemPrompt);
```
## Authentication
MCP servers can require authentication before accepting client connections (see [Authorization](https://modelcontextprotocol.io/specification/latest/basic/authorization) in the MCP specification). Pass an {@linkcode @modelcontextprotocol/client!client/auth.AuthProvider | AuthProvider} to {@linkcode @modelcontextprotocol/client!client/streamableHttp.StreamableHTTPClientTransport | StreamableHTTPClientTransport}. The transport calls `token()` before every request and `onUnauthorized()` (if provided) on 401, then retries once.
### Bearer tokens
For servers that accept bearer tokens managed outside the SDK — API keys, tokens from a gateway or proxy, service-account credentials — implement only `token()`. With no `onUnauthorized()`, a 401 throws {@linkcode @modelcontextprotocol/client!client/auth.UnauthorizedError | UnauthorizedError} immediately:
```ts source="../examples/client/src/clientGuide.examples.ts#auth_tokenProvider"
const authProvider: AuthProvider = { token: async () => getStoredToken() };
const transport = new StreamableHTTPClientTransport(new URL('http://localhost:3000/mcp'), { authProvider });
```
See [`simpleTokenProvider.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/client/src/simpleTokenProvider.ts) for a complete runnable example.
### Client credentials
{@linkcode @modelcontextprotocol/client!client/authExtensions.ClientCredentialsProvider | ClientCredentialsProvider} handles the `client_credentials` grant flow for service-to-service communication:
```ts source="../examples/client/src/clientGuide.examples.ts#auth_clientCredentials"
const authProvider = new ClientCredentialsProvider({
clientId: 'my-service',
clientSecret: 'my-secret'
});
const client = new Client({ name: 'my-client', version: '1.0.0' });
const transport = new StreamableHTTPClientTransport(new URL('http://localhost:3000/mcp'), { authProvider });
await client.connect(transport);
```
### Private key JWT
{@linkcode @modelcontextprotocol/client!client/authExtensions.PrivateKeyJwtProvider | PrivateKeyJwtProvider} signs JWT assertions for the `private_key_jwt` token endpoint auth method, avoiding a shared client secret:
```ts source="../examples/client/src/clientGuide.examples.ts#auth_privateKeyJwt"
const authProvider = new PrivateKeyJwtProvider({
clientId: 'my-service',
privateKey: pemEncodedKey,
algorithm: 'RS256'
});
const transport = new StreamableHTTPClientTransport(new URL('http://localhost:3000/mcp'), { authProvider });
```
For a runnable example supporting both auth methods via environment variables, see [`simpleClientCredentials.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/client/src/simpleClientCredentials.ts).
### Full OAuth with user authorization
For user-facing applications, implement the {@linkcode @modelcontextprotocol/client!client/auth.OAuthClientProvider | OAuthClientProvider} interface to handle the full authorization code flow (redirects, code verifiers, token storage, dynamic client registration). The {@linkcode @modelcontextprotocol/client!client/client.Client#connect | connect()} call will throw {@linkcode @modelcontextprotocol/client!client/auth.UnauthorizedError | UnauthorizedError} when authorization is needed — catch it, complete the browser flow, call {@linkcode @modelcontextprotocol/client!client/streamableHttp.StreamableHTTPClientTransport#finishAuth | transport.finishAuth(code)}, and reconnect.
For a complete working OAuth flow, see [`simpleOAuthClient.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/client/src/simpleOAuthClient.ts) and [`simpleOAuthClientProvider.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/client/src/simpleOAuthClientProvider.ts).
### Cross-App Access (Enterprise Managed Authorization)
{@linkcode @modelcontextprotocol/client!client/authExtensions.CrossAppAccessProvider | CrossAppAccessProvider} implements Enterprise Managed Authorization (SEP-990) for scenarios where users authenticate with an enterprise identity provider (IdP) and clients need to access protected MCP servers on their behalf.
This provider handles a two-step OAuth flow:
1. Exchange the user's ID Token from the enterprise IdP for a JWT Authorization Grant (JAG) via RFC 8693 token exchange
2. Exchange the JAG for an access token from the MCP server via RFC 7523 JWT bearer grant
```ts source="../examples/client/src/clientGuide.examples.ts#auth_crossAppAccess"
const authProvider = new CrossAppAccessProvider({
assertion: async ctx => {
// ctx provides: authorizationServerUrl, resourceUrl, scope, fetchFn
const result = await discoverAndRequestJwtAuthGrant({
idpUrl: 'https://idp.example.com',
audience: ctx.authorizationServerUrl,
resource: ctx.resourceUrl,
idToken: await getIdToken(),
clientId: 'my-idp-client',
clientSecret: 'my-idp-secret',
scope: ctx.scope,
fetchFn: ctx.fetchFn
});
return result.jwtAuthGrant;
},
clientId: 'my-mcp-client',
clientSecret: 'my-mcp-secret'
});
const transport = new StreamableHTTPClientTransport(new URL('http://localhost:3000/mcp'), { authProvider });
```
The `assertion` callback receives a context object with:
- `authorizationServerUrl` The MCP server's authorization server (discovered automatically)
- `resourceUrl` The MCP resource URL (discovered automatically)
- `scope` Optional scope passed to `auth()` or from `clientMetadata`
- `fetchFn` Fetch implementation to use for HTTP requests
For manual control over the token exchange steps, use the Layer 2 utilities from `@modelcontextprotocol/client`:
- `requestJwtAuthorizationGrant()` Exchange ID Token for JAG at IdP
- `discoverAndRequestJwtAuthGrant()` Discovery + JAG acquisition
- `exchangeJwtAuthGrant()` Exchange JAG for access token at MCP server
> [!NOTE]
> See [RFC 8693 (Token Exchange)](https://datatracker.ietf.org/doc/html/rfc8693), [RFC 7523 (JWT Bearer Grant)](https://datatracker.ietf.org/doc/html/rfc7523), and [RFC 9728 (Resource Discovery)](https://datatracker.ietf.org/doc/html/rfc9728) for the underlying OAuth standards.
## Tools
Tools are callable actions offered by servers — discovering and invoking them is usually how your client enables an LLM to take action (see [Tools](https://modelcontextprotocol.io/docs/learn/server-concepts#tools) in the MCP overview).
Use {@linkcode @modelcontextprotocol/client!client/client.Client#listTools | listTools()} to discover available tools, and {@linkcode @modelcontextprotocol/client!client/client.Client#callTool | callTool()} to invoke one. Results may be paginated — loop on `nextCursor` to collect all pages:
```ts source="../examples/client/src/clientGuide.examples.ts#callTool_basic"
const allTools: Tool[] = [];
let toolCursor: string | undefined;
do {
const { tools, nextCursor } = await client.listTools({ cursor: toolCursor });
allTools.push(...tools);
toolCursor = nextCursor;
} while (toolCursor);
console.log(
'Available tools:',
allTools.map(t => t.name)
);
const result = await client.callTool({
name: 'calculate-bmi',
arguments: { weightKg: 70, heightM: 1.75 }
});
console.log(result.content);
```
Tool results may include a `structuredContent` field — a machine-readable JSON object for programmatic use by the client application, complementing `content` which is for the LLM:
```ts source="../examples/client/src/clientGuide.examples.ts#callTool_structuredOutput"
const result = await client.callTool({
name: 'calculate-bmi',
arguments: { weightKg: 70, heightM: 1.75 }
});
// Machine-readable output for the client application
if (result.structuredContent) {
console.log(result.structuredContent); // e.g. { bmi: 22.86 }
}
```
### Tracking progress
Pass `onprogress` to receive incremental progress notifications from long-running tools. Use `resetTimeoutOnProgress` to keep the request alive while the server is actively reporting, and `maxTotalTimeout` as an absolute cap:
```ts source="../examples/client/src/clientGuide.examples.ts#callTool_progress"
const result = await client.callTool(
{ name: 'long-operation', arguments: {} },
{
onprogress: ({ progress, total }: { progress: number; total?: number }) => {
console.log(`Progress: ${progress}/${total ?? '?'}`);
},
resetTimeoutOnProgress: true,
maxTotalTimeout: 600_000
}
);
console.log(result.content);
```
## Resources
Resources are read-only data — files, database schemas, configuration — that your application can retrieve from a server and attach as context for the model (see [Resources](https://modelcontextprotocol.io/docs/learn/server-concepts#resources) in the MCP overview).
Use {@linkcode @modelcontextprotocol/client!client/client.Client#listResources | listResources()} and {@linkcode @modelcontextprotocol/client!client/client.Client#readResource | readResource()} to discover and read server-provided data. Results may be paginated — loop on `nextCursor` to collect all pages:
```ts source="../examples/client/src/clientGuide.examples.ts#readResource_basic"
const allResources: Resource[] = [];
let resourceCursor: string | undefined;
do {
const { resources, nextCursor } = await client.listResources({ cursor: resourceCursor });
allResources.push(...resources);
resourceCursor = nextCursor;
} while (resourceCursor);
console.log(
'Available resources:',
allResources.map(r => r.name)
);
const { contents } = await client.readResource({ uri: 'config://app' });
for (const item of contents) {
console.log(item);
}
```
To discover URI templates for dynamic resources, use {@linkcode @modelcontextprotocol/client!client/client.Client#listResourceTemplates | listResourceTemplates()}.
### Subscribing to resource changes
If the server supports resource subscriptions, use {@linkcode @modelcontextprotocol/client!client/client.Client#subscribeResource | subscribeResource()} to receive notifications when a resource changes, then re-read it:
```ts source="../examples/client/src/clientGuide.examples.ts#subscribeResource_basic"
await client.subscribeResource({ uri: 'config://app' });
client.setNotificationHandler('notifications/resources/updated', async notification => {
if (notification.params.uri === 'config://app') {
const { contents } = await client.readResource({ uri: 'config://app' });
console.log('Config updated:', contents);
}
});
// Later: stop receiving updates
await client.unsubscribeResource({ uri: 'config://app' });
```
## Prompts
Prompts are reusable message templates that servers offer to help structure interactions with models (see [Prompts](https://modelcontextprotocol.io/docs/learn/server-concepts#prompts) in the MCP overview).
Use {@linkcode @modelcontextprotocol/client!client/client.Client#listPrompts | listPrompts()} and {@linkcode @modelcontextprotocol/client!client/client.Client#getPrompt | getPrompt()} to list available prompts and retrieve them with arguments. Results may be paginated — loop on `nextCursor` to collect all pages:
```ts source="../examples/client/src/clientGuide.examples.ts#getPrompt_basic"
const allPrompts: Prompt[] = [];
let promptCursor: string | undefined;
do {
const { prompts, nextCursor } = await client.listPrompts({ cursor: promptCursor });
allPrompts.push(...prompts);
promptCursor = nextCursor;
} while (promptCursor);
console.log(
'Available prompts:',
allPrompts.map(p => p.name)
);
const { messages } = await client.getPrompt({
name: 'review-code',
arguments: { code: 'console.log("hello")' }
});
console.log(messages);
```
## Completions
Both prompts and resources can support argument completions. Use {@linkcode @modelcontextprotocol/client!client/client.Client#complete | complete()} to request autocompletion suggestions from the server as a user types:
```ts source="../examples/client/src/clientGuide.examples.ts#complete_basic"
const { completion } = await client.complete({
ref: {
type: 'ref/prompt',
name: 'review-code'
},
argument: {
name: 'language',
value: 'type'
}
});
console.log(completion.values); // e.g. ['typescript']
```
## Notifications
### Automatic list-change tracking
The {@linkcode @modelcontextprotocol/client!client/client.ClientOptions | listChanged} client option keeps a local cache of tools, prompts, or resources in sync with the server. It provides automatic server capability gating, debouncing (300 ms by default), auto-refresh, and error-first callbacks:
```ts source="../examples/client/src/clientGuide.examples.ts#listChanged_basic"
const client = new Client(
{ name: 'my-client', version: '1.0.0' },
{
listChanged: {
tools: {
onChanged: (error, tools) => {
if (error) {
console.error('Failed to refresh tools:', error);
return;
}
console.log('Tools updated:', tools);
}
},
prompts: {
onChanged: (error, prompts) => console.log('Prompts updated:', prompts)
}
}
}
);
```
### Manual notification handlers
For full control — or for notification types not covered by `listChanged` (such as log messages) — register handlers directly with {@linkcode @modelcontextprotocol/client!client/client.Client#setNotificationHandler | setNotificationHandler()}:
```ts source="../examples/client/src/clientGuide.examples.ts#notificationHandler_basic"
// Server log messages (sent by the server during request processing)
client.setNotificationHandler('notifications/message', notification => {
const { level, data } = notification.params;
console.log(`[${level}]`, data);
});
// Server's resource list changed — re-fetch the list
client.setNotificationHandler('notifications/resources/list_changed', async () => {
const { resources } = await client.listResources();
console.log('Resources changed:', resources.length);
});
```
To control the minimum severity of log messages the server sends, use {@linkcode @modelcontextprotocol/client!client/client.Client#setLoggingLevel | setLoggingLevel()}:
```ts source="../examples/client/src/clientGuide.examples.ts#setLoggingLevel_basic"
await client.setLoggingLevel('warning');
```
> [!WARNING]
> `listChanged` and {@linkcode @modelcontextprotocol/client!client/client.Client#setNotificationHandler | setNotificationHandler()} are mutually exclusive per notification type — using both for the same notification will cause the manual handler to be overwritten.
## Handling server-initiated requests
MCP is bidirectional — servers can send requests *to* the client during tool execution, as long as the client declares matching capabilities (see [Architecture](https://modelcontextprotocol.io/docs/learn/architecture) in the MCP overview). Declare the corresponding capability when constructing the {@linkcode @modelcontextprotocol/client!client/client.Client | Client} and register a request handler:
```ts source="../examples/client/src/clientGuide.examples.ts#capabilities_declaration"
const client = new Client(
{ name: 'my-client', version: '1.0.0' },
{
capabilities: {
sampling: {},
elicitation: { form: {} }
}
}
);
```
### Sampling
When a server needs an LLM completion during tool execution, it sends a `sampling/createMessage` request to the client (see [Sampling](https://modelcontextprotocol.io/docs/learn/client-concepts#sampling) in the MCP overview). Register a handler to fulfill it:
```ts source="../examples/client/src/clientGuide.examples.ts#sampling_handler"
client.setRequestHandler('sampling/createMessage', async request => {
const lastMessage = request.params.messages.at(-1);
console.log('Sampling request:', lastMessage);
// In production, send messages to your LLM here
return {
model: 'my-model',
role: 'assistant' as const,
content: {
type: 'text' as const,
text: 'Response from the model'
}
};
});
```
### Elicitation
When a server needs user input during tool execution, it sends an `elicitation/create` request to the client (see [Elicitation](https://modelcontextprotocol.io/docs/learn/client-concepts#elicitation) in the MCP overview). The client should present the form to the user and return the collected data, or `{ action: 'decline' }`:
```ts source="../examples/client/src/clientGuide.examples.ts#elicitation_handler"
client.setRequestHandler('elicitation/create', async request => {
console.log('Server asks:', request.params.message);
if (request.params.mode === 'form') {
// Present the schema-driven form to the user
console.log('Schema:', request.params.requestedSchema);
return { action: 'accept', content: { confirm: true } };
}
return { action: 'decline' };
});
```
For a full form-based elicitation handler with AJV validation, see [`simpleStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/client/src/simpleStreamableHttp.ts). For URL elicitation mode, see [`elicitationUrlExample.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/client/src/elicitationUrlExample.ts).
### Roots
Roots let the client expose filesystem boundaries to the server (see [Roots](https://modelcontextprotocol.io/docs/learn/client-concepts#roots) in the MCP overview). Declare the `roots` capability and register a `roots/list` handler:
```ts source="../examples/client/src/clientGuide.examples.ts#roots_handler"
client.setRequestHandler('roots/list', async () => {
return {
roots: [
{ uri: 'file:///home/user/projects/my-app', name: 'My App' },
{ uri: 'file:///home/user/data', name: 'Data' }
]
};
});
```
When the available roots change, notify the server with {@linkcode @modelcontextprotocol/client!client/client.Client#sendRootsListChanged | client.sendRootsListChanged()}.
## Error handling
### Tool errors vs protocol errors
{@linkcode @modelcontextprotocol/client!client/client.Client#callTool | callTool()} has two error surfaces: the tool can *run but report failure* via `isError: true` in the result, or the *request itself can fail* and throw an exception. Always check both:
```ts source="../examples/client/src/clientGuide.examples.ts#errorHandling_toolErrors"
try {
const result = await client.callTool({
name: 'fetch-data',
arguments: { url: 'https://example.com' }
});
// Tool-level error: the tool ran but reported a problem
if (result.isError) {
console.error('Tool error:', result.content);
return;
}
console.log('Success:', result.content);
} catch (error) {
// Protocol-level error: the request itself failed
if (error instanceof ProtocolError) {
console.error(`Protocol error ${error.code}: ${error.message}`);
} else if (error instanceof SdkError) {
console.error(`SDK error [${error.code}]: ${error.message}`);
} else {
throw error;
}
}
```
{@linkcode @modelcontextprotocol/client!index.ProtocolError | ProtocolError} represents JSON-RPC errors from the server (method not found, invalid params, internal error). {@linkcode @modelcontextprotocol/client!index.SdkError | SdkError} represents local SDK errors — {@linkcode @modelcontextprotocol/client!index.SdkErrorCode.RequestTimeout | REQUEST_TIMEOUT}, {@linkcode @modelcontextprotocol/client!index.SdkErrorCode.ConnectionClosed | CONNECTION_CLOSED}, {@linkcode @modelcontextprotocol/client!index.SdkErrorCode.CapabilityNotSupported | CAPABILITY_NOT_SUPPORTED}, and others.
### Connection lifecycle
Set {@linkcode @modelcontextprotocol/client!client/client.Client#onerror | client.onerror} to catch out-of-band transport errors (SSE disconnects, parse errors). Set {@linkcode @modelcontextprotocol/client!client/client.Client#onclose | client.onclose} to detect when the connection drops — pending requests are rejected with a {@linkcode @modelcontextprotocol/client!index.SdkErrorCode.ConnectionClosed | CONNECTION_CLOSED} error:
```ts source="../examples/client/src/clientGuide.examples.ts#errorHandling_lifecycle"
// Out-of-band errors (SSE disconnects, parse errors)
client.onerror = error => {
console.error('Transport error:', error.message);
};
// Connection closed (pending requests are rejected with CONNECTION_CLOSED)
client.onclose = () => {
console.log('Connection closed');
};
```
### Timeouts
All requests have a 60-second default timeout. Pass a custom `timeout` in the options to override it. On timeout, the SDK sends a cancellation notification to the server and rejects the promise with {@linkcode @modelcontextprotocol/client!index.SdkErrorCode.RequestTimeout | SdkErrorCode.RequestTimeout}:
```ts source="../examples/client/src/clientGuide.examples.ts#errorHandling_timeout"
try {
const result = await client.callTool(
{ name: 'slow-task', arguments: {} },
{ timeout: 120_000 } // 2 minutes instead of the default 60 seconds
);
console.log(result.content);
} catch (error) {
if (error instanceof SdkError && error.code === SdkErrorCode.RequestTimeout) {
console.error('Request timed out');
}
}
```
## Client middleware
Use {@linkcode @modelcontextprotocol/client!client/middleware.createMiddleware | createMiddleware()} and {@linkcode @modelcontextprotocol/client!client/middleware.applyMiddlewares | applyMiddlewares()} to compose fetch middleware pipelines. Middleware wraps the underlying `fetch` call and can add headers, handle retries, or log requests. Pass the enhanced fetch to the transport via the `fetch` option:
```ts source="../examples/client/src/clientGuide.examples.ts#middleware_basic"
const authMiddleware = createMiddleware(async (next, input, init) => {
const headers = new Headers(init?.headers);
headers.set('X-Custom-Header', 'my-value');
return next(input, { ...init, headers });
});
const transport = new StreamableHTTPClientTransport(new URL('http://localhost:3000/mcp'), {
fetch: applyMiddlewares(authMiddleware)(fetch)
});
```
## Resumption tokens
When using SSE-based streaming, the server can assign event IDs. Pass `onresumptiontoken` to track them, and `resumptionToken` to resume from where you left off after a disconnection:
```ts source="../examples/client/src/clientGuide.examples.ts#resumptionToken_basic"
let lastToken: string | undefined;
const result = await client.request(
{
method: 'tools/call',
params: { name: 'long-running-task', arguments: {} }
},
{
resumptionToken: lastToken,
onresumptiontoken: (token: string) => {
lastToken = token;
// Persist token to survive restarts
}
}
);
console.log(result);
```
For an end-to-end example of server-initiated SSE disconnection and automatic client reconnection with event replay, see [`ssePollingClient.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/client/src/ssePollingClient.ts).
## Tasks (experimental)
> [!WARNING]
> The tasks API is experimental and may change without notice.
Task-based execution enables "call-now, fetch-later" patterns for long-running operations (see [Tasks](https://modelcontextprotocol.io/specification/latest/basic/utilities/tasks) in the MCP specification). Instead of returning a result immediately, a tool creates a task that can be polled or resumed later. To use tasks:
- Call {@linkcode @modelcontextprotocol/client!experimental/tasks/client.ExperimentalClientTasks#callToolStream | client.experimental.tasks.callToolStream(...)} to start a tool call that may create a task and emit status updates over time.
- Call {@linkcode @modelcontextprotocol/client!experimental/tasks/client.ExperimentalClientTasks#getTask | client.experimental.tasks.getTask(...)} and {@linkcode @modelcontextprotocol/client!experimental/tasks/client.ExperimentalClientTasks#getTaskResult | getTaskResult(...)} to check status and fetch results after reconnecting.
For a full runnable example, see [`simpleTaskInteractiveClient.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/client/src/simpleTaskInteractiveClient.ts).
## See also
- [`examples/client/`](https://github.com/modelcontextprotocol/typescript-sdk/tree/main/examples/client) — Full runnable client examples
- [Server guide](./server.md) — Building MCP servers with this SDK
- [MCP overview](https://modelcontextprotocol.io/docs/learn/architecture) — Protocol-level concepts: participants, layers, primitives
- [Migration guide](./migration.md) — Upgrading from previous SDK versions
- [FAQ](./faq.md) — Frequently asked questions and troubleshooting
### Additional examples
| Feature | Description | Example |
|---------|-------------|---------|
| Parallel tool calls | Run multiple tool calls concurrently via `Promise.all` | [`parallelToolCallsClient.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/client/src/parallelToolCallsClient.ts) |
| SSE disconnect / reconnection | Server-initiated SSE disconnect with automatic reconnection and event replay | [`ssePollingClient.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/client/src/ssePollingClient.ts) |
| Multiple clients | Independent client lifecycles to the same server | [`multipleClientsParallel.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/client/src/multipleClientsParallel.ts) |
| URL elicitation | Handle sensitive data collection via browser | [`elicitationUrlExample.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/client/src/elicitationUrlExample.ts) |

17
docs/documents.md Normal file
View File

@ -0,0 +1,17 @@
---
title: Documents
children:
- ./server-quickstart.md
- ./server.md
- ./client-quickstart.md
- ./client.md
- ./faq.md
---
# Documents
- [Server Quickstart](./server-quickstart.md) build a weather server from scratch and connect it to VS Code
- [Server](./server.md) building MCP servers: transports, tools, resources, prompts, server-initiated requests, and deployment
- [Client Quickstart](./client-quickstart.md) build an LLM-powered chatbot that connects to an MCP server and calls its tools
- [Client](./client.md) building MCP clients: connecting, tools, resources, prompts, server-initiated requests, and error handling
- [FAQ](./faq.md) frequently asked questions and troubleshooting

85
docs/faq.md Normal file
View File

@ -0,0 +1,85 @@
---
title: FAQ
---
## FAQ
<details>
<summary>Table of Contents</summary>
- [General](#general)
- [Clients](#clients)
- [Servers](#servers)
- [v1 (legacy)](#v1-legacy)
</details>
## General
### Why do I see `TS2589: Type instantiation is excessively deep and possibly infinite` after upgrading the SDK?
This TypeScript error can appear when upgrading to newer SDK versions that support Zod v4 (for example, from older `@modelcontextprotocol/sdk` releases to newer `@modelcontextprotocol/client` / `@modelcontextprotocol/server` releases) **and** your project ends up with multiple
`zod` versions in the dependency tree.
When there are multiple copies or versions of `zod`, TypeScript may try to instantiate very complex, cross-version types and hit its recursion limits, resulting in `TS2589`. This scenario is discussed in GitHub issue
[#1180](https://github.com/modelcontextprotocol/typescript-sdk/issues/1180#event-21236550401).
To diagnose and fix this:
- **Inspect your installed `zod` versions**:
- Run `npm ls zod` or `npm explain zod`, `pnpm list zod` or `pnpm why zod`, or `yarn why zod` and check whether more than one version is installed.
- **Align on a single `zod` version**:
- Make sure all packages that depend on `zod` use a compatible version range so that your package manager can hoist a single copy.
- In monorepos, consider declaring `zod` at the workspace root and using compatible ranges in individual packages.
- **Use overrides/resolutions if necessary**:
- With npm, Yarn, or pnpm, you can use `overrides` / `resolutions` to force a single `zod` version if some transitive dependencies pull in a different one.
Once your project is using a single, compatible `zod` version, the `TS2589` error should no longer occur.
## Clients
### How do I enable Web Crypto (`globalThis.crypto`) for client authentication in older Node.js versions?
The SDKs OAuth client authentication helpers (for example, those in `packages/client/src/client/auth-extensions.ts` that use `jose`) rely on the Web Crypto API exposed as `globalThis.crypto`. This is especially important for **client credentials** and **JWT-based**
authentication flows used by MCP clients.
- **Node.js v19.0.0 and later**: `globalThis.crypto` is available by default.
- **Node.js v18.x**: `globalThis.crypto` may not be defined by default. In this repository we polyfill it for tests (see `packages/client/vitest.setup.js`), and you should do the same in your app if it is missing or alternatively, run Node with `--experimental-global-webcrypto`
as per your Node version documentation. (See https://nodejs.org/dist/latest-v18.x/docs/api/globals.html#crypto )
If you run clients on Node.js versions where `globalThis.crypto` is missing, you can polyfill it using the built-in `node:crypto` module, similar to the SDK's own `vitest.setup.ts`:
```typescript
import { webcrypto } from 'node:crypto';
if (typeof globalThis.crypto === 'undefined') {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(globalThis as any).crypto = webcrypto as unknown as Crypto;
}
```
For production use, you can either:
- Run clients on a Node.js version where `globalThis.crypto` is available by default (recommended), or
- Apply a similar polyfill early in your client's startup code when targeting older Node.js runtimes, so that OAuth client authentication works reliably.
## Servers
### Where can I find runnable server examples?
The [server quickstart](./server-quickstart.md) walks you through building a weather server from scratch. Its complete source lives in [`examples/server-quickstart/`](https://github.com/modelcontextprotocol/typescript-sdk/tree/main/examples/server-quickstart/). For more advanced examples (OAuth, streaming, sessions, etc.), see the server examples index in [`examples/server/README.md`](../examples/server/README.md).
### Where are the server auth helpers?
Resource Server helpers (`requireBearerAuth`, `mcpAuthMetadataRouter`, `OAuthTokenVerifier`) are first-class in `@modelcontextprotocol/express`. The Authorization Server helpers (`mcpAuthRouter`, `ProxyOAuthServerProvider`, etc.) have been removed from the core SDK; new code should use a dedicated IdP/OAuth library. Example packages provide a demo with `better-auth`.
### Why did we remove `server` SSE transport?
The SSE transport has been deprecated for a long time, and `v2` will not support it on the server side any more. Client side will keep supporting it in order to be able to connect to legacy SSE servers via the `v2` SDK, but serving SSE from `v2` will not be possible. Servers
wanting to switch to `v2` and using SSE should migrate to Streamable HTTP.
## v1 (legacy)
### Where do v1 documentation and v1-specific fixes live?
The v1 API documentation is available at [`https://ts.sdk.modelcontextprotocol.io/`](https://ts.sdk.modelcontextprotocol.io/). The v1 source code and any v1-specific fixes live on the long-lived [`v1.x` branch](https://github.com/modelcontextprotocol/typescript-sdk/tree/v1.x). V2 API docs are at [`/v2/`](https://ts.sdk.modelcontextprotocol.io/v2/).

548
docs/migration-SKILL.md Normal file
View File

@ -0,0 +1,548 @@
---
name: migrate-v1-to-v2
description: Migrate MCP TypeScript SDK code from v1 (@modelcontextprotocol/sdk) to v2 (@modelcontextprotocol/core, /client, /server). Use when a user asks to migrate, upgrade, or port their MCP TypeScript code from v1 to v2.
---
# MCP TypeScript SDK: v1 → v2 Migration
Apply these changes in order: dependencies → imports → API calls → type aliases.
## 1. Environment
- Node.js 20+ required (v18 dropped)
- ESM only (CJS dropped). If the project uses `require()`, convert to `import`/`export` or use dynamic `import()`.
## 2. Dependencies
Remove the old package and install only what you need:
```bash
npm uninstall @modelcontextprotocol/sdk
```
| You need | Install |
| --------------------- | ------------------------------------------------------------------------ |
| Client only | `npm install @modelcontextprotocol/client` |
| Server only | `npm install @modelcontextprotocol/server` |
| Server + Node.js HTTP | `npm install @modelcontextprotocol/server @modelcontextprotocol/node` |
| Server + Express | `npm install @modelcontextprotocol/server @modelcontextprotocol/express` |
| Server + Hono | `npm install @modelcontextprotocol/server @modelcontextprotocol/hono` |
`@modelcontextprotocol/core` is installed automatically as a dependency.
## 3. Import Mapping
Replace all `@modelcontextprotocol/sdk/...` imports using this table.
### Client imports
| v1 import path | v2 package |
| ---------------------------------------------------- | ------------------------------------------------------------------------------ |
| `@modelcontextprotocol/sdk/client/index.js` | `@modelcontextprotocol/client` |
| `@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/stdio` |
| `@modelcontextprotocol/sdk/client/websocket.js` | REMOVED (use Streamable HTTP or stdio; implement `Transport` for custom needs) |
### Server imports
| v1 import path | v2 package |
| ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `@modelcontextprotocol/sdk/server/mcp.js` | `@modelcontextprotocol/server` |
| `@modelcontextprotocol/sdk/server/index.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/*` | RS helpers (`requireBearerAuth`, `mcpAuthMetadataRouter`, `OAuthTokenVerifier`) → `@modelcontextprotocol/express`; AS helpers removed (use external IdP/OAuth library) |
| `@modelcontextprotocol/sdk/server/middleware.js` | `@modelcontextprotocol/express` (signature changed, see section 8) |
### Types / shared imports
| v1 import path | v2 package |
| ------------------------------------------------- | ---------------------------------------------------------------- |
| `@modelcontextprotocol/sdk/types.js` | `@modelcontextprotocol/client` or `@modelcontextprotocol/server` |
| `@modelcontextprotocol/sdk/shared/protocol.js` | `@modelcontextprotocol/client` or `@modelcontextprotocol/server` |
| `@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` (`ReadBuffer`, `serializeMessage`, `deserializeMessage` are in the root barrel; the `./stdio` subpath only has the transport class) |
Notes:
- `@modelcontextprotocol/client` and `@modelcontextprotocol/server` both re-export shared types from `@modelcontextprotocol/core`, so import from whichever package you already depend on. Do not import from `@modelcontextprotocol/core` directly — it is an internal package.
- When multiple v1 imports map to the same v2 package, consolidate them into a single import statement.
## 4. Renamed Symbols
| v1 symbol | v2 symbol | v2 package |
| ------------------------------- | ----------------------------------- | ---------------------------- |
| `StreamableHTTPServerTransport` | `NodeStreamableHTTPServerTransport` | `@modelcontextprotocol/node` |
## 5. Removed / Renamed Type Aliases and Symbols
| v1 (removed) | v2 (replacement) |
| ---------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| `JSONRPCError` | `JSONRPCErrorResponse` |
| `JSONRPCErrorSchema` | `JSONRPCErrorResponseSchema` |
| `isJSONRPCError` | `isJSONRPCErrorResponse` |
| `isJSONRPCResponse` (deprecated in v1) | `isJSONRPCResultResponse` (**not** v2's new `isJSONRPCResponse`, which correctly matches both result and error) |
| `ResourceReference` | `ResourceTemplateReference` |
| `ResourceReferenceSchema` | `ResourceTemplateReferenceSchema` |
| `IsomorphicHeaders` | REMOVED (use Web Standard `Headers`) |
| `AuthInfo` (from `server/auth/types.js`) | `AuthInfo` (now re-exported by `@modelcontextprotocol/client` and `@modelcontextprotocol/server`) |
| `McpError` | `ProtocolError` |
| `ErrorCode` | `ProtocolErrorCode` |
| `ErrorCode.RequestTimeout` | `SdkErrorCode.RequestTimeout` |
| `ErrorCode.ConnectionClosed` | `SdkErrorCode.ConnectionClosed` |
| `StreamableHTTPError` | REMOVED (use `SdkHttpError` with `SdkErrorCode.ClientHttp*`) |
| `WebSocketClientTransport` | REMOVED (use `StreamableHTTPClientTransport` or `StdioClientTransport`) |
All other **type** symbols from `@modelcontextprotocol/sdk/types.js` retain their original names. **Zod schemas** (e.g., `CallToolResultSchema`, `ListToolsResultSchema`) are no longer part of the public API — they are internal to the SDK. For runtime validation, use
`isSpecType.TypeName(value)` (e.g., `isSpecType.CallToolResult(v)`) or `specTypeSchemas.TypeName` for the `StandardSchemaV1Sync` validator object. The keys are typed as `SpecTypeName`, a literal union of all spec type names.
### Error class changes
Three error classes now exist:
- **`ProtocolError`** (renamed from `McpError`): Protocol errors that cross the wire as JSON-RPC responses
- **`SdkError`** (new): Local SDK errors that never cross the wire
- **`SdkHttpError`** (extends `SdkError`): HTTP transport errors with typed `.status` and `.statusText` accessors
| Error scenario | v1 type | v2 type |
| --------------------------------- | -------------------------------------------- | ----------------------------------------------------------------- |
| Request timeout | `McpError` with `ErrorCode.RequestTimeout` | `SdkError` with `SdkErrorCode.RequestTimeout` |
| Connection closed | `McpError` with `ErrorCode.ConnectionClosed` | `SdkError` with `SdkErrorCode.ConnectionClosed` |
| Capability not supported | `new Error(...)` | `SdkError` with `SdkErrorCode.CapabilityNotSupported` |
| Not connected | `new Error('Not connected')` | `SdkError` with `SdkErrorCode.NotConnected` |
| Invalid params (server response) | `McpError` with `ErrorCode.InvalidParams` | `ProtocolError` with `ProtocolErrorCode.InvalidParams` |
| HTTP transport error | `StreamableHTTPError` | `SdkHttpError` with `SdkErrorCode.ClientHttp*` |
| Failed to open SSE stream | `StreamableHTTPError` | `SdkHttpError` with `SdkErrorCode.ClientHttpFailedToOpenStream` |
| 401 after re-auth (circuit break) | `StreamableHTTPError` | `SdkHttpError` with `SdkErrorCode.ClientHttpAuthentication` |
| 403 after upscoping | `StreamableHTTPError` | `SdkHttpError` with `SdkErrorCode.ClientHttpForbidden` |
| Unexpected content type | `StreamableHTTPError` | `SdkError` with `SdkErrorCode.ClientHttpUnexpectedContent` |
| Session termination failed | `StreamableHTTPError` | `SdkHttpError` with `SdkErrorCode.ClientHttpFailedToTerminateSession` |
| Response result fails schema | `ZodError` (raw) | `SdkError` with `SdkErrorCode.InvalidResult` |
New `SdkErrorCode` enum values:
- `SdkErrorCode.NotConnected` = `'NOT_CONNECTED'`
- `SdkErrorCode.AlreadyConnected` = `'ALREADY_CONNECTED'`
- `SdkErrorCode.NotInitialized` = `'NOT_INITIALIZED'`
- `SdkErrorCode.CapabilityNotSupported` = `'CAPABILITY_NOT_SUPPORTED'`
- `SdkErrorCode.RequestTimeout` = `'REQUEST_TIMEOUT'`
- `SdkErrorCode.ConnectionClosed` = `'CONNECTION_CLOSED'`
- `SdkErrorCode.SendFailed` = `'SEND_FAILED'`
- `SdkErrorCode.InvalidResult` = `'INVALID_RESULT'`
- `SdkErrorCode.ClientHttpNotImplemented` = `'CLIENT_HTTP_NOT_IMPLEMENTED'`
- `SdkErrorCode.ClientHttpAuthentication` = `'CLIENT_HTTP_AUTHENTICATION'`
- `SdkErrorCode.ClientHttpForbidden` = `'CLIENT_HTTP_FORBIDDEN'`
- `SdkErrorCode.ClientHttpUnexpectedContent` = `'CLIENT_HTTP_UNEXPECTED_CONTENT'`
- `SdkErrorCode.ClientHttpFailedToOpenStream` = `'CLIENT_HTTP_FAILED_TO_OPEN_STREAM'`
- `SdkErrorCode.ClientHttpFailedToTerminateSession` = `'CLIENT_HTTP_FAILED_TO_TERMINATE_SESSION'`
Update error handling:
```typescript
// v1
if (error instanceof McpError && error.code === ErrorCode.RequestTimeout) { ... }
// v2
import { SdkError, SdkErrorCode } from '@modelcontextprotocol/client';
if (error instanceof SdkError && error.code === SdkErrorCode.RequestTimeout) { ... }
```
Update HTTP transport error handling:
```typescript
// v1
import { StreamableHTTPError } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
if (error instanceof StreamableHTTPError) {
console.log('HTTP status:', error.code);
}
// v2
import { SdkHttpError, SdkErrorCode } from '@modelcontextprotocol/client';
if (error instanceof SdkHttpError) {
console.log('HTTP status:', error.status); // number — typed accessor
console.log('Status text:', error.statusText); // string | undefined
switch (error.code) {
case SdkErrorCode.ClientHttpAuthentication: // 401 after re-auth
case SdkErrorCode.ClientHttpForbidden: // 403 after upscoping
case SdkErrorCode.ClientHttpFailedToOpenStream:
case SdkErrorCode.ClientHttpNotImplemented:
break;
}
}
```
### OAuth error consolidation
Individual OAuth error classes replaced with single `OAuthError` class and `OAuthErrorCode` enum:
| v1 Class | v2 Equivalent |
| ------------------------------ | ---------------------------------------------------------- |
| `InvalidRequestError` | `OAuthError` with `OAuthErrorCode.InvalidRequest` |
| `InvalidClientError` | `OAuthError` with `OAuthErrorCode.InvalidClient` |
| `InvalidGrantError` | `OAuthError` with `OAuthErrorCode.InvalidGrant` |
| `UnauthorizedClientError` | `OAuthError` with `OAuthErrorCode.UnauthorizedClient` |
| `UnsupportedGrantTypeError` | `OAuthError` with `OAuthErrorCode.UnsupportedGrantType` |
| `InvalidScopeError` | `OAuthError` with `OAuthErrorCode.InvalidScope` |
| `AccessDeniedError` | `OAuthError` with `OAuthErrorCode.AccessDenied` |
| `ServerError` | `OAuthError` with `OAuthErrorCode.ServerError` |
| `TemporarilyUnavailableError` | `OAuthError` with `OAuthErrorCode.TemporarilyUnavailable` |
| `UnsupportedResponseTypeError` | `OAuthError` with `OAuthErrorCode.UnsupportedResponseType` |
| `UnsupportedTokenTypeError` | `OAuthError` with `OAuthErrorCode.UnsupportedTokenType` |
| `InvalidTokenError` | `OAuthError` with `OAuthErrorCode.InvalidToken` |
| `MethodNotAllowedError` | `OAuthError` with `OAuthErrorCode.MethodNotAllowed` |
| `TooManyRequestsError` | `OAuthError` with `OAuthErrorCode.TooManyRequests` |
| `InvalidClientMetadataError` | `OAuthError` with `OAuthErrorCode.InvalidClientMetadata` |
| `InsufficientScopeError` | `OAuthError` with `OAuthErrorCode.InsufficientScope` |
| `InvalidTargetError` | `OAuthError` with `OAuthErrorCode.InvalidTarget` |
| `CustomOAuthError` | `new OAuthError(customCode, message)` |
Removed: `OAUTH_ERRORS` constant.
Update OAuth error handling:
```typescript
// v1
import { InvalidClientError, InvalidGrantError } from '@modelcontextprotocol/client';
if (error instanceof InvalidClientError) { ... }
// v2
import { OAuthError, OAuthErrorCode } from '@modelcontextprotocol/client';
if (error instanceof OAuthError && error.code === OAuthErrorCode.InvalidClient) { ... }
```
**Unchanged APIs** (only import paths changed): `Client` constructor and most methods, `McpServer` constructor, `server.connect()`, `server.close()`, all client transports (`StreamableHTTPClientTransport`, `SSEClientTransport`, `StdioClientTransport`), `StdioServerTransport`, all
Zod schemas, all callback return types. Note: `callTool()` and `request()` signatures changed (schema parameter removed, see section 11).
## 6. McpServer API Changes
The variadic `.tool()`, `.prompt()`, `.resource()` methods are removed. Use the `register*` methods with a config object.
**IMPORTANT**: v2 requires schema objects implementing [Standard Schema](https://standardschema.dev/) — raw shapes like `{ name: z.string() }` are no longer supported. Wrap with `z.object()` (Zod v4), or use ArkType's `type({...})`, or Valibot. For raw JSON Schema, wrap with
`fromJsonSchema(schema)` from `@modelcontextprotocol/server` (validator defaults automatically; pass an explicit validator for custom configurations). Applies to `inputSchema`, `outputSchema`, and `argsSchema`.
### Tools
```typescript
// v1: server.tool(name, schema, callback) - raw shape worked
server.tool('greet', { name: z.string() }, async ({ name }) => {
return { content: [{ type: 'text', text: `Hello, ${name}!` }] };
});
// v1: server.tool(name, description, schema, callback)
server.tool('greet', 'Greet a user', { name: z.string() }, async ({ name }) => {
return { content: [{ type: 'text', text: `Hello, ${name}!` }] };
});
// v2: server.registerTool(name, config, callback)
server.registerTool(
'greet',
{
description: 'Greet a user',
inputSchema: z.object({ name: z.string() })
},
async ({ name }) => {
return { content: [{ type: 'text', text: `Hello, ${name}!` }] };
}
);
```
Config object fields: `title?`, `description?`, `inputSchema?`, `outputSchema?`, `annotations?`, `_meta?`
### Prompts
```typescript
// v1: server.prompt(name, schema, callback) - raw shape worked
server.prompt('summarize', { text: z.string() }, async ({ text }) => {
return { messages: [{ role: 'user', content: { type: 'text', text } }] };
});
// v2: server.registerPrompt(name, config, callback)
server.registerPrompt(
'summarize',
{
argsSchema: z.object({ text: z.string() })
},
async ({ text }) => {
return { messages: [{ role: 'user', content: { type: 'text', text } }] };
}
);
```
Config object fields: `title?`, `description?`, `argsSchema?`
### Resources
```typescript
// v1: server.resource(name, uri, callback)
server.resource('config', 'config://app', async uri => {
return { contents: [{ uri: uri.href, text: '{}' }] };
});
// v2: server.registerResource(name, uri, metadata, callback)
server.registerResource('config', 'config://app', {}, async uri => {
return { contents: [{ uri: uri.href, text: '{}' }] };
});
```
Note: the third argument (`metadata`) is required — pass `{}` if no metadata.
### Schema Migration Quick Reference
| v1 (raw shape) | v2 (Standard Schema object) |
| ---------------------------------- | -------------------------------------------- |
| `{ name: z.string() }` | `z.object({ name: z.string() })` |
| `{ count: z.number().optional() }` | `z.object({ count: z.number().optional() })` |
| `{}` (empty) | `z.object({})` |
| `undefined` (no schema) | `undefined` or omit the field |
### Removed core exports
| Removed from `@modelcontextprotocol/core` | Replacement |
| ------------------------------------------------------------------------------------ | ----------------------------------------- |
| `schemaToJson(schema)` | `standardSchemaToJsonSchema(schema)` |
| `parseSchemaAsync(schema, data)` | `validateStandardSchema(schema, data)` |
| `SchemaInput<T>` | `StandardSchemaWithJSON.InferInput<T>` |
| `getSchemaShape`, `getSchemaDescription`, `isOptionalSchema`, `unwrapOptionalSchema` | none (internal Zod introspection helpers) |
## 7. Headers API
Transport constructors now use the Web Standard `Headers` object instead of plain objects. The custom `RequestInfo` type has been replaced with the standard Web `Request` object, giving access to headers, URL, query parameters, and method.
```typescript
// v1: plain object, bracket access, custom RequestInfo
headers: { 'Authorization': 'Bearer token' }
extra.requestInfo?.headers['mcp-session-id']
// v2: Headers object, .get() access, standard Web Request
headers: new Headers({ 'Authorization': 'Bearer token' })
ctx.http?.req?.headers.get('mcp-session-id')
new URL(ctx.http?.req?.url).searchParams.get('debug')
```
## 8. Removed Server Features
### SSE server transport
`SSEServerTransport` removed entirely. Migrate to `NodeStreamableHTTPServerTransport` (from `@modelcontextprotocol/node`). Client-side `SSEClientTransport` still available for connecting to legacy servers.
### Server-side auth
Resource Server helpers (`requireBearerAuth`, `mcpAuthMetadataRouter`, `getOAuthProtectedResourceMetadataUrl`, `OAuthTokenVerifier`) are first-class in `@modelcontextprotocol/express`. Authorization Server helpers (`mcpAuthRouter`, `OAuthServerProvider`, `ProxyOAuthServerProvider`, `authenticateClient`, `allowedMethods`, etc.) are removed from the core SDK; use an external IdP/OAuth library. See `examples/server/src/` for demos.
### Host header validation (Express)
`hostHeaderValidation()` and `localhostHostValidation()` moved from server package to `@modelcontextprotocol/express`. Signature changed: takes `string[]` instead of options object.
```typescript
// v1
import { hostHeaderValidation } from '@modelcontextprotocol/sdk/server/middleware.js';
app.use(hostHeaderValidation({ allowedHosts: ['example.com'] }));
// v2
import { hostHeaderValidation } from '@modelcontextprotocol/express';
app.use(hostHeaderValidation(['example.com']));
```
The server package now exports framework-agnostic alternatives: `validateHostHeader()`, `localhostAllowedHostnames()`, `hostHeaderValidationResponse()`.
## 9. `setRequestHandler` / `setNotificationHandler` API
The low-level handler registration methods now take a method string instead of a Zod schema.
```typescript
// v1: schema-based
server.setRequestHandler(InitializeRequestSchema, async (request) => { ... });
server.setNotificationHandler(LoggingMessageNotificationSchema, (notification) => { ... });
// v2: method string
server.setRequestHandler('initialize', async (request) => { ... });
server.setNotificationHandler('notifications/message', (notification) => { ... });
```
For custom (non-spec) methods, use the 3-arg form `(method, schemas, handler)`:
```typescript
// v1: Zod schema with method literal
server.setRequestHandler(z.object({ method: z.literal('acme/search'), params: P }), async req => { ... });
// v2: method string + schemas object; handler receives parsed params
server.setRequestHandler('acme/search', { params: P, result: R }, async (params, ctx) => { ... });
client.setNotificationHandler('acme/progress', { params: P }, (params, notification) => { ... });
```
The 3-arg notification handler receives the raw notification as its second argument, so `_meta` is recoverable via `notification.params?._meta`.
To send a custom-method request, pass a result schema as the second argument to `request()` (and `ctx.mcpReq.send()`):
```typescript
// v1
await client.request({ method: 'acme/search', params }, ResultSchema);
// v2 (unchanged; now any Standard Schema, not Zod-only)
await client.request({ method: 'acme/search', params }, ResultSchema);
```
Schema to method string mapping:
| v1 Schema | v2 Method String |
| --------------------------------------- | ---------------------------------------- |
| `InitializeRequestSchema` | `'initialize'` |
| `CallToolRequestSchema` | `'tools/call'` |
| `ListToolsRequestSchema` | `'tools/list'` |
| `ListPromptsRequestSchema` | `'prompts/list'` |
| `GetPromptRequestSchema` | `'prompts/get'` |
| `ListResourcesRequestSchema` | `'resources/list'` |
| `ReadResourceRequestSchema` | `'resources/read'` |
| `CreateMessageRequestSchema` | `'sampling/createMessage'` |
| `ElicitRequestSchema` | `'elicitation/create'` |
| `SetLevelRequestSchema` | `'logging/setLevel'` |
| `PingRequestSchema` | `'ping'` |
| `LoggingMessageNotificationSchema` | `'notifications/message'` |
| `ToolListChangedNotificationSchema` | `'notifications/tools/list_changed'` |
| `ResourceListChangedNotificationSchema` | `'notifications/resources/list_changed'` |
| `PromptListChangedNotificationSchema` | `'notifications/prompts/list_changed'` |
| `ProgressNotificationSchema` | `'notifications/progress'` |
| `CancelledNotificationSchema` | `'notifications/cancelled'` |
| `InitializedNotificationSchema` | `'notifications/initialized'` |
Request/notification params remain fully typed. Remove unused schema imports after migration.
## 10. Request Handler Context Types
`RequestHandlerExtra` → structured context types with nested groups. Rename `extra``ctx` in all handler callbacks.
| v1 | v2 |
| -------------------------------- | -------------------------------------------------------------------------- |
| `RequestHandlerExtra` | `ServerContext` (server) / `ClientContext` (client) / `BaseContext` (base) |
| `extra` (param name) | `ctx` |
| `extra.signal` | `ctx.mcpReq.signal` |
| `extra.requestId` | `ctx.mcpReq.id` |
| `extra._meta` | `ctx.mcpReq._meta` |
| `extra.sendRequest(...)` | `ctx.mcpReq.send(...)` |
| `extra.sendNotification(...)` | `ctx.mcpReq.notify(...)` |
| `extra.authInfo` | `ctx.http?.authInfo` |
| `extra.sessionId` | `ctx.sessionId` |
| `extra.requestInfo` | `ctx.http?.req` (standard Web `Request`, only `ServerContext`) |
| `extra.closeSSEStream` | `ctx.http?.closeSSE` (only `ServerContext`) |
| `extra.closeStandaloneSSEStream` | `ctx.http?.closeStandaloneSSE` (only `ServerContext`) |
| `extra.taskStore` | `ctx.task?.store` |
| `extra.taskId` | `ctx.task?.id` |
| `extra.taskRequestedTtl` | `ctx.task?.requestedTtl` |
`ServerContext` convenience methods (new in v2, no v1 equivalent):
| Method | Description | Replaces |
| ---------------------------------------------- | ------------------------------------------------------ | ---------------------------------------------------- |
| `ctx.mcpReq.log(level, data, logger?)` | Send log notification (respects client's level filter) | `server.sendLoggingMessage(...)` from within handler |
| `ctx.mcpReq.elicitInput(params, options?)` | Elicit user input (form or URL) | `server.elicitInput(...)` from within handler |
| `ctx.mcpReq.requestSampling(params, options?)` | Request LLM sampling from client | `server.createMessage(...)` from within handler |
## 11. Schema parameter removed from `request()`, `send()`, and `callTool()` (spec methods)
For **spec** methods, `Protocol.request()`, `BaseContext.mcpReq.send()`, and `Client.callTool()` no longer require a Zod result schema argument. The SDK resolves the schema internally from the method name.
```typescript
// v1: schema required
import { CallToolResultSchema, ElicitResultSchema } from '@modelcontextprotocol/sdk/types.js';
const result = await client.request({ method: 'tools/call', params: { ... } }, CallToolResultSchema);
const elicit = await ctx.mcpReq.send({ method: 'elicitation/create', params: { ... } }, ElicitResultSchema);
const tool = await client.callTool({ name: 'my-tool', arguments: {} }, CompatibilityCallToolResultSchema);
// v2: no schema argument
const result = await client.request({ method: 'tools/call', params: { ... } });
const elicit = await ctx.mcpReq.send({ method: 'elicitation/create', params: { ... } });
const tool = await client.callTool({ name: 'my-tool', arguments: {} });
```
| v1 call | v2 call |
| ------------------------------------------------------------ | ---------------------------------- |
| `client.request(req, ResultSchema)` | `client.request(req)` |
| `client.request(req, ResultSchema, options)` | `client.request(req, options)` |
| `ctx.mcpReq.send(req, ResultSchema)` | `ctx.mcpReq.send(req)` |
| `ctx.mcpReq.send(req, ResultSchema, options)` | `ctx.mcpReq.send(req, options)` |
| `client.callTool(params, CompatibilityCallToolResultSchema)` | `client.callTool(params)` |
| `client.callTool(params, schema, options)` | `client.callTool(params, options)` |
For **custom (non-spec)** methods, keep the result-schema argument — see §9. Only apply the rewrites above when `req.method` is a spec method.
Remove unused schema imports: `CallToolResultSchema`, `CompatibilityCallToolResultSchema`, `ElicitResultSchema`, `CreateMessageResultSchema`, etc., when they were only used in `request()`/`send()`/`callTool()` calls.
If a `*Schema` constant was used for **runtime validation** (not just as a `request()` argument), replace with `isSpecType` / `specTypeSchemas`:
| v1 pattern | v2 replacement |
| -------------------------------------------------- | -------------------------------------------------------------------------------------- |
| `CallToolResultSchema.safeParse(value).success` | `isSpecType.CallToolResult(value)` |
| `<TypeName>Schema.safeParse(value).success` | `isSpecType.<TypeName>(value)` |
| `<TypeName>Schema.parse(value)` | `specTypeSchemas.<TypeName>['~standard'].validate(value)` (returns a `Result` synchronously, not the value) |
| Passing `<TypeName>Schema` as a validator argument | `specTypeSchemas.<TypeName>` (a `StandardSchemaV1Sync<In, Out>`) |
`isCallToolResult(value)` still works, but `isSpecType` covers every spec type by name.
## 12. Experimental: `TaskCreationParams.ttl` no longer accepts `null`
`TaskCreationParams.ttl` changed from `z.union([z.number(), z.null()]).optional()` to `z.number().optional()`. Per the MCP spec, `null` TTL (unlimited lifetime) is only valid in server responses (`Task.ttl`), not in client requests. Omit `ttl` to let the server decide.
| v1 | v2 |
| ---------------------- | ---------------------------------- |
| `task: { ttl: null }` | `task: {}` (omit ttl) |
| `task: { ttl: 60000 }` | `task: { ttl: 60000 }` (unchanged) |
Type changes in handler context:
| Type | v1 | v2 |
| ------------------------------------------- | ----------------------------- | --------------------- |
| `TaskContext.requestedTtl` | `number \| null \| undefined` | `number \| undefined` |
| `CreateTaskServerContext.task.requestedTtl` | `number \| null \| undefined` | `number \| undefined` |
| `TaskServerContext.task.requestedTtl` | `number \| null \| undefined` | `number \| undefined` |
> These task APIs are `@experimental` and may change without notice.
## 13. Client Behavioral Changes
`Client.listPrompts()`, `listResources()`, `listResourceTemplates()`, `listTools()` now return empty results when the server lacks the corresponding capability (instead of sending the request). Set `enforceStrictCapabilities: true` in `ClientOptions` to throw an error instead.
## 14. Runtime-Specific JSON Schema Validators (Enhancement)
The SDK now auto-selects the appropriate JSON Schema validator based on runtime:
- Node.js → `AjvJsonSchemaValidator` (no change from v1)
- Cloudflare Workers (workerd) → `CfWorkerJsonSchemaValidator` (previously required manual config)
**No action required** for most users. Cloudflare Workers users can remove explicit `jsonSchemaValidator` configuration:
```typescript
// v1 (Cloudflare Workers): Required explicit validator
new McpServer(
{ name: 'server', version: '1.0.0' },
{
jsonSchemaValidator: new CfWorkerJsonSchemaValidator()
}
);
// v2 (Cloudflare Workers): Auto-selected, explicit config optional
new McpServer({ name: 'server', version: '1.0.0' }, {});
```
Access validators explicitly:
- Runtime-aware default: `import { DefaultJsonSchemaValidator } from '@modelcontextprotocol/server/_shims';`
- AJV (Node.js): `import { AjvJsonSchemaValidator } from '@modelcontextprotocol/server';`
- CF Worker: `import { CfWorkerJsonSchemaValidator } from '@modelcontextprotocol/server/validators/cf-worker';`
## 15. Migration Steps (apply in this order)
1. Update `package.json`: `npm uninstall @modelcontextprotocol/sdk`, install the appropriate v2 packages
2. Replace all imports from `@modelcontextprotocol/sdk/...` using the import mapping tables (sections 3-4), including `StreamableHTTPServerTransport``NodeStreamableHTTPServerTransport`
3. Replace removed type aliases (`JSONRPCError` → `JSONRPCErrorResponse`, etc.) per section 5
4. Replace `.tool()` / `.prompt()` / `.resource()` calls with `registerTool` / `registerPrompt` / `registerResource` per section 6
5. **Wrap all raw Zod shapes with `z.object()`**: Change `inputSchema: { name: z.string() }``inputSchema: z.object({ name: z.string() })`. Same for `outputSchema` in tools and `argsSchema` in prompts.
6. Replace plain header objects with `new Headers({...})` and bracket access (`headers['x']`) with `.get()` calls per section 7
7. If using `hostHeaderValidation` from server, update import and signature per section 8
8. If using server SSE transport, migrate to Streamable HTTP
9. If using server auth from the SDK: RS helpers (`requireBearerAuth`, `mcpAuthMetadataRouter`) → `@modelcontextprotocol/express`; AS helpers → external IdP/OAuth library
10. If relying on `listTools()`/`listPrompts()`/etc. throwing on missing capabilities, set `enforceStrictCapabilities: true`
11. Verify: build with `tsc` / run tests

970
docs/migration.md Normal file
View File

@ -0,0 +1,970 @@
# Migration Guide: v1 to v2
This guide covers the breaking changes introduced in v2 of the MCP TypeScript SDK and how to update your code.
## Overview
Version 2 of the MCP TypeScript SDK introduces several breaking changes to improve modularity, reduce dependency bloat, and provide a cleaner API surface. The biggest change is the split from a single `@modelcontextprotocol/sdk` package into separate `@modelcontextprotocol/core`,
`@modelcontextprotocol/client`, and `@modelcontextprotocol/server` packages.
## Breaking Changes
### Package split (monorepo)
The single `@modelcontextprotocol/sdk` package has been split into three packages:
| v1 | v2 |
| --------------------------- | ---------------------------------------------------------- |
| `@modelcontextprotocol/sdk` | `@modelcontextprotocol/core` (types, protocol, transports) |
| | `@modelcontextprotocol/client` (client implementation) |
| | `@modelcontextprotocol/server` (server implementation) |
Remove the old package and install only the packages you need:
```bash
npm uninstall @modelcontextprotocol/sdk
# If you only need a client
npm install @modelcontextprotocol/client
# If you only need a server
npm install @modelcontextprotocol/server
# Both packages depend on @modelcontextprotocol/core automatically
```
Update your imports accordingly:
**Before (v1):**
```typescript
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js';
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
```
**After (v2):**
```typescript
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';
```
Note: `@modelcontextprotocol/client` and `@modelcontextprotocol/server` both re-export shared types from `@modelcontextprotocol/core`, so you can import types and error classes from whichever package you already depend on. Do not import from `@modelcontextprotocol/core` directly
— it is an internal package.
### Dropped Node.js 18 and CommonJS
v2 requires **Node.js 20+** and ships **ESM only** (no more CommonJS builds).
If your project uses CommonJS (`require()`), you will need to either:
- Migrate to ESM (`import`/`export`)
- Use dynamic `import()` to load the SDK
### Server decoupled from HTTP frameworks
The server package no longer depends on Express or Hono. HTTP framework integrations are now separate middleware packages:
| v1 | v2 |
| -------------------------------------- | ------------------------------------------- |
| Built into `@modelcontextprotocol/sdk` | `@modelcontextprotocol/node` (Node.js HTTP) |
| | `@modelcontextprotocol/express` (Express) |
| | `@modelcontextprotocol/hono` (Hono) |
Install the middleware package for your framework:
```bash
npm install @modelcontextprotocol/node # Node.js native http
npm install @modelcontextprotocol/express # Express
npm install @modelcontextprotocol/hono # Hono
```
### `StreamableHTTPServerTransport` renamed
`StreamableHTTPServerTransport` has been renamed to `NodeStreamableHTTPServerTransport` and moved to `@modelcontextprotocol/node`.
**Before (v1):**
```typescript
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: () => randomUUID() });
```
**After (v2):**
```typescript
import { NodeStreamableHTTPServerTransport } from '@modelcontextprotocol/node';
const transport = new NodeStreamableHTTPServerTransport({ sessionIdGenerator: () => randomUUID() });
```
### Server-side SSE transport removed
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 split
Resource Server helpers (`requireBearerAuth`, `mcpAuthMetadataRouter`, `getOAuthProtectedResourceMetadataUrl`, `OAuthTokenVerifier`) are now first-class in `@modelcontextprotocol/express`.
Authorization Server helpers (`mcpAuthRouter`, `OAuthServerProvider`, `ProxyOAuthServerProvider`, `authenticateClient`, `allowedMethods`, etc.) have been removed from the core SDK; new code should use a dedicated IdP/OAuth library. See the [examples](../examples/server/src/) for a working demo with `better-auth`.
Note: `AuthInfo` has moved from `server/auth/types.ts` to the core types and is now re-exported by `@modelcontextprotocol/client` and `@modelcontextprotocol/server`.
### `Headers` object instead of plain objects
Transport APIs and `RequestInfo.headers` now use the Web Standard `Headers` object instead of plain `Record<string, string | string[] | undefined>` (`IsomorphicHeaders` has been removed).
This affects both transport constructors and request handler code that reads headers:
**Before (v1):**
```typescript
// Transport headers
const transport = new StreamableHTTPClientTransport(url, {
requestInit: {
headers: {
Authorization: 'Bearer token',
'X-Custom': 'value'
}
}
});
// Reading headers in a request handler
const sessionId = extra.requestInfo?.headers['mcp-session-id'];
```
**After (v2):**
```typescript
// Transport headers
const transport = new StreamableHTTPClientTransport(url, {
requestInit: {
headers: new Headers({
Authorization: 'Bearer token',
'X-Custom': 'value'
})
}
});
// Reading headers in a request handler (ctx.http.req is the standard Web Request object)
const sessionId = ctx.http?.req?.headers.get('mcp-session-id');
// Reading query parameters
const url = new URL(ctx.http!.req!.url);
const debug = url.searchParams.get('debug');
```
### `McpServer.tool()`, `.prompt()`, `.resource()` removed
The deprecated variadic-overload methods have been removed. Use `registerTool`, `registerPrompt`, and `registerResource` instead. These use an explicit config object rather than positional arguments.
**Before (v1):**
```typescript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
const server = new McpServer({ name: 'demo', version: '1.0.0' });
// Tool with schema
server.tool('greet', { name: z.string() }, async ({ name }) => {
return { content: [{ type: 'text', text: `Hello, ${name}!` }] };
});
// Tool with description
server.tool('greet', 'Greet a user', { name: z.string() }, async ({ name }) => {
return { content: [{ type: 'text', text: `Hello, ${name}!` }] };
});
// Prompt
server.prompt('summarize', { text: z.string() }, async ({ text }) => {
return { messages: [{ role: 'user', content: { type: 'text', text: `Summarize: ${text}` } }] };
});
// Resource
server.resource('config', 'config://app', async uri => {
return { contents: [{ uri: uri.href, text: '{}' }] };
});
```
**After (v2):**
```typescript
import { McpServer } from '@modelcontextprotocol/server';
import * as z from 'zod/v4';
const server = new McpServer({ name: 'demo', version: '1.0.0' });
// Tool with schema
server.registerTool('greet', { inputSchema: z.object({ name: z.string() }) }, async ({ name }) => {
return { content: [{ type: 'text', text: `Hello, ${name}!` }] };
});
// Tool with description
server.registerTool('greet', { description: 'Greet a user', inputSchema: z.object({ name: z.string() }) }, async ({ name }) => {
return { content: [{ type: 'text', text: `Hello, ${name}!` }] };
});
// Prompt
server.registerPrompt('summarize', { argsSchema: z.object({ text: z.string() }) }, async ({ text }) => {
return { messages: [{ role: 'user', content: { type: 'text', text: `Summarize: ${text}` } }] };
});
// Resource
server.registerResource('config', 'config://app', {}, async uri => {
return { contents: [{ uri: uri.href, text: '{}' }] };
});
```
### Standard Schema objects required (raw shapes no longer supported)
v2 requires schema objects implementing the [Standard Schema spec](https://standardschema.dev/) for `inputSchema`, `outputSchema`, and `argsSchema`. Raw object shapes are no longer accepted. Zod v4, ArkType, and Valibot all implement the spec.
**Before (v1):**
```typescript
// Raw shape (object with Zod fields) - worked in v1
server.tool('greet', { name: z.string() }, async ({ name }) => { ... });
server.registerTool('greet', {
inputSchema: { name: z.string() } // raw shape
}, callback);
```
**After (v2):**
```typescript
import * as z from 'zod/v4';
// Wrap with z.object() (or use any Standard Schema library)
server.registerTool('greet', {
inputSchema: z.object({ name: z.string() })
}, async ({ name }) => { ... });
// ArkType works too
import { type } from 'arktype';
server.registerTool('greet', {
inputSchema: type({ name: 'string' })
}, async ({ name }) => { ... });
// Raw JSON Schema via fromJsonSchema (validator defaults to runtime-appropriate choice)
import { fromJsonSchema } from '@modelcontextprotocol/server';
server.registerTool('greet', {
inputSchema: fromJsonSchema({ type: 'object', properties: { name: { type: 'string' } } })
}, handler);
// For tools with no parameters, use z.object({})
server.registerTool('ping', {
inputSchema: z.object({})
}, async () => { ... });
```
This applies to:
- `inputSchema` in `registerTool()`
- `outputSchema` in `registerTool()`
- `argsSchema` in `registerPrompt()`
**Removed Zod-specific helpers** from `@modelcontextprotocol/core` (use Standard Schema equivalents):
| Removed | Replacement |
| ------------------------------------------------------------------------------------ | ----------------------------------------------------------------- |
| `schemaToJson(schema)` | `standardSchemaToJsonSchema(schema)` |
| `parseSchemaAsync(schema, data)` | `validateStandardSchema(schema, data)` |
| `SchemaInput<T>` | `StandardSchemaWithJSON.InferInput<T>` |
| `getSchemaShape`, `getSchemaDescription`, `isOptionalSchema`, `unwrapOptionalSchema` | No replacement — these are now internal Zod introspection helpers |
### Host header validation moved
Express-specific middleware (`hostHeaderValidation()`, `localhostHostValidation()`) moved from the server package to `@modelcontextprotocol/express`. The server package now exports framework-agnostic functions instead: `validateHostHeader()`, `localhostAllowedHostnames()`,
`hostHeaderValidationResponse()`.
**Before (v1):**
```typescript
import { hostHeaderValidation } from '@modelcontextprotocol/sdk/server/middleware.js';
app.use(hostHeaderValidation({ allowedHosts: ['example.com'] }));
```
**After (v2):**
```typescript
import { hostHeaderValidation } from '@modelcontextprotocol/express';
app.use(hostHeaderValidation(['example.com']));
```
Note: the v2 signature takes a plain `string[]` instead of an options object.
### `setRequestHandler` and `setNotificationHandler` use method strings
The low-level `setRequestHandler` and `setNotificationHandler` methods on `Client`, `Server`, and `Protocol` now take a method string instead of a Zod schema.
**Before (v1):**
```typescript
import { Server, InitializeRequestSchema, LoggingMessageNotificationSchema } from '@modelcontextprotocol/sdk/server/index.js';
const server = new Server({ name: 'my-server', version: '1.0.0' });
// Request handler with schema
server.setRequestHandler(InitializeRequestSchema, async request => {
return { protocolVersion: '...', capabilities: {}, serverInfo: { name: '...', version: '...' } };
});
// Notification handler with schema
server.setNotificationHandler(LoggingMessageNotificationSchema, notification => {
console.log(notification.params.data);
});
```
**After (v2):**
```typescript
import { Server } from '@modelcontextprotocol/server';
const server = new Server({ name: 'my-server', version: '1.0.0' });
// Request handler with method string
server.setRequestHandler('initialize', async request => {
return { protocolVersion: '...', capabilities: {}, serverInfo: { name: '...', version: '...' } };
});
// Notification handler with method string
server.setNotificationHandler('notifications/message', notification => {
console.log(notification.params.data);
});
```
The request and notification parameters remain fully typed via `RequestTypeMap` and `NotificationTypeMap`. You no longer need to import the individual `*RequestSchema` or `*NotificationSchema` constants for handler registration.
#### Custom (non-spec) methods
For vendor-prefixed methods (anything not in the MCP spec), use the 3-arg form: pass the method string, a `{ params, result? }` schemas object, and the handler. Any [Standard Schema](https://standardschema.dev) library works (Zod, Valibot, ArkType).
**Before (v1):**
```typescript
const AcmeSearch = z.object({
method: z.literal('acme/search'),
params: z.object({ query: z.string(), limit: z.number().int() })
});
server.setRequestHandler(AcmeSearch, async request => {
return { items: [/* ... */] };
});
```
**After (v2):**
```typescript
const SearchParams = z.object({ query: z.string(), limit: z.number().int() });
const SearchResult = z.object({ items: z.array(z.string()) });
server.setRequestHandler('acme/search', { params: SearchParams, result: SearchResult }, async (params, ctx) => {
return { items: [/* ... */] };
});
```
The handler receives the parsed `params` directly (not the full request envelope). `_meta` is stripped before validation and is available as `ctx.mcpReq._meta`. Supplying `result` types the handler's return value; omit it to return any `Result`.
For `setNotificationHandler`, the 3-arg handler is `(params, notification) => void`. The raw notification is the second argument, so `_meta` is recoverable via `notification.params?._meta`.
#### Sending custom-method requests
`request()` and `ctx.mcpReq.send()` accept a result schema as the second argument; for custom methods this is required:
```typescript
const result = await client.request({ method: 'acme/search', params: { query: 'mcp', limit: 3 } }, SearchResult);
result.items; // string[]
```
For spec methods the 1-arg form still works and the result type is inferred from the method name.
Common method string replacements:
| Schema (v1) | Method string (v2) |
| --------------------------------------- | ---------------------------------------- |
| `InitializeRequestSchema` | `'initialize'` |
| `CallToolRequestSchema` | `'tools/call'` |
| `ListToolsRequestSchema` | `'tools/list'` |
| `ListPromptsRequestSchema` | `'prompts/list'` |
| `GetPromptRequestSchema` | `'prompts/get'` |
| `ListResourcesRequestSchema` | `'resources/list'` |
| `ReadResourceRequestSchema` | `'resources/read'` |
| `CreateMessageRequestSchema` | `'sampling/createMessage'` |
| `ElicitRequestSchema` | `'elicitation/create'` |
| `LoggingMessageNotificationSchema` | `'notifications/message'` |
| `ToolListChangedNotificationSchema` | `'notifications/tools/list_changed'` |
| `ResourceListChangedNotificationSchema` | `'notifications/resources/list_changed'` |
| `PromptListChangedNotificationSchema` | `'notifications/prompts/list_changed'` |
### `Protocol.request()`, `ctx.mcpReq.send()`, and `Client.callTool()` no longer require a schema parameter for spec methods
For **spec** methods, the public `Protocol.request()`, `BaseContext.mcpReq.send()`, and `Client.callTool()` methods no longer require a Zod result schema argument. The SDK now resolves the correct result schema internally based on the method name. This means you no longer need to import result schemas
like `CallToolResultSchema` or `ElicitResultSchema` when making spec-method requests.
**`client.request()` — Before (v1):**
```typescript
import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js';
const result = await client.request({ method: 'tools/call', params: { name: 'my-tool', arguments: {} } }, CallToolResultSchema);
```
**After (v2):**
```typescript
const result = await client.request({ method: 'tools/call', params: { name: 'my-tool', arguments: {} } });
```
**`ctx.mcpReq.send()` — Before (v1):**
```typescript
import { CreateMessageResultSchema } from '@modelcontextprotocol/sdk/types.js';
server.setRequestHandler('tools/call', async (request, ctx) => {
const samplingResult = await ctx.mcpReq.send(
{ method: 'sampling/createMessage', params: { messages: [...], maxTokens: 100 } },
CreateMessageResultSchema
);
return { content: [{ type: 'text', text: 'done' }] };
});
```
**After (v2):**
```typescript
server.setRequestHandler('tools/call', async (request, ctx) => {
const samplingResult = await ctx.mcpReq.send(
{ method: 'sampling/createMessage', params: { messages: [...], maxTokens: 100 } }
);
return { content: [{ type: 'text', text: 'done' }] };
});
```
**`client.callTool()` — Before (v1):**
```typescript
import { CompatibilityCallToolResultSchema } from '@modelcontextprotocol/sdk/types.js';
const result = await client.callTool({ name: 'my-tool', arguments: {} }, CompatibilityCallToolResultSchema);
```
**After (v2):**
```typescript
const result = await client.callTool({ name: 'my-tool', arguments: {} });
```
The return type is now inferred from the method name via `ResultTypeMap`. For example, `client.request({ method: 'tools/call', ... })` returns `Promise<CallToolResult | CreateTaskResult>`.
For **custom (non-spec)** methods, keep the result-schema argument — see [Sending custom-method requests](#sending-custom-method-requests). Only drop the schema when calling a spec method.
If you were using `CallToolResultSchema` (or any `*Schema` constant) for **runtime validation** (not just in `request()`/`callTool()` calls), use `isSpecType` or `specTypeSchemas`:
```typescript
// v1: runtime validation with Zod schema
import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js';
if (CallToolResultSchema.safeParse(value).success) {
/* ... */
}
// v2: keyed type predicate
import { isSpecType } from '@modelcontextprotocol/client';
if (isSpecType.CallToolResult(value)) {
/* ... */
}
const blocks = mixed.filter(isSpecType.ContentBlock);
// v2: or get the StandardSchemaV1Sync validator object directly
import { specTypeSchemas } from '@modelcontextprotocol/client';
const result = specTypeSchemas.CallToolResult['~standard'].validate(value);
```
`isSpecType` and `specTypeSchemas` are keyed by `SpecTypeName` — a literal union of every named type in the MCP spec — so you get autocomplete and a compile error on typos. `specTypeSchemas.X` is a `StandardSchemaV1Sync<In, Out>``validate()` returns the result synchronously, so you can access `.issues` / `.value` without `await`. It composes with any Standard-Schema-aware library. The pre-existing `isCallToolResult(value)` guard still works.
### Client list methods return empty results for missing capabilities
`Client.listPrompts()`, `listResources()`, `listResourceTemplates()`, and `listTools()` now return empty results when the server didn't advertise the corresponding capability, instead of sending the request. This respects the MCP spec's capability negotiation.
To restore v1 behavior (throw an error when capabilities are missing), set `enforceStrictCapabilities: true`:
```typescript
const client = new Client(
{ name: 'my-client', version: '1.0.0' },
{
enforceStrictCapabilities: true
}
);
```
### `InMemoryTransport` moved
`InMemoryTransport` is now exported from `@modelcontextprotocol/client` and `@modelcontextprotocol/server` (both re-export it). It is still intended for in-process client-server connections and testing.
```typescript
// v1
import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';
// v2
import { InMemoryTransport } from '@modelcontextprotocol/server';
// or
import { InMemoryTransport } from '@modelcontextprotocol/client';
```
### Removed type aliases and deprecated exports
The following deprecated type aliases have been removed from `@modelcontextprotocol/core`:
| Removed | Replacement |
| ---------------------------------------- | ------------------------------------------------------------------------------------------------- |
| `JSONRPCError` | `JSONRPCErrorResponse` |
| `JSONRPCErrorSchema` | `JSONRPCErrorResponseSchema` |
| `isJSONRPCError` | `isJSONRPCErrorResponse` |
| `isJSONRPCResponse` | `isJSONRPCResultResponse` (see note below) |
| `ResourceReferenceSchema` | `ResourceTemplateReferenceSchema` |
| `ResourceReference` | `ResourceTemplateReference` |
| `IsomorphicHeaders` | Use Web Standard `Headers` |
| `AuthInfo` (from `server/auth/types.js`) | `AuthInfo` (now re-exported by `@modelcontextprotocol/client` and `@modelcontextprotocol/server`) |
All other types and schemas exported from `@modelcontextprotocol/sdk/types.js` retain their original names — import them from `@modelcontextprotocol/client` or `@modelcontextprotocol/server`.
> **Note on `isJSONRPCResponse`:** v1's `isJSONRPCResponse` was a deprecated alias that only checked for _result_ responses (it was equivalent to `isJSONRPCResultResponse`). v2 removes the deprecated alias and introduces a **new** `isJSONRPCResponse` with corrected semantics — it
> checks for _any_ response (either result or error). If you are migrating v1 code that used `isJSONRPCResponse`, rename it to `isJSONRPCResultResponse` to preserve the original behavior. Use the new `isJSONRPCResponse` only when you want to match both result and error responses.
**Before (v1):**
```typescript
import { JSONRPCError, ResourceReference, isJSONRPCError } from '@modelcontextprotocol/sdk/types.js';
```
**After (v2):**
```typescript
import { JSONRPCErrorResponse, ResourceTemplateReference, isJSONRPCErrorResponse } from '@modelcontextprotocol/server';
```
### Request handler context types
The `RequestHandlerExtra` type has been replaced with a structured context type hierarchy using nested groups:
| v1 | v2 |
| ---------------------------------------- | ---------------------------------------------------------------------- |
| `RequestHandlerExtra` (flat, all fields) | `ServerContext` (server handlers) or `ClientContext` (client handlers) |
| `extra` parameter name | `ctx` parameter name |
| `extra.signal` | `ctx.mcpReq.signal` |
| `extra.requestId` | `ctx.mcpReq.id` |
| `extra._meta` | `ctx.mcpReq._meta` |
| `extra.sendRequest(...)` | `ctx.mcpReq.send(...)` |
| `extra.sendNotification(...)` | `ctx.mcpReq.notify(...)` |
| `extra.authInfo` | `ctx.http?.authInfo` |
| `extra.requestInfo` | `ctx.http?.req` (standard Web `Request`, only on `ServerContext`) |
| `extra.closeSSEStream` | `ctx.http?.closeSSE` (only on `ServerContext`) |
| `extra.closeStandaloneSSEStream` | `ctx.http?.closeStandaloneSSE` (only on `ServerContext`) |
| `extra.sessionId` | `ctx.sessionId` |
| `extra.taskStore` | `ctx.task?.store` |
| `extra.taskId` | `ctx.task?.id` |
| `extra.taskRequestedTtl` | `ctx.task?.requestedTtl` |
**Before (v1):**
```typescript
server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
const headers = extra.requestInfo?.headers;
const taskStore = extra.taskStore;
await extra.sendNotification({ method: 'notifications/progress', params: { progressToken: 'abc', progress: 50, total: 100 } });
return { content: [{ type: 'text', text: 'result' }] };
});
```
**After (v2):**
```typescript
server.setRequestHandler('tools/call', async (request, ctx) => {
const headers = ctx.http?.req?.headers; // standard Web Request object
const taskStore = ctx.task?.store;
await ctx.mcpReq.notify({ method: 'notifications/progress', params: { progressToken: 'abc', progress: 50, total: 100 } });
return { content: [{ type: 'text', text: 'result' }] };
});
```
Context fields are organized into 4 groups:
- **`mcpReq`** — request-level concerns: `id`, `method`, `_meta`, `signal`, `send()`, `notify()`, plus server-only `log()`, `elicitInput()`, and `requestSampling()`
- **`http?`** — HTTP transport concerns (undefined for stdio): `authInfo`, plus server-only `req`, `closeSSE`, `closeStandaloneSSE`
- **`task?`** — task lifecycle: `id`, `store`, `requestedTtl`
`BaseContext` is the common base type shared by both `ServerContext` and `ClientContext`. `ServerContext` extends each group with server-specific additions via type intersection.
`ServerContext` also provides convenience methods for common server→client operations:
```typescript
server.setRequestHandler('tools/call', async (request, ctx) => {
// Send a log message (respects client's log level filter)
await ctx.mcpReq.log('info', 'Processing tool call', 'my-logger');
// Request client to sample an LLM
const samplingResult = await ctx.mcpReq.requestSampling({
messages: [{ role: 'user', content: { type: 'text', text: 'Hello' } }],
maxTokens: 100
});
// Elicit user input via a form
const elicitResult = await ctx.mcpReq.elicitInput({
message: 'Please provide details',
requestedSchema: { type: 'object', properties: { name: { type: 'string' } } }
});
return { content: [{ type: 'text', text: 'done' }] };
});
```
These replace the pattern of calling `server.sendLoggingMessage()`, `server.createMessage()`, and `server.elicitInput()` from within handlers.
### Error hierarchy refactoring
The SDK now distinguishes between three types of errors:
1. **`ProtocolError`** (renamed from `McpError`): Protocol errors that cross the wire as JSON-RPC error responses
2. **`SdkError`**: Local SDK errors that never cross the wire (timeouts, connection issues, capability checks)
3. **`SdkHttpError`** (extends `SdkError`): HTTP transport errors with typed `.status` and `.statusText` accessors
#### Renamed exports
| v1 | v2 |
| ---------------------------- | ------------------------------- |
| `McpError` | `ProtocolError` |
| `ErrorCode` | `ProtocolErrorCode` |
| `ErrorCode.RequestTimeout` | `SdkErrorCode.RequestTimeout` |
| `ErrorCode.ConnectionClosed` | `SdkErrorCode.ConnectionClosed` |
**Before (v1):**
```typescript
import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
try {
await client.callTool({ name: 'test', arguments: {} });
} catch (error) {
if (error instanceof McpError && error.code === ErrorCode.RequestTimeout) {
console.log('Request timed out');
}
if (error instanceof McpError && error.code === ErrorCode.InvalidParams) {
console.log('Invalid parameters');
}
}
```
**After (v2):**
```typescript
import { ProtocolError, ProtocolErrorCode, SdkError, SdkErrorCode } from '@modelcontextprotocol/client';
try {
await client.callTool({ name: 'test', arguments: {} });
} catch (error) {
// Local timeout/connection errors are now SdkError
if (error instanceof SdkError && error.code === SdkErrorCode.RequestTimeout) {
console.log('Request timed out');
}
// Protocol errors from the server are still ProtocolError
if (error instanceof ProtocolError && error.code === ProtocolErrorCode.InvalidParams) {
console.log('Invalid parameters');
}
}
```
#### New `SdkErrorCode` enum
The new `SdkErrorCode` enum contains string-valued codes for local SDK errors:
| Code | Description |
| ------------------------------------------------- | ------------------------------------------- |
| `SdkErrorCode.NotConnected` | Transport is not connected |
| `SdkErrorCode.AlreadyConnected` | Transport is already connected |
| `SdkErrorCode.NotInitialized` | Protocol is not initialized |
| `SdkErrorCode.CapabilityNotSupported` | Required capability is not supported |
| `SdkErrorCode.RequestTimeout` | Request timed out waiting for response |
| `SdkErrorCode.ConnectionClosed` | Connection was closed |
| `SdkErrorCode.SendFailed` | Failed to send message |
| `SdkErrorCode.InvalidResult` | Response result failed local schema validation |
| `SdkErrorCode.ClientHttpNotImplemented` | HTTP POST request failed |
| `SdkErrorCode.ClientHttpAuthentication` | Server returned 401 after re-authentication |
| `SdkErrorCode.ClientHttpForbidden` | Server returned 403 after trying upscoping |
| `SdkErrorCode.ClientHttpUnexpectedContent` | Unexpected content type in HTTP response |
| `SdkErrorCode.ClientHttpFailedToOpenStream` | Failed to open SSE stream |
| `SdkErrorCode.ClientHttpFailedToTerminateSession` | Failed to terminate session |
#### `StreamableHTTPError` removed
The `StreamableHTTPError` class has been removed. HTTP transport errors are now thrown as `SdkHttpError` (a subclass of `SdkError` with typed `.status` and `.statusText` accessors) with specific `SdkErrorCode` values that provide more granular error information:
**Before (v1):**
```typescript
import { StreamableHTTPError } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
try {
await transport.send(message);
} catch (error) {
if (error instanceof StreamableHTTPError) {
console.log('HTTP error:', error.code); // HTTP status code
}
}
```
**After (v2):**
```typescript
import { SdkHttpError, SdkErrorCode } from '@modelcontextprotocol/client';
try {
await transport.send(message);
} catch (error) {
if (error instanceof SdkHttpError) {
console.log('HTTP status:', error.status); // number — no cast needed
console.log('Status text:', error.statusText); // string | undefined
switch (error.code) {
case SdkErrorCode.ClientHttpAuthentication:
console.log('Auth failed — server rejected token after re-auth');
break;
case SdkErrorCode.ClientHttpForbidden:
console.log('Forbidden after upscoping attempt');
break;
case SdkErrorCode.ClientHttpFailedToOpenStream:
console.log('Failed to open SSE stream');
break;
case SdkErrorCode.ClientHttpNotImplemented:
console.log('HTTP request failed');
break;
}
}
}
```
#### Why this change?
Previously, `ErrorCode.RequestTimeout` (-32001) and `ErrorCode.ConnectionClosed` (-32000) were used for local timeout/connection errors. However, these errors never cross the wire as JSON-RPC responses - they are rejected locally. Using protocol error codes for local errors was
semantically inconsistent.
The new design:
- `ProtocolError` with `ProtocolErrorCode`: For errors that are serialized and sent as JSON-RPC error responses
- `SdkError` with `SdkErrorCode`: For local errors that are thrown/rejected locally and never leave the SDK
### OAuth error refactoring
The OAuth error classes have been consolidated into a single `OAuthError` class with an `OAuthErrorCode` enum.
#### Removed classes
The following individual error classes have been removed in favor of `OAuthError` with the appropriate code:
| v1 Class | v2 Equivalent |
| ------------------------------ | ----------------------------------------------------------------- |
| `InvalidRequestError` | `new OAuthError(OAuthErrorCode.InvalidRequest, message)` |
| `InvalidClientError` | `new OAuthError(OAuthErrorCode.InvalidClient, message)` |
| `InvalidGrantError` | `new OAuthError(OAuthErrorCode.InvalidGrant, message)` |
| `UnauthorizedClientError` | `new OAuthError(OAuthErrorCode.UnauthorizedClient, message)` |
| `UnsupportedGrantTypeError` | `new OAuthError(OAuthErrorCode.UnsupportedGrantType, message)` |
| `InvalidScopeError` | `new OAuthError(OAuthErrorCode.InvalidScope, message)` |
| `AccessDeniedError` | `new OAuthError(OAuthErrorCode.AccessDenied, message)` |
| `ServerError` | `new OAuthError(OAuthErrorCode.ServerError, message)` |
| `TemporarilyUnavailableError` | `new OAuthError(OAuthErrorCode.TemporarilyUnavailable, message)` |
| `UnsupportedResponseTypeError` | `new OAuthError(OAuthErrorCode.UnsupportedResponseType, message)` |
| `UnsupportedTokenTypeError` | `new OAuthError(OAuthErrorCode.UnsupportedTokenType, message)` |
| `InvalidTokenError` | `new OAuthError(OAuthErrorCode.InvalidToken, message)` |
| `MethodNotAllowedError` | `new OAuthError(OAuthErrorCode.MethodNotAllowed, message)` |
| `TooManyRequestsError` | `new OAuthError(OAuthErrorCode.TooManyRequests, message)` |
| `InvalidClientMetadataError` | `new OAuthError(OAuthErrorCode.InvalidClientMetadata, message)` |
| `InsufficientScopeError` | `new OAuthError(OAuthErrorCode.InsufficientScope, message)` |
| `InvalidTargetError` | `new OAuthError(OAuthErrorCode.InvalidTarget, message)` |
| `CustomOAuthError` | `new OAuthError(customCode, message)` |
The `OAUTH_ERRORS` constant has also been removed.
**Before (v1):**
```typescript
import { InvalidClientError, InvalidGrantError, ServerError } from '@modelcontextprotocol/client';
try {
await refreshToken();
} catch (error) {
if (error instanceof InvalidClientError) {
// Handle invalid client
} else if (error instanceof InvalidGrantError) {
// Handle invalid grant
} else if (error instanceof ServerError) {
// Handle server error
}
}
```
**After (v2):**
```typescript
import { OAuthError, OAuthErrorCode } from '@modelcontextprotocol/client';
try {
await refreshToken();
} catch (error) {
if (error instanceof OAuthError) {
switch (error.code) {
case OAuthErrorCode.InvalidClient:
// Handle invalid client
break;
case OAuthErrorCode.InvalidGrant:
// Handle invalid grant
break;
case OAuthErrorCode.ServerError:
// Handle server error
break;
}
}
}
```
### Experimental: `TaskCreationParams.ttl` no longer accepts `null`
The `ttl` field in `TaskCreationParams` (used when requesting the server to create a task) no longer accepts `null`. Per the MCP spec, `null` TTL (meaning unlimited lifetime) is only valid in server responses (`Task.ttl`), not in client requests. Clients should omit `ttl` to let
the server decide the lifetime.
This also narrows the type of `requestedTtl` in `TaskContext`, `CreateTaskServerContext`, and `TaskServerContext` from `number | null | undefined` to `number | undefined`.
**Before (v1):**
```typescript
// Requesting unlimited lifetime by passing null
const result = await client.callTool({
name: 'long-task',
arguments: {},
task: { ttl: null }
});
// Handler context had number | null | undefined
server.setRequestHandler('tools/call', async (request, ctx) => {
const ttl: number | null | undefined = ctx.task?.requestedTtl;
});
```
**After (v2):**
```typescript
// Omit ttl to let the server decide (server may return null for unlimited)
const result = await client.callTool({
name: 'long-task',
arguments: {},
task: {}
});
// Handler context is now number | undefined
server.setRequestHandler('tools/call', async (request, ctx) => {
const ttl: number | undefined = ctx.task?.requestedTtl;
});
```
> **Note:** These task APIs are marked `@experimental` and may change without notice.
## Enhancements
### Automatic JSON Schema validator selection by runtime
The SDK now automatically selects the appropriate JSON Schema validator based on your runtime environment:
- **Node.js**: Uses `AjvJsonSchemaValidator` (same as v1 default)
- **Cloudflare Workers**: Uses `CfWorkerJsonSchemaValidator` (previously required manual configuration)
This means Cloudflare Workers users no longer need to explicitly pass the validator:
**Before (v1) - Cloudflare Workers required explicit configuration:**
```typescript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { CfWorkerJsonSchemaValidator } from '@modelcontextprotocol/sdk/validation/cfworker';
const server = new McpServer(
{ name: 'my-server', version: '1.0.0' },
{
capabilities: { tools: {} },
jsonSchemaValidator: new CfWorkerJsonSchemaValidator() // Required in v1
}
);
```
**After (v2) - Works automatically:**
```typescript
import { McpServer } from '@modelcontextprotocol/server';
const server = new McpServer(
{ name: 'my-server', version: '1.0.0' },
{ capabilities: { tools: {} } }
// Validator auto-selected based on runtime
);
```
You can still explicitly override the validator if needed:
```typescript
// Runtime-aware default (auto-selects AjvJsonSchemaValidator or CfWorkerJsonSchemaValidator)
import { DefaultJsonSchemaValidator } from '@modelcontextprotocol/server/_shims';
// Specific validators
import { AjvJsonSchemaValidator } from '@modelcontextprotocol/server';
import { CfWorkerJsonSchemaValidator } from '@modelcontextprotocol/server/validators/cf-worker';
```
## Unchanged APIs
The following APIs are unchanged between v1 and v2 (only the import paths changed):
- `Client` constructor and most client methods (`connect`, `listTools`, `listPrompts`, `listResources`, `readResource`, etc.) — note: `callTool()` signature changed (schema parameter removed)
- `McpServer` constructor, `server.connect(transport)`, `server.close()`
- `Server` (low-level) constructor and all methods
- `StreamableHTTPClientTransport`, `SSEClientTransport`, `StdioClientTransport` constructors and options
- `StdioServerTransport` constructor and options
- All Zod schemas and type definitions from `types.ts` (except the aliases listed above)
- Tool, prompt, and resource callback return types
## Using an LLM to migrate your code
An LLM-optimized version of this guide is available at [`docs/migration-SKILL.md`](migration-SKILL.md). It contains dense mapping tables designed for tools like Claude Code to mechanically apply all the changes described above. You can paste it into your LLM context or load it as
a skill.
## Need Help?
If you encounter issues during migration:
1. Check the [FAQ](faq.md) for common questions about v2 changes
2. Review the [examples](https://github.com/modelcontextprotocol/typescript-sdk/tree/main/examples) for updated usage patterns
3. Open an issue on [GitHub](https://github.com/modelcontextprotocol/typescript-sdk/issues) if you find a bug or need further assistance

476
docs/server-quickstart.md Normal file
View File

@ -0,0 +1,476 @@
---
title: Server Quickstart
---
# Quickstart: Build a weather server
In this tutorial, we'll build a simple MCP weather server and connect it to a host.
## What we'll be building
We'll build a server that exposes two tools: `get-alerts` and `get-forecast`. Then we'll connect the server to an MCP host (in this case, VS Code with GitHub Copilot).
## Core MCP Concepts
MCP servers can provide three main types of capabilities:
1. **[Resources](https://modelcontextprotocol.io/docs/learn/server-concepts#resources)**: File-like data that can be read by clients (like API responses or file contents)
2. **[Tools](https://modelcontextprotocol.io/docs/learn/server-concepts#tools)**: Functions that can be called by the LLM (with user approval)
3. **[Prompts](https://modelcontextprotocol.io/docs/learn/server-concepts#prompts)**: Pre-written templates that help users accomplish specific tasks
This tutorial will primarily focus on tools.
Let's get started with building our weather server! [You can find the complete code for what we'll be building here.](https://github.com/modelcontextprotocol/typescript-sdk/tree/main/examples/server-quickstart)
## Prerequisites
This quickstart assumes you have familiarity with:
- TypeScript
- LLMs like Claude
Make sure you have Node.js version 20 or higher installed. You can verify your installation:
```bash
node --version
npm --version
```
> [!TIP]
> The MCP SDK also works with **Bun** and **Deno**. This tutorial uses Node.js, but you can substitute `bun` or `deno` commands where appropriate. For HTTP-based servers on Bun or Deno, use `WebStandardStreamableHTTPServerTransport` instead of the Node.js-specific transport — see the [server guide](./server.md) for details.
## Set up your environment
First, let's install Node.js and npm if you haven't already. You can download them from [nodejs.org](https://nodejs.org/).
Now, let's create and set up our project:
**macOS/Linux:**
```bash
# Create a new directory for our project
mkdir weather
cd weather
# Initialize a new npm project
npm init -y
# Install dependencies
npm install @modelcontextprotocol/server zod
npm install -D @types/node typescript
# Create our files
mkdir src
touch src/index.ts
```
**Windows:**
```powershell
# Create a new directory for our project
md weather
cd weather
# Initialize a new npm project
npm init -y
# Install dependencies
npm install @modelcontextprotocol/server zod
npm install -D @types/node typescript
# Create our files
md src
new-item src\index.ts
```
Update your `package.json` to add `type: "module"` and a build script:
```json
{
"type": "module",
"bin": {
"weather": "./build/index.js"
},
"scripts": {
"build": "tsc && chmod 755 build/index.js"
},
"files": ["build"]
}
```
Create a `tsconfig.json` in the root of your project:
```json
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./build",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
```
Now let's dive into building your server.
## Building your server
### Importing packages and setting up the instance
Add these to the top of your `src/index.ts`:
```ts source="../examples/server-quickstart/src/index.ts#prelude"
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';
const USER_AGENT = 'weather-app/1.0';
// Create server instance
const server = new McpServer({
name: 'weather',
version: '1.0.0',
});
```
### Helper functions
Next, let's add our helper functions for querying and formatting the data from the National Weather Service API:
```ts source="../examples/server-quickstart/src/index.ts#helpers"
// Helper function for making NWS API requests
async function makeNWSRequest<T>(url: string): Promise<T | null> {
const headers = {
'User-Agent': USER_AGENT,
Accept: 'application/geo+json',
};
try {
const response = await fetch(url, { headers });
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return (await response.json()) as T;
} catch (error) {
console.error('Error making NWS request:', error);
return null;
}
}
interface AlertFeature {
properties: {
event?: string;
areaDesc?: string;
severity?: string;
status?: string;
headline?: string;
};
}
// Format alert data
function formatAlert(feature: AlertFeature): string {
const props = feature.properties;
return [
`Event: ${props.event || 'Unknown'}`,
`Area: ${props.areaDesc || 'Unknown'}`,
`Severity: ${props.severity || 'Unknown'}`,
`Status: ${props.status || 'Unknown'}`,
`Headline: ${props.headline || 'No headline'}`,
'---',
].join('\n');
}
interface ForecastPeriod {
name?: string;
temperature?: number;
temperatureUnit?: string;
windSpeed?: string;
windDirection?: string;
shortForecast?: string;
}
interface AlertsResponse {
features: AlertFeature[];
}
interface PointsResponse {
properties: {
forecast?: string;
};
}
interface ForecastResponse {
properties: {
periods: ForecastPeriod[];
};
}
```
### Registering tools
Each tool is registered with {@linkcode @modelcontextprotocol/server!server/mcp.McpServer#registerTool | server.registerTool()}, which takes the tool name, a configuration object (with description and input schema), and a callback that implements the tool logic. Let's register our two weather tools:
```ts source="../examples/server-quickstart/src/index.ts#registerTools"
// Register weather tools
server.registerTool(
'get-alerts',
{
title: 'Get Weather Alerts',
description: 'Get weather alerts for a state',
inputSchema: z.object({
state: z.string().length(2)
.describe('Two-letter state code (e.g. CA, NY)'),
}),
},
async ({ state }) => {
const stateCode = state.toUpperCase();
const alertsUrl = `${NWS_API_BASE}/alerts?area=${stateCode}`;
const alertsData = await makeNWSRequest<AlertsResponse>(alertsUrl);
if (!alertsData) {
return {
content: [{
type: 'text' as const,
text: 'Failed to retrieve alerts data',
}],
};
}
const features = alertsData.features || [];
if (features.length === 0) {
return {
content: [{
type: 'text' as const,
text: `No active alerts for ${stateCode}`,
}],
};
}
const formattedAlerts = features.map(formatAlert);
return {
content: [{
type: 'text' as const,
text: `Active alerts for ${stateCode}:\n\n${formattedAlerts.join('\n')}`,
}],
};
},
);
server.registerTool(
'get-forecast',
{
title: 'Get Weather Forecast',
description: 'Get weather forecast for a location',
inputSchema: z.object({
latitude: z.number().min(-90).max(90)
.describe('Latitude of the location'),
longitude: z.number().min(-180).max(180)
.describe('Longitude of the location'),
}),
},
async ({ latitude, longitude }) => {
// Get grid point data
const pointsUrl = `${NWS_API_BASE}/points/${latitude.toFixed(4)},${longitude.toFixed(4)}`;
const pointsData = await makeNWSRequest<PointsResponse>(pointsUrl);
if (!pointsData) {
return {
content: [{
type: 'text' as const,
text: `Failed to retrieve grid point data for coordinates: ${latitude}, ${longitude}. This location may not be supported by the NWS API (only US locations are supported).`,
}],
};
}
const forecastUrl = pointsData.properties?.forecast;
if (!forecastUrl) {
return {
content: [{
type: 'text' as const,
text: 'Failed to get forecast URL from grid point data',
}],
};
}
// Get forecast data
const forecastData = await makeNWSRequest<ForecastResponse>(forecastUrl);
if (!forecastData) {
return {
content: [{
type: 'text' as const,
text: 'Failed to retrieve forecast data',
}],
};
}
const periods = forecastData.properties?.periods || [];
if (periods.length === 0) {
return {
content: [{
type: 'text' as const,
text: 'No forecast periods available',
}],
};
}
// Format forecast periods
const formattedForecast = periods.map((period: ForecastPeriod) =>
[
`${period.name || 'Unknown'}:`,
`Temperature: ${period.temperature || 'Unknown'}°${period.temperatureUnit || 'F'}`,
`Wind: ${period.windSpeed || 'Unknown'} ${period.windDirection || ''}`,
`${period.shortForecast || 'No forecast available'}`,
'---',
].join('\n'),
);
return {
content: [{
type: 'text' as const,
text: `Forecast for ${latitude}, ${longitude}:\n\n${formattedForecast.join('\n')}`,
}],
};
},
);
```
### Running the server
Finally, implement the main function to run the server:
```ts source="../examples/server-quickstart/src/index.ts#main"
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('Weather MCP Server running on stdio');
}
main().catch((error) => {
console.error('Fatal error in main():', error);
process.exit(1);
});
```
> [!IMPORTANT]
> Always use `console.error()` instead of `console.log()` in stdio-based MCP servers. Standard output is reserved for JSON-RPC protocol messages, and writing to it with `console.log()` will corrupt the communication channel.
Make sure to run `npm run build` to build your server! This is a very important step in getting your server to connect.
Let's now test your server from an existing MCP host.
## Testing your server in VS Code
[VS Code](https://code.visualstudio.com/) with [GitHub Copilot](https://github.com/features/copilot) can discover and invoke MCP tools via agent mode. [Copilot Free](https://github.com/features/copilot/plans) is sufficient to follow along.
> [!NOTE]
> Servers can connect to any client. We've chosen VS Code here for simplicity, but we also have a guide on [building your own client](./client-quickstart.md) as well as a [list of other clients here](https://modelcontextprotocol.io/clients).
### Prerequisites
1. Install [VS Code](https://code.visualstudio.com/) (version 1.99 or later).
2. Install the **GitHub Copilot** extension from the VS Code Extensions marketplace.
3. Sign in to your GitHub account when prompted.
### Configure the MCP server
Create a `.vscode/mcp.json` file in your `weather` project root:
```json
{
"servers": {
"weather": {
"type": "stdio",
"command": "node",
"args": ["./build/index.js"]
}
}
}
```
VS Code may prompt you to trust the MCP server when it detects this file. If prompted, confirm to start the server.
To verify, run **MCP: List Servers** from the Command Palette (`Ctrl+Shift+P` / `Cmd+Shift+P`). The `weather` server should show a running status.
### Use the tools
1. Open **Copilot Chat** (`Ctrl+Alt+I` / `Ctrl+Cmd+I`).
2. Select **Agent** mode from the mode selector at the top of the chat panel.
3. Click the **Tools** button to confirm `get-alerts` and `get-forecast` appear.
4. Try these prompts:
- "What's the weather in Sacramento?"
- "What are the active weather alerts in Texas?"
> [!NOTE]
> Since this is the US National Weather Service, the queries will only work for US locations.
## What's happening under the hood
When you ask a question:
1. The client sends your question to the LLM
2. The LLM analyzes the available tools and decides which one(s) to use
3. The client executes the chosen tool(s) through the MCP server
4. The results are sent back to the LLM
5. The LLM formulates a natural language response
6. The response is displayed to you
## Troubleshooting
<details>
<summary>VS Code integration issues</summary>
**Server not appearing or fails to start**
1. Verify you have VS Code 1.99 or later (`Help > About`) and that GitHub Copilot is installed.
2. Verify the server builds without errors: run `npm run build` in the `weather` directory.
3. Test it manually: run `node build/index.js` — the process should start and wait for input. Press `Ctrl+C` to exit.
4. Check the server logs: in **MCP: List Servers**, select the server and choose **Show Output**.
5. If the `node` command is not found, use the full path to the Node binary.
**Tools don't appear in Copilot Chat**
1. Confirm you're in **Agent** mode (not Ask or Edit mode).
2. Run **MCP: Reset Cached Tools** from the Command Palette, then recheck the **Tools** list.
</details>
<details>
<summary>Weather API issues</summary>
**Error: Failed to retrieve grid point data**
This usually means either:
1. The coordinates are outside the US
2. The NWS API is having issues
3. You're being rate limited
Fix:
- Verify you're using US coordinates
- Add a small delay between requests
- Check the NWS API status page
**Error: No active alerts for [STATE]**
This isn't an error - it just means there are no current weather alerts for that state. Try a different state or check during severe weather.
</details>
## Next steps
Now that your server is running locally, here are some ways to go further:
- [**Server guide**](./server.md) — Add resources, prompts, logging, error handling, and remote transports to your server.
- [**Example servers**](https://github.com/modelcontextprotocol/typescript-sdk/tree/main/examples/server) — Browse runnable examples covering OAuth, streaming, sessions, and more.
- [**FAQ**](./faq.md) — Troubleshoot common errors (Zod version conflicts, transport issues, etc.).

592
docs/server.md Normal file
View File

@ -0,0 +1,592 @@
---
title: Server Guide
---
# Building MCP servers
This guide covers the TypeScript SDK APIs for building MCP servers. For protocol-level concepts — what tools, resources, and prompts are and when to use each — see the [MCP overview](https://modelcontextprotocol.io/docs/learn/architecture).
Building a server takes three steps:
1. Create an {@linkcode @modelcontextprotocol/server!server/mcp.McpServer | McpServer} and register your [tools](#tools), [resources](#resources), and [prompts](#prompts).
2. Create a transport — [Streamable HTTP](#streamable-http) for remote servers or [stdio](#stdio) for local integrations.
3. Connect them with `server.connect(transport)`.
## Imports
The examples below use these imports. Adjust based on which features and transport you need:
```ts source="../examples/server/src/serverGuide.examples.ts#imports"
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 } from '@modelcontextprotocol/server';
import { StdioServerTransport } from '@modelcontextprotocol/server/stdio';
import * as z from 'zod/v4';
```
## Transports
MCP supports two transport mechanisms (see [Transport layer](https://modelcontextprotocol.io/docs/learn/architecture#transport-layer) in the MCP overview). Choose based on deployment model:
- **Streamable HTTP** — for remote servers accessible over the network.
- **stdio** — for local servers spawned as child processes (Claude Desktop, CLI tools).
### Streamable HTTP
Create a {@linkcode @modelcontextprotocol/node!streamableHttp.NodeStreamableHTTPServerTransport | NodeStreamableHTTPServerTransport} and connect it to your server:
```ts source="../examples/server/src/serverGuide.examples.ts#streamableHttp_stateful"
const server = new McpServer({ name: 'my-server', version: '1.0.0' });
const transport = new NodeStreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID()
});
await server.connect(transport);
```
**Options:** Set `sessionIdGenerator` to a function (shown above) for stateful sessions. Set it to `undefined` for stateless mode (simpler, but does not support resumability). Set `enableJsonResponse: true` to return plain JSON instead of SSE streams.
For a complete server with sessions, logging, and CORS mounted on Express, see [`simpleStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/simpleStreamableHttp.ts).
### stdio
For local, process-spawned integrations, use {@linkcode @modelcontextprotocol/server!server/stdio.StdioServerTransport | StdioServerTransport}:
```ts source="../examples/server/src/serverGuide.examples.ts#stdio_basic"
const server = new McpServer({ name: 'my-server', version: '1.0.0' });
const transport = new StdioServerTransport();
await server.connect(transport);
```
## Server instructions
Instructions describe how to use the server and its features — cross-tool relationships, workflow patterns, and constraints (see [Instructions](https://modelcontextprotocol.io/specification/latest/basic/lifecycle#instructions) in the MCP specification). Clients may add them to the system prompt. Instructions should not duplicate information already in tool descriptions.
```ts source="../examples/server/src/serverGuide.examples.ts#instructions_basic"
const server = new McpServer(
{ name: 'db-server', version: '1.0.0' },
{
instructions:
'Always call list_tables before running queries. Use validate_schema before migrate_schema for safe migrations. Results are limited to 1000 rows.'
}
);
```
## Tools
Tools let clients invoke actions on your server — they are usually the main way LLMs call into your application (see [Tools](https://modelcontextprotocol.io/docs/learn/server-concepts#tools) in the MCP overview).
Register a tool with {@linkcode @modelcontextprotocol/server!server/mcp.McpServer#registerTool | registerTool}. Provide an `inputSchema` (Zod) to validate arguments, and optionally an `outputSchema` for structured return values:
```ts source="../examples/server/src/serverGuide.examples.ts#registerTool_basic"
server.registerTool(
'calculate-bmi',
{
title: 'BMI Calculator',
description: 'Calculate Body Mass Index',
inputSchema: z.object({
weightKg: z.number(),
heightM: z.number()
}),
outputSchema: z.object({ bmi: z.number() })
},
async ({ weightKg, heightM }) => {
const output = { bmi: weightKg / (heightM * heightM) };
return {
content: [{ type: 'text', text: JSON.stringify(output) }],
structuredContent: output
};
}
);
```
> [!NOTE]
> When defining a named type for `structuredContent`, use a `type` alias rather than an `interface`. Named interfaces lack implicit index signatures in TypeScript, so they aren't assignable to `{ [key: string]: unknown }`:
>
> ```ts
> type BmiResult = { bmi: number }; // assignable
> interface BmiResult { bmi: number } // type error
> ```
>
> Alternatively, spread the value: `structuredContent: { ...result }`.
### `ResourceLink` outputs
Tools can return `resource_link` content items to reference large resources without embedding them, letting clients fetch only what they need:
```ts source="../examples/server/src/serverGuide.examples.ts#registerTool_resourceLink"
server.registerTool(
'list-files',
{
title: 'List Files',
description: 'Returns files as resource links without embedding content'
},
async (): Promise<CallToolResult> => {
const links: ResourceLink[] = [
{
type: 'resource_link',
uri: 'file:///projects/readme.md',
name: 'README',
mimeType: 'text/markdown'
},
{
type: 'resource_link',
uri: 'file:///projects/config.json',
name: 'Config',
mimeType: 'application/json'
}
];
return { content: links };
}
);
```
### Tool annotations
Tools can include annotations that hint at their behavior — whether a tool is read-only, destructive, or idempotent. Annotations help clients present tools appropriately without changing execution semantics:
```ts source="../examples/server/src/serverGuide.examples.ts#registerTool_annotations"
server.registerTool(
'delete-file',
{
description: 'Delete a file from the project',
inputSchema: z.object({ path: z.string() }),
annotations: {
title: 'Delete File',
destructiveHint: true,
idempotentHint: true
}
},
async ({ path }): Promise<CallToolResult> => {
// ... perform deletion ...
return { content: [{ type: 'text', text: `Deleted ${path}` }] };
}
);
```
### Error handling
Return `isError: true` to report tool-level errors. The LLM sees these and can self-correct, unlike protocol-level errors which are hidden from it:
```ts source="../examples/server/src/serverGuide.examples.ts#registerTool_errorHandling"
server.registerTool(
'fetch-data',
{
description: 'Fetch data from a URL',
inputSchema: z.object({ url: z.string() })
},
async ({ url }): Promise<CallToolResult> => {
try {
const res = await fetch(url);
if (!res.ok) {
return {
content: [{ type: 'text', text: `HTTP ${res.status}: ${res.statusText}` }],
isError: true
};
}
const text = await res.text();
return { content: [{ type: 'text', text }] };
} catch (error) {
return {
content: [{ type: 'text', text: `Failed: ${error instanceof Error ? error.message : String(error)}` }],
isError: true
};
}
}
);
```
If a handler throws instead of returning `isError`, the SDK catches the exception and converts it to `{ isError: true }` automatically — so an explicit try/catch is optional but gives you control over the error message. When `isError` is true, output schema validation is skipped.
## Resources
Resources expose read-only data — files, database schemas, configuration — that the host application can retrieve and attach as context for the model (see [Resources](https://modelcontextprotocol.io/docs/learn/server-concepts#resources) in the MCP overview). Unlike [tools](#tools), which the LLM invokes on its own, resources are application-controlled: the host decides which resources to fetch and how to present them.
A static resource at a fixed URI:
```ts source="../examples/server/src/serverGuide.examples.ts#registerResource_static"
server.registerResource(
'config',
'config://app',
{
title: 'Application Config',
description: 'Application configuration data',
mimeType: 'text/plain'
},
async uri => ({
contents: [{ uri: uri.href, text: 'App configuration here' }]
})
);
```
Dynamic resources use {@linkcode @modelcontextprotocol/server!server/mcp.ResourceTemplate | ResourceTemplate} with URI patterns. The `list` callback lets clients discover available instances:
```ts source="../examples/server/src/serverGuide.examples.ts#registerResource_template"
server.registerResource(
'user-profile',
new ResourceTemplate('user://{userId}/profile', {
list: async () => ({
resources: [
{ uri: 'user://123/profile', name: 'Alice' },
{ uri: 'user://456/profile', name: 'Bob' }
]
})
}),
{
title: 'User Profile',
description: 'User profile data',
mimeType: 'application/json'
},
async (uri, { userId }) => ({
contents: [
{
uri: uri.href,
text: JSON.stringify({ userId, name: 'Example User' })
}
]
})
);
```
## Prompts
Prompts are reusable templates that help structure interactions with models (see [Prompts](https://modelcontextprotocol.io/docs/learn/server-concepts#prompts) in the MCP overview). Use a prompt when you want to offer a canned interaction pattern that users invoke explicitly; use a [tool](#tools) when the LLM should decide when to call it.
```ts source="../examples/server/src/serverGuide.examples.ts#registerPrompt_basic"
server.registerPrompt(
'review-code',
{
title: 'Code Review',
description: 'Review code for best practices and potential issues',
argsSchema: z.object({
code: z.string()
})
},
({ code }) => ({
messages: [
{
role: 'user' as const,
content: {
type: 'text' as const,
text: `Please review this code:\n\n${code}`
}
}
]
})
);
```
## Completions
Both prompts and resources can support argument completions. Wrap a field in the `argsSchema` with {@linkcode @modelcontextprotocol/server!server/completable.completable | completable()} to provide autocompletion suggestions:
```ts source="../examples/server/src/serverGuide.examples.ts#registerPrompt_completion"
server.registerPrompt(
'review-code',
{
title: 'Code Review',
description: 'Review code for best practices',
argsSchema: z.object({
language: completable(z.string().describe('Programming language'), value =>
['typescript', 'javascript', 'python', 'rust', 'go'].filter(lang => lang.startsWith(value))
)
})
},
({ language }) => ({
messages: [
{
role: 'user' as const,
content: {
type: 'text' as const,
text: `Review this ${language} code for best practices.`
}
}
]
})
);
```
## Logging
Logging lets your server send structured diagnostics — debug traces, progress updates, warnings — to the connected client as notifications (see [Logging](https://modelcontextprotocol.io/specification/latest/server/utilities/logging) in the MCP specification).
Declare the `logging` capability, then call `ctx.mcpReq.log(level, data)` (from {@linkcode @modelcontextprotocol/server!index.ServerContext | ServerContext}) inside any handler:
```ts source="../examples/server/src/serverGuide.examples.ts#logging_capability"
const server = new McpServer({ name: 'my-server', version: '1.0.0' }, { capabilities: { logging: {} } });
```
Then log from any handler:
```ts source="../examples/server/src/serverGuide.examples.ts#registerTool_logging"
server.registerTool(
'fetch-data',
{
description: 'Fetch data from an API',
inputSchema: z.object({ url: z.string() })
},
async ({ url }, ctx): Promise<CallToolResult> => {
await ctx.mcpReq.log('info', `Fetching ${url}`);
const res = await fetch(url);
await ctx.mcpReq.log('debug', `Response status: ${res.status}`);
const text = await res.text();
return { content: [{ type: 'text', text }] };
}
);
```
## Progress
Progress notifications let a tool report incremental status updates during long-running operations (see [Progress](https://modelcontextprotocol.io/specification/latest/basic/utilities/progress) in the MCP specification).
If the client includes a `progressToken` in the request `_meta`, send `notifications/progress` via `ctx.mcpReq.notify()` (from {@linkcode @modelcontextprotocol/server!index.BaseContext | BaseContext}):
```ts source="../examples/server/src/serverGuide.examples.ts#registerTool_progress"
server.registerTool(
'process-files',
{
description: 'Process files with progress updates',
inputSchema: z.object({ files: z.array(z.string()) })
},
async ({ files }, ctx): Promise<CallToolResult> => {
const progressToken = ctx.mcpReq._meta?.progressToken;
for (let i = 0; i < files.length; i++) {
// ... process files[i] ...
if (progressToken !== undefined) {
await ctx.mcpReq.notify({
method: 'notifications/progress',
params: {
progressToken,
progress: i + 1,
total: files.length,
message: `Processed ${files[i]}`
}
});
}
}
return { content: [{ type: 'text', text: `Processed ${files.length} files` }] };
}
);
```
`progress` must increase on each call. `total` and `message` are optional. If the client does not provide a `progressToken`, skip the notification.
## Server-initiated requests
MCP is bidirectional — servers can send requests *to* the client during tool execution, as long as the client declares matching capabilities (see [Architecture](https://modelcontextprotocol.io/docs/learn/architecture) in the MCP overview).
### Sampling
Sampling lets a tool handler request an LLM completion from the connected client — the handler describes a prompt and the client returns the model's response (see [Sampling](https://modelcontextprotocol.io/docs/learn/client-concepts#sampling) in the MCP overview). Use sampling when a tool needs the model to generate or transform text mid-execution.
Call `ctx.mcpReq.requestSampling(params)` (from {@linkcode @modelcontextprotocol/server!index.ServerContext | ServerContext}) inside a tool handler:
```ts source="../examples/server/src/serverGuide.examples.ts#registerTool_sampling"
server.registerTool(
'summarize',
{
description: 'Summarize text using the client LLM',
inputSchema: z.object({ text: z.string() })
},
async ({ text }, ctx): Promise<CallToolResult> => {
const response = await ctx.mcpReq.requestSampling({
messages: [
{
role: 'user',
content: {
type: 'text',
text: `Please summarize:\n\n${text}`
}
}
],
maxTokens: 500
});
return {
content: [
{
type: 'text',
text: `Model (${response.model}): ${JSON.stringify(response.content)}`
}
]
};
}
);
```
For a full runnable example, see [`toolWithSampleServer.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/toolWithSampleServer.ts).
### Elicitation
Elicitation lets a tool handler request direct input from the user — form fields, confirmations, or a redirect to a URL (see [Elicitation](https://modelcontextprotocol.io/docs/learn/client-concepts#elicitation) in the MCP overview). It supports two modes:
- **Form** (`mode: 'form'`) — collects non-sensitive data via a schema-driven form.
- **URL** (`mode: 'url'`) — opens a browser URL for sensitive data or secure flows (API keys, payments, OAuth).
> [!IMPORTANT]
> Sensitive information must not be collected via form elicitation; always use URL elicitation or out-of-band flows for secrets.
Call `ctx.mcpReq.elicitInput(params)` (from {@linkcode @modelcontextprotocol/server!index.ServerContext | ServerContext}) inside a tool handler:
```ts source="../examples/server/src/serverGuide.examples.ts#registerTool_elicitation"
server.registerTool(
'collect-feedback',
{
description: 'Collect user feedback via a form',
inputSchema: z.object({})
},
async (_args, ctx): Promise<CallToolResult> => {
const result = await ctx.mcpReq.elicitInput({
mode: 'form',
message: 'Please share your feedback:',
requestedSchema: {
type: 'object',
properties: {
rating: {
type: 'number',
title: 'Rating (1\u20135)',
minimum: 1,
maximum: 5
},
comment: { type: 'string', title: 'Comment' }
},
required: ['rating']
}
});
if (result.action === 'accept') {
return {
content: [
{
type: 'text',
text: `Thanks! ${JSON.stringify(result.content)}`
}
]
};
}
return { content: [{ type: 'text', text: 'Feedback declined.' }] };
}
);
```
For runnable examples, see [`elicitationFormExample.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/elicitationFormExample.ts) (form) and [`elicitationUrlExample.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/elicitationUrlExample.ts) (URL).
### Roots
Roots let a tool handler discover the client's workspace directories — for example, to scope a file search or identify project boundaries (see [Roots](https://modelcontextprotocol.io/docs/learn/client-concepts#roots) in the MCP overview). Call {@linkcode @modelcontextprotocol/server!server/server.Server#listRoots | server.server.listRoots()} (requires the client to declare the `roots` capability):
```ts source="../examples/server/src/serverGuide.examples.ts#registerTool_roots"
server.registerTool(
'list-workspace-files',
{
description: 'List files across all workspace roots',
inputSchema: z.object({})
},
async (_args, _ctx): Promise<CallToolResult> => {
const { roots } = await server.server.listRoots();
const summary = roots.map(r => `${r.name ?? r.uri}: ${r.uri}`).join('\n');
return { content: [{ type: 'text', text: summary }] };
}
);
```
## Tasks (experimental)
> [!WARNING]
> The tasks API is experimental and may change without notice.
Task-based execution enables "call-now, fetch-later" patterns for long-running operations (see [Tasks](https://modelcontextprotocol.io/specification/latest/basic/utilities/tasks) in the MCP specification). Instead of returning a result immediately, a tool creates a task that can be polled or resumed later. To use tasks:
- Provide a {@linkcode @modelcontextprotocol/server!index.TaskStore | TaskStore} implementation that persists task metadata and results (see {@linkcode @modelcontextprotocol/server!index.InMemoryTaskStore | InMemoryTaskStore} for reference).
- Enable the `tasks` capability when constructing the server.
- Register tools with {@linkcode @modelcontextprotocol/server!experimental/tasks/mcpServer.ExperimentalMcpServerTasks#registerToolTask | server.experimental.tasks.registerToolTask(...)}.
For a full runnable example, see [`simpleTaskInteractive.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/simpleTaskInteractive.ts).
## Shutdown
For stateful multi-session HTTP servers, capture the `http.Server` from `app.listen()` so you can stop accepting connections, then close each session transport:
```ts source="../examples/server/src/serverGuide.examples.ts#shutdown_statefulHttp"
// Capture the http.Server so it can be closed on shutdown
const httpServer = app.listen(3000);
process.on('SIGINT', async () => {
httpServer.close();
for (const [sessionId, transport] of transports) {
await transport.close();
transports.delete(sessionId);
}
process.exit(0);
});
```
Calling {@linkcode @modelcontextprotocol/server!index.Transport#close | transport.close()} closes SSE streams and rejects any pending outbound requests. In-flight tool handlers are not automatically drained — they are terminated when the process exits.
For stdio servers, {@linkcode @modelcontextprotocol/server!server/mcp.McpServer#close | server.close()} is sufficient:
```ts source="../examples/server/src/serverGuide.examples.ts#shutdown_stdio"
process.on('SIGINT', async () => {
await server.close();
process.exit(0);
});
```
For a complete multi-session server with shutdown handling, see [`simpleStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/simpleStreamableHttp.ts).
## Deployment
### DNS rebinding protection
Under normal circumstances, cross-origin browser restrictions limit what a malicious website can do to your localhost server. [DNS rebinding attacks](https://en.wikipedia.org/wiki/DNS_rebinding) get around those restrictions entirely by making the requests appear as same-origin, since the attacking domain resolves to localhost. Validating the host header on the server side protects against this scenario. **All localhost MCP servers should use DNS rebinding protection.**
The recommended approach is to use {@linkcode @modelcontextprotocol/express!express.createMcpExpressApp | createMcpExpressApp()} (from `@modelcontextprotocol/express`) or {@linkcode @modelcontextprotocol/hono!hono.createMcpHonoApp | createMcpHonoApp()} (from `@modelcontextprotocol/hono`), which enable Host header validation by default:
```ts source="../examples/server/src/serverGuide.examples.ts#dnsRebinding_basic"
// Default: DNS rebinding protection auto-enabled (host is 127.0.0.1)
const app = createMcpExpressApp();
// DNS rebinding protection also auto-enabled for localhost
const appLocal = createMcpExpressApp({ host: 'localhost' });
// No automatic protection when binding to all interfaces
const appOpen = createMcpExpressApp({ host: '0.0.0.0' });
```
When binding to `0.0.0.0` / `::`, provide an allow-list of hosts:
```ts source="../examples/server/src/serverGuide.examples.ts#dnsRebinding_allowedHosts"
const app = createMcpExpressApp({
host: '0.0.0.0',
allowedHosts: ['localhost', '127.0.0.1', 'myhost.local']
});
```
`createMcpHonoApp()` from `@modelcontextprotocol/hono` provides the same protection for Hono-based servers and Web Standard runtimes (Cloudflare Workers, Deno, Bun).
If you use `NodeStreamableHTTPServerTransport` directly with your own HTTP framework, you must implement Host header validation yourself. See the [`hostHeaderValidation`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/packages/middleware/express/src/express.ts) middleware source for reference.
## See also
- [`examples/server/`](https://github.com/modelcontextprotocol/typescript-sdk/tree/main/examples/server) — Full runnable server examples
- [Client guide](./client.md) — Building MCP clients with this SDK
- [MCP overview](https://modelcontextprotocol.io/docs/learn/architecture) — Protocol-level concepts: participants, layers, primitives
- [Migration guide](./migration.md) — Upgrading from previous SDK versions
- [FAQ](./faq.md) — Frequently asked questions and troubleshooting
### Additional examples
| Feature | Description | Example |
|---------|-------------|---------|
| Web Standard transport | Deploy on Cloudflare Workers, Deno, or Bun | [`honoWebStandardStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/honoWebStandardStreamableHttp.ts) |
| Session management | Per-session transport routing, initialization, and cleanup | [`simpleStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/simpleStreamableHttp.ts) |
| Resumability | Replay missed SSE events via an event store | [`inMemoryEventStore.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/inMemoryEventStore.ts) |
| CORS | Expose MCP headers for browser clients | [`simpleStreamableHttp.ts`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/src/simpleStreamableHttp.ts) |
| Multi-node deployment | Stateless, persistent-storage, and distributed routing patterns | [`examples/server/README.md`](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/examples/server/README.md#multi-node-deployment-patterns) |

9
docs/v2-banner.js Normal file
View File

@ -0,0 +1,9 @@
document.addEventListener("DOMContentLoaded", function () {
var banner = document.createElement("div");
banner.innerHTML =
"This documents a <strong>pre-release</strong> version of the SDK. Expect breaking changes. For the stable SDK, see the <a href='/'>V1 docs</a>.";
banner.style.cssText =
"background:#fff3cd;color:#856404;border-bottom:1px solid #ffc107;padding:8px 16px;text-align:center;font-size:14px;";
banner.querySelector("a").style.cssText = "color:#856404;text-decoration:underline;";
document.body.insertBefore(banner, document.body.firstChild);
});

Some files were not shown because too many files have changed in this diff Show More