Files

316 lines
9.8 KiB
Markdown

# Migration Guide: v1 to v2
This guide covers the breaking changes introduced in v2 of the MCP Python SDK and how to update your code.
## Overview
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.
## Breaking Changes
### `streamablehttp_client` removed
The deprecated `streamablehttp_client` function has been removed. Use `streamable_http_client` instead.
**Before (v1):**
```python
from mcp.client.streamable_http import streamablehttp_client
async with streamablehttp_client(
url="http://localhost:8000/mcp",
headers={"Authorization": "Bearer token"},
timeout=30,
sse_read_timeout=300,
auth=my_auth,
) as (read_stream, write_stream, get_session_id):
...
```
**After (v2):**
```python
import httpx
from mcp.client.streamable_http import streamable_http_client
# Configure headers, timeout, and auth on the httpx.AsyncClient
http_client = httpx.AsyncClient(
headers={"Authorization": "Bearer token"},
timeout=httpx.Timeout(30, read=300),
auth=my_auth,
)
async with http_client:
async with streamable_http_client(
url="http://localhost:8000/mcp",
http_client=http_client,
) as (read_stream, write_stream, get_session_id):
...
```
### `StreamableHTTPTransport` parameters removed
The `headers`, `timeout`, `sse_read_timeout`, and `auth` parameters have been removed from `StreamableHTTPTransport`. Configure these on the `httpx.AsyncClient` instead (see example above).
### Removed type aliases and classes
The following deprecated type aliases and classes have been removed from `mcp.types`:
| Removed | Replacement |
|---------|-------------|
| `Content` | `ContentBlock` |
| `ResourceReference` | `ResourceTemplateReference` |
**Before (v1):**
```python
from mcp.types import Content, ResourceReference
```
**After (v2):**
```python
from mcp.types import ContentBlock, ResourceTemplateReference
```
### `args` parameter removed from `ClientSessionGroup.call_tool()`
The deprecated `args` parameter has been removed from `ClientSessionGroup.call_tool()`. Use `arguments` instead.
**Before (v1):**
```python
result = await session_group.call_tool("my_tool", args={"key": "value"})
```
**After (v2):**
```python
result = await session_group.call_tool("my_tool", arguments={"key": "value"})
```
### `cursor` parameter removed from `ClientSession` list methods
The deprecated `cursor` parameter has been removed from the following `ClientSession` methods:
- `list_resources()`
- `list_resource_templates()`
- `list_prompts()`
- `list_tools()`
Use `params=PaginatedRequestParams(cursor=...)` instead.
**Before (v1):**
```python
result = await session.list_resources(cursor="next_page_token")
result = await session.list_tools(cursor="next_page_token")
```
**After (v2):**
```python
from mcp.types import PaginatedRequestParams
result = await session.list_resources(params=PaginatedRequestParams(cursor="next_page_token"))
result = await session.list_tools(params=PaginatedRequestParams(cursor="next_page_token"))
```
### `mount_path` parameter removed from FastMCP
The `mount_path` parameter has been removed from `FastMCP.__init__()`, `FastMCP.run()`, `FastMCP.run_sse_async()`, and `FastMCP.sse_app()`. It was also removed from the `Settings` class.
This parameter was redundant because the SSE transport already handles sub-path mounting via ASGI's standard `root_path` mechanism. When using Starlette's `Mount("/path", app=mcp.sse_app())`, Starlette automatically sets `root_path` in the ASGI scope, and the `SseServerTransport` uses this to construct the correct message endpoint path.
### Transport-specific parameters moved from FastMCP constructor to run()/app methods
Transport-specific parameters have been moved from the `FastMCP` constructor to the `run()`, `sse_app()`, and `streamable_http_app()` methods. This provides better separation of concerns - the constructor now only handles server identity and authentication, while transport configuration is passed when starting the server.
**Parameters moved:**
- `host`, `port` - HTTP server binding
- `sse_path`, `message_path` - SSE transport paths
- `streamable_http_path` - StreamableHTTP endpoint path
- `json_response`, `stateless_http` - StreamableHTTP behavior
- `event_store`, `retry_interval` - StreamableHTTP event handling
- `transport_security` - DNS rebinding protection
**Before (v1):**
```python
from mcp.server.fastmcp import FastMCP
# Transport params in constructor
mcp = FastMCP("Demo", json_response=True, stateless_http=True)
mcp.run(transport="streamable-http")
# Or for SSE
mcp = FastMCP("Server", host="0.0.0.0", port=9000, sse_path="/events")
mcp.run(transport="sse")
```
**After (v2):**
```python
from mcp.server.fastmcp import FastMCP
# Transport params passed to run()
mcp = FastMCP("Demo")
mcp.run(transport="streamable-http", json_response=True, stateless_http=True)
# Or for SSE
mcp = FastMCP("Server")
mcp.run(transport="sse", host="0.0.0.0", port=9000, sse_path="/events")
```
**For mounted apps:**
When mounting FastMCP in a Starlette app, pass transport params to the app methods:
```python
# Before (v1)
mcp = FastMCP("App", json_response=True)
app = Starlette(routes=[Mount("/", app=mcp.streamable_http_app())])
# After (v2)
mcp = FastMCP("App")
app = Starlette(routes=[Mount("/", app=mcp.streamable_http_app(json_response=True))])
```
**Note:** DNS rebinding protection is automatically enabled when `host` is `127.0.0.1`, `localhost`, or `::1`. This now happens in `sse_app()` and `streamable_http_app()` instead of the constructor.
### Replace `RootModel` by union types with `TypeAdapter` validation
The following union types are no longer `RootModel` subclasses:
- `ClientRequest`
- `ServerRequest`
- `ClientNotification`
- `ServerNotification`
- `ClientResult`
- `ServerResult`
- `JSONRPCMessage`
This means you can no longer access `.root` on these types or use `model_validate()` directly on them. Instead, use the provided `TypeAdapter` instances for validation.
**Before (v1):**
```python
from mcp.types import ClientRequest, ServerNotification
# Using RootModel.model_validate()
request = ClientRequest.model_validate(data)
actual_request = request.root # Accessing the wrapped value
notification = ServerNotification.model_validate(data)
actual_notification = notification.root
```
**After (v2):**
```python
from mcp.types import client_request_adapter, server_notification_adapter
# Using TypeAdapter.validate_python()
request = client_request_adapter.validate_python(data)
# No .root access needed - request is the actual type
notification = server_notification_adapter.validate_python(data)
# No .root access needed - notification is the actual type
```
**Available adapters:**
| Union Type | Adapter |
|------------|---------|
| `ClientRequest` | `client_request_adapter` |
| `ServerRequest` | `server_request_adapter` |
| `ClientNotification` | `client_notification_adapter` |
| `ServerNotification` | `server_notification_adapter` |
| `ClientResult` | `client_result_adapter` |
| `ServerResult` | `server_result_adapter` |
| `JSONRPCMessage` | `jsonrpc_message_adapter` |
All adapters are exported from `mcp.types`.
### Resource URI type changed from `AnyUrl` to `str`
The `uri` field on resource-related types now uses `str` instead of Pydantic's `AnyUrl`. This aligns with the [MCP specification schema](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/schema/draft/schema.ts) which defines URIs as plain strings (`uri: string`) without strict URL validation. This change allows relative paths like `users/me` that were previously rejected.
**Before (v1):**
```python
from pydantic import AnyUrl
from mcp.types import Resource
# Required wrapping in AnyUrl
resource = Resource(name="test", uri=AnyUrl("users/me")) # Would fail validation
```
**After (v2):**
```python
from mcp.types import Resource
# Plain strings accepted
resource = Resource(name="test", uri="users/me") # Works
resource = Resource(name="test", uri="custom://scheme") # Works
resource = Resource(name="test", uri="https://example.com") # Works
```
If your code passes `AnyUrl` objects to URI fields, convert them to strings:
```python
# If you have an AnyUrl from elsewhere
uri = str(my_any_url) # Convert to string
```
Affected types:
- `Resource.uri`
- `ReadResourceRequestParams.uri`
- `ResourceContents.uri` (and subclasses `TextResourceContents`, `BlobResourceContents`)
- `SubscribeRequestParams.uri`
- `UnsubscribeRequestParams.uri`
- `ResourceUpdatedNotificationParams.uri`
The `ClientSession.read_resource()`, `subscribe_resource()`, and `unsubscribe_resource()` methods now accept both `str` and `AnyUrl` for backwards compatibility.
## Deprecations
<!-- Add deprecations below -->
## New Features
### `streamable_http_app()` available on lowlevel Server
The `streamable_http_app()` method is now available directly on the lowlevel `Server` class, not just `FastMCP`. This allows using the streamable HTTP transport without the FastMCP wrapper.
```python
from mcp.server.lowlevel.server import Server
server = Server("my-server")
# Register handlers...
@server.list_tools()
async def list_tools():
return [...]
# Create a Starlette app for streamable HTTP
app = server.streamable_http_app(
streamable_http_path="/mcp",
json_response=False,
stateless_http=False,
)
```
The lowlevel `Server` also now exposes a `session_manager` property to access the `StreamableHTTPSessionManager` after calling `streamable_http_app()`.
## Need Help?
If you encounter issues during migration:
1. Check the [API Reference](api.md) for updated method signatures
2. Review the [examples](https://github.com/modelcontextprotocol/python-sdk/tree/main/examples) for updated usage patterns
3. Open an issue on [GitHub](https://github.com/modelcontextprotocol/python-sdk/issues) if you find a bug or need further assistance