Follow-ups to #1610: de-draft 2026-07-28 terminology, renumber error codes, ProtocolVersion floor (#1671)
This commit is contained in:
parent
90c853103d
commit
d6b1615988
1 changed files with 785 additions and 898 deletions
|
|
@ -114,7 +114,7 @@ public class HttpHeaderConformanceTests(ITestOutputHelper outputHelper) : Kestre
|
|||
public async Task Server_AcceptsUnionIntegerCanonicalForm()
|
||||
{
|
||||
await StartAsync();
|
||||
await InitializeWithDraftVersionAsync();
|
||||
await InitializeWithJuly2026ProtocolVersionAsync();
|
||||
|
||||
// Union-typed (["integer","null"]) parameter: header carries canonical "42" while the body
|
||||
// carries the decimal form 42.0. The server must treat the union type as integer and match.
|
||||
|
|
@ -135,7 +135,7 @@ public class HttpHeaderConformanceTests(ITestOutputHelper outputHelper) : Kestre
|
|||
public async Task Server_RejectsUnionIntegerOutsideSafeRange()
|
||||
{
|
||||
await StartAsync();
|
||||
await InitializeWithDraftVersionAsync();
|
||||
await InitializeWithJuly2026ProtocolVersionAsync();
|
||||
|
||||
var callJson = CallTool("union_test", """{"priority":9007199254740993}""");
|
||||
|
||||
|
|
@ -154,7 +154,7 @@ public class HttpHeaderConformanceTests(ITestOutputHelper outputHelper) : Kestre
|
|||
public async Task Server_AcceptsExponentBodyMatchingDecimalHeader()
|
||||
{
|
||||
await StartAsync();
|
||||
await InitializeWithDraftVersionAsync();
|
||||
await InitializeWithJuly2026ProtocolVersionAsync();
|
||||
|
||||
// Body carries the integer in exponent form (1e2 = 100); header carries the decimal "100".
|
||||
var callJson = CallTool("header_test", """{"region":"test","priority":1e2,"verbose":false,"emptyVal":""}""");
|
||||
|
|
@ -177,7 +177,7 @@ public class HttpHeaderConformanceTests(ITestOutputHelper outputHelper) : Kestre
|
|||
public async Task Server_AcceptsWhitespaceAroundMcpNameHeaderValue()
|
||||
{
|
||||
await StartAsync();
|
||||
await InitializeWithDraftVersionAsync();
|
||||
await InitializeWithJuly2026ProtocolVersionAsync();
|
||||
|
||||
// Per SEP-2243: servers MUST accept extra whitespace around header values
|
||||
// and compare the trimmed value to the request body.
|
||||
|
|
@ -201,7 +201,7 @@ public class HttpHeaderConformanceTests(ITestOutputHelper outputHelper) : Kestre
|
|||
public async Task Server_AcceptsWhitespaceAroundMcpMethodHeaderValue()
|
||||
{
|
||||
await StartAsync();
|
||||
await InitializeWithDraftVersionAsync();
|
||||
await InitializeWithJuly2026ProtocolVersionAsync();
|
||||
|
||||
// Per SEP-2243: servers MUST accept extra whitespace around header values
|
||||
var callJson = CallTool("header_test", """{"region":"us-west1","priority":42,"verbose":false,"emptyVal":""}""");
|
||||
|
|
@ -224,7 +224,7 @@ public class HttpHeaderConformanceTests(ITestOutputHelper outputHelper) : Kestre
|
|||
public async Task Server_ValidatesEmptyStringHeaderValue_AgainstBodyValue()
|
||||
{
|
||||
await StartAsync();
|
||||
await InitializeWithDraftVersionAsync();
|
||||
await InitializeWithJuly2026ProtocolVersionAsync();
|
||||
|
||||
// Send a tools/call with an empty string param that has an x-mcp-header.
|
||||
// The header should be present with an empty value, matching the body's empty string.
|
||||
|
|
@ -248,7 +248,7 @@ public class HttpHeaderConformanceTests(ITestOutputHelper outputHelper) : Kestre
|
|||
public async Task Server_RejectsHeaderMismatch_WhenEmptyHeaderDoesNotMatchBody()
|
||||
{
|
||||
await StartAsync();
|
||||
await InitializeWithDraftVersionAsync();
|
||||
await InitializeWithJuly2026ProtocolVersionAsync();
|
||||
|
||||
// Send a tools/call where the body has a non-empty value but the header is empty
|
||||
var callJson = CallTool("header_test", """{"region":"us-west1","priority":42,"verbose":false,"emptyVal":"some-value"}""");
|
||||
|
|
@ -271,7 +271,7 @@ public class HttpHeaderConformanceTests(ITestOutputHelper outputHelper) : Kestre
|
|||
public async Task Server_AcceptsBase64EncodedHeaderWithControlChars()
|
||||
{
|
||||
await StartAsync();
|
||||
await InitializeWithDraftVersionAsync();
|
||||
await InitializeWithJuly2026ProtocolVersionAsync();
|
||||
|
||||
// Encode a value with a newline control character using Base64
|
||||
var valueWithNewline = "line1\nline2";
|
||||
|
|
@ -297,7 +297,7 @@ public class HttpHeaderConformanceTests(ITestOutputHelper outputHelper) : Kestre
|
|||
public async Task Server_AcceptsMaxSafeIntegerWithFullPrecision()
|
||||
{
|
||||
await StartAsync();
|
||||
await InitializeWithDraftVersionAsync();
|
||||
await InitializeWithJuly2026ProtocolVersionAsync();
|
||||
|
||||
// The maximum safe integer (2^53 - 1) must be accepted, and compared exactly without
|
||||
// losing precision through a double conversion.
|
||||
|
|
@ -326,7 +326,7 @@ public class HttpHeaderConformanceTests(ITestOutputHelper outputHelper) : Kestre
|
|||
public async Task Server_RejectsIntegerOutsideSafeRange(string outOfRangeValue)
|
||||
{
|
||||
await StartAsync();
|
||||
await InitializeWithDraftVersionAsync();
|
||||
await InitializeWithJuly2026ProtocolVersionAsync();
|
||||
|
||||
// Per SEP-2243 integer values MUST be within the JavaScript safe integer range.
|
||||
// A matching header and body that are both outside the range must still be rejected.
|
||||
|
|
@ -355,7 +355,7 @@ public class HttpHeaderConformanceTests(ITestOutputHelper outputHelper) : Kestre
|
|||
public async Task Server_AcceptsNumericEquivalentHeaderValues(string headerValue, string bodyValue)
|
||||
{
|
||||
await StartAsync();
|
||||
await InitializeWithDraftVersionAsync();
|
||||
await InitializeWithJuly2026ProtocolVersionAsync();
|
||||
|
||||
// bodyValue is inserted as a raw JSON numeric literal so that forms such as "42.0" and
|
||||
// "4.2e1" are preserved in the body exactly as another SDK might serialize them.
|
||||
|
|
@ -382,7 +382,7 @@ public class HttpHeaderConformanceTests(ITestOutputHelper outputHelper) : Kestre
|
|||
public async Task Server_RejectsNonIntegerValue_EvenWhenHeaderAndBodyMatch(string nonIntegerValue)
|
||||
{
|
||||
await StartAsync();
|
||||
await InitializeWithDraftVersionAsync();
|
||||
await InitializeWithJuly2026ProtocolVersionAsync();
|
||||
|
||||
// For an integer-typed parameter a non-whole numeric value is invalid and must be rejected
|
||||
// even when the header and body strings are byte-for-byte identical (it must not slip through
|
||||
|
|
@ -407,7 +407,7 @@ public class HttpHeaderConformanceTests(ITestOutputHelper outputHelper) : Kestre
|
|||
public async Task Server_RejectsNonNumericMismatch_ForIntegerParam()
|
||||
{
|
||||
await StartAsync();
|
||||
await InitializeWithDraftVersionAsync();
|
||||
await InitializeWithJuly2026ProtocolVersionAsync();
|
||||
|
||||
// Header says "99" but body says priority:42 — must reject even with numeric comparison
|
||||
var callJson = CallTool("header_test", """{"region":"test","priority":42,"verbose":false,"emptyVal":""}""");
|
||||
|
|
@ -427,17 +427,17 @@ public class HttpHeaderConformanceTests(ITestOutputHelper outputHelper) : Kestre
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Server_SkipsHeaderValidation_ForNonDraftVersion()
|
||||
public async Task Server_SkipsHeaderValidation_ForLegacyVersion()
|
||||
{
|
||||
await StartAsync();
|
||||
await InitializeWithNonDraftVersionAsync();
|
||||
await InitializeWithLegacyVersionAsync();
|
||||
|
||||
// With non-draft version, Mcp-Param-* headers are NOT validated even if mismatched
|
||||
// With the legacy version, Mcp-Param-* headers are NOT validated even if mismatched
|
||||
var callJson = CallTool("header_test", """{"region":"us-west1","priority":42,"verbose":false,"emptyVal":""}""");
|
||||
|
||||
using var request = new HttpRequestMessage(HttpMethod.Post, "");
|
||||
request.Content = new StringContent(callJson, Encoding.UTF8, "application/json");
|
||||
// Send the WRONG header value — this should still succeed because version is non-draft
|
||||
// Send the WRONG header value. This should still succeed because the version is legacy.
|
||||
request.Headers.Add("MCP-Protocol-Version", "2025-11-25");
|
||||
request.Headers.Add("Mcp-Method", "tools/call");
|
||||
request.Headers.Add("Mcp-Name", "header_test");
|
||||
|
|
@ -451,7 +451,7 @@ public class HttpHeaderConformanceTests(ITestOutputHelper outputHelper) : Kestre
|
|||
public async Task Server_RejectsInvalidUtf8EncodedHeaderValue()
|
||||
{
|
||||
await StartAsync();
|
||||
await InitializeWithDraftVersionAsync();
|
||||
await InitializeWithJuly2026ProtocolVersionAsync();
|
||||
|
||||
// Create a separate HttpClient that sends raw UTF-8 bytes in Mcp-* headers
|
||||
// instead of properly base64-encoding non-ASCII values.
|
||||
|
|
@ -570,32 +570,32 @@ public class HttpHeaderConformanceTests(ITestOutputHelper outputHelper) : Kestre
|
|||
|
||||
#region Helpers
|
||||
|
||||
private async Task InitializeWithDraftVersionAsync()
|
||||
private async Task InitializeWithJuly2026ProtocolVersionAsync()
|
||||
{
|
||||
HttpClient.DefaultRequestHeaders.Remove("mcp-session-id");
|
||||
|
||||
using var request = new HttpRequestMessage(HttpMethod.Post, "");
|
||||
request.Content = JsonContent(InitializeRequestDraft);
|
||||
request.Content = JsonContent(InitializeRequestJuly2026Protocol);
|
||||
request.Headers.Add("MCP-Protocol-Version", "2026-07-28");
|
||||
request.Headers.Add("Mcp-Method", "initialize");
|
||||
|
||||
using var response = await HttpClient.SendAsync(request, TestContext.Current.CancellationToken);
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
// Draft protocol revision (SEP-2567) is sessionless: the server does not return a
|
||||
// Starting with the 2026-07-28 protocol revision (SEP-2567), Streamable HTTP does not return a
|
||||
// mcp-session-id header. Subsequent requests carry MCP-Protocol-Version=2026-07-28
|
||||
// to route through the sessionless path.
|
||||
// so each one is handled independently.
|
||||
}
|
||||
|
||||
private async Task InitializeWithNonDraftVersionAsync()
|
||||
private async Task InitializeWithLegacyVersionAsync()
|
||||
{
|
||||
HttpClient.DefaultRequestHeaders.Remove("mcp-session-id");
|
||||
|
||||
using var response = await HttpClient.PostAsync("", JsonContent(InitializeRequest), TestContext.Current.CancellationToken);
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
// Server is stateless by default (SEP-2567), so initializing with the non-draft protocol does not return
|
||||
// a mcp-session-id header. Subsequent requests are independent, just like the draft path.
|
||||
// Server is stateless by default (SEP-2567), so initializing with the legacy protocol does not return
|
||||
// a mcp-session-id header. Subsequent requests are independent, just like requests on the 2026-07-28 revision.
|
||||
}
|
||||
|
||||
private static StringContent JsonContent(string json) => new(json, Encoding.UTF8, "application/json");
|
||||
|
|
@ -614,7 +614,7 @@ public class HttpHeaderConformanceTests(ITestOutputHelper outputHelper) : Kestre
|
|||
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"TestClient","version":"1.0"}}}
|
||||
""";
|
||||
|
||||
private static string InitializeRequestDraft => """
|
||||
private static string InitializeRequestJuly2026Protocol => """
|
||||
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2026-07-28","capabilities":{},"clientInfo":{"name":"TestClient","version":"1.0"}}}
|
||||
""";
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue