Skip to content

feat: add v2 codemod draft#1950

Draft
KKonstantinov wants to merge 38 commits intomainfrom
feature/v2-codemode-draft
Draft

feat: add v2 codemod draft#1950
KKonstantinov wants to merge 38 commits intomainfrom
feature/v2-codemode-draft

Conversation

@KKonstantinov
Copy link
Copy Markdown
Contributor

@KKonstantinov KKonstantinov commented Apr 23, 2026

feat: add @modelcontextprotocol/codemod package for automated v1 → v2 migration

Adds a new @modelcontextprotocol/codemod package that automatically migrates MCP TypeScript SDK code from v1 (@modelcontextprotocol/sdk) to the v2 multi-package architecture (@modelcontextprotocol/client, /server, /core, /node, /express). Powered by ts-morph for AST-level precision and shipped as both a CLI (mcp-codemod) and a programmatic API. Also automatically updates package.json — removes the v1 SDK dependency and adds the correct v2 packages based on what the code actually imports.

Motivation and Context

The v2 SDK introduces a multi-package structure, renamed APIs, restructured context objects, and removed modules (WebSocket transport, server auth, Zod helpers). Manually migrating a codebase is tedious, error-prone, and blocks adoption. This codemod handles the mechanical 80-90% of migration — rewriting imports, renaming symbols, updating method signatures, and mapping context properties — while emitting actionable diagnostics for the remaining cases that require human judgment.

Architecture

Package Structure

packages/codemod/
├── src/
│   ├── cli.ts                  # Commander CLI entry point
│   ├── runner.ts               # Core orchestrator
│   ├── types.ts                # Transform / Migration / Diagnostic types
│   ├── index.ts                # Public API exports
│   ├── migrations/
│   │   ├── index.ts            # Migration registry
│   │   └── v1-to-v2/
│   │       ├── index.ts        # Migration definition
│   │       ├── mappings/       # Declarative lookup tables
│   │       │   ├── importMap.ts
│   │       │   ├── symbolMap.ts
│   │       │   ├── schemaToMethodMap.ts
│   │       │   └── contextPropertyMap.ts
│   │       └── transforms/     # Ordered AST transforms
│   │           ├── index.ts
│   │           ├── importPaths.ts
│   │           ├── symbolRenames.ts
│   │           ├── removedApis.ts
│   │           ├── mcpServerApi.ts
│   │           ├── handlerRegistration.ts
│   │           ├── schemaParamRemoval.ts
│   │           ├── expressMiddleware.ts
│   │           ├── contextTypes.ts
│   │           └── mockPaths.ts
│   ├── generated/
│   │   └── versions.ts            # Build-time v2 package versions
│   └── utils/
│       ├── astUtils.ts            # AST rename helpers
│       ├── diagnostics.ts         # Diagnostic factories
│       ├── importUtils.ts         # Import manipulation
│       ├── packageJsonUpdater.ts  # Automatic package.json updates
│       └── projectAnalyzer.ts     # Detects client/server/both
├── scripts/
│   └── generate-versions.ts       # Reads sibling package versions at build time
└── test/                          # 14 test suites, 201 test cases

High-Level Flow

flowchart TD
    A["mcp-codemod v1-to-v2 ./src"] --> B[CLI parses args]
    B --> C[Runner loads migration]
    C --> D[Analyze project type<br/>from package.json]
    D --> E[Create ts-morph Project<br/>glob .ts/.tsx/.mts files]
    E --> F[Filter out node_modules,<br/>dist, .d.ts, .d.mts]
    F --> G{For each<br/>source file}
    G --> H[Apply transforms<br/>in order]
    H --> I[Collect diagnostics,<br/>change counts,<br/>& used v2 packages]
    I --> G
    G -->|done| P[Update package.json:<br/>remove v1 SDK,<br/>add detected v2 packages]
    P --> J{Dry run?}
    J -->|yes| K[Report changes<br/>without saving]
    J -->|no| L[Save modified files<br/>& package.json to disk]
    K --> M[Print summary:<br/>files changed,<br/>package.json changes,<br/>diagnostics]
    L --> M
Loading

Transform Pipeline

Transforms run in a strict order per file. Each transform receives the SourceFile AST, mutates it in place, and returns a change count plus diagnostics. One failing transform does not block the others.

