Skip to content

fix(codemod): flag legacy-only server entries - #2978

Open
boss477 wants to merge 3 commits into
modelcontextprotocol:mainfrom
boss477:fix/2977-codemod-modern-serving-notice
Open

boss477 wants to merge 3 commits into
modelcontextprotocol:mainfrom
boss477:fix/2977-codemod-modern-serving-notice

Conversation

@boss477

@boss477 boss477 commented Oct 9, 2026

Copy link
Copy Markdown

Summary

  • add an advisory-only codemod notice when migrated stdio servers still construct StdioServerTransport
  • point migrated Node and web-standard Streamable HTTP server entries at createMcpHandler
  • follow imported bindings so aliases work, while unused server imports and client transports stay silent
  • add a patch changeset for @modelcontextprotocol/codemod

The codemod does not rewrite these serving entries automatically because adopting the 2026-07-28 protocol revision is a separate choice. The new info diagnostic makes that choice visible at the end of the v1-to-v2 migration instead of leaving a clean-looking, legacy-only server.

Fixes #2977

Verification

  • regression first: the two original server cases failed because no modern-serving diagnostic existed
  • pnpm --filter @modelcontextprotocol/codemod typecheck
  • pnpm --filter @modelcontextprotocol/codemod build
  • codemod tests: 648 passed, 1 skipped
  • changed-source ESLint passed
  • changed-file Prettier check passed
  • pnpm changeset status --since=origin/main passed

The one skipped test creates a directory symlink cycle and cannot run without Windows symlink privileges. The full pre-push hook also encountered checkout-level limits unrelated to this diff: the workspace was installed with the codemod-only filter, and the Windows CRLF checkout makes sync:snippets --check report existing tracked files as out of sync. Package-scoped build, typecheck, lint, formatting, and tests are covered above; CI remains the full-workspace check.

@boss477
boss477 requested a review from a team as a code owner October 9, 2026 14:44
@changeset-bot

changeset-bot Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: d6c26e8

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 6 packages
Name Type
@modelcontextprotocol/codemod Patch
@modelcontextprotocol/client Patch
@modelcontextprotocol/core Patch
@modelcontextprotocol/server-legacy Patch
@modelcontextprotocol/server Patch
@modelcontextprotocol/core-internal Patch

Not sure what this means? Click here to learn what changesets are.

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

@pkg-pr-new

pkg-pr-new Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

@modelcontextprotocol/client

npm i https://pkg.pr.new/@modelcontextprotocol/client@2978

@modelcontextprotocol/codemod

npm i https://pkg.pr.new/@modelcontextprotocol/codemod@2978

@modelcontextprotocol/core

npm i https://pkg.pr.new/@modelcontextprotocol/core@2978

@modelcontextprotocol/server

npm i https://pkg.pr.new/@modelcontextprotocol/server@2978

@modelcontextprotocol/server-legacy

npm i https://pkg.pr.new/@modelcontextprotocol/server-legacy@2978

@modelcontextprotocol/express

npm i https://pkg.pr.new/@modelcontextprotocol/express@2978

@modelcontextprotocol/fastify

npm i https://pkg.pr.new/@modelcontextprotocol/fastify@2978

@modelcontextprotocol/hono

npm i https://pkg.pr.new/@modelcontextprotocol/hono@2978

@modelcontextprotocol/node

npm i https://pkg.pr.new/@modelcontextprotocol/node@2978

commit: d6c26e8

@ANcpLua ANcpLua left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The approach looks right to me. It is an INFO notice marked advisoryOnly that makes no code change, so upgrading the packages stays separate from opting into 2026-07-28. The three detected names match what importMap.ts rewrites v1 imports into. I ran the branch (f4b6fc5) against main (b022522): it merges cleanly, all 649 codemod tests pass, and typecheck passes. serveStdio (@modelcontextprotocol/server/stdio) and createMcpHandler (@modelcontextprotocol/server) both exist on main, and the doc anchor resolves on the live site.

I probed it with extra fixtures and found four gaps. All four are in the reference lookup at modernServerEntryNotice.ts:38-42.

1. A file that only uses the type can get a notice at another file's line number. findReferencesAsNodes() searches the whole project. Once @modelcontextprotocol/server resolves (I installed the published 2.3.1 next to the fixture), it returns references from other files too. .find() then accepts the first new it sees, and the notice is filed under the current file. It depends on processing order: it shows when the type-only file is processed after the constructing file has been rewritten.

// main.ts
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { server } from './server.js';
// …5 comment lines…
await server.connect(new StdioServerTransport()); // line 8

// types.ts (3 lines)
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
export type Entry = StdioServerTransport;
main.ts:8  (text there: "await server.connect(new StdioServerTransport());")
types.ts:8 (file has 3 lines; text there: <past EOF>)

Without node_modules the bug doesn't show, so the current tests can't catch it. Filtering with reference.getSourceFile() === sourceFile fixes it.

2. Only one notice per binding, not one per call site. The lookup uses .find(), so it stops at the first match:

if (process.argv[2] === 'a') await a.connect(new StdioServerTransport());   // notice
else await b.connect(new StdioServerTransport());                            // no notice

3. The line number goes stale when a later transform rewrites the imports above the call. This transform runs second, so the line it records is from before the later rewrites. The runner only re-resolves lines for diagnostics that set resolveCurrentLine. With a v1 import of StreamableHTTPError, or an import type of IsomorphicHeaders, in the same file, a later transform removes import lines and the reported line no longer points at the call:

s.ts:7 -> ""          (StreamableHTTPError fixture; the construct is on line 6)
s.ts:7 -> undefined   (IsomorphicHeaders fixture; past EOF, construct on line 5)

Adding resolveCurrentLine: () => construction.getStartLineNumber() fixes this.

4. Namespace imports are not covered. Only named imports are scanned, so this gets no notice:

import * as stdio from '@modelcontextprotocol/sdk/server/stdio.js';
await server.connect(new stdio.StdioServerTransport());

The PR's other claims hold. The notice fires for aliases, for a transport assigned to a variable, for sessionful Node HTTP, for web-standard HTTP and for .mjs. It stays silent for client transports, unused imports, type-only use, files already on serveStdio/createMcpHandler, and re-runs. Barrel re-exports, subclasses and CommonJS require get no notice either. That is consistent with the codemod, which doesn't rewrite require, so I'd leave those out of scope.

The smallest fix I tested keeps findReferencesAsNodes: filter with reference.getSourceFile() === sourceFile, change .find to .filter, and add resolveCurrentLine: () => construction.getStartLineNumber(). That fixes 1–3, and a locally declared class StdioServerTransport {} stays silent as it does today. Namespace imports (4) still need their own handling. A regression test for 3 should assert the text at the reported line, since a line count alone doesn't catch the drift. Happy to share the diff and tests if useful.

Two smaller wording points:

  • createServer() reads like an SDK API, but it is the user's own factory, and Node's http module has a createServer too. serveStdio(() => buildServer()) matches support-2026-07-28.md:201.
  • For sessionful Node HTTP, createMcpHandler(...) on its own drops session state. The linked section covers keeping it with isLegacyRequest in front of legacy: 'reject'. "To also serve 2026-07-28, see …" would describe the decision more accurately than presenting a drop-in replacement.

Probes, patch and this review were drafted with AI assistance (Claude). The outputs quoted are from real runs.

@claude claude Bot added the v2 Ideas, requests and plans for v2 of the SDK which will incorporate major changes and fixes label Oct 10, 2026
@ANcpLua

ANcpLua commented Oct 10, 2026

Copy link
Copy Markdown

Thanks for picking this up! A ready-made test case: plugins/mcp-builder-v2/evals/ts-migrate-v1/fixture in https://github.com/ANcpLua/mcp-builder-v2 is a small v1 stdio server. Today's codemod (2.3.1) migrates it to new StdioServerTransport() + server.connect(transport) on @modelcontextprotocol/server and prints no notice about it, so it's exactly the case this PR should flag. (Written with Claude, which ran that codemod check.)

boss477 commented Oct 10, 2026

Copy link
Copy Markdown
Author

Thanks for the detailed probes. I pushed a focused follow-up that addresses gaps 1–3:

  • filters references to the current source file, preventing cross-file diagnostics
  • emits a notice for every construction call site instead of only the first
  • uses resolveCurrentLine so later import rewrites do not leave stale line numbers
  • adds regressions for multiple constructions and post-transform line resolution

I left namespace imports out of this follow-up for now because they need separate member-access handling. CI will provide the full validation for the new commits.

This branch has not been deployed

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

Labels

v2 Ideas, requests and plans for v2 of the SDK which will incorporate major changes and fixes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

v1→v2 upgrade path (TL;DR + codemod) ends at a server that 2026-07-28 clients cannot connect to; add a closing step or notice

2 participants