Skip to content

Commit 3a52324

Browse files
committed
docs: fill migration guide gaps surfaced by upgrade eval
Adds missing migration coverage identified by running 24 automated upgrade workers across 8 v1 codebases using only docs/migration.md as reference. - Show Context import path in FastMCP→MCPServer section and get_context() example - Document camelCase→snake_case field rename with common-field table - Add complete on_* handler reference table (decorator → kwarg → params → return) - Note that all mcp.server.fastmcp.* submodules moved to mcp.server.mcpserver.* - Document create_connected_server_and_client_session removal (use Client instead) - Add raise-side example for MCPError constructor signature change - State explicitly that mcp.shared.context module was removed - Fix _meta example to use arbitrary keys (avoid progressToken alias confusion) - Document private _add_request_handler workaround for handlers MCPServer doesn't expose (subscribe/unsubscribe/set_logging_level)
1 parent cf4e435 commit 3a52324

File tree

1 file changed

+185
-3
lines changed

1 file changed

+185
-3
lines changed

docs/migration.md

Lines changed: 185 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,52 @@ from mcp.types import ContentBlock, ResourceTemplateReference
126126
# Use `str` instead of `Cursor` for pagination cursors
127127
```
128128

129+
### Field names changed from camelCase to snake_case
130+
131+
All Pydantic model fields in `mcp.types` now use snake_case names for Python attribute access. The JSON wire format is unchanged — serialization still uses camelCase via Pydantic aliases.
132+
133+
**Before (v1):**
134+
135+
```python
136+
result = await session.call_tool("my_tool", {"x": 1})
137+
if result.isError:
138+
...
139+
140+
tools = await session.list_tools()
141+
cursor = tools.nextCursor
142+
schema = tools.tools[0].inputSchema
143+
```
144+
145+
**After (v2):**
146+
147+
```python
148+
result = await session.call_tool("my_tool", {"x": 1})
149+
if result.is_error:
150+
...
151+
152+
tools = await session.list_tools()
153+
cursor = tools.next_cursor
154+
schema = tools.tools[0].input_schema
155+
```
156+
157+
Common renames:
158+
159+
| v1 (camelCase) | v2 (snake_case) |
160+
|----------------|-----------------|
161+
| `inputSchema` | `input_schema` |
162+
| `outputSchema` | `output_schema` |
163+
| `isError` | `is_error` |
164+
| `nextCursor` | `next_cursor` |
165+
| `mimeType` | `mime_type` |
166+
| `structuredContent` | `structured_content` |
167+
| `serverInfo` | `server_info` |
168+
| `protocolVersion` | `protocol_version` |
169+
| `uriTemplate` | `uri_template` |
170+
| `listChanged` | `list_changed` |
171+
| `progressToken` | `progress_token` |
172+
173+
Because `populate_by_name=True` is set, the old camelCase names still work as constructor kwargs (e.g., `Tool(inputSchema={...})` is accepted), but attribute access must use snake_case (`tool.input_schema`).
174+
129175
### `args` parameter removed from `ClientSessionGroup.call_tool()`
130176

131177
The deprecated `args` parameter has been removed from `ClientSessionGroup.call_tool()`. Use `arguments` instead.
@@ -225,6 +271,28 @@ except MCPError as e:
225271
from mcp import MCPError
226272
```
227273

274+
The constructor signature also changed — it now takes `code`, `message`, and optional `data` directly instead of wrapping an `ErrorData`:
275+
276+
**Before (v1):**
277+
278+
```python
279+
from mcp.shared.exceptions import McpError
280+
from mcp.types import ErrorData, INVALID_REQUEST
281+
282+
raise McpError(ErrorData(code=INVALID_REQUEST, message="bad input"))
283+
```
284+
285+
**After (v2):**
286+
287+
```python
288+
from mcp.shared.exceptions import MCPError
289+
from mcp.types import INVALID_REQUEST
290+
291+
raise MCPError(INVALID_REQUEST, "bad input")
292+
# or, if you already have an ErrorData:
293+
raise MCPError.from_error_data(error_data)
294+
```
295+
228296
### `FastMCP` renamed to `MCPServer`
229297

230298
The `FastMCP` class has been renamed to `MCPServer` to better reflect its role as the main server class in the SDK. This is a simple rename with no functional changes to the class itself.
@@ -240,11 +308,19 @@ mcp = FastMCP("Demo")
240308
**After (v2):**
241309

242310
```python
243-
from mcp.server.mcpserver import MCPServer
311+
from mcp.server.mcpserver import MCPServer, Context
244312

245313
mcp = MCPServer("Demo")
246314
```
247315

