Skip to content

Commit 8fa82f3

Browse files
feat(codemod): close v1-to-v2 mechanical gaps — outputSchema wrap, importMap entries; drop dead expressMiddleware transform (#2361)
1 parent cadcb86 commit 8fa82f3

13 files changed

Lines changed: 137 additions & 214 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@modelcontextprotocol/codemod': patch
3+
---
4+
5+
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.

docs/client.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,7 @@ client.getNegotiatedProtocolVersion(); // '2026-07-28' or '2025-11-25'
118118
- **`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).
119119

120120
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
121-
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,
122-
probe policy, and the `'auto'`-mode compatibility table.
121+
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.
123122

124123
#### Skipping the probe: `connect({ prior })`
125124

docs/migration/upgrade-to-v2.md

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

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

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

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

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

335331
```typescript
336332
// v1 — raw shape, variadic
@@ -806,7 +802,7 @@ surfaced per-tool on `callTool`).
806802
A tool may now register an `outputSchema` whose root is `type:"array"`, `type:"string"`,
807803
etc.; toward 2025-era clients the codec wraps it in a `{result:…}` envelope, and toward
808804
every era a non-object `structuredContent` with no `text` block of its own gets a
809-
`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.
805+
`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.
810806

811807
### Behavioral changes
812808

packages/codemod/README.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,9 @@ The mechanical rename mappings are the source of truth — see
2727
`extra.*``ctx.mcpReq.*` / `ctx.http?.*`
2828

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

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

6364
The codemod handles the v1→v2 SDK surface upgrade only. Adopting the 2026-07-28
6465
protocol revision (`createMcpHandler`, multi-round-trip requests, `versionNegotiation`)

packages/codemod/src/migrations/v1-to-v2/mappings/importMap.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ export const IMPORT_MAP: Record<string, ImportMapping> = {
2525
target: '@modelcontextprotocol/client',
2626
status: 'moved'
2727
},
28+
'@modelcontextprotocol/sdk/client/auth-extensions.js': {
29+
target: '@modelcontextprotocol/client',
30+
status: 'moved'
31+
},
2832
'@modelcontextprotocol/sdk/client/streamableHttp.js': {
2933
target: '@modelcontextprotocol/client',
3034
status: 'moved'
@@ -83,6 +87,14 @@ export const IMPORT_MAP: Record<string, ImportMapping> = {
8387
target: '@modelcontextprotocol/express',
8488
status: 'moved'
8589
},
90+
'@modelcontextprotocol/sdk/server/middleware/hostHeaderValidation.js': {
91+
target: '@modelcontextprotocol/express',
92+
status: 'moved'
93+
},
94+
'@modelcontextprotocol/sdk/server/express.js': {
95+
target: '@modelcontextprotocol/express',
96+
status: 'moved'
97+
},
8698
'@modelcontextprotocol/sdk/server/zod-compat.js': {
8799
target: '',
88100
status: 'removed',
@@ -131,6 +143,14 @@ export const IMPORT_MAP: Record<string, ImportMapping> = {
131143
target: 'RESOLVE_BY_CONTEXT',
132144
status: 'moved'
133145
},
146+
'@modelcontextprotocol/sdk/shared/auth-utils.js': {
147+
target: 'RESOLVE_BY_CONTEXT',
148+
status: 'moved'
149+
},
150+
'@modelcontextprotocol/sdk/client/middleware.js': {
151+
target: '@modelcontextprotocol/client',
152+
status: 'moved'
153+
},
134154
'@modelcontextprotocol/sdk/shared/uriTemplate.js': {
135155
target: 'RESOLVE_BY_CONTEXT',
136156
status: 'moved'

packages/codemod/src/migrations/v1-to-v2/transforms/expressMiddleware.ts

Lines changed: 0 additions & 61 deletions
This file was deleted.

packages/codemod/src/migrations/v1-to-v2/transforms/index.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import type { Transform } from '../../../types.js';
22
import { contextTypesTransform } from './contextTypes.js';
3-
import { expressMiddlewareTransform } from './expressMiddleware.js';
43
import { handlerRegistrationTransform } from './handlerRegistration.js';
54
import { importPathsTransform } from './importPaths.js';
65
import { mcpServerApiTransform } from './mcpServerApi.js';
@@ -28,8 +27,8 @@ import { symbolRenamesTransform } from './symbolRenames.js';
2827
// to .registerTool() etc. contextTypes handles both old and new names,
2928
// but running mcpServerApi first ensures consistent argument structure.
3029
//
31-
// 5. handlerRegistration, schemaParamRemoval, and expressMiddleware are
32-
// independent of each other but all depend on importPaths having run.
30+
// 5. handlerRegistration and schemaParamRemoval are independent of each
31+
// other but both depend on importPaths having run.
3332
//
3433
// 6. specSchemaAccess runs after handlerRegistration and schemaParamRemoval:
3534
// those transforms remove spec schema references they handle. specSchemaAccess
@@ -45,7 +44,6 @@ export const v1ToV2Transforms: Transform[] = [
4544
handlerRegistrationTransform,
4645
schemaParamRemovalTransform,
4746
specSchemaAccessTransform,
48-
expressMiddlewareTransform,
4947
contextTypesTransform,
5048
mockPathsTransform
5149
];

packages/codemod/src/migrations/v1-to-v2/transforms/mcpServerApi.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,9 @@ export const mcpServerApiTransform: Transform = {
107107
if (wrapSchemaInConfig(call, 'inputSchema', sourceFile, diagnostics)) {
108108
changesCount++;
109109
}
110+
if (wrapSchemaInConfig(call, 'outputSchema', sourceFile, diagnostics)) {
111+
changesCount++;
112+
}
110113
}
111114

112115
for (const call of registerPromptCalls) {

packages/codemod/test/integration.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ describe('integration', () => {
293293
expect(output).toContain('McpError');
294294
});
295295

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

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

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

338338
// Diagnostics emitted
339339
expect(result.diagnostics.length).toBeGreaterThan(0);
@@ -470,7 +470,7 @@ describe('integration', () => {
470470
[
471471
`import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';`,
472472
`import { hostHeaderValidation } from '@modelcontextprotocol/sdk/server/middleware.js';`,
473-
`app.use(hostHeaderValidation({ allowedHosts: ['localhost'] }));`,
473+
`app.use(hostHeaderValidation(['localhost']));`,
474474
``
475475
].join('\n')
476476
);

0 commit comments

Comments
 (0)