You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs: server identity moved to result _meta; clientInfo optional
Migration guide gets a grouped section for the spec #3002 reshape (field
removal, stamping default and opt-out, Optional server_info, pair-only
requests and the client_capabilities accessor). Whats-new, protocol-versions,
client, session-groups, media, testing, and low-level pages updated to the
new shapes.
The `_meta` block is the server's identity stamp: the SDK adds it to every 2026-era result, with the `version` taken from the constructor or the installed package. Pass `include_server_info=False` to the server to turn it off.
111
+
109
112
The server never compares the two fields. This SDK's `Client` does: return `structured_content` that doesn't satisfy the `output_schema` you declared and `call_tool` raises a `RuntimeError` that starts with `Invalid structured content returned by tool search_books` and goes on to quote the `jsonschema` failure. Promising a schema is cheap; keeping it is on you. The whole ladder of return types and schemas is in **[Structured Output](../servers/structured-output.md)**.
Copy file name to clipboardExpand all lines: docs/client/index.md
+2-2Lines changed: 2 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -30,7 +30,7 @@ Everything else on this page is identical across all three. Headers, subprocesse
30
30
31
31
Four read-only properties, populated the moment you enter the block:
32
32
33
-
*`client.server_info`: the server's identity. `server_info.name` here is `"Bookshop"`, `server_info.version` is whatever the server reports.
33
+
*`client.server_info`: the server's identity, or `None` for a 2026-era server that does not report one (python-sdk servers do by default). `server_info.name` here is `"Bookshop"`, `server_info.version` is whatever the server reports.
34
34
*`client.server_capabilities`: what the server can do (`tools`, `resources`, `prompts`, `completions`, ...). A capability the server doesn't have is `None`.
35
35
*`client.protocol_version`: the protocol version the two sides agreed on. Here it is `"2026-07-28"`.
36
36
*`client.instructions`: the server's `instructions=` string, or `None` if it didn't set one.
@@ -202,7 +202,7 @@ There is one constructor flag built for that: `Client(mcp, raise_exceptions=True
202
202
## Recap
203
203
204
204
*`Client(x)` connects in-memory to a server object, over Streamable HTTP to a URL string, and over anything else via a transport.
205
-
*`async with` is the whole lifecycle. Inside it, `server_info`, `server_capabilities`, `protocol_version` and `instructions` are already populated.
205
+
*`async with` is the whole lifecycle. Inside it, `server_capabilities` and `protocol_version` are already populated; `server_info` and `instructions` are too when the server provides them.
206
206
*`list_tools()` gives you each tool's `name`, `title`, `description` and `input_schema`.
207
207
*`call_tool()` returns `content` for the model, `structured_content` for your code, and `is_error`. A raising tool is a result, not an exception.
208
208
*`content` is a union of block types; narrow with `isinstance` before reading.
Copy file name to clipboardExpand all lines: docs/client/session-groups.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -64,7 +64,7 @@ Run it again. `print(sorted(group.tools))` now shows both:
64
64
65
65
`connect_to_server` returns the `ClientSession` it opened. Keep it if you ever want that server gone: `await group.disconnect_from_server(session)` removes its tools, resources, and prompts from the group.
66
66
67
-
If you already hold a connected `ClientSession` (`Client.session` is one), hand it to `await group.connect_with_session(server_info, session)` instead of opening a new transport. It aggregates the same way. The group never closes a session it didn't open.
67
+
If you already hold a connected `ClientSession` (`Client.session` is one), hand it to `await group.connect_with_session(server_info, session)` instead of opening a new transport. It aggregates the same way. The group never closes a session it didn't open.`server_info` names the server for component prefixes; on a 2026-era connection `client.server_info` can be `None` (identity is optional), so pass your own `Implementation(name=..., version=...)` in that case.
`serverInfo` is no longer a field of the `server/discover` result. Servers now
476
+
report who they are by stamping `io.modelcontextprotocol/serverInfo` into the
477
+
`_meta` of every 2026-era result, and the request-side `clientInfo``_meta` key
478
+
became optional (`protocolVersion` and `clientCapabilities` remain required).
479
+
480
+
What changed in the SDK:
481
+
482
+
-`DiscoverResult` no longer has a `server_info` field. Read or write identity
483
+
through result `_meta`; the key is exported as `mcp_types.SERVER_INFO_META_KEY`.
484
+
- Servers stamp `serverInfo` into every 2026-era result's `_meta` by default,
485
+
built from the constructor identity fields (`name`, `version`, `title`, and
486
+
so on). Pass `include_server_info=False` to `Server(...)` or `MCPServer(...)`
487
+
to turn it off. A `serverInfo` value your handler already set in `_meta` is
488
+
never overwritten. Handshake-era responses are unchanged, and notifications
489
+
and error responses are never stamped.
490
+
-`client.server_info` and `session.server_info` are `Implementation | None`
491
+
on 2026-era connections: identity is optional on the wire, so a server that
492
+
does not stamp it reads as `None`. Handshake-era connections still always
493
+
have it. A pinned `Client(target, mode="2026-07-28")` without
494
+
`prior_discover=` now reports `None` instead of an `Implementation` with
495
+
empty strings. The stamp is display-only in the spec, so a malformed value
496
+
from a misbehaving server also reads as `None` rather than failing the call.
497
+
- Requests that omit `clientInfo` are now accepted. On the server,
498
+
`ctx.session.client_params` is `None` for such requests; capability checks
499
+
should use `ctx.session.client_capabilities` (or `ctx.client_capabilities`
500
+
in `MCPServer` handlers), which is recorded whether or not the client
501
+
identified itself.
502
+
471
503
### `SUPPORTED_PROTOCOL_VERSIONS` deprecated; `LATEST_PROTOCOL_VERSION` changed meaning
472
504
473
505
`SUPPORTED_PROTOCOL_VERSIONS` is deprecated — it's now the union of `HANDSHAKE_PROTOCOL_VERSIONS` (initialize-handshake versions) and `MODERN_PROTOCOL_VERSIONS` (per-request-envelope versions). If you were using it to mean "versions the initialize handshake accepts", switch to `HANDSHAKE_PROTOCOL_VERSIONS`. Named scalars derived from these tuples are now exported alongside them — `LATEST_HANDSHAKE_VERSION`, `LATEST_MODERN_VERSION`, `OLDEST_SUPPORTED_VERSION` — so prefer those over indexing the tuples directly. All of these live in `mcp_types.version` (previously `mcp.shared.version`): `from mcp_types.version import HANDSHAKE_PROTOCOL_VERSIONS`.
@@ -1498,7 +1530,7 @@ version = session.protocol_version
1498
1530
1499
1531
The raw handshake result is also retained: `session.initialize_result` is set after `initialize()` (≤2025-11-25 servers — including `stateless_http=True` servers, which still answer `initialize`); `session.discover_result` is set after `discover()` (2026-07-28+ servers). At most one is non-`None`.
1500
1532
1501
-
On the high-level `Client`, `client.server_capabilities`, `client.server_info`, and `client.protocol_version` are non-nullable inside the context manager. `client.instructions` remains `str | None` since the server may omit it. (The lowlevel `ClientSession` still lets you call methods before any handshake, as in v1; `Client` always connects on enter — by default it probes `server/discover` and falls back to the initialize handshake.)
1533
+
On the high-level `Client`, `client.server_capabilities`and `client.protocol_version` are non-nullable inside the context manager. `client.instructions` remains `str | None` since the server may omit it, and `client.server_info` is `Implementation | None`: on 2026-era connections identity is optional wire metadata, so a server that does not report it reads as `None` (see [Server identity moved to result `_meta`](#server-identity-moved-from-the-serverdiscover-result-body-to-result-_meta)). (The lowlevel `ClientSession` still lets you call methods before any handshake, as in v1; `Client` always connects on enter — by default it probes `server/discover` and falls back to the initialize handshake.)
1502
1534
1503
1535
### `cursor` parameter removed from `ClientSession` list methods
The client never asked the server who it is, so `server_info` is a blank. `client.server_capabilities`
74
+
The client never asked the server who it is, so `server_info` is `None`. `client.server_capabilities`
75
75
is the same story: every capability is `None`. Tool calls still work (the protocol needs none of it);
76
76
code that reads `server_capabilities` to decide what to offer does not.
77
77
@@ -87,7 +87,7 @@ ValueError: mode must be 'legacy', 'auto', or one of ['2026-07-28']; got '2025-0
87
87
88
88
The probe is cheap, but it is still a round trip you pay on every reconnect, and the answer almost never changes.
89
89
90
-
So keep it. After an `auto` connection, `client.session.discover_result` holds the exact `DiscoverResult` the server sent: its `supported_versions`, its `capabilities`, its `server_info`, its `instructions`. Hand it back as `prior_discover=` the next time:
90
+
So keep it. After an `auto` connection, `client.session.discover_result` holds the exact `DiscoverResult` the server sent: its `supported_versions`, its `capabilities`, its `instructions`, and the identity the server stamped into the result's `_meta`. Hand it back as `prior_discover=` the next time:
91
91
92
92
```python title="client.py" hl_lines="15 17"
93
93
--8<--"docs_src/protocol_versions/tutorial004.py"
@@ -112,7 +112,7 @@ The second connection made **zero** negotiation round trips and still knows exac
112
112
| --- | --- | --- |
113
113
|`Client(target)`| one `server/discover` probe; the `initialize` handshake if it fails | the newest version both sides speak, whichever era |
114
114
|`Client(target, mode="legacy")`| the `initialize` handshake | a handshake-era version; server-initiated requests work |
115
-
|`Client(target, mode="2026-07-28")`| none | that version, pinned, with a blank `server_info`|
115
+
|`Client(target, mode="2026-07-28")`| none | that version, pinned, with `server_info` as `None`|
116
116
|`Client(target, mode="2026-07-28", prior_discover=saved)`| none | that version, pinned, *and* the identity you saved last time |
117
117
118
118
## Recap
@@ -121,7 +121,7 @@ The second connection made **zero** negotiation round trips and still knows exac
121
121
*`mode="auto"` is the default: probe, fall back. Leave it alone unless one of the other three rows describes you.
122
122
*`client.protocol_version` is always the answer to "what did I get?".
123
123
*`mode="legacy"` forces the handshake. It is what you need for server-initiated requests: sampling, push elicitation, `message_handler`.
124
-
* A version pin (`mode="2026-07-28"`) sends no negotiation traffic at all, at the cost of a blank `server_info`.
124
+
* A version pin (`mode="2026-07-28"`) sends no negotiation traffic at all, at the cost of `client.server_info` being `None`.
125
125
*`prior_discover=` pays that cost back: save `client.session.discover_result`, reconnect with it, get both.
126
126
127
127
A modern connection has no push channel, so how does a 2026 server ask you a question mid-call? It returns it: **[Multi-round-trip requests](handlers/multi-round-trip.md)**.
Copy file name to clipboardExpand all lines: docs/servers/media.md
+2-1Lines changed: 2 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -97,9 +97,10 @@ The same `icons=[...]` keyword is accepted by `MCPServer(...)`, `@mcp.tool()`, `
97
97
98
98
### Where a client sees them
99
99
100
-
Icons travel with whatever they decorate. The server's arrive when the client connects, on `client.server_info`:
100
+
Icons travel with whatever they decorate. The server's arrive when the client connects, on `client.server_info` (optional on 2026-era connections, so narrow it first):
101
101
102
102
```python
103
+
assert client.server_info isnotNone# python-sdk servers identify themselves by default
Copy file name to clipboardExpand all lines: docs/whats-new.md
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -197,6 +197,7 @@ At 2026-07-28 the standalone HTTP GET stream and `resources/subscribe` are repla
197
197
198
198
### The rest, quickly
199
199
200
+
***Identity is optional, per-message metadata.** The request-side `clientInfo``_meta` key is optional (the required pair is `protocolVersion` + `clientCapabilities`), and `serverInfo` moved out of the `server/discover` result body: servers stamp it into every 2026-era result's `_meta` instead (since `2.0.0b3`; [spec #3002](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/3002)). The SDK stamps by default and `include_server_info=False` turns it off; `client.server_info` is `None` when a server chooses not to identify itself. The **[Migration Guide](migration.md#server-identity-moved-from-the-serverdiscover-result-body-to-result-_meta)** has the details.
200
201
***Requests are routable without parsing bodies.** Modern HTTP requests carry `Mcp-Method` (and, for the three tool-ish calls, `Mcp-Name`); a tool input-schema property annotated with `x-mcp-header` is mirrored into an `Mcp-Param-*` header and cross-checked by the server ([SEP-2243](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2243)). Gateways and rate limiters can route on headers alone; the **[Migration Guide](migration.md#servers-validate-mcp-param-headers-against-the-request-body-sep-2243)** has the rules.
201
202
***Results carry cache hints.** List and read results declare `ttlMs` and `cacheScope` ([SEP-2549](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2549)); you set them per method with `cache_hints=`, and `Client` honors them with a built-in response cache. A server that sends no hints (every pre-2026 server) sees identical, uncached traffic. **[Caching hints](client/caching.md)**.
202
203
***Extensions are first class.** Servers and clients declare optional capability bundles under reverse-DNS identifiers ([SEP-2133](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2133)); the built-in `Apps` extension (MCP Apps) is the reference. **[Extensions](advanced/extensions.md)** and **[MCP Apps](advanced/apps.md)**.
0 commit comments