Skip to content

Commit 3d5f673

Browse files
committed
Docs and docstrings catch up with recent contract changes
- extensions.md interception section: call_next returns the handler's result, short-circuits are serialized and stamped like handler results, the validated params argument decides the tool invocation, and no middleware is involved either way. - whats-new client recap: server_info is Implementation | None. - Server.server_info and a transport-test helper docstring still described the removed package-version fallback. - _stamp_server_info docstring states the explicit-null posture (null reads as absent, mirroring clientInfo). No-Verification-Needed: docs and docstring-only edits
1 parent b608b23 commit 3d5f673

5 files changed

Lines changed: 22 additions & 13 deletions

File tree

docs/advanced/extensions.md

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -133,14 +133,21 @@ or veto a tool call:
133133
```
134134

135135
* `params` is the validated `CallToolRequestParams`: you get `params.name` and
136-
`params.arguments` without touching raw JSON.
137-
* `call_next(ctx)` runs the rest of the chain. Return its result unchanged (observe),
138-
return something else (replace), or raise an `MCPError` (refuse).
136+
`params.arguments` without touching raw JSON. It is also what decides which
137+
tool call runs: passing a rewritten context through `call_next` changes what
138+
the handler observes on `ctx`, not the tool invocation. Wire-level request
139+
rewriting belongs to [Middleware](middleware.md).
140+
* `call_next(ctx)` runs the rest of the chain and returns the handler's result.
141+
Return it unchanged (observe), return something else (replace), or raise an
142+
`MCPError` (refuse). Whatever you return is serialized like any handler
143+
result, including the 2026-era `serverInfo` identity stamp, so a
144+
short-circuiting interceptor never produces an anonymous or off-schema
145+
response.
139146
* With several extensions, interceptors nest in registration order: the first
140147
extension in `extensions=[...]` is outermost.
141148
* The default implementation is a pass-through, and a server whose extensions never
142-
override this hook installs **no** middleware at all. You don't pay for what
143-
you don't use.
149+
override this hook keeps the bare `tools/call` handler untouched. You don't
150+
pay for what you don't use.
144151

145152
The hook wraps `tools/call` and nothing else. For every-message concerns, use
146153
[Middleware](middleware.md). That is what it is for.

docs/whats-new.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ v1 handed you three nested layers: a transport context manager yielding raw stre
4444
--8<-- "docs_src/client/tutorial001.py"
4545
```
4646

47-
`Client` takes a server object (in memory, no transport: the testing story), a URL (Streamable HTTP), or any transport context manager such as `stdio_client(...)`. Entering `async with` connects and negotiates the protocol version, whichever era the server speaks; `client.server_info`, `client.server_capabilities`, and `client.protocol_version` are simply there afterwards. The sampling and elicitation callbacks you registered in v1 still work (their bodies see the same snake_case attribute rename as everything else on this page), they now also answer the 2026-style requests-inside-results (below), and they run concurrently instead of one at a time. `ClientSession` is still underneath for anyone who wants the low-level surface, and `client.session` hands it to you; it moved too (it runs on the new dispatcher engine, and some of its own signatures changed), so read the **[Migration Guide](migration.md#clientsession-now-runs-on-jsonrpcdispatcher-basesession-removed)** before you drop down.
47+
`Client` takes a server object (in memory, no transport: the testing story), a URL (Streamable HTTP), or any transport context manager such as `stdio_client(...)`. Entering `async with` connects and negotiates the protocol version, whichever era the server speaks; `client.server_capabilities` and `client.protocol_version` are simply there afterwards, and `client.server_info` is too when the server identifies itself (it is `Implementation | None` now, since 2026-era identity is optional). The sampling and elicitation callbacks you registered in v1 still work (their bodies see the same snake_case attribute rename as everything else on this page), they now also answer the 2026-style requests-inside-results (below), and they run concurrently instead of one at a time. `ClientSession` is still underneath for anyone who wants the low-level surface, and `client.session` hands it to you; it moved too (it runs on the new dispatcher engine, and some of its own signatures changed), so read the **[Migration Guide](migration.md#clientsession-now-runs-on-jsonrpcdispatcher-basesession-removed)** before you drop down.
4848

4949
**[The Client](client/index.md)** introduces it, **[Client transports](client/transports.md)** covers the three connection forms, **[Client callbacks](client/callbacks.md)** covers the callbacks themselves, and **[Testing](get-started/testing.md)** shows the in-memory pattern that replaces v1's `create_connected_server_and_client_session()` helper.
5050

src/mcp/server/lowlevel/server.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -628,8 +628,8 @@ def get_capabilities(
628628
def server_info(self) -> types.Implementation:
629629
"""The `serverInfo` block describing this implementation.
630630
631-
Derived from the constructor's identity fields. `version` falls back to
632-
the installed `mcp` package version when not supplied explicitly.
631+
Derived from the constructor's identity fields. An unversioned server
632+
reports an empty `version`; the SDK never substitutes its own.
633633
"""
634634
return types.Implementation(
635635
name=self.name,

src/mcp/server/runner.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -382,8 +382,11 @@ def _serialize(self, method: str, version: str, result: HandlerResult) -> dict[s
382382
def _stamp_server_info(self, version: str, result: dict[str, Any]) -> dict[str, Any]:
383383
"""Fill the `serverInfo` `_meta` stamp on a 2026-era result (spec #3002).
384384
385-
A handler-authored value wins, a non-mapping `_meta` is the handler's
386-
to own, and handshake-era results are never stamped. `result` is
385+
A handler-authored value wins; an explicit `null` reads as absent and
386+
is stamped over, mirroring the request-side `clientInfo` posture (a
387+
`null` is not a valid `Implementation`, so presence means a value). A
388+
non-mapping `_meta` is the handler's to own, and handshake-era results
389+
are never stamped. `result` is
387390
pipeline-owned (`_dump_result` copies dicts; the spec-method sieve
388391
re-dumps), but `_meta` may still be the handler's object, so the stamp
389392
replaces it rather than writing into it. `server_info_stamp` is a

tests/interaction/transports/test_hosting_http_modern.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,8 @@ def _meta_envelope() -> dict[str, object]:
8080
def _server(*, on_meta: Callable[[dict[str, Any]], None] | None = None) -> Server:
8181
"""A low-level server with one `add` tool for the raw-httpx2 tests below.
8282
83-
The explicit version pins the `_meta` serverInfo stamp every 2026 result carries, so
84-
wire-level snapshots stay deterministic (the default falls back to the installed package
85-
version).
83+
The explicit version gives the `_meta` serverInfo stamp every 2026 result
84+
carries a non-empty value for the wire-level snapshots.
8685
"""
8786

8887
async def list_tools(ctx: ServerRequestContext, params: PaginatedRequestParams | None) -> ListToolsResult:

0 commit comments

Comments
 (0)