Skip to content

Commit 8c88d27

Browse files
committed
Tighten the public API surface: root exports, listen types, dead-code removal
Root `mcp` package: export `MCPServer` (alongside the existing `Client`, so `from mcp import MCPServer, Client` works) and `NoBackChannelError`; drop the v1-carryover `SamplingRole` alias (`mcp.types.Role`). `mcp.client` now re-exports the types `Client.listen()` yields and raises: `Subscription`, `SubscriptionLost`, `ListenNotSupportedError`. Remove dead surface: `StreamableHTTPError`/`ResumptionError` (never raised from a reachable path) and `StreamableHTTPTransport.get_session_id()` on the client transport; the no-op `RegistrationRequest`/`TokenSuccessResponse` auth-handler aliases; the `validate_extension_identifier` re-export alias; the unreachable `__main__` guard in `mcp/cli/__init__.py`. The unused lowlevel server `Context` moves to the private `mcp.server._context` module. `_handle_resumption_request` now takes the resumption token as a required argument instead of re-deriving it and carrying an unreachable no-token branch. Enable ruff `RUF022` and sort every `__all__`; `mcp_types/__init__.py` is exempted since its `__all__` is grouped by section comments on purpose.
1 parent 528e366 commit 8c88d27

26 files changed

Lines changed: 186 additions & 205 deletions

docs/get-started/first-steps.md

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,6 @@ Three plain functions, three decorators. Each decorator is the entire registrati
4646

4747
Everything else (the name, the description, the argument schema) the SDK reads from the function itself: its name, its docstring, its type hints. You never declared any of it separately.
4848

49-
!!! tip
50-
The two halves of the SDK have two import paths: `from mcp import Client` and
51-
`from mcp.server import MCPServer`. There is no `from mcp import MCPServer`.
52-
5349
### Try it
5450

5551
Run it with the MCP Inspector:

docs/migration.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@ The following type aliases and classes have been removed from the protocol types
265265
| `AnyFunction` | Use `Callable[..., Any]` directly |
266266
| `ClientRequestType`, `ClientNotificationType`, `ClientResultType`, `ServerRequestType`, `ServerNotificationType`, `ServerResultType` | The union is now the bare name: `ClientRequest`, `ClientNotification`, `ClientResult`, `ServerRequest`, `ServerNotification`, `ServerResult` |
267267
| `TaskExecutionMode`, `TASK_FORBIDDEN`, `TASK_OPTIONAL`, `TASK_REQUIRED`, `TASK_STATUS_*` | Use string literals; `TaskStatus` remains as the literal-union type |
268+
| `SamplingRole` (a top-level `mcp` alias for `Role`, never a `mcp.types` name) | `mcp.types.Role` |
268269

269270
**Before (v1):**
270271

@@ -657,6 +658,8 @@ from mcp.server.mcpserver import MCPServer, Context
657658
mcp = MCPServer("Demo")
658659
```
659660

661+
`MCPServer` is also exported from the top-level `mcp` package, next to `Client` (`from mcp import MCPServer, Client`); `mcp.server` and `mcp.server.mcpserver` keep working as import paths.
662+
660663
`Context` is the type annotation for the `ctx` parameter injected into tools, resources, and prompts (see [`get_context()` removed](#mcpserverget_context-removed) below). The `ctx.fastmcp` property is now `ctx.mcp_server`.
661664

662665
All submodules under `mcp.server.fastmcp.*` are now under `mcp.server.mcpserver.*` with the same structure. Common imports:
@@ -2166,6 +2169,12 @@ The `headers`, `timeout`, `sse_read_timeout`, and `auth` parameters have been re
21662169

21672170
`sse_client` is unchanged apart from the `httpx2` retyping: it still takes `url`, `headers`, `timeout`, `sse_read_timeout`, `httpx_client_factory` (which must now return an `httpx2.AsyncClient`), `auth` (now `httpx2.Auth | None`), and `on_session_created`. Only the streamable HTTP transport dropped its transport-level parameters; `StreamableHTTPTransport(url)` now takes just the URL.
21682171

2172+
### `StreamableHTTPError`, `ResumptionError`, and `StreamableHTTPTransport.get_session_id()` removed
2173+
2174+
`mcp.client.streamable_http` no longer defines `StreamableHTTPError` or its `ResumptionError` subclass: the transport never raised either from a code path a caller could catch, so `from mcp.client.streamable_http import StreamableHTTPError, ResumptionError` now raises `ImportError`. Transport failures surface as per-request JSON-RPC errors (see [Streamable HTTP: non-2xx responses now surface as per-request JSON-RPC errors](#streamable-http-non-2xx-responses-now-surface-as-per-request-json-rpc-errors)) or as the underlying `httpx2` exception; catch those instead.
2175+
2176+
The unused `StreamableHTTPTransport.get_session_id()` method is gone as well; read the `session_id` attribute directly (or capture the header, as in [`get_session_id` callback removed](#get_session_id-callback-removed-from-streamable_http_client) above).
2177+
21692178
### `StreamableHTTPTransport.protocol_version` attribute removed
21702179

21712180
The transport no longer holds per-connection protocol state; era-dependent headers (e.g. `MCP-Protocol-Version`) are now supplied per-message by the session. If you were reading `transport.protocol_version` to learn the negotiated version, read `session.protocol_version` (or `client.protocol_version` on the high-level `Client`) instead.
@@ -2771,7 +2780,7 @@ The user-facing methods for these features now carry `typing_extensions.deprecat
27712780

