同步完整源码 - 2026-05-25
This commit is contained in:
commit
7e65fec24a
|
|
@ -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)
|
||||
|
|
@ -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.
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'@modelcontextprotocol/fastify': minor
|
||||
---
|
||||
|
||||
Add Fastify middleware adapter for MCP servers, following the same pattern as the Express and Hono adapters.
|
||||
|
|
@ -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.
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'@modelcontextprotocol/core': patch
|
||||
---
|
||||
|
||||
Add missing `size` field to `ResourceSchema` to match the MCP specification
|
||||
|
|
@ -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).
|
||||
|
|
@ -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.
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
'@modelcontextprotocol/client': patch
|
||||
'@modelcontextprotocol/server': patch
|
||||
---
|
||||
|
||||
tasks - disallow requesting a null TTL
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
'@modelcontextprotocol/core': patch
|
||||
'@modelcontextprotocol/server': patch
|
||||
---
|
||||
|
||||
Fix ReDoS vulnerability in UriTemplate regex patterns (CVE-2026-0621)
|
||||
|
|
@ -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.
|
||||
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
|
|
@ -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`.
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'@modelcontextprotocol/server': patch
|
||||
---
|
||||
|
||||
missing change for fix(client): replace body.cancel() with text() to prevent hanging
|
||||
|
|
@ -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.
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
'@modelcontextprotocol/server': patch
|
||||
'@modelcontextprotocol/client': patch
|
||||
---
|
||||
|
||||
Export `InMemoryTransport` for in-process testing.
|
||||
|
|
@ -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()`.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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`
|
||||
|
|
@ -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
|
||||
|
|
@ -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`.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'@modelcontextprotocol/server': patch
|
||||
---
|
||||
|
||||
fix(server): propagate negotiated protocol version to transport in _oninitialize
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'@modelcontextprotocol/server': patch
|
||||
---
|
||||
|
||||
Prevent stack overflow in StreamableHTTPServerTransport.close() with re-entrant guard
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'@modelcontextprotocol/client': patch
|
||||
---
|
||||
|
||||
Fix StreamableHTTPClientTransport to handle error responses in SSE streams
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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`.
|
||||
|
|
@ -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.
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
'@modelcontextprotocol/node': patch
|
||||
'@modelcontextprotocol/test-integration': patch
|
||||
'@modelcontextprotocol/server': patch
|
||||
'@modelcontextprotocol/core': patch
|
||||
---
|
||||
|
||||
remove deprecated .tool, .prompt, .resource method signatures
|
||||
|
|
@ -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.
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'@modelcontextprotocol/server': patch
|
||||
---
|
||||
|
||||
reverting application/json in notifications
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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()`.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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`.
|
||||
|
|
@ -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
|
||||
|
|
@ -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.
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
'@modelcontextprotocol/node': patch
|
||||
'@modelcontextprotocol/test-integration': patch
|
||||
'@modelcontextprotocol/server': patch
|
||||
'@modelcontextprotocol/core': patch
|
||||
---
|
||||
|
||||
deprecated .tool, .prompt, .resource method removal
|
||||
|
|
@ -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.
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'@modelcontextprotocol/client': minor
|
||||
---
|
||||
|
||||
The client credentials providers now support scopes being added to the token request.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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).
|
||||
|
|
@ -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."
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
'@modelcontextprotocol/client': patch
|
||||
'@modelcontextprotocol/server': patch
|
||||
---
|
||||
|
||||
Initial 2.0.0-alpha.0 client and server package
|
||||
|
|
@ -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.
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"@modelcontextprotocol/express": patch
|
||||
---
|
||||
|
||||
Add jsonLimit option to createMcpExpressApp
|
||||
|
|
@ -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.
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
'@modelcontextprotocol/core': patch
|
||||
'@modelcontextprotocol/client': patch
|
||||
'@modelcontextprotocol/server': patch
|
||||
---
|
||||
|
||||
refactor: subclasses override `_wrapHandler` hook instead of redeclaring `setRequestHandler`.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.0–4.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,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
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: 'github-actions'
|
||||
directory: '/'
|
||||
schedule:
|
||||
interval: 'weekly'
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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 }}
|
||||
|
||||
|
|
@ -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'
|
||||
|
|
@ -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'
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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/
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -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);
|
||||
```
|
||||
|
|
@ -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>.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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/).
|
||||
|
||||
[](https://www.npmjs.com/package/@modelcontextprotocol/server)
|
||||
[](https://www.npmjs.com/package/@modelcontextprotocol/client) 
|
||||
|
||||
<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.
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
);
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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"]
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"include": ["./"],
|
||||
"extends": "@modelcontextprotocol/tsconfig",
|
||||
"compilerOptions": {
|
||||
"noEmit": true,
|
||||
"allowJs": true
|
||||
}
|
||||
}
|
||||
|
|
@ -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()]
|
||||
});
|
||||
|
|
@ -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.
|
||||
|
|
@ -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) |
|
||||
|
|
@ -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
|
||||
|
|
@ -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 SDK’s 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/).
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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.).
|
||||
|
|
@ -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) |
|
||||
|
|
@ -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
Loading…
Reference in New Issue