Skip to content
23 changes: 22 additions & 1 deletion src/ModelContextProtocol.Core/McpSessionHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -536,7 +536,28 @@ public async Task<JsonRpcResponse> SendRequestAsync(JsonRpcRequest request, Canc
LogSendingRequest(EndpointName, request.Method);
}

await SendToRelatedTransportAsync(request, cancellationToken).ConfigureAwait(false);
// Wait for either the transport send to complete or for the response to arrive via a
Comment thread
stephentoub marked this conversation as resolved.
Outdated
// concurrent channel (e.g. the background GET SSE stream in Streamable HTTP). Without
// this, the foreground transport send could block indefinitely waiting for a response
// that was already delivered via a different stream.
using var sendCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
Task sendTask = SendToRelatedTransportAsync(request, sendCts.Token);
if (sendTask != await Task.WhenAny(sendTask, tcs.Task).ConfigureAwait(false))
{
// The response arrived via a concurrent channel before the transport send completed.
// Cancel the still-running send and observe any exception to prevent unobserved task exceptions.
sendCts.Cancel();
_ = sendTask.ContinueWith(
static (t, _) => _ = t.Exception,
null,
CancellationToken.None,
TaskContinuationOptions.OnlyOnFaulted,
TaskScheduler.Default);
Comment thread
stephentoub marked this conversation as resolved.
Outdated
}
else
{
await sendTask.ConfigureAwait(false);
Comment thread
stephentoub marked this conversation as resolved.
Outdated
}

// Now that the request has been sent, register for cancellation. If we registered before,
// a cancellation request could arrive before the server knew about that request ID, in which
Expand Down
Loading