Skip to content

Validate MCP-Protocol-Version header in Streamable HTTP handler#1277

Merged
stephentoub merged 10 commits intomainfrom
copilot/validate-mcp-protocol-header
Feb 19, 2026
Merged

Validate MCP-Protocol-Version header in Streamable HTTP handler#1277
stephentoub merged 10 commits intomainfrom
copilot/validate-mcp-protocol-header

Conversation

Copy link
Contributor

Copilot AI commented Feb 15, 2026

Per the 2025-06-18 MCP spec: if the server receives a request with an invalid or unsupported MCP-Protocol-Version header, it MUST respond with 400 Bad Request. A missing header is not an error (backwards compatibility).

Changes

  • StreamableHttpHandler.cs — Added protocol version validation at the top of POST, GET, and DELETE handlers. Supported versions are defined as a private HashSet<string> directly in the handler, with a comment to keep in sync with the canonical list. Present but unsupported values → 400; absent header → allowed.
  • McpSessionHandler.cs — Added a sync comment on the internal SupportedProtocolVersions list referencing the copy in StreamableHttpHandler.
  • StreamableHttpServerConformanceTests.cs — Added tests for invalid versions (400), missing header (OK), and valid header (OK) on both POST and GET.
Original prompt

This section details on the original issue you should resolve

<issue_title>The server should validate the MCP-Protocol-Version header value</issue_title>
<issue_description>Describe the bug

The 2025-06-18 version of the MCP spec added this requirement on clients:

If using HTTP, the client MUST include the MCP-Protocol-Version: HTTP header on all subsequent requests to the MCP server

The MCP Server framework in the C# SDK does not verify that the client has provided the MCP-Protocol-Version header, and if it was specified, does not verify that it matches the negotiated protocol version for the session or even if it is a valid protocol version.

To Reproduce

Start a session with the new protocol version 2025-06-18. Then send a "tools/list" request that omits the MCP-Protocol-Version header, or with an MCP-Protocol-Version header with a bogus value.

Expected behavior

If the Streaming HTTP session is using protocol version 2025-06-18, a request without an MCP-Protocol-Version header should be rejected, or at the very least generate a warning log message.

If an MCP-Protocol-Version header is provided, the server should verify that a) the value is a valid / known protocol version, and b) it matches the protocol version negotiated for the session, and fail the request if either of these conditions are not satisfied.

Additional context

The ProtocolVersion project in the v0.3.0 branch of this repo, with a few small modifications, can be used to reproduce this problem.

</issue_description>

<agent_instructions>Please ensure this part is appropriately handled in the implementation and tests:

"If the server receives a request with an invalid or unsupported MCP-Protocol-Version, it MUST respond with 400 Bad Request.

So it's an error to have an invalid header but not an error to not have the header at all." </agent_instructions>

Comments on the Issue (you are @copilot in this section)

@stephentoub > If the Streaming HTTP session is using protocol version 2025-06-18, a request without an MCP-Protocol-Version header should be rejected

The spec states:

For backwards compatibility, if the server does not receive an MCP-Protocol-Version header, and has no other way to identify the version - for example, by relying on the protocol version negotiated during initialization - the server SHOULD assume protocol version 2025-06-18.

If the server receives a request with an invalid or unsupported MCP-Protocol-Version, it MUST respond with 400 Bad Request.

So it's an error to have an invalid header but not an error to not have the header at all.</comment_new>
<comment_new>@mikekistler
If the protocol version was negotiated during initialization to be 2025-06-18, wouldn't it be the case that the server has a way to identify the version for the session?</comment_new>
<comment_new>@stephentoub

If the protocol version was negotiated during initialization to be 2025-06-18, wouldn't it be the case that the server has a way to identify the version for the session?

Yes</comment_new>


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

If the server receives a request with an invalid or unsupported
MCP-Protocol-Version header, respond with 400 Bad Request per the
MCP spec. A missing header is allowed for backwards compatibility.

Exposes McpSession.SupportedProtocolVersions as a public static
property so the AspNetCore handler can validate against it.

Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
Copilot AI changed the title [WIP] Add validation for MCP-Protocol-Version header Validate MCP-Protocol-Version header in Streamable HTTP handler Feb 15, 2026
Copilot AI requested a review from stephentoub February 15, 2026 12:48
@stephentoub stephentoub marked this pull request as ready for review February 15, 2026 12:53
@stephentoub stephentoub requested a review from halter73 February 15, 2026 12:53
…ns class, use nullable out param

- Move SupportedProtocolVersions from McpSession to new ProtocolVersions
  static class in Protocol namespace (following RequestMethods/NotificationMethods pattern)
- Revert McpSession.cs to original state
- Change out string to out string? with null on success
- Add ProtocolVersionsTests for the new public property

Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
The SupportedProtocolVersions doesn't need to be exposed publicly.
Instead, use InternalsVisibleTo to let ModelContextProtocol.AspNetCore
access the internal McpSessionHandler.SupportedProtocolVersions directly.

- Delete ProtocolVersions.cs public static class
- Delete ProtocolVersionsTests.cs
- Add InternalsVisibleTo in Core csproj for AspNetCore
- Reference McpSessionHandler.SupportedProtocolVersions directly

Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
…idation

Define supported protocol versions as a private HashSet directly in
StreamableHttpHandler instead of referencing McpSessionHandler internals
or adding any cross-assembly visibility. No new public API surface needed.

Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
Copilot AI and others added 2 commits February 15, 2026 15:05
Add abstract SupportedProtocolVersions property to McpServer, implemented
by McpServerImpl (delegating to McpSessionHandler.SupportedProtocolVersions)
and DestinationBoundMcpServer. Protocol version validation in
StreamableHttpHandler now occurs after session resolution, querying the
server instance directly. Remove private HashSet from handler.

Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
Use TryGetValue to check version first, then TryRemove to actually
delete. This prevents losing the session if validation fails.

Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
…yList<string>

Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
…nc comments

Remove the abstract SupportedProtocolVersions property from McpServer,
McpServerImpl, and DestinationBoundMcpServer. Instead, keep a private
HashSet in StreamableHttpHandler with the supported protocol versions,
and add comments on both lists about keeping them in sync.

Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
auto-merge was automatically disabled February 18, 2026 10:59

Head branch was pushed to by a user without write access

@stephentoub stephentoub requested a review from halter73 February 18, 2026 11:41
@stephentoub stephentoub enabled auto-merge (squash) February 19, 2026 03:17
@stephentoub stephentoub merged commit 5da0fe5 into main Feb 19, 2026
9 of 10 checks passed
@stephentoub stephentoub deleted the copilot/validate-mcp-protocol-header branch February 19, 2026 03:18
@jeffhandley jeffhandley added the breaking-change This issue or PR introduces a breaking change label Feb 19, 2026
@jeffhandley jeffhandley added this to the Stable public API milestone Feb 19, 2026
@jeffhandley
Copy link
Collaborator

@copilot Capture brief notes on why this is a behavioral breaking change. Ideally, include a small sample client repro that would experience the change in behavior.

@jeffhandley
Copy link
Collaborator

Breaking Behavioral Change

  • Server now returns HTTP 400 for requests with an invalid/unsupported MCP-Protocol-Version header
  • Missing header is still allowed for backwards compatibility
  • Impact: Clients sending invalid protocol version headers will now receive 400 errors instead of the request being processed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking-change This issue or PR introduces a breaking change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The server should validate the MCP-Protocol-Version header value

4 participants