Skip to content
Open
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
3 changes: 2 additions & 1 deletion .changeset/spec-type-schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
'@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 `StandardSchemaV1<ContentBlock>` validator. Guards are standalone functions, so `arr.filter(isSpecType.ContentBlock)` works. Also export the `StandardSchemaV1`, `SpecTypeName`, and `SpecTypes` types.
Export `isSpecType` and `specTypeSchema` for runtime validation of any MCP spec type by name. `isSpecType('ContentBlock', value)` is a type predicate; `specTypeSchema('ContentBlock')` returns a `StandardSchemaV1<ContentBlock>` validator. Also export the `StandardSchemaV1`,
`SpecTypeName`, and `SpecTypes` types.
35 changes: 18 additions & 17 deletions docs/migration-SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,21 +51,21 @@ Replace all `@modelcontextprotocol/sdk/...` imports using this table.
| ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `@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/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` |
| 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:
Expand Down Expand Up @@ -99,7 +99,7 @@ Notes:
| `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 `StandardSchemaV1` validator object. The keys are typed as `SpecTypeName`, a literal union of all spec type names.
`isSpecType('TypeName', value)` (e.g., `isSpecType('CallToolResult', v)`) or `specTypeSchema('TypeName')` for the `StandardSchemaV1` validator object. The first argument is typed as `SpecTypeName`, a literal union of all spec type names.

### Error class changes

Expand Down Expand Up @@ -323,7 +323,8 @@ new URL(ctx.http?.req?.url).searchParams.get('debug')

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

Expand Down Expand Up @@ -462,14 +463,14 @@ For **custom (non-spec)** methods, keep the result-schema argument — see §9.

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`:
If a `*Schema` constant was used for **runtime validation** (not just as a `request()` argument), replace with `isSpecType` / `specTypeSchema`:

| v1 pattern | v2 replacement |
| -------------------------------------------------- | -------------------------------------------------------------------------------------- |
| `CallToolResultSchema.safeParse(value).success` | `isSpecType.CallToolResult(value)` |
| `<TypeName>Schema.safeParse(value).success` | `isSpecType.<TypeName>(value)` |
| `<TypeName>Schema.parse(value)` | `await specTypeSchemas.<TypeName>['~standard'].validate(value)` (returns a `Result`, not the value) |
| Passing `<TypeName>Schema` as a validator argument | `specTypeSchemas.<TypeName>` (a `StandardSchemaV1<In, Out>`) |
| v1 pattern | v2 replacement |
| -------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
| `CallToolResultSchema.safeParse(value).success` | `isSpecType('CallToolResult', value)` |
| `<TypeName>Schema.safeParse(value).success` | `isSpecType('<TypeName>', value)` |
| `<TypeName>Schema.parse(value)` | `await specTypeSchema('<TypeName>')['~standard'].validate(value)` (returns a `Result`, not the value) |
| Passing `<TypeName>Schema` as a validator argument | `specTypeSchema('<TypeName>')` (a `StandardSchemaV1<In, Out>`) |

`isCallToolResult(value)` still works, but `isSpecType` covers every spec type by name.

Expand Down
64 changes: 37 additions & 27 deletions docs/migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,8 @@

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

Expand Down Expand Up @@ -379,7 +380,11 @@
params: z.object({ query: z.string(), limit: z.number().int() })
});
server.setRequestHandler(AcmeSearch, async request => {
return { items: [/* ... */] };
return {
items: [
/* ... */
]
};
});
```

Expand All @@ -390,7 +395,11 @@
const SearchResult = z.object({ items: z.array(z.string()) });

server.setRequestHandler('acme/search', { params: SearchParams, result: SearchResult }, async (params, ctx) => {
return { items: [/* ... */] };
return {
items: [
/* ... */
]
};
});
```

Expand Down Expand Up @@ -429,8 +438,8 @@

### `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.
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):**

Expand Down Expand Up @@ -489,7 +498,7 @@

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`:
If you were using `CallToolResultSchema` (or any `*Schema` constant) for **runtime validation** (not just in `request()`/`callTool()` calls), use `isSpecType` or `specTypeSchema`:

```typescript
// v1: runtime validation with Zod schema
Expand All @@ -498,19 +507,20 @@
/* ... */
}

