Skip to main content

mcp-codemod

tests PyPI Python

Migrates MCP Python SDK v1 code to v2 (the 2026-07-28 spec revision).

The TypeScript and Go SDKs ship a codemod for this migration. Python does not. Python developers get a 2,879-line migration guide and a lot of manual find-and-replace. This tool fills that gap.

Installation

pipx install mcp-codemod

pipx is the recommended way to install a command-line tool. It keeps the tool in its own environment and puts the command on your PATH.

If you would rather install into an existing virtualenv, pip works too:

pip install mcp-codemod

Note that on Ubuntu 24.04, Debian 12, Fedora, and other distributions that mark the system Python as externally managed (PEP 668), a bare pip install outside a virtualenv will be refused. Use pipx there.

Usage

mcp-codemod path/to/your/server           # dry run, prints a diff
mcp-codemod path/to/your/server --write   # applies the changes

Dry run is the default. Review the diff before writing.

Why not find-and-replace

The v2 models still accept camelCase at construction time. Only attribute access changed:

tool = Tool(name="forecast", inputSchema={"type": "object"})   # valid in v2
print(tool.inputSchema)                                        # broken in v2

Running sed -i 's/inputSchema/input_schema/g' breaks the first line while fixing the second. This tool handles both correctly because it operates on the concrete syntax tree, where a keyword argument is a Name node and attribute access is an Attribute node. Visiting only Attribute nodes cannot reach the keyword argument.

 tool = Tool(name="forecast", inputSchema={"type": "object"})
-print(tool.inputSchema, tool.outputSchema)
+print(tool.input_schema, tool.output_schema)

The implementation uses LibCST rather than the standard library ast module. A round trip through ast discards formatting: it reflows the file, strips comments, and normalises quote style. LibCST preserves every byte it does not deliberately change, so the resulting diff contains only the migration.

Changes applied automatically

Change Example
FastMCP to MCPServer mcp = FastMCP("x") becomes mcp = MCPServer("x")
mcp.server.fastmcp.* to mcp.server.mcpserver.* includes all submodules
McpError to MCPError
camelCase to snake_case attribute access 11 fields including inputSchema, isError, nextCursor, mimeType
ctx.fastmcp to ctx.mcp_server only on parameters annotated Context
Content to ContentBlock
ResourceReference to ResourceTemplateReference
ClientRequestType to ClientRequest and the five other *Type unions
streamablehttp_client to streamable_http_client
mcp.shared.version to mcp.types.version module was removed in v2
timedelta timeouts to float seconds read_timeout_seconds=timedelta(minutes=2) becomes =120

Changes reported for manual review

Some v2 changes depend on runtime types, or on intent that the source code does not express. Applying those automatically would risk introducing bugs, so they are reported and the source is left unmodified.

Code Description
F001 model_dump() without by_alias=True
F002 .root access on a union that is no longer a RootModel
F003 httpx or httpx-sse imported, SDK moved to httpx2
F004 Name removed in v2 with no drop-in replacement (Cursor, AnyFunction, others)
F005 RequestParams.Meta is now a TypedDict, attribute access becomes .get()
F006 Deprecated name (SUPPORTED_PROTOCOL_VERSIONS)
F007 timedelta timeout too dynamic to convert safely
F008 Lowlevel @server.list_tools() decorator, now an on_list_tools= constructor parameter
F009 Transport parameter (host, port, stateless_http, others) still on the MCPServer constructor
F010 request_ctx or server.request_context, both removed in v2

F001 deserves particular attention. In v1, model_dump() emitted camelCase because the model fields themselves were camelCase. In v2 the same call emits snake_case, which other MCP implementations will not recognise. The migration guide describes the consequence directly:

No error is raised; the output is silently in the wrong shape.

This is not corrected automatically because the receiver cannot be statically proven to be an MCP protocol type. Adding by_alias=True to an unrelated Pydantic model would corrupt that model's output instead.

F009 is worth reading too. Eleven transport parameters moved off the MCPServer constructor and onto run() in v2, so this still raises TypeError at startup even after the class is renamed:

mcp = MCPServer("demo", host="127.0.0.1", port=8788)   # crashes in v2

They are not moved automatically because the destination is a different call site, which may be elsewhere in the file, in another module, or absent when the server is mounted as an ASGI app.

If you use the lowlevel Server

That migration is not automated, and the reason is worth stating plainly. Three things change at once: registration moves to the constructor, the handler signature becomes (ctx, params), and the handler must return the full result type instead of an unwrapped value. The last two mean rewriting the body, which means understanding what the body does. There is also an ordering trap, since the constructor is normally written above the handlers, so inserting on_list_tools=handler there raises NameError at import.

What the tool does instead is name the exact replacement for every handler it finds:

F008 stdio_server.py:56: Lowlevel `@…list_tools()` decorator. In v2, pass
`on_list_tools=<handler>` to the Server(...) constructor instead. The handler
signature becomes `(ctx: ServerRequestContext, params: PaginatedRequestParams
| None) -> ListToolsResult`, and it must return a full ListToolsResult rather
than an unwrapped value.

That covers all twelve lowlevel handlers, each with its own params and return type. F010 additionally catches request_ctx and server.request_context, which were removed outright.

Imports that are intentionally left alone

The mcp.types module moved to a standalone mcp-types distribution, but this is a no-op for projects that depend on mcp. The migration guide is explicit:

mcp.types is a permanent alias that mirrors mcp_types exactly. Keep importing through mcp, the package you actually depend on, rather than writing import mcp_types, which would reach past your declared dependency into a transitive one.

Rewriting those imports would introduce an undeclared dependency, so mcp-codemod never does. There is a test covering this.

Relationship to mcp-migration

mcp-migration detects behavioural hazards that a codemod cannot rewrite, such as in-memory state mutated inside tool handlers, session-id dependencies, and live server readiness. It is not a codemod. This tool is not a hazard detector. The two are complementary:

mcp-codemod .          # apply the mechanical changes
mcp-migration scan .   # then check for behavioural hazards

Limitations

  • Coverage is the migration guide's "changes almost every project hits" table plus the removed-alias set. The full guide is 2,879 lines and this tool does not implement all of it.
  • Rewrites are skipped in files that do not import mcp. A module that handles MCP types without importing from mcp will not be processed.
  • Symbol renames are name-based. A local variable named Content in a file that also imports mcp would be renamed. Review the diff.
  • Import order is left untouched. Use isort if you need it.

Development

pip install -e ".[dev]"
pytest

Author

Sanjay Keerthan (@8crsk)

License

MIT. See LICENSE.

Metadata

Release files for mcp-codemod 0.1.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for mcp-codemod 0.1.3
File Size Uploaded Provenance Actions
mcp_codemod-0.1.3.tar.gz 22.6 kB With provenance Details for mcp_codemod-0.1.3.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for mcp-codemod 0.1.3
File Interpreter ABI Platform Actions
mcp_codemod-0.1.3-py3-none-any.whl Python 3 none any Details for mcp_codemod-0.1.3-py3-none-any.whl

Total release size: 42.1 kB

Release files / mcp_codemod-0.1.3.tar.gz

Download URL mcp_codemod-0.1.3.tar.gz
Size 22.6 kB
Tags Source
SHA-256 checksum
How to use checksums
daf5f9ab2721eebcc0f9d163ae71a443b2ecb4bec0a2518a4c57fe3b83e3ca2d
BLAKE2b-256 checksum
How to use checksums
28e2a919c168c41f5934dbeabfac1a5d5efa80e6e9f09d73dffd3c12a5170466
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 1, 2026.

Transparency log

Release files / mcp_codemod-0.1.3-py3-none-any.whl

Download URL mcp_codemod-0.1.3-py3-none-any.whl
Size 19.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
fb08a227e455d76a580a03a1603a4115b3320bb545c946259a1fe69136481858
BLAKE2b-256 checksum
How to use checksums
a6a0c517ba870a5b9879fbeda5f26b8b1fbd59c22bff63abf0f494ac62f8b42f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 1, 2026.

Transparency log

Security for mcp-codemod 0.1.3

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for mcp_codemod-0.1.3.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 1, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page