27722781
- Sampling: `ServerSession.create_message()`, `ClientPeer.sample()`
27732782
- Roots: `ServerSession.list_roots()`, `ClientPeer.list_roots()`, `ClientSession.send_roots_list_changed()`, `Client.send_roots_list_changed()`
2774-
- Logging: `ServerSession.send_log_message()`, `Connection.log()`, `ClientSession.set_logging_level()`, `Client.set_logging_level()`, `mcp.server.context.Context.log()` (the lowlevel `Context`), and the `MCPServer` `Context` helpers `log()`, `debug()`, `info()`, `warning()`, `error()`
2783+
- Logging: `ServerSession.send_log_message()`, `Connection.log()`, `ClientSession.set_logging_level()`, `Client.set_logging_level()`, and the `MCPServer` `Context` helpers `log()`, `debug()`, `info()`, `warning()`, `error()`
27752784

27762785
Registering a handler for a deprecated capability is deprecated too. The `Server.__init__` parameters `on_set_logging_level` (Logging) and `on_roots_list_changed` (Roots) are now split out into a `typing_extensions.deprecated` overload, so passing either is flagged by type checkers and emits `mcp.MCPDeprecationWarning` at construction time. `on_progress` follows the same pattern (see below). The non-deprecated overload omits these parameters, so the common case stays warning-free.
27772786

@@ -2804,7 +2813,7 @@ rest of this guide stays focused on the v1-to-v2 upgrade itself.
28042813

