Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/publish-pypi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ jobs:
run: |
uv build --package mcp
uv build --package mcp-types
uv build --package mcp-codemod

- name: Upload artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
>
> **v1.x is the only stable release line and remains recommended for production.** It lives on the [`v1.x` branch](https://github.com/modelcontextprotocol/python-sdk/tree/v1.x) and continues to receive critical bug fixes and security patches; see [the v1.x README](https://github.com/modelcontextprotocol/python-sdk/blob/v1.x/README.md) for its documentation. `pip` and `uv` don't select a pre-release unless you explicitly request one, so existing installs are unaffected. **If your package depends on `mcp`, add a `<2` upper bound to your version constraint (for example `mcp>=1.27,<2`) before the stable release lands.**
>
> v2 is a major rework of the SDK, both to support the [2026-07-28 MCP specification release](https://blog.modelcontextprotocol.io/posts/2026-07-28-release-candidate/) and to fix long-standing architectural issues. See the [migration guide](https://py.sdk.modelcontextprotocol.io/v2/migration/) for what's changed. Stable v2 is targeted for 2026-07-27, alongside the spec release. Try the pre-releases and [tell us what breaks](https://github.com/modelcontextprotocol/python-sdk/issues/new?template=v2-feedback.yaml) — or discuss in [#python-sdk-dev on the MCP Contributors Discord](https://discord.gg/6CSzBmMkjX).
> v2 is a major rework of the SDK, both to support the [2026-07-28 MCP specification release](https://blog.modelcontextprotocol.io/posts/2026-07-28-release-candidate/) and to fix long-standing architectural issues. See the [migration guide](https://py.sdk.modelcontextprotocol.io/v2/migration/) for what's changed; `uvx mcp-codemod v1-to-v2 ./src` automates the mechanical half of it and marks the rest with `# mcp-codemod:` comments. Stable v2 is targeted for 2026-07-27, alongside the spec release. Try the pre-releases and [tell us what breaks](https://github.com/modelcontextprotocol/python-sdk/issues/new?template=v2-feedback.yaml) — or discuss in [#python-sdk-dev on the MCP Contributors Discord](https://discord.gg/6CSzBmMkjX).

## Documentation

Expand Down
38 changes: 37 additions & 1 deletion docs/migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@ This guide covers the breaking changes introduced in v2 of the MCP Python SDK an

Version 2 of the MCP Python SDK introduces several breaking changes to improve the API, align with the MCP specification, and provide better type safety.

## Automated migration

The `mcp-codemod` tool (published from `src/mcp-codemod` in this repository) rewrites every change in this guide whose meaning is unambiguous from the file alone -- the import moves, the symbol renames, the `MCPError` reshape, the camelCase to snake_case field renames, the lowlevel `@server.*()` decorator registrations (through generated adapters that keep your handler bodies untouched), and the `mcp` requirement in `pyproject.toml` / `requirements*.txt` -- and inserts a `# mcp-codemod:` comment above every site it recognized but would not guess at. Run it on a clean branch first, then work through what it marked:

```bash
uvx mcp-codemod v1-to-v2 ./src
grep -rn '# mcp-codemod:' ./src
```

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. Re-running on its own output is a no-op, so it is safe to apply again after a manual fix-up. To preview without writing anything, pass `--dry-run` (add `--diff` to see the full unified diff).

The sections below remain the reference for the changes it cannot make for you: the lowlevel `Server` handler rewrite, relocating transport keyword arguments off the `MCPServer` constructor, and every behavioural change that has no source-level signature.

## Breaking Changes

### `MCPServer.call_tool()` returns `CallToolResult`
Expand Down Expand Up @@ -48,6 +61,13 @@ the raised `code`, `message`, and `data` intact. Previously the tool wrapper
caught it like any other exception and returned `CallToolResult(isError=True)`,
which discarded the error code and structured `data`.

The same applies to `@mcp.prompt()` and resource-template functions: an
`MCPError` raised there propagates verbatim as the JSON-RPC error. On v1
those wrappers converted it into a generic rendering error (for prompts, a
`ValueError("Error rendering prompt ...")`), so a client matching on the
error code or message will see the raised values instead of the wrapped
generic ones.

`MCPError` carries `ErrorData` and is the SDK's protocol-error type — raise it
when the request itself should be rejected (missing client capability,
elicitation required, invalid parameters). For tool *execution* failures the
Expand Down Expand Up @@ -537,9 +557,14 @@ from mcp.shared.exceptions import MCPError
try:
result = await session.call_tool("my_tool")
except MCPError as e:
print(f"Error: {e.message}")
print(f"Error: {e.error.message}")
```

Only the exception's name changes: `MCPError.error` still carries the full
`ErrorData`, so an existing handler body keeps working as written. `e.code`,
`e.message`, and `e.data` also exist as direct read-only properties if you
prefer the shorter spelling.

`MCPError` is also exported from the top-level `mcp` package:

```python
Expand Down Expand Up @@ -950,6 +975,17 @@ async def handle_call_tool(ctx: ServerRequestContext, params: CallToolRequestPar
server = Server("my-server", on_call_tool=handle_call_tool)
```

Registration does not have to move to the constructor: `add_request_handler`
(see [below](#lowlevel-server-add_request_handler-is-now-public-and-takes-params_type))
registers the same handler into the same registry at any point before `run()`,
which preserves your module's statement order — `mcp-codemod` migrates decorator
registrations this way for exactly that reason:

```python
server = Server("my-server")
server.add_request_handler("tools/call", CallToolRequestParams, handle_call_tool)
```

### `RequestContext` type parameters simplified

The `mcp.shared.context` module has been removed. `RequestContext` is now split into `ClientRequestContext` (in `mcp.client.context`) and `ServerRequestContext` (in `mcp.server.context`).
Expand Down
16 changes: 14 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ build-constraint-dependencies = [
dev = [
# We add mcp[cli] so `uv sync` considers the extras.
"mcp[cli]",
# The codemod is a standalone tool, not a dependency of `mcp`; pull it in here
# so the workspace's test environment has it.
"mcp-codemod",
"mcp-example-stories",
"tomli>=2.0; python_version < '3.11'",
"pyright>=1.1.400",
Expand Down Expand Up @@ -135,6 +138,7 @@ packages = ["src/mcp"]
typeCheckingMode = "strict"
include = [
"src/mcp",
"src/mcp-codemod/mcp_codemod",
"src/mcp-types/mcp_types",
"tests",
"docs_src",
Expand Down Expand Up @@ -212,10 +216,18 @@ max-returns = 13 # Default is 6
max-statements = 102 # Default is 50

[tool.uv.workspace]
members = ["src/mcp-types", "examples", "examples/clients/*", "examples/servers/*", "examples/snippets"]
members = [
"src/mcp-codemod",
"src/mcp-types",
"examples",
"examples/clients/*",
"examples/servers/*",
"examples/snippets",
]

[tool.uv.sources]
mcp = { workspace = true }
mcp-codemod = { workspace = true }
mcp-example-stories = { workspace = true }
mcp-types = { workspace = true }
strict-no-cover = { git = "https://github.com/pydantic/strict-no-cover" }
Expand Down Expand Up @@ -264,7 +276,7 @@ MD059 = false # descriptive-link-text
branch = true
patch = ["subprocess"]
concurrency = ["multiprocessing", "thread"]
source = ["src", "src/mcp-types/mcp_types", "tests"]
source = ["src", "src/mcp-codemod/mcp_codemod", "src/mcp-types/mcp_types", "tests"]
omit = [
"src/mcp/client/__main__.py",
"src/mcp/server/__main__.py",
Expand Down
2 changes: 2 additions & 0 deletions scripts/codemod-batch-test/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
work/
work/
42 changes: 42 additions & 0 deletions scripts/codemod-batch-test/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# 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`:

1. Clone the pinned commit (shallow).
2. Run the codemod (sources and dependency files) over a copy.
3. 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.
4. 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.
5. 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):

```bash
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.
99 changes: 99 additions & 0 deletions scripts/codemod-batch-test/repos.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
[
{
"slug": "official-servers",
"url": "https://github.com/modelcontextprotocol/servers",
"sha": "7b1170d1da1e36bc9f553f51e76e64cbfd652b3e",
"include": [
"src/fetch",
"src/git",
"src/time"
],
"note": "The official reference servers; lowlevel Server and FastMCP usage."
},
{
"slug": "mcp-obsidian",
"url": "https://github.com/MarkusPfundstein/mcp-obsidian",
"sha": "32285e9ac07049a8a23ea7d7903603a3e48a1bf7",
"include": [],
"note": "Popular community server; lowlevel Server with mcp.types throughout."
},
{
"slug": "awslabs-aws-documentation",
"url": "https://github.com/awslabs/mcp",
"sha": "3a5294539de4de3a91d0ee72d5487bc8b8b1fcd7",
"include": [
"src/aws-documentation-mcp-server"
],
"note": "One server from the awslabs monorepo; production FastMCP usage."
},
{
"slug": "android-mcp-server",
"url": "https://github.com/minhalvp/android-mcp-server",
"sha": "451d255a7305e6efef8a1a2b7374a21c512bba45",
"include": [],
"note": "Small community FastMCP server."
},
{
"slug": "mysql-mcp-server",
"url": "https://github.com/designcomputer/mysql_mcp_server",
"sha": "e25be7fc4e9e79d7efc52eb69d776129429a837f",
"include": [
"src/mysql_mcp_server"
],
"note": "Seven lowlevel decorators on a module-level Server in one file; stdio + SSE wiring."
},
{
"slug": "kaltura-mcp",
"url": "https://github.com/zoharbabin/kaltura-mcp",
"sha": "567f016e536692959e294c1ee94c9fc901576cd8",
"include": [
"src/kaltura_mcp"
],
"note": "Full seven-decorator lowlevel server with an explicit mcp>=1,<2 pin; handlers dispatch into other modules."
},
{
"slug": "arxiv-mcp-server",
"url": "https://github.com/blazickjp/arxiv-mcp-server",
"sha": "d58901760d7ede4adb162eaba1725209a933f100",
"include": [
"src/arxiv_mcp_server"
],
"note": "Decorator-registered lowlevel server whose handlers live across tools/, prompts/, resources/ subpackages."
},
{
"slug": "fastapi-mcp",
"url": "https://github.com/tadata-org/fastapi_mcp",
"sha": "e5cad13cabfc725bbcb047e526816d887d96da62",
"include": [
"fastapi_mcp"
],
"note": "Decorators on a method-local Server closing over self, with request_context introspection: the marker path."
},
{
"slug": "langchain-mcp-adapters",
"url": "https://github.com/langchain-ai/langchain-mcp-adapters",
"sha": "6a10b83516e825b8ff73870e5595113acc1c8c6d",
"include": [
"langchain_mcp_adapters"
],
"note": "Client library exercising every v1 transport, session kwargs pass-through, and cursor pagination."
},
{
"slug": "mcpadapt",
"url": "https://github.com/grll/mcpadapt",
"sha": "538cd85628b555ef4ad9392b7270b52274f444d4",
"include": [
"src/mcpadapt"
],
"note": "Client library on the old streamablehttp_client spelling with a positional timedelta session timeout."
},
{
"slug": "chroma-mcp",
"url": "https://github.com/chroma-core/chroma-mcp",
"sha": "98ff67589bdcc31b730a5415ff9529433f949077",
"include": [
"src/chroma_mcp"
],
"note": "Org-backed FastMCP server frozen on mcp[cli]==1.6.0: the exact-pin dependency rewrite."
}
]
Loading
Loading