316+
`Context` is the type annotation for the `ctx` parameter injected into tools, resources, and prompts (see [`get_context()` removed](#mcpserverget_context-removed) below).
317+
318+
All submodules under `mcp.server.fastmcp.*` are now under `mcp.server.mcpserver.*` with the same structure. Common imports:
319+
320+
- `Image`, `Audio` — from `mcp.server.mcpserver` (or `.utilities.types`)
321+
- `UserMessage`, `AssistantMessage` — from `mcp.server.mcpserver.prompts.base`
322+
- `ToolError`, `ResourceError` — from `mcp.server.mcpserver.exceptions`
323+
248324
### `mount_path` parameter removed from MCPServer
249325

250326
The `mount_path` parameter has been removed from `MCPServer.__init__()`, `MCPServer.run()`, `MCPServer.run_sse_async()`, and `MCPServer.sse_app()`. It was also removed from the `Settings` class.
@@ -331,6 +407,8 @@ async def my_tool(x: int) -> str:
331407
**After (v2):**
332408

333409
```python
410+
from mcp.server.mcpserver import Context
411+
334412
@mcp.tool()
335413
async def my_tool(x: int, ctx: Context) -> str:
336414
await ctx.info("Processing...")
@@ -343,6 +421,45 @@ async def my_tool(x: int, ctx: Context) -> str:
343421

344422
The internal layers (`ToolManager.call_tool`, `Tool.run`, `Prompt.render`, `ResourceTemplate.create_resource`, etc.) now require `context` as a positional argument.
345423

424+
### Registering lowlevel handlers on `MCPServer` (workaround)
425+
426+
`MCPServer` does not expose public APIs for `subscribe_resource`, `unsubscribe_resource`, or `set_logging_level` handlers. In v1, the workaround was to reach into the private lowlevel server and use its decorator methods:
427+
428+
**Before (v1):**
429+
430+
```python
431+
@mcp._mcp_server.set_logging_level() # pyright: ignore[reportPrivateUsage]
432+
async def handle_set_logging_level(level: str) -> None:
433+
...
434+
435+
mcp._mcp_server.subscribe_resource()(handle_subscribe) # pyright: ignore[reportPrivateUsage]
436+
```
437+
438+
In v2, the lowlevel `Server` no longer has decorator methods (handlers are constructor-only), so the equivalent workaround is `_add_request_handler`:
439+
440+
**After (v2):**
441+
442+
```python
443+
from mcp.server import ServerRequestContext
444+
from mcp.types import EmptyResult, SetLevelRequestParams, SubscribeRequestParams
445+
446+
447+
async def handle_set_logging_level(ctx: ServerRequestContext, params: SetLevelRequestParams) -> EmptyResult:
448+
...
449+
return EmptyResult()
450+
451+
452+
async def handle_subscribe(ctx: ServerRequestContext, params: SubscribeRequestParams) -> EmptyResult:
453+
...
454+
return EmptyResult()
455+
456+
457+
mcp._lowlevel_server._add_request_handler("logging/setLevel", handle_set_logging_level) # pyright: ignore[reportPrivateUsage]
458+
mcp._lowlevel_server._add_request_handler("resources/subscribe", handle_subscribe) # pyright: ignore[reportPrivateUsage]
459+
```
460+
461+
This is a private API and may change. A public way to register these handlers on `MCPServer` is planned; until then, use this workaround or use the lowlevel `Server` directly.
462+
346463
### Replace `RootModel` by union types with `TypeAdapter` validation
347464

348465
The following union types are no longer `RootModel` subclasses:
@@ -428,6 +545,8 @@ server = Server("my-server", on_call_tool=handle_call_tool)
428545

429546
### `RequestContext` type parameters simplified
430547

548+
The `mcp.shared.context` module has been removed. `RequestContext` is now split into `ClientRequestContext` (in `mcp.client.context`) and `ServerRequestContext` (in `mcp.server.context`).
549+
431550
The `RequestContext` class has been split to separate shared fields from server-specific fields. The shared `RequestContext` now only takes 1 type parameter (the session type) instead of 3.
432551

433552
**`RequestContext` changes:**
@@ -462,7 +581,7 @@ server_ctx: ServerRequestContext[LifespanContextT, RequestT]
462581

463582
The `mcp.shared.progress` module (`ProgressContext`, `Progress`, and the `progress()` context manager) has been removed. This module had no real-world adoption — all users send progress notifications via `Context.report_progress()` or `session.send_progress_notification()` directly.
464583

465-
**Before:**
584+
**Before (v1):**
466585

467586
```python
468587
from mcp.shared.progress import progress
@@ -490,6 +609,46 @@ await session.send_progress_notification(
490609
)
491610
```
492611

612+
### `create_connected_server_and_client_session` removed
613+
614+
The `create_connected_server_and_client_session` helper in `mcp.shared.memory` has been removed. Use `mcp.client.Client` instead — it accepts a `Server` or `MCPServer` instance directly and handles the in-memory transport and session setup for you.
615+
616+
**Before (v1):**
617+
618+
```python
619+
from mcp.shared.memory import create_connected_server_and_client_session
620+
621+
async with create_connected_server_and_client_session(server) as session:
622+
result = await session.call_tool("my_tool", {"x": 1})
623+
```
624+
625+
**After (v2):**
626+
627+
```python
628+
from mcp.client import Client
629+
630+
async with Client(server) as client:
631+
result = await client.call_tool("my_tool", {"x": 1})
632+
```
633+
634+
`Client` accepts the same callback parameters the old helper did (`sampling_callback`, `list_roots_callback`, `logging_callback`, `message_handler`, `elicitation_callback`, `client_info`) plus `raise_exceptions` to surface server-side errors.
635+
636+
If you need direct access to the underlying `ClientSession` and memory streams (e.g., for low-level transport testing), `create_client_server_memory_streams` is still available in `mcp.shared.memory`:
637+
638+
```python
639+
import anyio
640+
from mcp.client.session import ClientSession
641+
from mcp.shared.memory import create_client_server_memory_streams
642+
643+
async with create_client_server_memory_streams() as (client_streams, server_streams):
644+
async with anyio.create_task_group() as tg:
645+
tg.start_soon(lambda: server.run(*server_streams, server.create_initialization_options()))
646+
async with ClientSession(*client_streams) as session:
647+
await session.initialize()
648+
...
649+
tg.cancel_scope.cancel()
650+
```
651+
493652
### Resource URI type changed from `AnyUrl` to `str`
494653

495654
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.
@@ -645,6 +804,29 @@ server = Server("my-server", on_list_tools=handle_list_tools, on_call_tool=handl
645804
- Handlers return the full result type (e.g. `ListToolsResult`) rather than unwrapped values (e.g. `list[Tool]`).
646805
- The automatic `jsonschema` input/output validation that the old `call_tool()` decorator performed has been removed. There is no built-in replacement — if you relied on schema validation in the lowlevel server, you will need to validate inputs yourself in your handler.
647806

807+
**Complete handler reference:**
808+
809+
All handlers receive `ctx: ServerRequestContext` as the first argument. The second argument and return type are:
810+
811+
| v1 decorator | v2 constructor kwarg | `params` type | return type |
812+
|---|---|---|---|
813+
| `@server.list_tools()` | `on_list_tools` | `PaginatedRequestParams \| None` | `ListToolsResult` |
814+
| `@server.call_tool()` | `on_call_tool` | `CallToolRequestParams` | `CallToolResult \| CreateTaskResult` |
815+
| `@server.list_resources()` | `on_list_resources` | `PaginatedRequestParams \| None` | `ListResourcesResult` |
816+
| `@server.list_resource_templates()` | `on_list_resource_templates` | `PaginatedRequestParams \| None` | `ListResourceTemplatesResult` |
817+
| `@server.read_resource()` | `on_read_resource` | `ReadResourceRequestParams` | `ReadResourceResult` |
818+
| `@server.subscribe_resource()` | `on_subscribe_resource` | `SubscribeRequestParams` | `EmptyResult` |
819+
| `@server.unsubscribe_resource()` | `on_unsubscribe_resource` | `UnsubscribeRequestParams` | `EmptyResult` |
820+
| `@server.list_prompts()` | `on_list_prompts` | `PaginatedRequestParams \| None` | `ListPromptsResult` |
821+
| `@server.get_prompt()` | `on_get_prompt` | `GetPromptRequestParams` | `GetPromptResult` |
822+
| `@server.completion()` | `on_completion` | `CompleteRequestParams` | `CompleteResult` |
823+
| `@server.set_logging_level()` | `on_set_logging_level` | `SetLevelRequestParams` | `EmptyResult` |
824+
|| `on_ping` | `RequestParams \| None` | `EmptyResult` |
825+
| `@server.progress_notification()` | `on_progress` | `ProgressNotificationParams` | `None` |
826+
|| `on_roots_list_changed` | `NotificationParams \| None` | `None` |
827+
828+
All `params` and return types are importable from `mcp.types`.
829+
648830
**Notification handlers:**
649831

650832
```python
@@ -849,7 +1031,7 @@ params = CallToolRequestParams(
8491031
params = CallToolRequestParams(
8501032
name="my_tool",
8511033
arguments={},
852-
_meta={"progressToken": "tok", "customField": "value"}, # OK
1034+
_meta={"my_custom_key": "value", "another": 123}, # OK
8531035
)
8541036
```
8551037

0 commit comments

Comments
 (0)