Include response body in HttpRequestException for transport client errors (#1193)

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
This commit is contained in:
Copilot 2026-01-29 17:23:21 -05:00 • committed by GitHub
commit 7c85ac6706
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 149 additions and 6 deletions

View file

@ -499,7 +499,7 @@ internal sealed partial class ClientOAuthProvider : McpHttpClient
};
using var httpResponse = await _httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
httpResponse.EnsureSuccessStatusCode();
await httpResponse.EnsureSuccessStatusCodeWithResponseBodyAsync(cancellationToken).ConfigureAwait(false);
var tokens = await HandleSuccessfulTokenResponseAsync(httpResponse, cancellationToken).ConfigureAwait(false);
LogOAuthAuthorizationCompleted();
@ -544,7 +544,7 @@ internal sealed partial class ClientOAuthProvider : McpHttpClient
using var httpResponse = await _httpClient.GetAsync(metadataUrl, cancellationToken).ConfigureAwait(false);
if (requireSuccess)
{
httpResponse.EnsureSuccessStatusCode();
await httpResponse.EnsureSuccessStatusCodeWithResponseBodyAsync(cancellationToken).ConfigureAwait(false);
}
else if (!httpResponse.IsSuccessStatusCode)
{

View file

@ -88,16 +88,19 @@ internal sealed partial class SseClientSessionTransport : TransportBase
if (!response.IsSuccessStatusCode)
{
// Read the response body once to include in both logging and exception
string responseBody = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
if (_logger.IsEnabled(LogLevel.Trace))
{
LogRejectedPostSensitive(Name, messageId, await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false));
LogRejectedPostSensitive(Name, messageId, responseBody);
}
else
{
LogRejectedPost(Name, messageId);
}
response.EnsureSuccessStatusCode();
throw HttpResponseMessageExtensions.CreateHttpRequestException(response, responseBody);
}
}
@ -148,7 +151,7 @@ internal sealed partial class SseClientSessionTransport : TransportBase
using var response = await _httpClient.SendAsync(request, message: null, cancellationToken).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
await response.EnsureSuccessStatusCodeWithResponseBodyAsync(cancellationToken).ConfigureAwait(false);
using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);

View file

@ -61,7 +61,7 @@ internal sealed partial class StreamableHttpClientSessionTransport : TransportBa
{
// Immediately dispose the response. SendHttpRequestAsync only returns the response so the auto transport can look at it.
using var response = await SendHttpRequestAsync(message, cancellationToken).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
await response.EnsureSuccessStatusCodeWithResponseBodyAsync(cancellationToken).ConfigureAwait(false);
}
// This is used by the auto transport so it can fall back and try SSE given a non-200 response without catching an exception.

View file

@ -25,6 +25,7 @@
<Compile Include="..\Common\Throw.cs" Link="Throw.cs" />
<Compile Include="..\Common\Obsoletions.cs" Link="Obsoletions.cs" />
<Compile Include="..\Common\Experimentals.cs" Link="Experimentals.cs" />
<Compile Include="..\Common\HttpResponseMessageExtensions.cs" Link="HttpResponseMessageExtensions.cs" />
<Compile Include="..\Common\ServerSentEvents\**\*.cs" Link="ServerSentEvents\%(RecursiveDir)%(FileName)%(Extension)" />
</ItemGroup>