docs: fill migration guide gaps surfaced by automated upgrade eval#2412
docs: fill migration guide gaps surfaced by automated upgrade eval#2412
Conversation
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)
Follow-up to the previous commit covering the lower-severity gaps that caused hesitation but not failures during the automated upgrade eval. - Note that sse_client retains headers/timeout/auth params (only streamable HTTP changed) - Add Iterable[ReadResourceContents] to the read_resource wrapping-removed example (was the recommended v1 return type) - Note that mcp.settings no longer holds transport fields; debug and log_level remain on the constructor - Document Context generic params change (3→2, drop ServerSessionT) with before/after - Note there is currently no public handler introspection API - Show RootModel construction direction (drop wrapper call), not just parsing direction - Add follow_redirects=True to the primary streamable_http_client example and explain v1 set it internally
There was a problem hiding this comment.
LGTM — comprehensive doc improvements, the inline comment flags a minor type-precision nit in the Context generics example.
Extended reasoning...
Overview
Documentation-only PR modifying docs/migration.md. Adds 17 new sections/clarifications covering migration gaps surfaced by an automated eval: Context import paths, camelCase→snake_case rename rule, lowlevel Server handler reference table, mcp.server.fastmcp.* rename, create_connected_server_and_client_session removal, MCPError constructor change, mcp.shared.context removal, _add_request_handler workaround, RootModel construction change, and several minor friction points.
Security risks
None — documentation only, no code changes.
Level of scrutiny
Low. All changes are prose and code examples in a migration guide. The PR description notes that all 34 from mcp... import statements were verified to resolve, and behavioral claims were validated against the installed package. The sole bug found is a nit (inline comment flagged) affecting only static type checkers: Context[ServerSession, None] → bare Context is not type-preserving; Context[None] would be the precise equivalent. Runtime behavior is unaffected.
Other factors
No prior reviews on this PR. The PR is well-scoped (single file), clearly motivated, and the fixes are independently verifiable against the SDK source. The nit is minor enough that it does not block approval — it is already surfaced via inline comment.
| server_ctx: ServerRequestContext[LifespanContextT, RequestT] | ||
| ``` | ||
|
|
||
| The high-level `Context` class (injected into `@mcp.tool()` etc.) similarly dropped its `ServerSessionT` parameter: `Context[ServerSessionT, LifespanContextT, RequestT]` → `Context[LifespanContextT, RequestT]`. Both remaining parameters have defaults, so bare `Context` is usually sufficient: | ||
|
|
||
| **Before (v1):** | ||
|
|
||
| ```python | ||
| async def my_tool(ctx: Context[ServerSession, None]) -> str: ... | ||
| ``` | ||
|
|
||
| **After (v2):** | ||
|
|
||
| ```python | ||
| async def my_tool(ctx: Context) -> str: ... | ||
| # or, with an explicit lifespan type: | ||
| async def my_tool(ctx: Context[MyLifespanState]) -> str: ... | ||
| ``` | ||
|
|
||
| ### `ProgressContext` and `progress()` context manager removed | ||
|
|
||
| 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. | ||
|
|
||
| **Before:** | ||
| **Before (v1):** | ||
|
|
||
| ```python | ||
| from mcp.shared.progress import progress |
There was a problem hiding this comment.
🟡 The migration guide shows Context[ServerSession, None] → bare Context, but this silently changes LifespanContextT from None to dict[str, Any] for type-checker users. The type-accurate v2 equivalent of Context[ServerSession, None] is Context[None], not bare Context.
Extended reasoning...
What the bug is: In the migration guide's "Context generic params" section (lines 600–626), the before example is Context[ServerSession, None] where the second type param is LifespanContextT=None. The after example shows bare Context, which in v2 resolves to Context[dict[str, Any], Any] because LifespanContextT has a default of dict[str, Any].
The specific code path: In src/mcp/server/context.py, LifespanContextT is defined as TypeVar('LifespanContextT', default=dict[str, Any]). So bare Context expands to Context[dict[str, Any], Any] under a type checker, not Context[None, Any].
Why existing doc text doesn't prevent it: The guide says bare Context is "usually sufficient", which partially mitigates the concern, but the direct before/after pairing of Context[ServerSession, None] → Context implies they are type-equivalent when they are not. Users following this one-to-one mapping will silently change LifespanContextT from None to dict[str, Any].
Impact: Runtime behavior is completely unaffected — TypeVar defaults only matter to static type checkers. However, users who had Context[ServerSession, None] specifically to signal "no lifespan" and follow this guide will end up with Context (i.e., Context[dict[str, Any], Any]), which could cause false type-checker positives or negatives if they try to access ctx.request_context.lifespan_context and expect it to be None.
How to fix: Change the after example to show Context[None] as the direct type-preserving equivalent, and note bare Context separately as a convenience when lifespan type precision is not required:
# After (v2) — type-preserving equivalent:
async def my_tool(ctx: Context[None]) -> str: ...
# or, if lifespan type precision is not needed:
async def my_tool(ctx: Context) -> str: ...Step-by-step proof:
- v1 code:
async def my_tool(ctx: Context[ServerSession, None]) -> str: ...— hereLifespanContextT = None - Migration guide says: migrate to
async def my_tool(ctx: Context) -> str: ... - In v2,
Contextwithout type params uses TypeVar defaults:LifespanContextT = TypeVar('LifespanContextT', default=dict[str, Any]) - So bare
Context=Context[dict[str, Any], Any], notContext[None, Any] - The type-preserving equivalent is
Context[None](keepingLifespanContextT=None), which the guide does not show as an option
Fills gaps in
docs/migration.mdidentified by an automated migration eval: 24 workers × 8 v1 codebases, each tasked with upgrading to v2 using only the migration guide. The eval surfaced 139 gap reports (20/24 successful migrations, 4 partial), aggregated into 10 priority findings and 10 minor friction points.Motivation and Context
The eval showed real migration friction the doc didn't address — workers hit
ImportError/ModuleNotFoundError/AttributeErrorand had to read SDK source to recover. These gaps would translate directly to support burden and failed upgrades for end users.Priority gaps fixed (commit 1):
Contextimport path never shown (highest-frequency gap, ~9 reports)on_*handler reference table for lowlevelServermcp.server.fastmcp.*submodule rename notecreate_connected_server_and_client_sessionremoval — point toClient(server)insteadMCPErrorraise-side constructor changemcp.shared.contextmodule removal stated explicitly_metaexample fixed to avoidprogressTokenalias confusion_add_request_handlerworkaround for handlersMCPServerdoesn't exposeMinor gaps fixed (commit 2):
sse_clientparams unchanged noteIterable[ReadResourceContents]added to read_resource wrapping examplemcp.settingsno longer holds transport fields;debug/log_levelremain on constructorContextgeneric params 3→2 with before/afterfollow_redirects=Trueadded to primarystreamable_http_clientexample with rationaleTwo gaps were skipped as not-migration-guide-territory (
AnyUrl.pathparsing,timedeltaconversion). One gap (dependencies=removal) was skipped because the parameter was restored in #2358.How Has This Been Tested?
All 34
from mcp...import statements in the doc's v2 code blocks were extracted and verified to resolve against the rebased code. Behavioral claims (camelCase/snake_case construction vs attribute access,MCPErrorconstructor,_add_request_handlerworkaround,Contextgenerics) were verified by running them against the installed package.Breaking Changes
None — documentation only.
Types of changes
Checklist
Additional context
The
RequestParamsMetaTypedDict has an asymmetric key-transformation behavior (onlyprogress_tokengets aliased; extra keys pass through untouched) that this PR works around in the doc but doesn't fix in code — flagged for separate follow-up.AI Disclaimer