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-v1-to-v2-gaps.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@modelcontextprotocol/codemod': patch
---

v1-to-v2: now wraps `outputSchema` raw shapes with `z.object()`; importMap covers `sdk/server/express.js`, `sdk/server/middleware/hostHeaderValidation.js`, and `sdk/client/auth-extensions.js`. The unreachable `expressMiddleware` transform is removed.
3 changes: 1 addition & 2 deletions docs/client.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,7 @@ client.getNegotiatedProtocolVersion(); // '2026-07-28' or '2025-11-25'
- **`mode: { pin: '2026-07-28' }`** — modern era at exactly that revision; no fallback. Against a 2025-only server `connect()` rejects with a typed error. Use `pin` where a silent downgrade would be worse than an error (tests, CI, servers you control).

Once a modern era is negotiated, the client automatically attaches the per-request `_meta` envelope (the reserved protocol-version / client-info / client-capabilities keys) to every outgoing request and notification. You can also configure negotiation pre-connect on an
already-constructed instance via {@linkcode @modelcontextprotocol/client!client/client.Client#setVersionNegotiation | client.setVersionNegotiation()}. See the [2026-07-28 support guide](./migration/support-2026-07-28.md#serving-the-2026-07-28-revision) for the full failure semantics,
probe policy, and the `'auto'`-mode compatibility table.
already-constructed instance via {@linkcode @modelcontextprotocol/client!client/client.Client#setVersionNegotiation | client.setVersionNegotiation()}. See the [2026-07-28 support guide › Probe policy](./migration/support-2026-07-28.md#probe-policy) for the full failure semantics and probe-timeout behavior.

### Disconnecting

Expand Down
28 changes: 12 additions & 16 deletions docs/migration/upgrade-to-v2.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ In addition the codemod:
- Updates `package.json` dependencies (`@modelcontextprotocol/sdk` → the v2 packages
your imports actually use).
- Rewrites `.tool()` / `.prompt()` / `.resource()` to `registerTool` / `registerPrompt`
/ `registerResource` and wraps `inputSchema` / `argsSchema` / `uriSchema` raw Zod
shapes with `z.object()`.
/ `registerResource` and wraps `inputSchema` / `outputSchema` / `argsSchema` /
`uriSchema` raw Zod shapes with `z.object()`.
- Drops the result-schema argument from `client.request()` / `client.callTool()` for
spec methods.
- Rewrites standalone Zod-spec-schema usages: `XSchema.safeParse(v).success` →
Expand Down Expand Up @@ -104,9 +104,6 @@ recognized but could not safely rewrite with an `@mcp-codemod-error` comment.
- **`ctx.mcpReq.send()` schema-arg drop** — the codemod drops the schema arg from
`client.request()` / `client.callTool()` but leaves nested `ctx.mcpReq.send()` calls
alone. → [Low-level protocol](#low-level-protocol--handler-context-ctx)
- **`outputSchema` `z.object()` wrap** — `inputSchema`, `argsSchema`, and `uriSchema`
raw shapes are wrapped; `outputSchema` is left for review (it was rarely a raw shape
in v1). → [Server registration API](#server-registration-api)
- **OAuth error-class consolidation** — `instanceof InvalidGrantError` → `OAuthError` +
`OAuthErrorCode` is a judgment rewrite. → [Auth](#auth)
- **`SdkErrorCode` branch selection** — the codemod renames `StreamableHTTPError` →
Expand Down Expand Up @@ -207,13 +204,13 @@ A few transports need a decision the codemod can't make:
is now re-exported by `@modelcontextprotocol/client` and `@modelcontextprotocol/server`.

The codemod's [`importMap.ts`](../../packages/codemod/src/migrations/v1-to-v2/mappings/importMap.ts)
does **not** cover the per-file deep auth/middleware paths
(`…/server/auth/middleware/{bearerAuth,allowedMethods,clientAuth}.js`,
`…/server/auth/handlers/{authorize,metadata,register,revoke,token}.js`,
`/server/auth/providers/proxyProvider.js`,
`…/server/middleware/hostHeaderValidation.js`, `…/server/express.js`) — rewrite those
imports by hand to the RS/AS targets above (`createMcpExpressApp` /
`CreateMcpExpressAppOptions` from `…/server/express.js` → `@modelcontextprotocol/express`).
routes every `…/server/auth/**` deep path (including
`…/server/auth/middleware/{bearerAuth,allowedMethods,clientAuth}.js`,
`…/server/auth/handlers/*.js`, `…/server/auth/providers/proxyProvider.js`) to
`@modelcontextprotocol/server-legacy/auth`, and `…/server/express.js` /
`…/server/middleware/hostHeaderValidation.js` to `@modelcontextprotocol/express`. The
AS→`server-legacy` routing is conservative — re-point RS-only call sites
(`requireBearerAuth`, `mcpAuthMetadataRouter`) at `@modelcontextprotocol/express` by hand.

### Low-level protocol & handler context (`ctx`)

Expand Down Expand Up @@ -328,9 +325,8 @@ The return type is inferred from the method name via `ResultTypeMap` (e.g.

The deprecated variadic `.tool()`, `.prompt()`, `.resource()` are removed. Use
`registerTool` / `registerPrompt` / `registerResource` with an explicit config object.
The codemod converts the call shape and wraps `inputSchema` / `argsSchema` / `uriSchema`
raw shapes; verify `outputSchema` (which the codemod does not wrap) is wrapped where
present.
The codemod converts the call shape and wraps `inputSchema` / `outputSchema` /
`argsSchema` / `uriSchema` raw shapes.

```typescript
// v1 — raw shape, variadic
Expand Down Expand Up @@ -806,7 +802,7 @@ surfaced per-tool on `callTool`).
A tool may now register an `outputSchema` whose root is `type:"array"`, `type:"string"`,
etc.; toward 2025-era clients the codec wraps it in a `{result:…}` envelope, and toward
every era a non-object `structuredContent` with no `text` block of its own gets a
`JSON.stringify(...)` `text` block auto-appended. See [support-2026-07-28.md](./support-2026-07-28.md#per-era-wire-codecs) for the per-era projection rules.
`JSON.stringify(...)` `text` block auto-appended. See [support-2026-07-28.md › Per-era wire codecs](./support-2026-07-28.md#per-era-wire-codecs) for how the codec applies these per era.

### Behavioral changes

Expand Down
13 changes: 7 additions & 6 deletions packages/codemod/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ The mechanical rename mappings are the source of truth — see
`extra.*` → `ctx.mcpReq.*` / `ctx.http?.*`

Transforms in `src/migrations/v1-to-v2/transforms/` also rewrite `.tool()` →
`registerTool` (with `z.object()` wrap), drop the result-schema argument from
`client.request()` / `client.callTool()` for spec methods, rewrite spec-`*Schema`
`registerTool` (wrapping `inputSchema` / `outputSchema` / `argsSchema` / `uriSchema`
raw shapes with `z.object()`), drop the result-schema argument from `client.request()`
/ `client.callTool()` for spec methods, rewrite spec-`*Schema`
constant accesses (`.safeParse` → `isSpecType` / `specTypeSchemas`), rename
`StreamableHTTPError` → `SdkHttpError` / `IsomorphicHeaders` → `Headers`, rewrite
`SchemaInput<T>` → `StandardSchemaWithJSON.InferInput<T>`, route
Expand All @@ -55,10 +56,10 @@ grep -rn '@mcp-codemod-error' .

CJS→ESM / Node 20 pre-flight, `new Headers()` / `.get()` access rewrites, OAuth
error-class consolidation (`instanceof InvalidGrantError` → `OAuthError` +
`OAuthErrorCode`), per-scenario `SdkErrorCode` branch selection, `outputSchema`
`z.object()` wrap, `ctx.mcpReq.send()` schema-arg drop, and behavioral adaptation are
manual — see the [migration guide](../../docs/migration/upgrade-to-v2.md) for what to
do after the codemod runs.
`OAuthErrorCode`), per-scenario `SdkErrorCode` branch selection, `ctx.mcpReq.send()`
schema-arg drop, and behavioral adaptation are manual — see the
[migration guide](../../docs/migration/upgrade-to-v2.md) for what to do after the
codemod runs.

The codemod handles the v1→v2 SDK surface upgrade only. Adopting the 2026-07-28
protocol revision (`createMcpHandler`, multi-round-trip requests, `versionNegotiation`)
Expand Down
20 changes: 20 additions & 0 deletions packages/codemod/src/migrations/v1-to-v2/mappings/importMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ export const IMPORT_MAP: Record<string, ImportMapping> = {
target: '@modelcontextprotocol/client',
status: 'moved'
},
'@modelcontextprotocol/sdk/client/auth-extensions.js': {
target: '@modelcontextprotocol/client',
status: 'moved'
},
Comment thread
claude[bot] marked this conversation as resolved.
'@modelcontextprotocol/sdk/client/streamableHttp.js': {
target: '@modelcontextprotocol/client',
status: 'moved'
Expand Down Expand Up @@ -83,6 +87,14 @@ export const IMPORT_MAP: Record<string, ImportMapping> = {
target: '@modelcontextprotocol/express',
status: 'moved'
},
'@modelcontextprotocol/sdk/server/middleware/hostHeaderValidation.js': {
target: '@modelcontextprotocol/express',
status: 'moved'
},
'@modelcontextprotocol/sdk/server/express.js': {
target: '@modelcontextprotocol/express',
status: 'moved'
},
'@modelcontextprotocol/sdk/server/zod-compat.js': {
target: '',
status: 'removed',
Expand Down Expand Up @@ -131,6 +143,14 @@ export const IMPORT_MAP: Record<string, ImportMapping> = {
target: 'RESOLVE_BY_CONTEXT',
status: 'moved'
},
'@modelcontextprotocol/sdk/shared/auth-utils.js': {
target: 'RESOLVE_BY_CONTEXT',
status: 'moved'
},
'@modelcontextprotocol/sdk/client/middleware.js': {
target: '@modelcontextprotocol/client',
status: 'moved'
},
'@modelcontextprotocol/sdk/shared/uriTemplate.js': {
target: 'RESOLVE_BY_CONTEXT',
status: 'moved'
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { Transform } from '../../../types.js';
import { contextTypesTransform } from './contextTypes.js';
import { expressMiddlewareTransform } from './expressMiddleware.js';
import { handlerRegistrationTransform } from './handlerRegistration.js';
import { importPathsTransform } from './importPaths.js';
import { mcpServerApiTransform } from './mcpServerApi.js';
Expand Down Expand Up @@ -28,8 +27,8 @@ import { symbolRenamesTransform } from './symbolRenames.js';
// to .registerTool() etc. contextTypes handles both old and new names,
// but running mcpServerApi first ensures consistent argument structure.
//
// 5. handlerRegistration, schemaParamRemoval, and expressMiddleware are
// independent of each other but all depend on importPaths having run.
// 5. handlerRegistration and schemaParamRemoval are independent of each
// other but both depend on importPaths having run.
//
// 6. specSchemaAccess runs after handlerRegistration and schemaParamRemoval:
// those transforms remove spec schema references they handle. specSchemaAccess
Expand All @@ -45,7 +44,6 @@ export const v1ToV2Transforms: Transform[] = [
handlerRegistrationTransform,
schemaParamRemovalTransform,
specSchemaAccessTransform,
expressMiddlewareTransform,
contextTypesTransform,
mockPathsTransform
];
Comment thread
claude[bot] marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ export const mcpServerApiTransform: Transform = {
if (wrapSchemaInConfig(call, 'inputSchema', sourceFile, diagnostics)) {
changesCount++;
}
if (wrapSchemaInConfig(call, 'outputSchema', sourceFile, diagnostics)) {
changesCount++;
}
Comment thread
claude[bot] marked this conversation as resolved.
}

for (const call of registerPromptCalls) {
Expand Down
10 changes: 5 additions & 5 deletions packages/codemod/test/integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ describe('integration', () => {
expect(output).toContain('McpError');
});

it('applies new transforms (removed APIs, SchemaInput, express middleware)', () => {
it('applies new transforms (removed APIs, SchemaInput, middleware import)', () => {
const dir = createTempDir();
const input = [
`import { McpServer, schemaToJson, IsomorphicHeaders } from '@modelcontextprotocol/sdk/server/mcp.js';`,
Expand All @@ -304,7 +304,7 @@ describe('integration', () => {
`type Input = SchemaInput<typeof mySchema>;`,
`const h: IsomorphicHeaders = {};`,
`if (error instanceof StreamableHTTPError) {}`,
`app.use(hostHeaderValidation({ allowedHosts: ['localhost'] }));`,
`app.use(hostHeaderValidation(['localhost']));`,
``
].join('\n');

Expand All @@ -331,9 +331,9 @@ describe('integration', () => {
// schemaToJson removed (import gone)
expect(output).not.toContain('schemaToJson');

// hostHeaderValidation signature migrated
// hostHeaderValidation import rewritten to @modelcontextprotocol/express; call unchanged
expect(output).toContain("hostHeaderValidation(['localhost'])");
expect(output).not.toContain('allowedHosts');
expect(output).toContain('@modelcontextprotocol/express');

// Diagnostics emitted
expect(result.diagnostics.length).toBeGreaterThan(0);
Expand Down Expand Up @@ -470,7 +470,7 @@ describe('integration', () => {
[
`import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';`,
`import { hostHeaderValidation } from '@modelcontextprotocol/sdk/server/middleware.js';`,
`app.use(hostHeaderValidation({ allowedHosts: ['localhost'] }));`,
`app.use(hostHeaderValidation(['localhost']));`,
``
].join('\n')
);
Expand Down
Loading
Loading