Describe the bug
When a client negotiates "protocolVersion": "2025-11-25" via the initialize handshake, the server (v2.0.0-preview.3) still includes resultType, ttlMs, and cacheScope in every JSON-RPC result object. These fields are exclusive to the 2026-07-28 draft spec and must be absent from responses on a 2025-11-25 session. Clients that strictly validate against the 2025-11-25 schema — such as MCP Inspector 1.0.0 — treat them as unrecognized keys and fail the handshake.
To Reproduce
- Start a C# MCP server built with v2.0.0-preview.3 (stateless or stateful).
- Send an initialize request with "protocolVersion": "2025-11-25".
- Observe that the InitializeResult contains "resultType": "complete" despite the server echoing back "protocolVersion": "2025-11-25".
- Send a tools/list request.
- Observe that the result contains "resultType": "complete", "ttlMs": 0, and "cacheScope": "private".
- Point MCP Inspector 1.0.0 at the server — it rejects the handshake on these unrecognized fields.
Expected behavior
For sessions where the negotiated protocol version is 2025-11-25, resultType, ttlMs, and cacheScope must be absent from all result objects. The serializer already uses WhenWritingNull — if these properties are left null they are correctly omitted. The server should only stamp them when the negotiated (or per-request) version is 2026-07-28 or later.
Logs
InitializeResult wire shape (negotiated version 2025-11-25):
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2025-11-25",
"resultType": "complete",
"serverInfo": { "..." : "..." },
"capabilities": {}
}
}
tools/list wire shape (same session):
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"tools": [],
"resultType": "complete",
"ttlMs": 0,
"cacheScope": "private"
}
}
Additional context
This appears to have been introduced by two PRs without protocol-version gating:
The fix should gate both stampings on IsJuly2026OrLaterProtocolRequest(jsonRpcRequest), which requires moving the logic from the inner McpRequestHandler closures into the outer _requestHandlers.Set(...) lambda where jsonRpcRequest is in scope. SetWithAlternateHandler and SetTaskAugmentedHandler have the same pattern and need the same fix. The hardcoded ResultType = "complete" on InitializeResult at line 613 also needs a version gate — negotiatedProtocolVersion is already in scope at that point.
Describe the bug
When a client negotiates "protocolVersion": "2025-11-25" via the initialize handshake, the server (v2.0.0-preview.3) still includes resultType, ttlMs, and cacheScope in every JSON-RPC result object. These fields are exclusive to the 2026-07-28 draft spec and must be absent from responses on a 2025-11-25 session. Clients that strictly validate against the 2025-11-25 schema — such as MCP Inspector 1.0.0 — treat them as unrecognized keys and fail the handshake.
To Reproduce
Expected behavior
For sessions where the negotiated protocol version is 2025-11-25, resultType, ttlMs, and cacheScope must be absent from all result objects. The serializer already uses WhenWritingNull — if these properties are left null they are correctly omitted. The server should only stamp them when the negotiated (or per-request) version is 2026-07-28 or later.
Logs
InitializeResult wire shape (negotiated version 2025-11-25):
{ "jsonrpc": "2.0", "id": 1, "result": { "protocolVersion": "2025-11-25", "resultType": "complete", "serverInfo": { "..." : "..." }, "capabilities": {} } }tools/list wire shape (same session):
{ "jsonrpc": "2.0", "id": 2, "result": { "tools": [], "resultType": "complete", "ttlMs": 0, "cacheScope": "private" } }Additional context
This appears to have been introduced by two PRs without protocol-version gating:
resultType: "complete" — added by Fix missing resultType on complete result responses #1684 (fixing Draft protocol: complete result responses omit required resultType #1676). The fix wraps every SetHandler result callback and stamps ResultType = "complete" unconditionally, and also hardcodes it directly on InitializeResult in ConfigureInitialize. The closures receive only McpRequest, not JsonRpcRequest, so the existing IsJuly2026OrLaterProtocolRequest(jsonRpcRequest) helper is never consulted. Relevant code: McpServerImpl.cs lines 613 and 1665–1678.
ttlMs / cacheScope defaults — added by the SEP-2549 implementation (Add SEP-2549 caching hints (ttlMs and cacheScope) to cacheable results #1623, related to Warn (don't throw) on the client when a draft-protocol server omits required SEP-2549 ttlMs/cacheScope #1650). Same pattern: unconditional defaulting to TimeSpan.Zero / CacheScope.Private in SetHandler for any ICacheableResult, with no version check. Relevant code: McpServerImpl.cs lines 1649–1663.
The fix should gate both stampings on IsJuly2026OrLaterProtocolRequest(jsonRpcRequest), which requires moving the logic from the inner McpRequestHandler closures into the outer _requestHandlers.Set(...) lambda where jsonRpcRequest is in scope. SetWithAlternateHandler and SetTaskAugmentedHandler have the same pattern and need the same fix. The hardcoded ResultType = "complete" on InitializeResult at line 613 also needs a version gate — negotiatedProtocolVersion is already in scope at that point.