// v2: keyed type predicate
// v2: type predicate by name
import { isSpecType } from '@modelcontextprotocol/client';
if (isSpecType.CallToolResult(value)) {
if (isSpecType('CallToolResult', value)) {
/* ... */
}
const blocks = mixed.filter(isSpecType.ContentBlock);
const blocks = mixed.filter(v => isSpecType('ContentBlock', v));

// v2: or get the StandardSchemaV1 validator object directly
import { specTypeSchemas } from '@modelcontextprotocol/client';
const result = await specTypeSchemas.CallToolResult['~standard'].validate(value);
import { specTypeSchema } from '@modelcontextprotocol/client';
const result = await specTypeSchema('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 `StandardSchemaV1<In, Out>`, which composes with any Standard-Schema-aware library. The pre-existing `isCallToolResult(value)` guard still works.
The first argument to `isSpecType` and `specTypeSchema` is a `SpecTypeName` — a literal union of every named type in the MCP spec — so you get autocomplete and a compile error on typos. `specTypeSchema(name)` returns a `StandardSchemaV1<In, Out>`, which composes with any
Standard-Schema-aware library. The pre-existing `isCallToolResult(value)` guard still works.

### Client list methods return empty results for missing capabilities

Expand Down Expand Up @@ -706,22 +716,22 @@

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

Check warning on line 734 in docs/migration.md

View check run for this annotation

Claude / Claude Code Review

Unrelated Prettier reformatting inflates the comparison diff

nit: Several hunks in `docs/migration.md` and `docs/migration-SKILL.md` are pure Prettier reformatting unrelated to the `isSpecType`/`specTypeSchema` change — this SdkErrorCode table re-padding, the `items: [/* ... */]` code-block reflow at 382-389/397-402, the prose re-wraps at 140-141/441-442, and the Types/shared-imports table re-padding in `migration-SKILL.md`. Since the PR's stated purpose is that the diff against #1887 "shows only the API-shape delta", it'd be worth running Prettier on the
Comment on lines +719 to +734
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 nit: Several hunks in docs/migration.md and docs/migration-SKILL.md are pure Prettier reformatting unrelated to the isSpecType/specTypeSchema change — this SdkErrorCode table re-padding, the items: [/* ... */] code-block reflow at 382-389/397-402, the prose re-wraps at 140-141/441-442, and the Types/shared-imports table re-padding in migration-SKILL.md. Since the PR's stated purpose is that the diff against #1887 "shows only the API-shape delta", it'd be worth running Prettier on the #1887 branch first so these whitespace hunks disappear from the comparison (you can't just drop them here — the format check would re-add them).

Extended reasoning...

What this is

This PR is explicitly a comparison artifact: the description says it "targets the #1887 branch so the diff shows only the API-shape delta." Its value to reviewers is that scrolling the diff should reveal exactly what changes when you swap isSpecType.X(v) / specTypeSchemas.X for isSpecType('X', v) / specTypeSchema('X') — and nothing else.

However, the diff contains several hunks that are pure Prettier reformatting with zero semantic relation to the API-shape change:

  • docs/migration.md:719-734 — the entire SdkErrorCode table is re-padded because one row (InvalidResult, "Response result failed local schema validation") is wider than the previous max, so every other row's trailing-space padding shifts. No content changed.
  • docs/migration.md:382-389 and 397-402return { items: [/* ... */] }; is reflowed to a 5-line block. This code sample is in the §"Custom (non-spec) methods" section and has nothing to do with isSpecType.
  • docs/migration.md:140-141 and 441-442 — two prose paragraphs (the auth-helpers paragraph and the request() schema-parameter paragraph) are re-wrapped at a different column. Content identical.
  • docs/migration-SKILL.md — the "Types / shared imports" table is re-padded to a much wider second column, and the §8 auth paragraph is re-wrapped.

Step-by-step proof

Take the SdkErrorCode table hunk anchored on this line. Comparing old vs. new:

old new
NotConnected row Transport is not connected + 18 trailing spaces + ` `
InvalidResult row `Response result failed local schema validation ` (no extra padding)

Every changed line in this hunk differs only in trailing-space count inside the table cell. Rendered Markdown is byte-for-byte identical. The same holds for the migration-SKILL.md table (the shared/stdio.js row's long parenthetical forces the column wider, and Prettier re-pads every other row to match). None of these hunks mention isSpecType, specTypeSchema, SpecTypeName, or anything else this PR is about.

Why existing tooling doesn't prevent it

It's the opposite — the repo's Prettier enforcement causes it. The base branch (#1887) evidently left these files in a non-Prettier-normalized state (likely a row was added to each table / a phrase was edited without re-running the formatter on the whole file). When this PR touched the same files to update the isSpecType examples, format-on-save / pnpm format normalized the surrounding content too, and those normalizations show up in the inter-branch diff.

Addressing the counter-argument

One could argue this isn't worth a comment because (a) the author can't simply drop these hunks — Prettier CI would fail or re-add them, (b) GitHub's ?w=1 whitespace toggle hides the table re-padding and prose re-wraps, and (c) the actionable fix lives on #1887, not here.

All three points are correct, and (a) in particular is why the suggested fix is not "drop these hunks from this PR" but rather "Prettier-clean the base #1887 branch and rebase." That is a one-command fix on #1887, after which this PR's diff collapses to exactly the API-shape delta it advertises. Point (b) mitigates but doesn't eliminate the noise: ?w=1 hides the table padding and prose wraps, but the items: [/* ... */] → multiline reflow at 382-389/397-402 still appears as a content change even with whitespace hidden, since it inserts newlines between tokens. And (c) is precisely why this is filed as a nit rather than a blocking issue — it's process feedback about the comparison's readability, surfaced here because this is where the noise is visible.

Impact and fix

No correctness impact whatsoever — rendered docs are identical. The only cost is reviewer attention: someone comparing the two API shapes has to mentally discard ~40 lines of table-padding diff and a couple of code-block reflows to find the ~15 lines that actually demonstrate the API difference. Given the PR exists specifically to make that comparison easy, tightening it up is worth a one-liner.

Fix: On the #1887 branch, run pnpm format (or equivalent) over docs/migration.md and docs/migration-SKILL.md and commit. Then rebase this branch; the formatting hunks vanish and only the isSpecType/specTypeSchema edits remain.


#### `StreamableHTTPError` removed

Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/exports/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ export { InMemoryTaskMessageQueue, InMemoryTaskStore } from '../../experimental/

// Validator types and classes
export type { SpecTypeName, SpecTypes } from '../../types/specTypeSchema.js';
export { isSpecType, specTypeSchemas } from '../../types/specTypeSchema.js';
export { isSpecType, specTypeSchema } from '../../types/specTypeSchema.js';
export type { StandardSchemaV1, StandardSchemaWithJSON } from '../../util/standardSchema.js';
export { AjvJsonSchemaValidator } from '../../validators/ajvProvider.js';
export type { CfWorkerSchemaDraft } from '../../validators/cfWorkerProvider.js';
Expand Down
Loading
Loading