Three additions to mcp-codemod, closing the gaps a comparison with the
TypeScript codemod surfaced:
Imports of module namespaces v2 deleted outright (the experimental tasks
namespaces, the WebSocket transports, `mcp.shared.progress`) are now
marked with replacement guidance. A new ratchet test freezes the 107
public modules v1 shipped and asserts every one imports on v2, is
renamed, or is in the removed table, so the whole v1 module namespace is
provably accounted for.
The codemod now also updates the `mcp` requirement in `pyproject.toml`
(PEP 621 tables and dependency groups) and `requirements*.txt` to
`>=2,<3` -- only where the current constraint cannot accept any v2
release, and only the version specifier: name, extras, environment
marker, and spacing keep the user's spelling. Poetry tables and the
removed `ws` extra are marked instead of guessed at, under the same
`# mcp-codemod:` contract as source markers.
`scripts/codemod-batch-test/` runs the codemod against pinned real
repositories and audits the marker contract end to end: it type-checks
the pristine clone against the latest v1 and the migrated copy against
this workspace's v2 with identical pyright settings, then requires every
error that exists only on the migrated side to sit next to a marker.
Across the four repos in the manifest every migration-surface error is
covered, and the audit caught two real bugs now fixed here: `Context`
imported from the old `.server` submodule is rehomed to the package (the
submodule holds the name at runtime, but a type checker treats a
non-re-exported name as private), and `request_context` on a receiver
the pre-pass proved holds a lowlevel `Server` is flagged again --
receiver-matched, so the live `ctx.request_context` idiom stays
untouched.
A new `mcp-codemod` workspace package (`uvx mcp-codemod v1-to-v2 ./src`)
that rewrites every v1 -> v2 change whose meaning is unambiguous from the
file alone, and inserts a `# mcp-codemod:` comment above every site it
recognized but would not guess at. Built on libCST.
Names are resolved through each file's imports, never matched as text, so
an aliased import or an unrelated symbol that shares a name with an SDK
one is never touched. The camelCase to snake_case rename is restricted to
the field names v1's `mcp.types` actually declared. Anything whose correct
rewrite depends on information that is not in the file -- the lowlevel
decorator to `on_*` relocation, the transport keywords on the `MCPServer`
constructor -- is left exactly as written and marked instead, so the
remaining work is one grep. Re-running on the output is a no-op.
The mapping tables are pinned against the installed v2 package by ratchet
tests so they cannot silently drift: every rename target must resolve,
every removed API must be provably absent, and no flagged constructor
keyword may survive on `MCPServer.__init__`. Measured against the example
files that exist on both `v1.x` and `main` (whose diff is the hand-written
migration), the codemod fully reproduces 13 of the 51 with a real
migration diff, improves 35 more, and makes none worse.
Also adds an "Automated migration" section to docs/migration.md, a mention
of the tool in README.v2.md, and the package to the publish workflow's
build step (the PyPI project and its trusted publisher must exist before a
release is tagged with this in it).