The goal is that migrated v1 code runs on v2 on its legacy paths, not that it adopts v2 idioms. Applying that bar: - Leave e.error.code / .message / .data chains alone: v2's MCPError keeps a typed .error ErrorData, so the v1 spelling runs and type-checks unchanged. The except-binding tracking goes with it. - Rewrite one-argument McpError(...) calls to MCPError.from_error_data(...) instead of flattening the inline ErrorData: the user's expression is kept as written and the non-inline form no longer needs a marker. - Convert v1 positional arguments on the lowlevel Server constructor to keywords (v2 is keyword-only after name but kept v1's names and order), pinned against the installed signature by a new ratchet test. - Reword every marker message that pointed at replaced internals or at the successor of the removed experimental tasks API; state removals plainly instead of steering users onto new surfaces. - Teach the batch harness that a reportArgumentType error naming a detonating argument type (timedelta, AnyUrl) is a real break, never v2 strictness drift, and ignore stale work/ directories.
Codemod batch test
Runs the mcp-codemod v1 -> v2 migration against real, pinned repositories and
audits the result with pyright, to find silent misses the unit tests and the
in-repo example corpus cannot.
How it works
For each repository in repos.json:
- Clone the pinned commit (shallow).
- Run the codemod (sources and dependency files) over a copy.
- Type-check the pristine clone against an environment holding the latest v1 SDK, and the migrated copy against this workspace's v2 environment, with identical pyright settings.
- Diff the two error sets. Errors only on the migrated side are the migration surface; baseline noise (the repo's own issues, missing third-party stubs) appears on both sides and cancels out.
- Correlate each new error with the inserted
# mcp-codemod:markers.
The codemod's contract is that the markers are the complete list of remaining
manual work, so every new error should sit on or next to a marker. A new
error with no nearby marker is a silent miss -- those are printed, written to
work/results/<slug>.json, and make the run exit 1.
Usage
From the repository root (the v1 environment is created on first run):
uv run --frozen python scripts/codemod-batch-test/run.py # all repos
uv run --frozen python scripts/codemod-batch-test/run.py --repo mcp-obsidian
uv run --frozen python scripts/codemod-batch-test/run.py --fresh # re-clone
Adding a repository
Add an entry to repos.json with a pinned sha (never a branch), an
include list when only part of the repository uses the SDK (empty means the
whole tree), and a one-line note. Prefer repositories that depend on the
mcp package directly; servers built on the external FastMCP library exercise
that library's surface, not this SDK's.