Skip to content

Commit bac97ef

Browse files
authored
Remove batching support from StreamableHttpServerTransport (#372)
1 parent b743889 commit bac97ef

File tree

4 files changed

+5
-77
lines changed

4 files changed

+5
-77
lines changed

src/ModelContextProtocol/Protocol/Transport/StreamClientSessionTransport.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ public override async Task SendMessageAsync(JsonRpcMessage message, Cancellation
7070
id = messageWithId.Id.ToString();
7171
}
7272

73-
var json = JsonSerializer.Serialize(message, McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonRpcMessage)));
73+
var json = JsonSerializer.Serialize(message, McpJsonUtilities.JsonContext.Default.JsonRpcMessage);
7474

7575
using var _ = await _sendLock.LockAsync(cancellationToken).ConfigureAwait(false);
7676
try

src/ModelContextProtocol/Protocol/Transport/StreamableHttpPostTransport.cs

Lines changed: 3 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ public async ValueTask<bool> RunAsync(CancellationToken cancellationToken)
3232
// The incomingChannel is null to handle the potential client GET request to handle unsolicited JsonRpcMessages.
3333
if (incomingChannel is not null)
3434
{
35-
await OnPostBodyReceivedAsync(httpBodies.Input, cancellationToken).ConfigureAwait(false);
35+
var message = await JsonSerializer.DeserializeAsync(httpBodies.Input.AsStream(),
36+
McpJsonUtilities.JsonContext.Default.JsonRpcMessage, cancellationToken).ConfigureAwait(false);
37+
await OnMessageReceivedAsync(message, cancellationToken).ConfigureAwait(false);
3638
}
3739

3840
if (_pendingRequests.Count == 0)
@@ -72,24 +74,6 @@ public async ValueTask DisposeAsync()
7274
}
7375
}
7476

75-
private async ValueTask OnPostBodyReceivedAsync(PipeReader streamableHttpRequestBody, CancellationToken cancellationToken)
76-
{
77-
if (!await IsJsonArrayAsync(streamableHttpRequestBody, cancellationToken).ConfigureAwait(false))
78-
{
79-
var message = await JsonSerializer.DeserializeAsync(streamableHttpRequestBody.AsStream(), McpJsonUtilities.JsonContext.Default.JsonRpcMessage, cancellationToken).ConfigureAwait(false);
80-
await OnMessageReceivedAsync(message, cancellationToken).ConfigureAwait(false);
81-
}
82-
else
83-
{
84-
// Batched JSON-RPC message
85-
var messages = JsonSerializer.DeserializeAsyncEnumerable(streamableHttpRequestBody.AsStream(), McpJsonUtilities.JsonContext.Default.JsonRpcMessage, cancellationToken).ConfigureAwait(false);
86-
await foreach (var message in messages.WithCancellation(cancellationToken))
87-
{
88-
await OnMessageReceivedAsync(message, cancellationToken).ConfigureAwait(false);
89-
}
90-
}
91-
}
92-
9377
private async ValueTask OnMessageReceivedAsync(JsonRpcMessage? message, CancellationToken cancellationToken)
9478
{
9579
if (message is null)
@@ -108,27 +92,4 @@ private async ValueTask OnMessageReceivedAsync(JsonRpcMessage? message, Cancella
10892
Throw.IfNull(incomingChannel);
10993
await incomingChannel.WriteAsync(message, cancellationToken).ConfigureAwait(false);
11094
}
111-
112-
private async ValueTask<bool> IsJsonArrayAsync(PipeReader requestBody, CancellationToken cancellationToken)
113-
{
114-
// REVIEW: Should we bother trimming whitespace before checking for '['?
115-
var firstCharacterResult = await requestBody.ReadAtLeastAsync(1, cancellationToken).ConfigureAwait(false);
116-
117-
try
118-
{
119-
if (firstCharacterResult.Buffer.Length == 0)
120-
{
121-
return false;
122-
}
123-
124-
Span<byte> firstCharBuffer = stackalloc byte[1];
125-
firstCharacterResult.Buffer.Slice(0, 1).CopyTo(firstCharBuffer);
126-
return firstCharBuffer[0] == (byte)'[';
127-
}
128-
finally
129-
{
130-
// Never consume data when checking for '['. System.Text.Json still needs to consume it.
131-
requestBody.AdvanceTo(firstCharacterResult.Buffer.Start);
132-
}
133-
}
13495
}

src/ModelContextProtocol/Shared/McpSession.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ public McpSession(
7575
StdioClientSessionTransport or StdioServerTransport => "stdio",
7676
StreamClientSessionTransport or StreamServerTransport => "stream",
7777
SseClientSessionTransport or SseResponseStreamTransport => "sse",
78-
StreamableHttpServerTransport or StreamableHttpPostTransport => "http",
78+
StreamableHttpClientSessionTransport or StreamableHttpServerTransport or StreamableHttpPostTransport => "http",
7979
_ => "unknownTransport"
8080
};
8181

tests/ModelContextProtocol.AspNetCore.Tests/StreamableHttpServerConformanceTests.cs

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -182,39 +182,6 @@ public async Task InitializeJsonRpcRequest_IsHandled_WithCompleteSseResponse()
182182
await CallInitializeAndValidateAsync();
183183
}
184184

185-
[Fact]
186-
public async Task BatchedJsonRpcRequests_IsHandled_WithCompleteSseResponse()
187-
{
188-
await StartAsync();
189-
190-
using var response = await HttpClient.PostAsync("", JsonContent($"[{InitializeRequest},{EchoRequest}]"), TestContext.Current.CancellationToken);
191-
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
192-
193-
var eventCount = 0;
194-
await foreach (var sseEvent in ReadSseAsync(response.Content))
195-
{
196-
var jsonRpcResponse = JsonSerializer.Deserialize(sseEvent, GetJsonTypeInfo<JsonRpcResponse>());
197-
Assert.NotNull(jsonRpcResponse);
198-
var responseId = Assert.IsType<long>(jsonRpcResponse.Id.Id);
199-
200-
switch (responseId)
201-
{
202-
case 1:
203-
AssertServerInfo(jsonRpcResponse);
204-
break;
205-
case 2:
206-
AssertEchoResponse(jsonRpcResponse);
207-
break;
208-
default:
209-
throw new Exception($"Unexpected response ID: {jsonRpcResponse.Id}");
210-
}
211-
212-
eventCount++;
213-
}
214-
215-
Assert.Equal(2, eventCount);
216-
}
217-
218185
[Fact]
219186
public async Task SingleJsonRpcRequest_ThatThrowsIsHandled_WithCompleteSseResponse()
220187
{

0 commit comments

Comments
 (0)