flowchart LR
    subgraph "Phase 1: Foundation"
        T1["1. importPaths<br/>Rewrite import specifiers<br/>from v1 → v2 packages"]
    end

    subgraph "Phase 2: Symbols"
        T2["2. symbolRenames<br/>McpError → ProtocolError<br/>ErrorCode split, etc."]
        T3["3. removedApis<br/>Drop Zod helpers,<br/>IsomorphicHeaders"]
    end

    subgraph "Phase 3: API Surface"
        T4["4. mcpServerApi<br/>.tool() → .registerTool()<br/>restructure args"]
        T5["5. handlerRegistration<br/>Schema → method string"]
        T6["6. schemaParamRemoval<br/>Remove schema args"]
        T7["7. expressMiddleware<br/>hostHeaderValidation<br/>signature update"]
    end

    subgraph "Phase 4: Context & Tests"
        T8["8. contextTypes<br/>extra → ctx,<br/>property path remapping"]
        T9["9. mockPaths<br/>vi.mock / jest.mock<br/>specifier updates"]
    end

    T1 --> T2 --> T3 --> T4 --> T5 & T6 & T7 --> T8 --> T9
Loading

Import Resolution Strategy

Some v1 paths (e.g., @modelcontextprotocol/sdk/types.js) are shared code that could belong to either the client or server package. The codemod resolves these contextually:

flowchart TD
    A["v1 import path"] --> B{Path in<br/>IMPORT_MAP?}
    B -->|no| Z[Leave unchanged]
    B -->|yes| C{Status?}
    C -->|removed| D["Remove import +<br/>emit warning diagnostic"]
    C -->|moved| E{Target is<br/>RESOLVE_BY_CONTEXT?}
    C -->|renamed| F["Rewrite path +<br/>rename symbols"]
    E -->|no| G["Rewrite to<br/>fixed target package"]
    E -->|yes| H{Project type?}
    H -->|client only| I["→ @modelcontextprotocol/client"]
    H -->|server only| J["→ @modelcontextprotocol/server"]
    H -->|both or unknown| K["→ @modelcontextprotocol/server<br/>(safe default)"]
Loading

Context Property Remapping

The v2 SDK restructures the handler context from a flat object into nested groups. The contextTypes transform handles this remapping:

flowchart LR
    subgraph "v1 — flat RequestHandlerExtra"
        E1["extra.signal"]
        E2["extra.requestId"]
        E3["extra.authInfo"]
        E4["extra.sendRequest(...)"]
        E5["extra.sendNotification(...)"]
        E6["extra.taskStore"]
    end

    subgraph "v2 — nested BaseContext"
        C1["ctx.mcpReq.signal"]
        C2["ctx.mcpReq.id"]
        C3["ctx.http?.authInfo"]
        C4["ctx.mcpReq.send(...)"]
        C5["ctx.mcpReq.notify(...)"]
        C6["ctx.task?.store"]
    end

    E1 --> C1
    E2 --> C2
    E3 --> C3
    E4 --> C4
    E5 --> C5
    E6 --> C6
Loading

What Each Transform Does

# Transform ID Description
1 imports Rewrites all @modelcontextprotocol/sdk/... import paths to their v2 package destinations. Merges duplicate imports, separates type-only imports, resolves ambiguous shared paths by project type. Splits imports when symbols from a single v1 module map to different v2 packages (e.g., StreamableHTTPServerTransport/node, EventStore/server). Tracks which v2 packages are used for automatic package.json updates.
2 symbols Renames 9 symbols (e.g., McpErrorProtocolError). Splits ErrorCode into ProtocolErrorCode + SdkErrorCode based on member usage. Converts RequestHandlerExtraServerContext/ClientContext. Replaces SchemaInput<T> with StandardSchemaWithJSON.InferInput<T>.
3 removed-apis Removes references to dropped Zod helpers (schemaToJson, parseSchemaAsync, etc.), renames IsomorphicHeadersHeaders, converts StreamableHTTPErrorSdkError with constructor mapping warnings.
4 mcpserver-api Rewrites McpServer method calls: .tool().registerTool(), .prompt().registerPrompt(), .resource().registerResource(). Restructures 2-4 positional args into (name, config, callback) form. Wraps raw object schemas with z.object().
5 handlers Converts server.setRequestHandler(CallToolRequestSchema, ...) to server.setRequestHandler('tools/call', ...) using the schema-to-method mapping table. Covers 15 request schemas and 8 notification schemas.
6 schema-params Removes the schema parameter from .request(), .callTool(), and .send() calls where the second argument is a schema reference (ends with Schema).
7 express-middleware Updates hostHeaderValidation({ allowedHosts: [...] })hostHeaderValidation([...]).
8 context Renames the extra callback parameter to ctx. Rewrites 13 property access paths from the flat v1 context to the nested v2 context structure. Warns on destructuring patterns that need manual review.
9 mock-paths Rewrites vi.mock() / jest.mock() specifiers from v1 to v2 paths. Updates import() expressions in mock factories. Renames symbol references inside mock factory return objects.

CLI Usage

# Run all transforms
mcp-codemod v1-to-v2 ./src

# Dry run — preview without writing
mcp-codemod v1-to-v2 ./src --dry-run --verbose

# Run specific transforms only
mcp-codemod v1-to-v2 ./src --transforms imports,symbols,context

# List available transforms
mcp-codemod v1-to-v2 --list

# Ignore additional patterns
mcp-codemod v1-to-v2 ./src --ignore "legacy/**" "generated/**"

Programmatic API

import { getMigration, run } from '@modelcontextprotocol/codemod';

const migration = getMigration('v1-to-v2')!;
const result = run(migration, {
  targetDir: './src',
  dryRun: false,
  verbose: true,
  transforms: ['imports', 'symbols'],
  ignore: ['test/**']
});

console.log(`${result.filesChanged} files changed, ${result.totalChanges} total changes`);
for (const d of result.diagnostics) {
  console.log(`[${d.level}] ${d.file}:${d.line}${d.message}`);
}

How Has This Been Tested?

  • 201 test cases across 14 test suites covering every transform, the CLI, the runner, the project analyzer, and the package.json updater.
  • Each transform has its own dedicated test suite with isolated ts-morph in-memory filesystem tests.
  • Integration tests run the full pipeline against realistic v1 source files and verify complete output including package.json updates.
  • Package.json updater has dedicated unit tests covering: deps/devDeps/both sections, partial migration, dry-run, malformed JSON, private package filtering, indentation and trailing newline preservation.
  • Integration tests verify end-to-end package detection for client-only, server-only, client+server, express middleware, and split-import (symbolTargetOverrides) scenarios.
  • Edge cases tested: duplicate imports, type-only imports, removed APIs, ambiguous shared paths, ErrorCode member splitting, destructuring patterns, mock factories, .d.ts exclusion, unknown transform ID validation.

Breaking Changes

None — this is a new package with no existing users.

Types of changes

  • New feature (non-breaking change which adds functionality)

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

Design Decisions

  1. ts-morph over jscodeshift: ts-morph provides full TypeScript type information and a more ergonomic API for the precise symbol-level transforms this codemod requires (e.g., distinguishing ErrorCode.RequestTimeout from ErrorCode.InvalidRequest for the ProtocolErrorCode/SdkErrorCode split).

  2. Ordered transforms with per-file isolation: Transforms run in a declared order (imports first, mocks last). If a transform throws for a given file, the remaining transforms are skipped for that file (since the AST may be in a partially-mutated state), but processing continues for other files. An error diagnostic is emitted for the affected file.

  3. Declarative mapping tables: All rename/remap rules live in dedicated mapping files (importMap.ts, symbolMap.ts, etc.) rather than being scattered across transform logic. This makes the migration rules auditable and easy to extend.

  4. Context-aware import resolution: Shared v1 paths like @modelcontextprotocol/sdk/types.js are resolved to client or server packages based on package.json dependency analysis, not just hardcoded defaults.

  5. Diagnostic-first approach for removals: When the codemod encounters removed APIs (WebSocket transport, server auth, Zod helpers), it doesn't silently drop them — it emits clear warning diagnostics with migration guidance so users know what needs manual attention.

  6. Automatic package.json updates: As transforms rewrite imports, they track which v2 packages are targeted. After all source transforms complete, the runner removes @modelcontextprotocol/sdk from package.json and adds exactly the v2 packages the code actually uses. Version specifiers are injected at build time from sibling package versions via scripts/generate-versions.ts. Private packages (@modelcontextprotocol/core) are filtered out. The updater preserves the original indentation and trailing newline, and respects dry-run mode.

  7. Import splitting with symbolTargetOverrides: When symbols from a single v1 module map to different v2 packages (e.g., StreamableHTTPServerTransport@modelcontextprotocol/node but EventStore@modelcontextprotocol/server), the import map supports per-symbol target overrides. The transform splits the import into separate declarations for each target package.

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 23, 2026

⚠️ No Changeset found

Latest commit: 3d43f78

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@KKonstantinov
Copy link
Copy Markdown
Contributor Author

@claude review

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Apr 23, 2026

Open in StackBlitz

@modelcontextprotocol/client

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/client@1950

@modelcontextprotocol/codemod

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/codemod@1950

@modelcontextprotocol/server

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/server@1950

@modelcontextprotocol/express

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/express@1950

@modelcontextprotocol/fastify

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/fastify@1950

@modelcontextprotocol/hono

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/hono@1950

@modelcontextprotocol/node

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/node@1950

commit: 3d43f78

Comment thread packages/codemod/src/migrations/v1-to-v2/transforms/mockPaths.ts Outdated
Comment thread packages/codemod/src/migrations/v1-to-v2/transforms/importPaths.ts Outdated
Comment thread packages/codemod/src/migrations/v1-to-v2/transforms/importPaths.ts Outdated
Comment thread packages/codemod/test/v1-to-v2/transforms/mockPaths.test.ts Outdated
@KKonstantinov
Copy link
Copy Markdown
Contributor Author

@claude review

Comment thread packages/codemod/src/cli.ts Outdated
Comment thread packages/codemod/src/cli.ts
Comment thread packages/codemod/src/utils/projectAnalyzer.ts
@KKonstantinov KKonstantinov changed the title feat: add v2 codemode draft feat: add v2 codemod draft Apr 23, 2026
@KKonstantinov
Copy link
Copy Markdown
Contributor Author

@claude review

Comment thread packages/codemod/src/migrations/v1-to-v2/transforms/mcpServerApi.ts
Comment thread packages/codemod/src/migrations/v1-to-v2/transforms/symbolRenames.ts Outdated
@KKonstantinov
Copy link
Copy Markdown
Contributor Author

@claude review

@KKonstantinov
Copy link
Copy Markdown
Contributor Author

Todo:

  • Test on sample size MCP SDK (v1) dependents
  • Test on everything-server
  • Test on v1 MCP SDK examples

Comment thread packages/codemod/src/migrations/v1-to-v2/transforms/mockPaths.ts
@KKonstantinov
Copy link
Copy Markdown
Contributor Author

@claude review

Comment thread packages/codemod/src/runner.ts Outdated
Comment thread packages/codemod/src/runner.ts Outdated
@KKonstantinov
Copy link
Copy Markdown
Contributor Author

@claude review

Comment thread packages/codemod/src/utils/astUtils.ts
Comment thread packages/codemod/src/migrations/v1-to-v2/transforms/importPaths.ts
Comment thread packages/codemod/src/migrations/v1-to-v2/transforms/removedApis.ts
@KKonstantinov
Copy link
Copy Markdown
Contributor Author

@claude review

Comment thread packages/codemod/src/cli.ts Outdated
Comment thread packages/codemod/src/index.ts
Comment thread packages/codemod/src/runner.ts Outdated
Comment thread packages/codemod/src/migrations/v1-to-v2/transforms/importPaths.ts
@KKonstantinov
Copy link
Copy Markdown
Contributor Author

@claude review

Comment thread packages/codemod/test/integration.test.ts Outdated
Comment thread packages/codemod/src/utils/importUtils.ts Outdated
@KKonstantinov
Copy link
Copy Markdown
Contributor Author

@claude review

Comment thread packages/client/src/validators/cfWorker.ts Outdated
Comment thread packages/codemod/src/migrations/v1-to-v2/transforms/mockPaths.ts
@KKonstantinov
Copy link
Copy Markdown
Contributor Author

@claude review

Comment thread packages/codemod/src/migrations/v1-to-v2/mappings/importMap.ts
Comment thread packages/codemod/src/migrations/v1-to-v2/mappings/importMap.ts
Comment thread packages/codemod/src/runner.ts
@KKonstantinov
Copy link
Copy Markdown
Contributor Author

@claude review

@KKonstantinov
Copy link
Copy Markdown
Contributor Author

@claude review

@KKonstantinov
Copy link
Copy Markdown
Contributor Author

@claude review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant