Default draft protocol support: sessionless + handshake-less (SEP-2575 + SEP-2567) (#1610)
This commit is contained in:
parent
3368bb7349
commit
6c323e783b
1 changed files with 5704 additions and 712 deletions
|
|
@ -760,4 +760,71 @@ public static class JsonRpcMessageConverterTests
|
|||
var error = (JsonRpcError)message;
|
||||
Assert.NotNull(error.Error.Data);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void Deserialize_ErrorWithNullId_IsValid()
|
||||
{
|
||||
// Per JSON-RPC 2.0 §5.1, when an error occurs before the request id can be determined
|
||||
// (parse error or invalid request), the server MUST respond with id=null. This shape is
|
||||
// produced by some peers (e.g. Python's simple-streamablehttp-stateless on a draft probe)
|
||||
// and must be accepted so the HTTP-fallback path can recognize the structured signal.
|
||||
string json = """{"jsonrpc":"2.0","id":null,"error":{"code":-32600,"message":"Bad Request"}}""";
|
||||
|
||||
var message = JsonSerializer.Deserialize<JsonRpcMessage>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
Assert.NotNull(message);
|
||||
var error = Assert.IsType<JsonRpcError>(message);
|
||||
Assert.Equal(default(RequestId), error.Id);
|
||||
Assert.Equal(-32600, error.Error.Code);
|
||||
Assert.Equal("Bad Request", error.Error.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void Deserialize_ErrorWithMissingId_IsValid()
|
||||
{
|
||||
// Some peers omit `id` entirely on pre-routing errors; treat as null per JSON-RPC 2.0 §5.1.
|
||||
string json = """{"jsonrpc":"2.0","error":{"code":-32700,"message":"Parse error"}}""";
|
||||
|
||||
var message = JsonSerializer.Deserialize<JsonRpcMessage>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
Assert.NotNull(message);
|
||||
var error = Assert.IsType<JsonRpcError>(message);
|
||||
Assert.Equal(default(RequestId), error.Id);
|
||||
Assert.Equal(-32700, error.Error.Code);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void Deserialize_RequestWithExplicitNullId_Throws()
|
||||
{
|
||||
// A message carrying a `method` and an explicit `id:null` is a malformed request. Per the MCP
|
||||
// base protocol the request id "MUST NOT be null", and a null id does NOT denote a notification
|
||||
// (JSON-RPC 2.0 notifications omit the id member entirely). The converter must reject it rather
|
||||
// than silently downgrading to a notification (which would swallow the id and skip the response).
|
||||
string json = """{"jsonrpc":"2.0","id":null,"method":"tools/list"}""";
|
||||
|
||||
Assert.Throws<JsonException>(() =>
|
||||
JsonSerializer.Deserialize<JsonRpcMessage>(json, McpJsonUtilities.DefaultOptions));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void Deserialize_RequestWithExplicitNullIdAndParams_Throws()
|
||||
{
|
||||
string json = """{"jsonrpc":"2.0","id":null,"method":"tools/call","params":{"name":"echo"}}""";
|
||||
|
||||
Assert.Throws<JsonException>(() =>
|
||||
JsonSerializer.Deserialize<JsonRpcMessage>(json, McpJsonUtilities.DefaultOptions));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void Deserialize_NotificationWithoutIdMember_IsNotConfusedWithNullIdRequest()
|
||||
{
|
||||
// Contrast with the explicit-null-id case above: omitting the id member entirely is a valid
|
||||
// notification and must continue to deserialize as one.
|
||||
string json = """{"jsonrpc":"2.0","method":"notifications/cancelled","params":{"requestId":1}}""";
|
||||
|
||||
var message = JsonSerializer.Deserialize<JsonRpcMessage>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
var notification = Assert.IsType<JsonRpcNotification>(message);
|
||||
Assert.Equal("notifications/cancelled", notification.Method);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue