Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/codemod-fixed-group.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@modelcontextprotocol/codemod': patch
---

Version the codemod together with the core SDK packages, matching the migration guide's shared-version guarantee.
22 changes: 19 additions & 3 deletions .changeset/config.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,27 @@
{
"$schema": "https://unpkg.com/@changesets/config@3.1.2/schema.json",
"changelog": ["@changesets/changelog-github", { "repo": "modelcontextprotocol/typescript-sdk" }],
"changelog": [
"@changesets/changelog-github",
{
"repo": "modelcontextprotocol/typescript-sdk"
}
],
"commit": false,
"fixed": [],
"fixed": [
[
"@modelcontextprotocol/core",
"@modelcontextprotocol/client",
"@modelcontextprotocol/server",
"@modelcontextprotocol/server-legacy",
"@modelcontextprotocol/codemod"
]
],
"linked": [],
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": ["@modelcontextprotocol/examples", "@mcp-examples/*"]
"ignore": [
"@modelcontextprotocol/examples",
"@mcp-examples/*"
]
}
6 changes: 6 additions & 0 deletions .changeset/lazy-ajv-engine.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@modelcontextprotocol/client': patch
'@modelcontextprotocol/server': patch
---

Construct the default Ajv validation engine lazily on first validation. Creating a `Client` or `Server` no longer pays the ajv + ajv-formats instantiation cost at startup when no JSON Schema validation ever runs.
7 changes: 7 additions & 0 deletions .changeset/lazy-era-wire-schemas.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@modelcontextprotocol/client': patch
'@modelcontextprotocol/server': patch
'@modelcontextprotocol/server-legacy': patch
---

Build protocol-revision wire schemas lazily on first validation instead of at import. Each revision's schema set is now constructed by a module-level memoized factory, so importing the client or server package no longer pays the construction cost of both frozen wire-schema graphs up front. Method membership in the revision registries stays static, the schemas themselves are unchanged, and registry lookups keep returning reference-identical schema objects.
8 changes: 7 additions & 1 deletion .changeset/pre.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,25 @@
"calltoolresult-content-default",
"cjs-ajv-validator-subpath",
"cjs-support-v2-packages",
"codemod-fixed-group",
"codemod-iterations-5",
"codemod-versions-from-manifests",
"content-type-media-type-validation",
"cross-bundle-error-instanceof",
"examples-protected-wiring",
"initialize-session-hygiene",
"lazy-ajv-engine",
"lazy-era-wire-schemas",
"malformed-resource-uri-invalid-params",
"post-dispatch-32021-http-400",
"probe-window-handler-restore",
"response-cache-document-codec",
"schemas-source-home",
"silent-validators-wave",
"standard-header-ows",
"standard-schema-elicitation",
"web-standard-bearer-auth",
"web-standard-oauth-metadata"
"web-standard-oauth-metadata",
"workerd-schema-preload"
]
}
7 changes: 7 additions & 0 deletions .changeset/response-cache-document-codec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@modelcontextprotocol/client': minor
---

The response cache now stores results as JSON-serialized documents (serialize on write, parse on read) instead of live object graphs isolated with `structuredClone`. Same mutation isolation, but no dependency on the `structuredClone` global — whose absence (jest+jsdom, Node < 17) previously made every cache write throw into the store-error swallow, silently disabling caching and output-schema lookups for the session. A value without a JSON representation now fails the write loudly to the error sink, and an undecodable document in an external store is reported, dropped, and read as a miss.

Migration for custom `ResponseCacheStore` implementations: `CacheEntry.value` (and the `set()` entry value) is now `string` — persist and return it verbatim, `JSON.parse` to inspect. Entries persisted by a previous SDK version fail decode once (reported, dropped) and are rewritten on the next fetch.
8 changes: 8 additions & 0 deletions .changeset/schemas-source-home.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@modelcontextprotocol/core': minor
'@modelcontextprotocol/client': minor
'@modelcontextprotocol/server': minor
'@modelcontextprotocol/server-legacy': minor
---

Move the schema source modules (spec schemas, OAuth schemas, protocol constants) into `@modelcontextprotocol/core` and resolve them from there as a regular runtime dependency instead of bundling a private copy into each package. An application importing more than one of the packages now evaluates a single shared schema graph with shared object identity. `@modelcontextprotocol/core` gains a `./internal` subpath (SDK-internal contract; may change in any release) and the four packages now version together.
6 changes: 6 additions & 0 deletions .changeset/workerd-schema-preload.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@modelcontextprotocol/client': minor
'@modelcontextprotocol/server': minor
---

Add `preloadSchemas()`, an explicit opt-in to eager wire-schema construction, and call it automatically in the Cloudflare Workers builds. The wire schemas are built lazily by default, which is the right trade on process-per-invocation runtimes — but on isolate platforms that bill request CPU while module evaluation runs during isolate warm-up, laziness moves construction into the first request each fresh isolate serves. Calling `preloadSchemas()` at module scope (it is synchronous and idempotent) moves that one-time cost back to module evaluation; the packages' workerd export condition now does this automatically, while the Node and browser builds stay lazy. The server package gains a dedicated browser shim for this (its `browser` condition previously reused the workerd shim), so browser bundles keep lazy construction.
19 changes: 10 additions & 9 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ Include what changed, why, and how to migrate. Search for related sections and g

### 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`.
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.

Expand All @@ -70,9 +70,10 @@ The SDK separates internal code from the public API surface:
- **`@modelcontextprotocol/core-internal`** (main entry, `packages/core-internal/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-internal/public`** (`packages/core-internal/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-internal/public`.
- **`@modelcontextprotocol/core`** (`packages/core/src/index.ts`) — Public Zod-schema package. Re-exports **only** the `*Schema` Zod constants (MCP spec + OAuth/OpenID), bundled from `core-internal` at build time. The published home for raw runtime validation (`CallToolResultSchema.parse(...)`); runtime-neutral (`zod` is its only dependency). Not consumed by the sibling packages`client`/`server` keep their own bundled schema copies and stay Zod-free in their public surface.
- **`@modelcontextprotocol/core`** (`packages/core/src/index.ts`) — Public Zod-schema package and the canonical home of the schema source modules (`src/schemas.ts`, `src/auth.ts`, `src/constants.ts`). The root entry re-exports **only** the `*Schema` Zod constants (MCP spec + OAuth/OpenID) — the published home for raw runtime validation (`CallToolResultSchema.parse(...)`); runtime-neutral (`zod` is its only dependency). The `./internal` subpath re-exports the schema modules wholesale for the sibling packages: `core-internal` re-exports them at the old module paths, and the `client`/`server`/`server-legacy` bundles resolve `@modelcontextprotocol/core/internal` as a real external dependency instead of carrying their own schema copies (their public surfaces stay Zod-free).

When modifying exports:

- Use explicit named exports, not `export *`, in package `index.ts` files and `core-internal/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-internal/public` or package index files.
Expand Down Expand Up @@ -197,14 +198,14 @@ The `ctx` parameter in handlers provides a structured context:

- `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
- `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
- `authInfo?`: Validated auth token info

**`ServerContext`** extends `BaseContext.mcpReq` and `BaseContext.http?` via type intersection:

Expand Down
3 changes: 2 additions & 1 deletion docs/advanced/wire-schemas.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
shape: how-to
---

# Wire schemas

`@modelcontextprotocol/core` exports the **wire schemas** — the exact Zod constants the SDK validates protocol and OAuth payloads against — for code that holds raw JSON instead of SDK objects.
Expand Down Expand Up @@ -56,7 +57,7 @@ These are the `*Schema` constants v1 exported from `@modelcontextprotocol/sdk/ty

If you build with `McpServer` or `Client`, skip this package: [tools](../servers/tools.md) arrive in your handler already validated, and [tool calls](../clients/calling.md) come back as typed results. Reach for `@modelcontextprotocol/core` when nothing stands between you and the JSON — gateways, proxies, test harnesses, [worker fleets](./gateway.md).

Install it separately (`npm install @modelcontextprotocol/core`) — `@modelcontextprotocol/server` and `@modelcontextprotocol/client` keep a Zod-free public surface and never depend on it. The package is runtime-neutral; `zod` is its only dependency.
`@modelcontextprotocol/server` and `@modelcontextprotocol/client` keep a Zod-free public surface, but they resolve their shared schema graph from this package at runtime, so it already arrives transitively in your tree. Add it to your own `dependencies` (`npm install @modelcontextprotocol/core`) when you import from it directly. The package is runtime-neutral; `zod` is its only dependency.

## Pick the schema for the message you hold

Expand Down
2 changes: 1 addition & 1 deletion docs/clients/caching.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ const store = new InMemoryResponseCacheStore({ maxEntries: 2048 });
const client = new Client({ name: 'my-client', version: '1.0.0' }, { responseCacheStore: store });
```

Every method on the `ResponseCacheStore` interface may return a promise, so a Redis-style store implements the same five methods. Entries are keyed by connected-server identity, so one store can back many clients: connections to different servers never collide.
Every method on the `ResponseCacheStore` interface may return a promise, so a Redis-style store implements the same five methods. `CacheEntry.value` is the JSON-serialized result document — a persistent store persists the string verbatim, no extra serialization step, and behaves identically to the in-memory default. Entries are keyed by connected-server identity, so one store can back many clients: connections to different servers never collide.

## Partition the store per user

Expand Down
2 changes: 1 addition & 1 deletion docs/clients/calling.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ A failed tool call is still a result: check `isError` on it before trusting `con

## Let the SDK walk the pages

That `listTools()` already walked every page: when a server splits its list, the SDK follows `nextCursor` page by page and returns one aggregated list with `nextCursor: undefined`. `listPrompts()`, `listResources()`, and `listResourceTemplates()` aggregate the same way.
That `listTools()` already walked every page: when a server splits its list, the SDK follows `nextCursor` page by page and returns one aggregated list with no `nextCursor`. `listPrompts()`, `listResources()`, and `listResourceTemplates()` aggregate the same way.

Pass a `cursor` — a page's `nextCursor` your application held on to — and `listTools` returns exactly that page, raw.

Expand Down
2 changes: 1 addition & 1 deletion docs/get-started/packages.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ Four adapters exist: `@modelcontextprotocol/node` for Node's built-in `http` ser

## Reach for `core` only to validate raw wire JSON

`@modelcontextprotocol/core` exports the Zod schema constants the SDK validates protocol payloads against, for code that handles raw JSON-RPC payloads itself — gateways, proxies, log pipelines. Neither `server` nor `client` exports a Zod schema, and the matching TypeScript types ship with both, so if you only call `registerTool` and `callTool` you never install it. [Wire schemas](../advanced/wire-schemas.md) is the how-to.
`@modelcontextprotocol/core` exports the Zod schema constants the SDK validates protocol payloads against, for code that handles raw JSON-RPC payloads itself — gateways, proxies, log pipelines. Neither `server` nor `client` exports a Zod schema, and the matching TypeScript types ship with both, so if you only call `registerTool` and `callTool` you never import it directly — it arrives transitively, since `server` and `client` resolve their shared schema graph from it at runtime. [Wire schemas](../advanced/wire-schemas.md) is the how-to.

## Leave `server-legacy` and `codemod` to the migration guide

Expand Down
12 changes: 8 additions & 4 deletions docs/migration/upgrade-to-v2.md
Original file line number Diff line number Diff line change
Expand Up @@ -848,7 +848,7 @@ hierarchy also exposes the same check as an explicit static guard
TypeScript — use whichever style your codebase prefers; both read the same brand.
Fine print (applies equally to `instanceof` and `isInstance`):

- **Version skew** — matching needs *both* copies at a brand-aware release; against an
- **Version skew** — matching needs _both_ copies at a brand-aware release; against an
older copy, behavior degrades to plain prototype `instanceof` (false across bundles).
During mixed-version rollouts, recognize errors without class identity: match
`error.name` plus the class's discriminant field (`code`, `status`), or reconstruct
Expand Down Expand Up @@ -1325,8 +1325,9 @@ if (CallToolResultSchema.safeParse(value).success) { ... }

`@modelcontextprotocol/core` is the canonical home for the spec's Zod schema constants
(and the OAuth/OpenID metadata schemas). It is runtime-neutral (its only dependency is
`zod`) and is **not** required by `client` / `server` — install it only if you import the
raw schemas directly.
`zod`) and arrives transitively as the shared runtime schema graph of `client` /
`server` — add it to your own `dependencies` only when you import the raw schemas
directly.

If you would rather keep your project Zod-free, the **`isSpecType` / `specTypeSchemas`**
alternatives are exported from `@modelcontextprotocol/client` and `…/server`:
Expand Down Expand Up @@ -1587,7 +1588,7 @@ rewrite required unless noted.
instead of sending the request. Set `enforceStrictCapabilities: true` in `ClientOptions`
to restore the v1 throw.
- Called **without a `cursor`**, the same methods now **auto-aggregate every page** and
return `nextCursor: undefined`. Passing `{ cursor }` still fetches one page. Manual
return an aggregate with no `nextCursor`. Passing `{ cursor }` still fetches one page. Manual
pagination loops keep working (the first iteration returns everything); replace them
with the bare no-arg call. The walk is capped at `ClientOptions.listMaxPages` (default
64); overrun throws `SdkError(ListPaginationExceeded)`. There is no way to fetch only
Expand All @@ -1605,6 +1606,9 @@ rewrite required unless noted.
trip. Per-call override: `{ cacheMode: 'refresh' | 'bypass' }`. New `ClientOptions`:
`cachePartition`, `defaultCacheTtlMs`. `ResponseCacheStore` gained `delete(key)`;
`InMemoryResponseCacheStore` is now bounded (`{ maxEntries }`, default 512).
`CacheEntry.value` (and the `set()` entry value) is the JSON-serialized result
document (`string`, not `unknown`): persist and return it verbatim, `JSON.parse`
it to inspect.

#### Server (Streamable HTTP transport)

Expand Down
5 changes: 3 additions & 2 deletions examples/caching/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@ class MyStore implements ResponseCacheStore {
async get(key: CacheKey): Promise<CacheEntry | undefined> {
/* read {value, stamp, expiresAt, scope} from your backend */
}
async set(key: CacheKey, entry: { value: unknown; expiresAt?: number; scope?: CacheScope }): Promise<number> {
/* write entry under key; return a monotonically-increasing stamp */
async set(key: CacheKey, entry: { value: string; expiresAt?: number; scope?: CacheScope }): Promise<number> {
/* write entry under key (value is the JSON-serialized result document —
persist the string verbatim); return a monotonically-increasing stamp */
}
async delete(key: CacheKey): Promise<void> {
/* drop the single entry under key (no-op if absent) */
Expand Down
3 changes: 3 additions & 0 deletions examples/client-quickstart/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
"@modelcontextprotocol/core-internal": [
"./node_modules/@modelcontextprotocol/client/node_modules/@modelcontextprotocol/core-internal/src/index.ts"
],
"@modelcontextprotocol/core/internal": [
"./node_modules/@modelcontextprotocol/client/node_modules/@modelcontextprotocol/core/src/internal.ts"
],
"@modelcontextprotocol/core-internal/public": [
"./node_modules/@modelcontextprotocol/client/node_modules/@modelcontextprotocol/core-internal/src/exports/public/index.ts"
]
Expand Down
3 changes: 3 additions & 0 deletions examples/server-quickstart/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
"@modelcontextprotocol/core-internal": [
"./node_modules/@modelcontextprotocol/server/node_modules/@modelcontextprotocol/core-internal/src/index.ts"
],
"@modelcontextprotocol/core/internal": [
"./node_modules/@modelcontextprotocol/server/node_modules/@modelcontextprotocol/core/src/internal.ts"
],
"@modelcontextprotocol/core-internal/public": [
"./node_modules/@modelcontextprotocol/server/node_modules/@modelcontextprotocol/core-internal/src/exports/public/index.ts"
]
Expand Down
3 changes: 3 additions & 0 deletions examples/shared/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
"@modelcontextprotocol/core-internal": [
"./node_modules/@modelcontextprotocol/server/node_modules/@modelcontextprotocol/core-internal/src/index.ts"
],
"@modelcontextprotocol/core/internal": [
"./node_modules/@modelcontextprotocol/server/node_modules/@modelcontextprotocol/core/src/internal.ts"
],
"@modelcontextprotocol/core-internal/public": [
"./node_modules/@modelcontextprotocol/server/node_modules/@modelcontextprotocol/core-internal/src/exports/public/index.ts"
],
Expand Down
Loading
Loading