Skip to content

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

Open
Copilot wants to merge 10 commits intomainfrom
copilot/validate-mcp-protocol-header
Open

Validate MCP-Protocol-Version header in Streamable HTTP handler#1277
Copilot wants to merge 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>
if (sessionManager.TryGetValue(sessionId, out var session))
{
await session.DisposeAsync();
if (!ValidateProtocolVersionHeader(context, session.Server, out var errorMessage))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't love the layering here. We shouldn't have to pull the McpServer instance out of the sessionManager to determine whether the protocol version is valid. This might make sense if the StreamableHttpHandler made the McpServer replaceable with a custom implementation, but that's not the case. Furthermore, is possible that we might have to put HTTP-specific protocol version dependent logic in StreamableHttpHandler that takes place before retrieving the session.

Why not just copy the internal static list from McpSessionHandler.SupportedProtocolVersions into StreamableHttpHandler with a note to keep them in sync? If we don't like that, we can make SupportedProtocolVersions public.

Copy link
Contributor

@stephentoub stephentoub Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because it's up to the (abstract) McpSession implementation what versions are supported?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But at least for now, StreamableHttpHandler doesn't support replacing the McpServer implementation, so it seems wrong to be making public API changes specifically so the StreamableHttpHandler can support alternative implementations right now.

var server = McpServer.Create(transport, mcpServerOptions, loggerFactory, mcpServerServices);

Also, it seems wrong to me that the StreamableHttpHandler should have to create an McpServer instance to even start validating the MCP-Protocol-Version. So, in the future if/when we add support for extending StreamableHttpHandler with custom McpServer implementations, we'd probably want to offer a different public API for declaring with protocol versions you support anyway.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't agree. But we can do the less maintainable thing for now and just maintain the supported list in two places.

@copilot please remove the new API and instead copy the supported protocol list into the other assembly as private/internal. Ensure there's a comment on each list about keeping them in sync.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverted the SupportedProtocolVersions public API from McpServer, McpServerImpl, and DestinationBoundMcpServer. The supported protocol versions are now a private HashSet<string> directly in StreamableHttpHandler, with comments on both lists (in StreamableHttpHandler and McpSessionHandler) about keeping them in sync. Validation is back to before session resolution. a340156

…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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

3 participants

Comments