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
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,7 @@ private async Task<AuthorizationServerMetadata> GetAuthServerMetadataAsync(Uri a
var response = await _httpClient.GetAsync(wellKnownEndpoint, cancellationToken).ConfigureAwait(false);
if (!response.IsSuccessStatusCode)
{
LogAuthServerMetadataNonSuccessStatusCode(wellKnownEndpoint, (int)response.StatusCode);
continue;
}

Expand Down Expand Up @@ -443,6 +444,7 @@ private static IEnumerable<Uri> GetWellKnownAuthorizationServerMetadataUris(Uri

if (!httpResponse.IsSuccessStatusCode)
{
LogOAuthTokenRefreshFailed((int)httpResponse.StatusCode);
return null;
}

Expand Down Expand Up @@ -542,6 +544,10 @@ private async Task<string> ExchangeCodeForTokenAsync(
using var request = CreateTokenRequest(authServerMetadata.TokenEndpoint, formFields);

using var httpResponse = await _httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
if (!httpResponse.IsSuccessStatusCode)
{
LogOAuthTokenExchangeFailed((int)httpResponse.StatusCode);
}
await httpResponse.EnsureSuccessStatusCodeWithResponseBodyAsync(cancellationToken).ConfigureAwait(false);

var tokens = await HandleSuccessfulTokenResponseAsync(httpResponse, cancellationToken).ConfigureAwait(false);
Expand Down Expand Up @@ -619,10 +625,15 @@ private async Task<TokenContainer> HandleSuccessfulTokenResponseAsync(HttpRespon
using var httpResponse = await _httpClient.GetAsync(metadataUrl, cancellationToken).ConfigureAwait(false);
if (requireSuccess)
{
if (!httpResponse.IsSuccessStatusCode)
{
LogProtectedResourceMetadataNonSuccessStatusCode(metadataUrl, (int)httpResponse.StatusCode);
}
await httpResponse.EnsureSuccessStatusCodeWithResponseBodyAsync(cancellationToken).ConfigureAwait(false);
}
else if (!httpResponse.IsSuccessStatusCode)
{
LogProtectedResourceMetadataNonSuccessStatusCode(metadataUrl, (int)httpResponse.StatusCode);
return null;
}

Expand Down Expand Up @@ -674,6 +685,7 @@ private async Task PerformDynamicClientRegistrationAsync(

if (!httpResponse.IsSuccessStatusCode)
{
LogDynamicClientRegistrationFailed((int)httpResponse.StatusCode);
var errorContent = await httpResponse.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
ThrowFailedToHandleUnauthorizedResponse($"Dynamic client registration failed with status {httpResponse.StatusCode}: {errorContent}");
}
Expand Down Expand Up @@ -1003,4 +1015,19 @@ private static void ThrowFailedToHandleUnauthorizedResponse(string message) =>

[LoggerMessage(Level = LogLevel.Debug, Message = "Missing resource_metadata parameter from WWW-Authenticate header. Falling back to {MetadataUri}")]
partial void LogMissingResourceMetadataParameter(Uri metadataUri);

[LoggerMessage(Level = LogLevel.Warning, Message = "Auth server metadata request to {Endpoint} received non-success status code {StatusCode}")]
partial void LogAuthServerMetadataNonSuccessStatusCode(Uri endpoint, int statusCode);

[LoggerMessage(Level = LogLevel.Warning, Message = "OAuth token refresh received non-success status code {StatusCode}")]
partial void LogOAuthTokenRefreshFailed(int statusCode);

[LoggerMessage(Level = LogLevel.Warning, Message = "OAuth token exchange received non-success status code {StatusCode}")]
partial void LogOAuthTokenExchangeFailed(int statusCode);

[LoggerMessage(Level = LogLevel.Warning, Message = "Protected resource metadata request to {MetadataUrl} received non-success status code {StatusCode}")]
partial void LogProtectedResourceMetadataNonSuccessStatusCode(Uri metadataUrl, int statusCode);

[LoggerMessage(Level = LogLevel.Warning, Message = "Dynamic client registration received non-success status code {StatusCode}")]
partial void LogDynamicClientRegistrationFailed(int statusCode);
}
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ private async Task ReceiveMessagesAsync(CancellationToken cancellationToken)
if (!response.IsSuccessStatusCode)
{
failureStatusCode = response.StatusCode;
LogHttpGetSseNonSuccessStatusCode(Name, (int)response.StatusCode);
}

await response.EnsureSuccessStatusCodeWithResponseBodyAsync(cancellationToken).ConfigureAwait(false);
Expand Down Expand Up @@ -257,12 +258,12 @@ private void HandleEndpointEvent(string data)
_connectionEstablished.TrySetResult(true);
}

[LoggerMessage(Level = LogLevel.Information, Message = "{EndpointName} accepted SSE transport POST for message ID '{MessageId}'.")]
private partial void LogAcceptedPost(string endpointName, string messageId);

[LoggerMessage(Level = LogLevel.Information, Message = "{EndpointName} rejected SSE transport POST for message ID '{MessageId}'.")]
[LoggerMessage(Level = LogLevel.Warning, Message = "{EndpointName} rejected SSE transport POST for message ID '{MessageId}'.")]
private partial void LogRejectedPost(string endpointName, string messageId);

[LoggerMessage(Level = LogLevel.Trace, Message = "{EndpointName} rejected SSE transport POST for message ID '{MessageId}'. Server response: '{responseContent}'.")]
private partial void LogRejectedPostSensitive(string endpointName, string messageId, string responseContent);

[LoggerMessage(Level = LogLevel.Warning, Message = "{EndpointName} HTTP GET SSE received non-success status code {StatusCode}.")]
private partial void LogHttpGetSseNonSuccessStatusCode(string endpointName, int statusCode);
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,22 @@ internal async Task<HttpResponseMessage> SendHttpRequestAsync(JsonRpcMessage mes

CopyAdditionalHeaders(httpRequestMessage.Headers, _options.AdditionalHeaders, SessionId, _negotiatedProtocolVersion);

var response = await _httpClient.SendAsync(httpRequestMessage, message, cancellationToken).ConfigureAwait(false);
HttpResponseMessage response;
try
{
response = await _httpClient.SendAsync(httpRequestMessage, message, cancellationToken).ConfigureAwait(false);
}
catch (Exception ex) when (ex is not OperationCanceledException)
{
LogHttpPostRequestFailed(Name, ex);
throw;
}

// We'll let the caller decide whether to throw or fall back given an unsuccessful response.
if (!response.IsSuccessStatusCode)
{
LogHttpPostNonSuccessStatusCode(Name, (int)response.StatusCode);

// Per the MCP spec, a 404 response to a request containing an Mcp-Session-Id
// indicates the session has ended. Signal completion so McpClient.Completion resolves.
if (response.StatusCode == HttpStatusCode.NotFound && SessionId is not null)
Expand Down Expand Up @@ -273,8 +284,9 @@ await SendGetSseRequestWithRetriesAsync(
{
response = await _httpClient.SendAsync(request, message: null, cancellationToken).ConfigureAwait(false);
}
catch (HttpRequestException)
catch (HttpRequestException ex)
{
LogHttpGetSseRequestFailed(Name, ex);
attempt++;
continue;
}
Expand All @@ -284,12 +296,15 @@ await SendGetSseRequestWithRetriesAsync(
if (response.StatusCode >= HttpStatusCode.InternalServerError)
{
// Server error; retry.
LogHttpGetSseNonSuccessStatusCode(Name, (int)response.StatusCode);
attempt++;
continue;
}

if (!response.IsSuccessStatusCode)
{
LogHttpGetSseNonSuccessStatusCode(Name, (int)response.StatusCode);

// Per the MCP spec, a 404 response to a request containing an Mcp-Session-Id
// indicates the session has ended. Signal completion so McpClient.Completion resolves.
if (response.StatusCode == HttpStatusCode.NotFound && SessionId is not null)
Expand Down Expand Up @@ -406,8 +421,25 @@ private async Task SendDeleteRequest()
using var deleteRequest = new HttpRequestMessage(HttpMethod.Delete, _options.Endpoint);
CopyAdditionalHeaders(deleteRequest.Headers, _options.AdditionalHeaders, SessionId, _negotiatedProtocolVersion);

// Do not validate we get a successful status code, because server support for the DELETE request is optional
(await _httpClient.SendAsync(deleteRequest, message: null, CancellationToken.None).ConfigureAwait(false)).Dispose();
HttpResponseMessage response;
try
{
response = await _httpClient.SendAsync(deleteRequest, message: null, CancellationToken.None).ConfigureAwait(false);
}
catch (Exception ex) when (ex is not OperationCanceledException)
{
LogHttpDeleteRequestFailed(Name, ex);
return;
}

using (response)
{
// Server support for the DELETE request is optional, so a 405 Method Not Allowed is expected.
if (!response.IsSuccessStatusCode)
{
LogHttpDeleteNonSuccessStatusCode(Name, (int)response.StatusCode);
}
}
}

private void LogJsonException(JsonException ex, string data)
Expand Down Expand Up @@ -505,4 +537,22 @@ private void SetSessionExpired()

SetDisconnected(_disconnectError);
}

[LoggerMessage(Level = LogLevel.Warning, Message = "{EndpointName} HTTP POST request failed.")]
private partial void LogHttpPostRequestFailed(string endpointName, Exception exception);

[LoggerMessage(Level = LogLevel.Warning, Message = "{EndpointName} HTTP POST received non-success status code {StatusCode}.")]
private partial void LogHttpPostNonSuccessStatusCode(string endpointName, int statusCode);

[LoggerMessage(Level = LogLevel.Warning, Message = "{EndpointName} HTTP GET SSE request failed.")]
private partial void LogHttpGetSseRequestFailed(string endpointName, Exception exception);

[LoggerMessage(Level = LogLevel.Warning, Message = "{EndpointName} HTTP GET SSE received non-success status code {StatusCode}.")]
private partial void LogHttpGetSseNonSuccessStatusCode(string endpointName, int statusCode);

[LoggerMessage(Level = LogLevel.Warning, Message = "{EndpointName} HTTP DELETE request failed.")]
private partial void LogHttpDeleteRequestFailed(string endpointName, Exception exception);

[LoggerMessage(Level = LogLevel.Information, Message = "{EndpointName} HTTP DELETE received non-success status code {StatusCode}.")]
private partial void LogHttpDeleteNonSuccessStatusCode(string endpointName, int statusCode);
}
Loading