Relax legacy request metadata validation - #1852
Conversation
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Legacy requests with a malformed _meta/io.modelcontextprotocol/protocolVersion still throw InvalidParams due to unconditional parsing, which contradicts the intended “ignore malformed future metadata on legacy” behavior.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review tier: Lite
Findings: 1
New issues introduced by this change (1)
| Severity | Finding |
|---|---|
src/ModelContextProtocol.Core/Server/McpServerImpl.cs — ReadRequestMetadata calls GetProtocolVersionMeta unconditionally, which throws InvalidParams if… |
What changed in this PR
This PR relaxes validation of legacy per-request _meta so legacy clients (notably ChatGPT negotiating 2025-11-25) aren’t rejected when they include auxiliary metadata introduced in later protocol eras, while keeping modern (2026-07-28+) protocol/version claims and required metadata strict. It also adjusts server/discover capability advertisement to avoid claiming deprecated logging on modern discover responses.
Changes:
- Reworks server-side
_metaparsing/projection and protocol boundary validation to treat known legacy-era metadata as advisory while enforcing strictness for modern envelopes and version switching. - Omits deprecated
loggingcapability from2026-07-28server/discoverresponses while retaining it for legacyinitialize. - Adds regression coverage across server/client behavior, including legacy session isolation vs per-request metadata and malformed metadata handling.
| File | Description |
|---|---|
| tests/ModelContextProtocol.Tests/Server/TaskProtocolGatingTests.cs | Updates legacy task-gating expectation to tolerate per-request capability metadata without enabling tasks. |
| tests/ModelContextProtocol.Tests/Server/NegotiatedProtocolVersionTests.cs | Expands coverage for advisory legacy version metadata, legacy session isolation, malformed metadata handling, and modern version switching. |
| tests/ModelContextProtocol.Tests/Server/McpServerTests.cs | Validates legacy transport header remains authoritative over advisory _meta protocol version; asserts legacy initialize includes logging capability. |
| tests/ModelContextProtocol.Tests/Client/McpClientMetaTests.cs | Adds coverage that legacy per-request client metadata does not override initialized session identity/capabilities. |
| tests/ModelContextProtocol.AspNetCore.Tests/RawHttpConformanceTests.cs | Adds conformance checks for discover capability suppression and legacy raw POST tolerance of auxiliary per-request metadata. |
| src/ModelContextProtocol.Core/Server/McpServerImpl.cs | Implements the relaxed legacy _meta semantics, strict modern validation, and logging capability suppression on discover. |
| src/ModelContextProtocol.Core/Protocol/JsonRpcMessageContext.cs | Updates documentation to clarify authoritative protocol version semantics and advisory legacy _meta behavior. |
| src/ModelContextProtocol.Core/McpSessionHandler.cs | Removes session-handler _meta projection now handled by server pipeline filter logic. |
Suppressed comments (1)
src/ModelContextProtocol.Core/Server/McpServerImpl.cs:318
- ProjectInitializeRequestMetadata also uses GetProtocolVersionMeta, so an
initializerequest carrying a malformed_meta/io.modelcontextprotocol/protocolVersion(or other invalid value type) will currently fail with InvalidParams. Since initialize is a legacy handshake path and_metais meant to be extensible there, consider treating a malformedprotocolVersionentry as advisory/absent rather than rejecting the request.
JsonObject? meta = GetRequestMeta(request);
string? metadataProtocolVersion = GetProtocolVersionMeta(meta, out bool hasProtocolVersionMeta);
string? transportProtocolVersion = request.Context?.ProtocolVersion;
ValidateProtocolVersionMatch(transportProtocolVersion, metadataProtocolVersion);
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| private void ReadRequestMetadata(JsonRpcRequest request) | ||
| { | ||
| JsonObject? meta = GetRequestMeta(request); | ||
| string? metadataProtocolVersion = GetProtocolVersionMeta(meta, out bool hasProtocolVersionMeta); | ||
| string? transportProtocolVersion = request.Context?.ProtocolVersion; | ||
|
|
||
| ValidateProtocolVersionMatch(transportProtocolVersion, metadataProtocolVersion); | ||
|
|
||
| bool establishedModernProtocol = McpProtocolVersions.RequiresPerRequestMetadata(_negotiatedProtocolVersion); | ||
| bool transportClaimsModernProtocol = | ||
| transportProtocolVersion is not null && | ||
| !McpProtocolVersions.SupportsInitializeHandshake(transportProtocolVersion); | ||
| bool metadataClaimsModernProtocol = | ||
| hasProtocolVersionMeta && | ||
| !McpProtocolVersions.SupportsInitializeHandshake(metadataProtocolVersion); | ||
| bool serverRequiresModernProtocol = | ||
| _initializeHandshakeProtocolVersions.Length == 0 && | ||
| _perRequestMetadataProtocolVersions.Length > 0; | ||
|
|

ChatGPT currently negotiates
2025-11-25but includes metadata introduced in2026-07-28. Legacy_metais extensible, so rejecting these requests is unnecessarily strict and prevents otherwise compatible clients from using the SDK.This change:
initializefor stateful legacy sessions while exposing well-formed auxiliary metadata to request filters and stateless handlers.loggingfrom2026-07-28server/discoverresponses while retaining it for legacyinitializeresponses.Added regression coverage for the ChatGPT request shape, advisory legacy version metadata, malformed metadata boundaries, initialized-session isolation, modern version switching, capability spoofing, and protocol-specific logging advertisement.
Fixes: #1783
Fixes: #1774