Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 44 additions & 2 deletions src/Common/HttpResponseMessageExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,52 @@ public static HttpRequestException CreateHttpRequestException(HttpResponseMessag
? $"Response status code does not indicate success: {statusCodeInt} ({response.ReasonPhrase})."
: $"Response status code does not indicate success: {statusCodeInt} ({response.ReasonPhrase}). Response body: {responseBody}";

return HttpRequestExceptionExtensions.Create(message, innerException: null, response.StatusCode);
}
}

/// <summary>
/// Helpers for preserving HTTP status codes on <see cref="HttpRequestException"/> across all target frameworks.
/// </summary>
internal static class HttpRequestExceptionExtensions
{
internal const string StatusCodeDataKey = "ModelContextProtocol.HttpStatusCode";

/// <summary>
/// Creates an <see cref="HttpRequestException"/> and preserves its HTTP status code.
/// </summary>
public static HttpRequestException Create(string message, Exception? innerException, HttpStatusCode? statusCode)
{
#if NET
return new HttpRequestException(message, inner: null, response.StatusCode);
var exception = new HttpRequestException(message, innerException, statusCode);
#else
return new HttpRequestException(message);
var exception = new HttpRequestException(message, innerException);
#endif

if (statusCode is not null)
{
exception.Data[StatusCodeDataKey] = statusCode.Value;
}

return exception;
}

/// <summary>
/// Gets the preserved HTTP status code from an <see cref="HttpRequestException"/>.
/// </summary>
public static HttpStatusCode? GetStatusCode(this HttpRequestException exception)
{
#if NET
if (exception.StatusCode is { } statusCode)
{
return statusCode;
}
#endif

return exception.Data[StatusCodeDataKey] switch
{
HttpStatusCode storedStatusCode => storedStatusCode,
_ => null,
Comment thread
PranavSenthilnathan marked this conversation as resolved.
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -146,14 +146,10 @@ private async Task InitializeSseTransportAsync(JsonRpcMessage message, HttpReque
// keep HttpRequestException as the surfaced type so existing callers can still catch it and read StatusCode.
await sseTransport.DisposeAsync().ConfigureAwait(false);
LogSseFallbackFailedAfterStreamableHttp(_name, sseError);
#if NET
throw new HttpRequestException(streamableHttpError.Message, sseError, streamableHttpError.StatusCode);
#else
// net472 has no HttpRequestException overload that carries a status code, so this target
// preserves the status text in the message but not a programmatic StatusCode. Preserving the
// status code is intentionally net5+ only rather than an oversight.
throw new HttpRequestException(streamableHttpError.Message, sseError);
#endif
throw HttpRequestExceptionExtensions.Create(
streamableHttpError.Message,
sseError,
streamableHttpError.GetStatusCode());
}
catch
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ public async Task AutoDetectMode_WhenBothTransportsFail_PreservesStreamableHttpE
Assert.Contains("403", ex.Message);
Assert.IsType<HttpRequestException>(ex.InnerException);
Assert.Contains("405", ex.InnerException.Message);
Assert.Equal(HttpStatusCode.Forbidden, ex.Data["ModelContextProtocol.HttpStatusCode"]);
#if NET
Assert.Equal(HttpStatusCode.Forbidden, ex.StatusCode);
#endif
}

[Fact]
Expand Down Expand Up @@ -300,6 +304,7 @@ public async Task AutoDetectMode_SurfacesStreamableHttpError_WithSseAsInner_When
var httpEx = Assert.IsType<HttpRequestException>(ex);
Assert.Contains("415", httpEx.Message);
Assert.Contains(streamableHttpBody, httpEx.Message);
Assert.Equal(HttpStatusCode.UnsupportedMediaType, httpEx.Data["ModelContextProtocol.HttpStatusCode"]);
#if NET
Assert.Equal(HttpStatusCode.UnsupportedMediaType, httpEx.StatusCode);
#endif
Expand Down