Skip to content

Commit 618342f

Browse files
Copilotstephentoub
andauthored
Add logging for HTTP call completions with exceptions or non-success status codes
- StreamableHttpClientSessionTransport: Add logging for POST failures and non-success, GET SSE request failures and non-success, DELETE request failures and non-success - SseClientSessionTransport: Add logging for GET SSE non-success status codes, upgrade LogRejectedPost to Warning level, remove unused LogAcceptedPost - ClientOAuthProvider: Add logging for auth server metadata non-success, token refresh failure, token exchange failure, protected resource metadata non-success, and dynamic client registration failure Agent-Logs-Url: https://github.com/modelcontextprotocol/csharp-sdk/sessions/d9f395f7-797a-47f1-a07f-1788d91811c4 Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
1 parent 54087e7 commit 618342f

3 files changed

Lines changed: 86 additions & 8 deletions

File tree

src/ModelContextProtocol.Core/Authentication/ClientOAuthProvider.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,7 @@ private async Task<AuthorizationServerMetadata> GetAuthServerMetadataAsync(Uri a
341341
var response = await _httpClient.GetAsync(wellKnownEndpoint, cancellationToken).ConfigureAwait(false);
342342
if (!response.IsSuccessStatusCode)
343343
{
344+
LogAuthServerMetadataNonSuccessStatusCode(wellKnownEndpoint, (int)response.StatusCode);
344345
continue;
345346
}
346347

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

444445
if (!httpResponse.IsSuccessStatusCode)
445446
{
447+
LogOAuthTokenRefreshFailed((int)httpResponse.StatusCode);
446448
return null;
447449
}
448450

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

544546
using var httpResponse = await _httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
547+
if (!httpResponse.IsSuccessStatusCode)
548+
{
549+
LogOAuthTokenExchangeFailed((int)httpResponse.StatusCode);
550+
}
545551
await httpResponse.EnsureSuccessStatusCodeWithResponseBodyAsync(cancellationToken).ConfigureAwait(false);
546552

547553
var tokens = await HandleSuccessfulTokenResponseAsync(httpResponse, cancellationToken).ConfigureAwait(false);
@@ -619,10 +625,15 @@ private async Task<TokenContainer> HandleSuccessfulTokenResponseAsync(HttpRespon
619625
using var httpResponse = await _httpClient.GetAsync(metadataUrl, cancellationToken).ConfigureAwait(false);
620626
if (requireSuccess)
621627
{
628+
if (!httpResponse.IsSuccessStatusCode)
629+
{
630+
LogProtectedResourceMetadataNonSuccessStatusCode(metadataUrl, (int)httpResponse.StatusCode);
631+
}
622632
await httpResponse.EnsureSuccessStatusCodeWithResponseBodyAsync(cancellationToken).ConfigureAwait(false);
623633
}
624634
else if (!httpResponse.IsSuccessStatusCode)
625635
{
636+
LogProtectedResourceMetadataNonSuccessStatusCode(metadataUrl, (int)httpResponse.StatusCode);
626637
return null;
627638
}
628639

@@ -674,6 +685,7 @@ private async Task PerformDynamicClientRegistrationAsync(
674685

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

10041016
[LoggerMessage(Level = LogLevel.Debug, Message = "Missing resource_metadata parameter from WWW-Authenticate header. Falling back to {MetadataUri}")]
10051017
partial void LogMissingResourceMetadataParameter(Uri metadataUri);
1018+
1019+
[LoggerMessage(Level = LogLevel.Warning, Message = "Auth server metadata request to {Endpoint} received non-success status code {StatusCode}")]
1020+
partial void LogAuthServerMetadataNonSuccessStatusCode(Uri endpoint, int statusCode);
1021+
1022+
[LoggerMessage(Level = LogLevel.Warning, Message = "OAuth token refresh received non-success status code {StatusCode}")]
1023+
partial void LogOAuthTokenRefreshFailed(int statusCode);
1024+
1025+
[LoggerMessage(Level = LogLevel.Warning, Message = "OAuth token exchange received non-success status code {StatusCode}")]
1026+
partial void LogOAuthTokenExchangeFailed(int statusCode);
1027+
1028+
[LoggerMessage(Level = LogLevel.Warning, Message = "Protected resource metadata request to {MetadataUrl} received non-success status code {StatusCode}")]
1029+
partial void LogProtectedResourceMetadataNonSuccessStatusCode(Uri metadataUrl, int statusCode);
1030+
1031+
[LoggerMessage(Level = LogLevel.Warning, Message = "Dynamic client registration received non-success status code {StatusCode}")]
1032+
partial void LogDynamicClientRegistrationFailed(int statusCode);
10061033
}

src/ModelContextProtocol.Core/Client/SseClientSessionTransport.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ private async Task ReceiveMessagesAsync(CancellationToken cancellationToken)
160160
if (!response.IsSuccessStatusCode)
161161
{
162162
failureStatusCode = response.StatusCode;
163+
LogHttpGetSseNonSuccessStatusCode(Name, (int)response.StatusCode);
163164
}
164165

165166
await response.EnsureSuccessStatusCodeWithResponseBodyAsync(cancellationToken).ConfigureAwait(false);
@@ -257,12 +258,12 @@ private void HandleEndpointEvent(string data)
257258
_connectionEstablished.TrySetResult(true);
258259
}
259260

260-
[LoggerMessage(Level = LogLevel.Information, Message = "{EndpointName} accepted SSE transport POST for message ID '{MessageId}'.")]
261-
private partial void LogAcceptedPost(string endpointName, string messageId);
262-
263-
[LoggerMessage(Level = LogLevel.Information, Message = "{EndpointName} rejected SSE transport POST for message ID '{MessageId}'.")]
261+
[LoggerMessage(Level = LogLevel.Warning, Message = "{EndpointName} rejected SSE transport POST for message ID '{MessageId}'.")]
264262
private partial void LogRejectedPost(string endpointName, string messageId);
265263

266264
[LoggerMessage(Level = LogLevel.Trace, Message = "{EndpointName} rejected SSE transport POST for message ID '{MessageId}'. Server response: '{responseContent}'.")]
267265
private partial void LogRejectedPostSensitive(string endpointName, string messageId, string responseContent);
266+
267+
[LoggerMessage(Level = LogLevel.Warning, Message = "{EndpointName} HTTP GET SSE received non-success status code {StatusCode}.")]
268+
private partial void LogHttpGetSseNonSuccessStatusCode(string endpointName, int statusCode);
268269
}