28052814
The 2026-07-28 protocol has no server-initiated requests, so a handler that reaches back to the client mid-request — `ctx.elicit()`, `ctx.elicit_url()`, `ctx.session.create_message()`, `ctx.session.list_roots()`, or any other `ServerSession` request helper — raises `NoBackChannelError` on such a connection instead of sending. An in-process `Client(server)` negotiates 2026-07-28 by default (see [`Client` defaults to `mode='auto'`](#client-defaults-to-modeauto)), so the first smoke test of an unchanged v1 sampling or elicitation tool fails, and setting `sampling_callback=` / `elicitation_callback=` on the client changes nothing because no request ever reaches the client.
28062815

2807-
`NoBackChannelError` lives in `mcp.shared.exceptions` and subclasses `MCPError` (code `-32600`, message `Cannot send '<method>': this transport context has no back-channel for server-initiated requests.`). Raised inside an `@mcp.tool()` it reaches the client as a top-level JSON-RPC error, not `CallToolResult(is_error=True)` — see [`MCPError` raised from an `@mcp.tool()` handler now surfaces as a JSON-RPC error](#mcperror-raised-from-an-mcptool-handler-now-surfaces-as-a-json-rpc-error) — and the [Troubleshooting](troubleshooting.md) page walks through the client-side traceback. The same exception is raised on a legacy session against a `stateless_http=True` server, and on the request-scoped channel of a stateful legacy session against a `json_response=True` server (a JSON body carries exactly one response, so a mid-request `ctx.elicit()` cannot ride it; the session's standalone `GET` stream still carries unrelated messages) — both places v1 dropped the message and stalled ([`Server.run()` no longer takes a `stateless` flag](#serverrun-no-longer-takes-a-stateless-flag)). Notifications never raise it: `send_log_message()`, `send_tool_list_changed()`, and the other notification helpers are dropped with a debug log where no channel exists, and `UrlElicitationRequiredError` from a tool is unaffected (it is an error response, not a request).
2816+
`NoBackChannelError` is exported from the top-level `mcp` package (`from mcp import NoBackChannelError`; it lives in `mcp.shared.exceptions`) and subclasses `MCPError` (code `-32600`, message `Cannot send '<method>': this transport context has no back-channel for server-initiated requests.`). Raised inside an `@mcp.tool()` it reaches the client as a top-level JSON-RPC error, not `CallToolResult(is_error=True)` — see [`MCPError` raised from an `@mcp.tool()` handler now surfaces as a JSON-RPC error](#mcperror-raised-from-an-mcptool-handler-now-surfaces-as-a-json-rpc-error) — and the [Troubleshooting](troubleshooting.md) page walks through the client-side traceback. The same exception is raised on a legacy session against a `stateless_http=True` server, and on the request-scoped channel of a stateful legacy session against a `json_response=True` server (a JSON body carries exactly one response, so a mid-request `ctx.elicit()` cannot ride it; the session's standalone `GET` stream still carries unrelated messages) — both places v1 dropped the message and stalled ([`Server.run()` no longer takes a `stateless` flag](#serverrun-no-longer-takes-a-stateless-flag)). Notifications never raise it: `send_log_message()`, `send_tool_list_changed()`, and the other notification helpers are dropped with a debug log where no channel exists, and `UrlElicitationRequiredError` from a tool is unaffected (it is an error response, not a request).
28082817

28092818
Two ways to migrate:
28102819

docs/whats-new.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ from mcp.server import MCPServer # v1: from mcp.server.fastmcp import FastMCP
2424
mcp = MCPServer("Demo") # v1: FastMCP("Demo")
2525
```
2626

27+
Both halves of the SDK are also on the top-level package, so `from mcp import MCPServer, Client` works.
28+
2729
It is also, for a decorator-built server, most of the port. `@mcp.tool()`, `@mcp.resource()`, and `@mcp.prompt()` accept what they accepted in v1 (`@mcp.resource()` adds one optional `security=` keyword), and the input schema still comes from your type hints. Around the edges: everything under `mcp.server.fastmcp.*` now lives under `mcp.server.mcpserver.*`, `ctx.fastmcp` is `ctx.mcp_server`, `get_context()` is gone (declare a `ctx: Context` parameter instead), and the exception base `FastMCPError` is `MCPServerError`. The **[Migration Guide](migration.md#fastmcp-renamed-to-mcpserver)** has the import table.
2830

2931
### `Resolve`: the new way to ask the user for input

docs_src/subscriptions/tutorial005.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import anyio
22

33
from mcp import Client
4-
from mcp.client.subscriptions import SubscriptionLost
4+
from mcp.client import SubscriptionLost
55

66
from .tutorial003 import read_board
77

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ select = [
203203
"F", # pyflakes
204204
"I", # isort
205205
"PERF", # Perflint
206+
"RUF022", # unsorted-dunder-all: keep `__all__` sorted
206207
"UP", # pyupgrade
207208
"TID251", # https://docs.astral.sh/ruff/rules/banned-api/
208209
]
@@ -219,6 +220,8 @@ max-complexity = 24 # Default is 10
219220
"__init__.py" = ["F401"]
220221
# The mcp.types package is an alias that mirrors mcp_types namespaces by design.
221222
"src/mcp/types/*.py" = ["F403"]
223+
# mcp_types' __all__ is grouped by section comments (capabilities, content types, requests, ...) rather than sorted.
224+
"src/mcp-types/mcp_types/__init__.py" = ["RUF022"]
222225
# Generated by scripts/gen_surface_types.py: raw datamodel-codegen output (TID251 lifts the repo-wide RootModel ban for these generated validators).
223226
"src/mcp-types/mcp_types/_v*/__init__.py" = ["D212", "E501", "I001", "TID251", "UP007", "UP037"]
224227
"tests/server/mcpserver/test_func_metadata.py" = ["E501"]

src/mcp-types/mcp_types/methods.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
"CLIENT_NOTIFICATIONS",
2828
"CLIENT_REQUESTS",
2929
"CLIENT_RESULTS",
30-
"CacheableMethod",
3130
"INPUT_REQUIRED_METHODS",
3231
"MONOLITH_NOTIFICATIONS",
3332
"MONOLITH_REQUESTS",
@@ -37,6 +36,7 @@
3736
"SERVER_RESULTS",
3837
"SPEC_CLIENT_METHODS",
3938
"SPEC_CLIENT_NOTIFICATION_METHODS",
39+
"CacheableMethod",
4040
"is_input_required",
4141
"parse_client_notification",
4242
"parse_client_request",

src/mcp-types/mcp_types/version.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@
1010
from typing import Final
1111

1212
__all__ = [
13-
"KNOWN_PROTOCOL_VERSIONS",
1413
"HANDSHAKE_PROTOCOL_VERSIONS",
15-
"MODERN_PROTOCOL_VERSIONS",
16-
"SUPPORTED_PROTOCOL_VERSIONS",
17-
"LATEST_PROTOCOL_VERSION",
14+
"KNOWN_PROTOCOL_VERSIONS",
1815
"LATEST_HANDSHAKE_VERSION",
1916
"LATEST_MODERN_VERSION",
17+
"LATEST_PROTOCOL_VERSION",
18+
"MODERN_PROTOCOL_VERSIONS",
2019
"OLDEST_SUPPORTED_VERSION",
20+
"SUPPORTED_PROTOCOL_VERSIONS",
2121
"is_version_at_least",
2222
]
2323

src/mcp/__init__.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@
5656
ToolUseContent,
5757
UnsubscribeRequest,
5858
)
59-
from mcp_types import Role as SamplingRole
6059

6160
# Bind the `mcp.types` submodule on the package, as v1's `from .types import
6261
# ...` did, so `import mcp` followed by `mcp.types.Tool` keeps working.
@@ -66,9 +65,10 @@
6665
from .client.session import ClientSession
6766
from .client.session_group import ClientSessionGroup
6867
from .client.stdio import StdioServerParameters, stdio_client
68+
from .server.mcpserver import MCPServer
6969
from .server.session import ServerSession
7070
from .server.stdio import stdio_server
71-
from .shared.exceptions import MCPDeprecationWarning, MCPError, UrlElicitationRequiredError
71+
from .shared.exceptions import MCPDeprecationWarning, MCPError, NoBackChannelError, UrlElicitationRequiredError
7272
from .shared.uri_template import InvalidUriTemplate, UriTemplate
7373

7474
__all__ = [
@@ -93,6 +93,7 @@
9393
"InitializeResult",
9494
"InitializedNotification",
9595
"InputRequiredRoundsExceededError",
96+
"InvalidUriTemplate",
9697
"JSONRPCError",
9798
"JSONRPCRequest",
9899
"JSONRPCResponse",
@@ -105,22 +106,23 @@
105106
"LoggingMessageNotification",
106107
"MCPDeprecationWarning",
107108
"MCPError",
109+
"MCPServer",
110+
"NoBackChannelError",
108111
"Notification",
109112
"PingRequest",
110113
"ProgressNotification",
111114
"PromptsCapability",
112115
"ReadResourceRequest",
113116
"ReadResourceResult",
114117
"Resource",
115-
"ResourcesCapability",
116118
"ResourceUpdatedNotification",
119+
"ResourcesCapability",
117120
"RootsCapability",
118121
"SamplingCapability",
119122
"SamplingContent",
120123
"SamplingContextCapability",
121124
"SamplingMessage",
122125
"SamplingMessageContentBlock",
123-
"SamplingRole",
124126
"SamplingToolsCapability",
125127
"ServerCapabilities",
126128
"ServerNotification",
@@ -134,12 +136,11 @@
134136
"Tool",
135137
"ToolChoice",
136138
"ToolResultContent",
137-
"ToolsCapability",
138139
"ToolUseContent",
140+
"ToolsCapability",
139141
"UnsubscribeRequest",
140142
"UriTemplate",
141143
"UrlElicitationRequiredError",
142-
"InvalidUriTemplate",
143144
"stdio_client",
144145
"stdio_server",
145146
]

src/mcp/cli/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,4 @@
22

33
from .cli import app
44

5-
if __name__ == "__main__": # pragma: no cover
6-
app()
5+
__all__ = ["app"]

src/mcp/client/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
advertise,
2222
)
2323
from mcp.client.session import ClientSession, IncomingMessage
24+
from mcp.client.subscriptions import ListenNotSupportedError, Subscription, SubscriptionLost
2425

2526
__all__ = [
2627
"CacheConfig",
@@ -32,12 +33,15 @@
3233
"ClientExtension",
3334
"ClientRequestContext",
3435
"ClientSession",
35-
"IncomingMessage",
3636
"InMemoryResponseCacheStore",
37+
"IncomingMessage",
3738
"InputRequiredRoundsExceededError",
39+
"ListenNotSupportedError",
3840
"NotificationBinding",
3941
"ResponseCacheStore",
4042
"ResultClaim",
43+
"Subscription",
44+
"SubscriptionLost",
4145
"Transport",
4246
"UnexpectedClaimedResult",
4347
"advertise",

0 commit comments

Comments
 (0)