feat(schema): add optional serverInfo response metadata and make clientInfo optional#3002
Conversation
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Workflows to automatically generate PRs for you. |
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Workflows to automatically generate PRs for you. |
|
Cross-posting from Discord my concern for visibility. I worry that MAY doesn't give MCP server implementers much incentive to actually report their server info. To recap the parallel drawn in the Discord thread: this field is effectively a user-agent equivalent, and the HTTP RFC uses considerably stronger language for User-Agent (SHOULD), meaning the User-Agent should be sent by default unless explicitly configured otherwise. I think this model could work as well, even though the preferred choice would be to make the serverInfo mandatory as is the clientInfo in the current draft spec. This would allow enforcing visibility much easier on the network side. @olaservo, @ostefano I would be curious to hear your thoughts on this since you participated on the Discord thread. Also worth noticing that this PR actually reduces visibility by making clientInfo optional as well, while it was previously required. |
|
There are 3 open decisions on this: 1. MAY vs SHOULD vs MUSTIn a recent CM meeting we discussed making this info available on every request but optional (similar to how user agents are handled in HTTP today). For context:
For comparison, RFC 7231 says: From the SDK perspective it looks like it's currently required in most if not all -- so users more or less have to specify it today. 2. Schema requiredness of _meta.io.modelcontextprotocol/serverInfoJSON Schema required is hard validation, so this is coupled to (1):
3. server/discover duplication
|
|
The discussion w/ @pja-ant and @nickcoai this morning our preference is the following:
|
I agree with this outcome. |
9af8954 to
9db5542
Compare
|
I am a little dubious, some people do put garbage in initialize, but GitHub get's tremendous value out of the client field, to the extent we literally implemented persistent sessions in Redis with zero SDK support explicitly to persist the initialize payload across associated requests. There are other things we can correlate (like the App identity of the client from auth perspective), but the app is often not the actual name of the client. Having read all the above I still don't know that making client info optional is directionally right. I understand the arguments, it's just a user provided string, it's not a strong or reliable identity, should never be used for auth etc. but if this change would lead to clients not providing the values, that would be damaging to the protocol from my perspective. I guess I am personally only happy to give up sessions with the guarantee that the the data that was required to create a session is provided statelessly. This sounds like a walkback on that, now I might lose something I actual find useful. 😅 |
If this goes ahead, I really hope this remains accurate and in practice it's only a technical change and the reality is that everyone continues to provide this (which I will concede is the case with user agents, with should language). |
…ntInfo optional Add a ResultMetaObject _meta type with an optional io.modelcontextprotocol/serverInfo field so servers can identify themselves on responses, restoring the server identity that was previously exchanged in the initialize handshake removed by SEP-2575. Make io.modelcontextprotocol/clientInfo optional on requests to match; protocolVersion and clientCapabilities remain required. Document in the schema and spec docs that both fields are self-reported and unverified, are intended for display, logging, and debugging, and SHOULD NOT be used to change client or server behavior nor be relied on for security decisions.
…cate discover identity Strengthen the optional identity fields from MAY to SHOULD: clients SHOULD send io.modelcontextprotocol/clientInfo on every request and servers SHOULD send io.modelcontextprotocol/serverInfo on every response, unless specifically configured not to do so (mirroring User-Agent handling in RFC 9110). Both fields stay optional in the schema so omitting them remains valid. Remove the serverInfo body field from DiscoverResult so the server identity lives in exactly one place, the result _meta, eliminating the risk of the two copies drifting.
9db5542 to
c734d64
Compare
|
Because this ended up being a change to the schema, it went up for a vote to the Core Maintainers today. It passed with 7/7 voters approving. |
|
I appreciate moving to SHOULD for the client info. |
This PR implements the latest changes introduced by modelcontextprotocol/modelcontextprotocol#3002
Part of dart-lang#162. Adds `handleRequestScopedMessage` and `MCPServerFactory`: each decoded JSON-RPC message is served on a fresh `MCPServer` instance (following dart-lang#524 (comment)), which gets `initialize(MCPServerInitialization)` with the per-request context and a completed `initialized` before the message is delivered. The shape follows the sketch in dart-lang#162 (comment): a server factory plus a handle function, no new transport abstraction. Each message round-trips through an in-memory string channel, so the exchange runs through the exact same `Peer` validation and dispatch path as a wire connection; a message-typed channel could drop the extra encode/decode later. Notifications emitted while handling are surfaced through a callback so a transport can decide how to deliver them, and server-to-client requests fail with an error inside their handler instead of deadlocking the exchange; real routing for those comes with the transport work. Also makes `MCPServerInitialization.clientInfo` (and `MCPServer.clientInfo`) nullable, since modelcontextprotocol/modelcontextprotocol#3002 made the per-request client info optional, and records the server implementation on responses under the reserved `io.modelcontextprotocol/serverInfo` result metadata key which the same spec change added. `RootsTrackingSupport` now tolerates the connection closing while `listRoots` is in flight, which the per-request teardown made easy to hit. Tests: - `dart_mcp`: analyze clean; format clean (dev SDK); 828/828 tests across chrome/vm and dart2wasm/dart2js/kernel/exe. 22 new request-scoped tests cover fresh-instance isolation, per-request capabilities, absent client info, server info stamping (including malformed metadata), notification passthrough, server-to-client request failure, malformed, legacy, and response-shaped message rejection, early shutdown, and initialization failure, plus a legacy handshake regression test for the nullable client info. - `dart_mcp_examples` (path-overrides the local `dart_mcp`): analyze clean. This PR does not add Streamable HTTP or any transport; wire formats, envelope validation, and `server/discover` land separately.
|
@kurtisvg thank you and the core team for your patience while discussing this change over the course of the past few weeks, and for driving this to a conclusion 👍 |
Part of #162. Adds `handleRequestScopedMessage` and `MCPServerFactory`: each decoded JSON-RPC message is served on a fresh `MCPServer` instance (following #524 (comment)), which gets `initialize(MCPServerInitialization)` with the per-request context and a completed `initialized` before the message is delivered. The shape follows the sketch in #162 (comment): a server factory plus a handle function, no new transport abstraction. Each message round-trips through an in-memory string channel, so the exchange runs through the exact same `Peer` validation and dispatch path as a wire connection; a message-typed channel could drop the extra encode/decode later. Notifications emitted while handling are surfaced through a callback so a transport can decide how to deliver them, and server-to-client requests fail with an error inside their handler instead of deadlocking the exchange; real routing for those comes with the transport work. Also makes `MCPServerInitialization.clientInfo` (and `MCPServer.clientInfo`) nullable, since modelcontextprotocol/modelcontextprotocol#3002 made the per-request client info optional, and records the server implementation on responses under the reserved `io.modelcontextprotocol/serverInfo` result metadata key which the same spec change added. `RootsTrackingSupport` now tolerates the connection closing while `listRoots` is in flight, which the per-request teardown made easy to hit. Tests: - `dart_mcp`: analyze clean; format clean (dev SDK); 828/828 tests across chrome/vm and dart2wasm/dart2js/kernel/exe. 22 new request-scoped tests cover fresh-instance isolation, per-request capabilities, absent client info, server info stamping (including malformed metadata), notification passthrough, server-to-client request failure, malformed, legacy, and response-shaped message rejection, early shutdown, and initialization failure, plus a legacy handshake regression test for the nullable client info. - `dart_mcp_examples` (path-overrides the local `dart_mcp`): analyze clean. This PR does not add Streamable HTTP or any transport; wire formats, envelope validation, and `server/discover` land separately.
Add a ResultMetaObject _meta type with an optional io.modelcontextprotocol/serverInfo field so servers can identify themselves on responses, restoring the server identity that was previously exchanged in the initialize handshake removed by SEP-2575.
Make io.modelcontextprotocol/clientInfo optional on requests to match; protocolVersion and clientCapabilities remain required. Document in the schema and spec docs that both fields are self-reported and unverified, are intended for display, logging, and debugging, and SHOULD NOT be used to change client or server behavior nor be relied on for security decisions.