|
| 1 | +# mcp-codemod |
| 2 | + |
| 3 | +Automated rewrites for migrating code between major versions of the |
| 4 | +[MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk). |
| 5 | + |
| 6 | +```bash |
| 7 | +uvx mcp-codemod v1-to-v2 ./src |
| 8 | +``` |
| 9 | + |
| 10 | +It rewrites every change whose meaning is unambiguous from the file alone, and |
| 11 | +inserts a `# mcp-codemod:` comment above every site it recognized but would not |
| 12 | +guess at. After a run, this is the complete list of what is left for a human: |
| 13 | + |
| 14 | +```bash |
| 15 | +grep -rn '# mcp-codemod:' ./src |
| 16 | +``` |
| 17 | + |
| 18 | +Run it on a clean branch, read the diff, and follow the markers into the |
| 19 | +[migration guide](https://github.com/modelcontextprotocol/python-sdk/blob/main/docs/migration.md). |
| 20 | +Re-running on its own output is a no-op, so it is safe to apply again after a |
| 21 | +manual fix-up. |
| 22 | + |
| 23 | +## What it rewrites |
| 24 | + |
| 25 | +- Import paths that moved (`mcp.server.fastmcp` -> `mcp.server.mcpserver`, |
| 26 | + `mcp.types` -> `mcp_types`), including `from mcp import types`. |
| 27 | +- Renamed symbols (`FastMCP` -> `MCPServer`, `McpError` -> `MCPError`, |
| 28 | + `streamablehttp_client` -> `streamable_http_client`), resolved through the |
| 29 | + file's imports so an aliased import or an unrelated symbol with the same name |
| 30 | + is never touched. |
| 31 | +- `McpError(ErrorData(code=..., message=...))` to the flat `MCPError(...)` |
| 32 | + constructor, and `e.error.code` / `e.error.message` / `e.error.data` to |
| 33 | + `e.code` / `e.message` / `e.data` inside an `except McpError as e:` block. |
| 34 | +- camelCase attribute reads on `mcp.types` models to their snake_case v2 |
| 35 | + spellings (`.inputSchema` -> `.input_schema`), restricted to the field names |
| 36 | + the v1 types actually declared. Other camelCase APIs (`logging.getLogger`, a |
| 37 | + receiver that resolves to another package) are never considered, and a name |
| 38 | + that one of your own classes declares (`inputSchema` on your own model) is |
| 39 | + marked for you to split rather than renamed, since your declaration does not |
| 40 | + change. |
| 41 | +- The `streamable_http_client(...) as (read, write, _)` three-tuple to the v2 |
| 42 | + two-tuple. |
| 43 | + |
| 44 | +## What it marks instead |
| 45 | + |
| 46 | +Some changes cannot be made safely without information that is not in the file. |
| 47 | +The codemod never guesses at these; it leaves them exactly as written and adds a |
| 48 | +`# mcp-codemod:` comment explaining what to do: |
| 49 | + |
| 50 | +- Removed APIs that have no drop-in replacement (`create_connected_server_and_client_session`, |
| 51 | + the WebSocket transport, `mcp.shared.progress`, `get_context()`). |
| 52 | +- The v1 `mcp.types` names with no v2 home (`Cursor`, the `TASK_*` constants, the |
| 53 | + type-machinery aliases). `mcp_types` is not a name-superset of v1's `mcp.types`, |
| 54 | + so these are marked with their replacement instead of being rewritten into an |
| 55 | + import that cannot resolve. |
| 56 | +- A `streamablehttp_client(...)` call used anywhere other than directly as a |
| 57 | + `with` item (for example through `AsyncExitStack.enter_async_context`): it now |
| 58 | + yields two values, not three, and only the inline `as (read, write, _)` form |
| 59 | + can be rewritten safely, so every other form is marked. |
| 60 | +- Transport keywords on the `MCPServer` constructor (`host=`, `port=`, |
| 61 | + `stateless_http=`, ...), which moved to `run()` or one of the app methods. The |
| 62 | + right destination depends on how you start the server, so the kwarg is left in |
| 63 | + place -- v2 then fails loudly -- rather than silently dropped. |
| 64 | +- Lowlevel `@server.call_tool()` decorators, which became `on_call_tool=` |
| 65 | + constructor arguments with a different handler signature. Rewriting the |
| 66 | + registration also means rewriting the handler body, which is yours to do. |
| 67 | +- Renames the codemod applied but cannot prove are right: a camelCase rename |
| 68 | + whose receiver could plausibly not be an mcp type gets a `# mcp-codemod: review:` |
| 69 | + marker so you look at it instead of trusting it. |
| 70 | + |
| 71 | +`--dry-run` writes nothing, and `--diff` prints a unified diff of every change; |
| 72 | +combine the two to preview a run. |
0 commit comments