sdk
_sdk
¶
One import site for the MCP SDK, so a renamed module is a one-line fix.
mcp 2.0.0 removed mcp.server.fastmcp. The server class moved to
mcp.server.mcpserver and was renamed FastMCP -> MCPServer. Every
tool, resource and prompt in this package imported Context from the old
path, so a fresh pip install resolving mcp 2.x produced a server that could
not import at all, with 26 import sites to chase.
The public API this package uses is unchanged between the two: MCPServer(name,
instructions), the tool()/prompt()/resource(uri) decorators, and
Context. So supporting both is an import shim rather than a port, and the
dependency does not need pinning to a major version.
Functions:
| Name | Description |
|---|---|
build_server |
Construct the SDK server and give it a version, on either major. |
forbid_unknown_arguments |
Make tool |
report_progress |
Route SDK 1 progress onto the call's POST stream, not a session GET. |
run_server |
Run the server; on SDK 2, HTTP without protocol sessions. |
unknown_argument_message |
The refusal for unknown arguments: what each might have meant, and what exists. |
Functions:¶
build_server
¶
Construct the SDK server and give it a version, on either major.
The version is why this is a function rather than a bare class re-export.
mcp 1.x has no public way to set it, so the only option was poking
server._mcp_server.version. mcp 2.0 accepts version on the
constructor and has no _mcp_server at all, so the poke raised
AttributeError at import time -- which is a broken server, not a missing
version string.
forbid_unknown_arguments
¶
forbid_unknown_arguments(server, name: str) -> None
Make tool name refuse arguments it does not declare.
The SDK's argument model ignores extra keys, so a misspelled filter
(node_typ="geo") was dropped and the call answered as if unfiltered,
which reads as a filter that matched everything. With extra="forbid"
the call fails, and the advertised schema says
additionalProperties: false. _tool_manager and fn_metadata are
private but identical on 1.x and 2.x, which is why this lives here.
pydantic's own refusal ("Extra inputs are not permitted") names the key
but not the argument meant: create_node(node_name=...) left the caller
to find name in the schema. A before-validator on a subclass of the
tool's model refuses first, with the closest accepted names and the full
list (unknown_argument_message).
report_progress
async
¶
Route SDK 1 progress onto the call's POST stream, not a session GET.
Early 1.x (1.21.1) sends it without related_request_id; 1.29.0 already passes it.
run_server
¶
run_server(server, *, transport: str) -> None
Run the server; on SDK 2, HTTP without protocol sessions.
Stateless, a client keeps no Mcp-Session-Id between requests (2026-07-28 clients never had one: SDK 2 serves them statelessly either way). SDK 2 enters the lifespan once per session manager, so the Houdini bridge lives as long as the process. SDK 1 enters it once per stateless request: a new bridge and port scan for every call, and connect_houdini forgotten as soon as it answers. SDK 1 stays stateful.
unknown_argument_message
¶
The refusal for unknown arguments: what each might have meant, and what exists.
A name that contains an accepted one comes first (node_name ->
name), then the near spellings (node_typ -> node_type).