src/ModelContextProtocol.Core/Client/StreamableHttpClientSessionTransport.cs

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,22 @@ internal async Task<HttpResponseMessage> SendHttpRequestAsync(JsonRpcMessage mes
9191

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

94-
var response = await _httpClient.SendAsync(httpRequestMessage, message, cancellationToken).ConfigureAwait(false);
94+
HttpResponseMessage response;
95+
try
96+
{
97+
response = await _httpClient.SendAsync(httpRequestMessage, message, cancellationToken).ConfigureAwait(false);
98+
}
99+
catch (Exception ex) when (ex is not OperationCanceledException)
100+
{
101+
LogHttpPostRequestFailed(Name, ex);
102+
throw;
103+
}
95104

96105
// We'll let the caller decide whether to throw or fall back given an unsuccessful response.
97106
if (!response.IsSuccessStatusCode)
98107
{
108+
LogHttpPostNonSuccessStatusCode(Name, (int)response.StatusCode);
109+
99110
// Per the MCP spec, a 404 response to a request containing an Mcp-Session-Id
100111
// indicates the session has ended. Signal completion so McpClient.Completion resolves.
101112
if (response.StatusCode == HttpStatusCode.NotFound && SessionId is not null)
@@ -273,8 +284,9 @@ await SendGetSseRequestWithRetriesAsync(
273284
{
274285
response = await _httpClient.SendAsync(request, message: null, cancellationToken).ConfigureAwait(false);
275286
}
276-
catch (HttpRequestException)
287+
catch (HttpRequestException ex)
277288
{
289+
LogHttpGetSseRequestFailed(Name, ex);
278290
attempt++;
279291
continue;
280292
}
@@ -284,12 +296,15 @@ await SendGetSseRequestWithRetriesAsync(
284296
if (response.StatusCode >= HttpStatusCode.InternalServerError)
285297
{
286298
// Server error; retry.
299+
LogHttpGetSseNonSuccessStatusCode(Name, (int)response.StatusCode);
287300
attempt++;
288301
continue;
289302
}
290303

291304
if (!response.IsSuccessStatusCode)
292305
{
306+
LogHttpGetSseNonSuccessStatusCode(Name, (int)response.StatusCode);
307+
293308
// Per the MCP spec, a 404 response to a request containing an Mcp-Session-Id
294309
// indicates the session has ended. Signal completion so McpClient.Completion resolves.
295310
if (response.StatusCode == HttpStatusCode.NotFound && SessionId is not null)
@@ -406,8 +421,25 @@ private async Task SendDeleteRequest()
406421
using var deleteRequest = new HttpRequestMessage(HttpMethod.Delete, _options.Endpoint);
407422
CopyAdditionalHeaders(deleteRequest.Headers, _options.AdditionalHeaders, SessionId, _negotiatedProtocolVersion);
408423

409-
// Do not validate we get a successful status code, because server support for the DELETE request is optional
410-
(await _httpClient.SendAsync(deleteRequest, message: null, CancellationToken.None).ConfigureAwait(false)).Dispose();
424+
HttpResponseMessage response;
425+
try
426+
{
427+
response = await _httpClient.SendAsync(deleteRequest, message: null, CancellationToken.None).ConfigureAwait(false);
428+
}
429+
catch (Exception ex) when (ex is not OperationCanceledException)
430+
{
431+
LogHttpDeleteRequestFailed(Name, ex);
432+
return;
433+
}
434+
435+
using (response)
436+
{
437+
// Server support for the DELETE request is optional, so a 405 Method Not Allowed is expected.
438+
if (!response.IsSuccessStatusCode)
439+
{
440+
LogHttpDeleteNonSuccessStatusCode(Name, (int)response.StatusCode);
441+
}
442+
}
411443
}
412444

413445
private void LogJsonException(JsonException ex, string data)
@@ -505,4 +537,22 @@ private void SetSessionExpired()
505537

506538
SetDisconnected(_disconnectError);
507539
}
540+
541+
[LoggerMessage(Level = LogLevel.Warning, Message = "{EndpointName} HTTP POST request failed.")]
542+
private partial void LogHttpPostRequestFailed(string endpointName, Exception exception);
543+
544+
[LoggerMessage(Level = LogLevel.Warning, Message = "{EndpointName} HTTP POST received non-success status code {StatusCode}.")]
545+
private partial void LogHttpPostNonSuccessStatusCode(string endpointName, int statusCode);
546+
547+
[LoggerMessage(Level = LogLevel.Warning, Message = "{EndpointName} HTTP GET SSE request failed.")]
548+
private partial void LogHttpGetSseRequestFailed(string endpointName, Exception exception);
549+
550+
[LoggerMessage(Level = LogLevel.Warning, Message = "{EndpointName} HTTP GET SSE received non-success status code {StatusCode}.")]
551+
private partial void LogHttpGetSseNonSuccessStatusCode(string endpointName, int statusCode);
552+
553+
[LoggerMessage(Level = LogLevel.Warning, Message = "{EndpointName} HTTP DELETE request failed.")]
554+
private partial void LogHttpDeleteRequestFailed(string endpointName, Exception exception);
555+
556+
[LoggerMessage(Level = LogLevel.Information, Message = "{EndpointName} HTTP DELETE received non-success status code {StatusCode}.")]
557+
private partial void LogHttpDeleteNonSuccessStatusCode(string endpointName, int statusCode);
508558
}

0 commit comments

Comments
 (0)