Fall back to well-known URL in client if 401 response is missing resource_metadata parameter (#1054)

This commit is contained in:
Stephen Halter 2025-12-05 14:12:51 -08:00 • committed by GitHub
commit 970207989e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 497 additions and 134 deletions

View file

@ -3,7 +3,6 @@ using Microsoft.Extensions.Logging;
using ModelContextProtocol.Client;
using ModelContextProtocol.Protocol;
using ModelContextProtocol.Server;
using ModelContextProtocol.Tests.Utils;
namespace ModelContextProtocol.Tests.Configuration;
@ -14,8 +13,6 @@ public class McpServerBuilderExtensionsFilterTests : ClientServerTestBase
{
}
private MockLoggerProvider _mockLoggerProvider = new();
private static ILogger GetLogger(IServiceProvider? services, string categoryName)
{
var loggerFactory = services?.GetRequiredService<ILoggerFactory>() ?? throw new InvalidOperationException("LoggerFactory not available");
@ -133,8 +130,6 @@ public class McpServerBuilderExtensionsFilterTests : ClientServerTestBase
{
Completion = new() { Values = ["test"] }
});
services.AddSingleton<ILoggerProvider>(_mockLoggerProvider);
}
[Fact]
@ -144,7 +139,7 @@ public class McpServerBuilderExtensionsFilterTests : ClientServerTestBase
await client.ListResourceTemplatesAsync(cancellationToken: TestContext.Current.CancellationToken);
var logMessage = Assert.Single(_mockLoggerProvider.LogMessages, m => m.Message == "ListResourceTemplatesFilter executed");
var logMessage = Assert.Single(MockLoggerProvider.LogMessages, m => m.Message == "ListResourceTemplatesFilter executed");
Assert.Equal(LogLevel.Information, logMessage.LogLevel);
Assert.Equal("ListResourceTemplatesFilter", logMessage.Category);
}
@ -156,7 +151,7 @@ public class McpServerBuilderExtensionsFilterTests : ClientServerTestBase
await client.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken);
var logMessage = Assert.Single(_mockLoggerProvider.LogMessages, m => m.Message == "ListToolsFilter executed");
var logMessage = Assert.Single(MockLoggerProvider.LogMessages, m => m.Message == "ListToolsFilter executed");
Assert.Equal(LogLevel.Information, logMessage.LogLevel);
Assert.Equal("ListToolsFilter", logMessage.Category);
}
@ -168,7 +163,7 @@ public class McpServerBuilderExtensionsFilterTests : ClientServerTestBase
await client.CallToolAsync("test_tool_method", cancellationToken: TestContext.Current.CancellationToken);
var logMessage = Assert.Single(_mockLoggerProvider.LogMessages, m => m.Message == "CallToolFilter executed for tool: test_tool_method");
var logMessage = Assert.Single(MockLoggerProvider.LogMessages, m => m.Message == "CallToolFilter executed for tool: test_tool_method");
Assert.Equal(LogLevel.Information, logMessage.LogLevel);
Assert.Equal("CallToolFilter", logMessage.Category);
}
@ -194,7 +189,7 @@ public class McpServerBuilderExtensionsFilterTests : ClientServerTestBase
await client.ListPromptsAsync(cancellationToken: TestContext.Current.CancellationToken);
var logMessage = Assert.Single(_mockLoggerProvider.LogMessages, m => m.Message == "ListPromptsFilter executed");
var logMessage = Assert.Single(MockLoggerProvider.LogMessages, m => m.Message == "ListPromptsFilter executed");
Assert.Equal(LogLevel.Information, logMessage.LogLevel);
Assert.Equal("ListPromptsFilter", logMessage.Category);
}
@ -206,7 +201,7 @@ public class McpServerBuilderExtensionsFilterTests : ClientServerTestBase
await client.GetPromptAsync("test_prompt_method", cancellationToken: TestContext.Current.CancellationToken);
var logMessage = Assert.Single(_mockLoggerProvider.LogMessages, m => m.Message == "GetPromptFilter executed for prompt: test_prompt_method");
var logMessage = Assert.Single(MockLoggerProvider.LogMessages, m => m.Message == "GetPromptFilter executed for prompt: test_prompt_method");
Assert.Equal(LogLevel.Information, logMessage.LogLevel);
Assert.Equal("GetPromptFilter", logMessage.Category);
}
@ -218,7 +213,7 @@ public class McpServerBuilderExtensionsFilterTests : ClientServerTestBase
await client.ListResourcesAsync(cancellationToken: TestContext.Current.CancellationToken);
var logMessage = Assert.Single(_mockLoggerProvider.LogMessages, m => m.Message == "ListResourcesFilter executed");
var logMessage = Assert.Single(MockLoggerProvider.LogMessages, m => m.Message == "ListResourcesFilter executed");
Assert.Equal(LogLevel.Information, logMessage.LogLevel);
Assert.Equal("ListResourcesFilter", logMessage.Category);
}
@ -230,7 +225,7 @@ public class McpServerBuilderExtensionsFilterTests : ClientServerTestBase
await client.ReadResourceAsync("test://resource/123", cancellationToken: TestContext.Current.CancellationToken);
var logMessage = Assert.Single(_mockLoggerProvider.LogMessages, m => m.Message == "ReadResourceFilter executed for resource: test://resource/{id}");
var logMessage = Assert.Single(MockLoggerProvider.LogMessages, m => m.Message == "ReadResourceFilter executed for resource: test://resource/{id}");
Assert.Equal(LogLevel.Information, logMessage.LogLevel);
Assert.Equal("ReadResourceFilter", logMessage.Category);
}
@ -243,7 +238,7 @@ public class McpServerBuilderExtensionsFilterTests : ClientServerTestBase
var reference = new PromptReference { Name = "test_prompt_method" };
await client.CompleteAsync(reference, "argument", "value", cancellationToken: TestContext.Current.CancellationToken);
var logMessage = Assert.Single(_mockLoggerProvider.LogMessages, m => m.Message == "CompleteFilter executed");
var logMessage = Assert.Single(MockLoggerProvider.LogMessages, m => m.Message == "CompleteFilter executed");
Assert.Equal(LogLevel.Information, logMessage.LogLevel);
Assert.Equal("CompleteFilter", logMessage.Category);
}
@ -255,7 +250,7 @@ public class McpServerBuilderExtensionsFilterTests : ClientServerTestBase
await client.SubscribeToResourceAsync("test://resource/123", cancellationToken: TestContext.Current.CancellationToken);
var logMessage = Assert.Single(_mockLoggerProvider.LogMessages, m => m.Message == "SubscribeToResourcesFilter executed");
var logMessage = Assert.Single(MockLoggerProvider.LogMessages, m => m.Message == "SubscribeToResourcesFilter executed");
Assert.Equal(LogLevel.Information, logMessage.LogLevel);
Assert.Equal("SubscribeToResourcesFilter", logMessage.Category);
}
@ -267,7 +262,7 @@ public class McpServerBuilderExtensionsFilterTests : ClientServerTestBase
await client.UnsubscribeFromResourceAsync("test://resource/123", cancellationToken: TestContext.Current.CancellationToken);
var logMessage = Assert.Single(_mockLoggerProvider.LogMessages, m => m.Message == "UnsubscribeFromResourcesFilter executed");
var logMessage = Assert.Single(MockLoggerProvider.LogMessages, m => m.Message == "UnsubscribeFromResourcesFilter executed");
Assert.Equal(LogLevel.Information, logMessage.LogLevel);
Assert.Equal("UnsubscribeFromResourcesFilter", logMessage.Category);
}
@ -279,7 +274,7 @@ public class McpServerBuilderExtensionsFilterTests : ClientServerTestBase
await client.SetLoggingLevelAsync(LoggingLevel.Info, cancellationToken: TestContext.Current.CancellationToken);
var logMessage = Assert.Single(_mockLoggerProvider.LogMessages, m => m.Message == "SetLoggingLevelFilter executed");
var logMessage = Assert.Single(MockLoggerProvider.LogMessages, m => m.Message == "SetLoggingLevelFilter executed");
Assert.Equal(LogLevel.Information, logMessage.LogLevel);
Assert.Equal("SetLoggingLevelFilter", logMessage.Category);
}
@ -291,7 +286,7 @@ public class McpServerBuilderExtensionsFilterTests : ClientServerTestBase
await client.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken);
var logMessages = _mockLoggerProvider.LogMessages
var logMessages = MockLoggerProvider.LogMessages
.Where(m => m.Category.StartsWith("ListToolsOrder"))
.Select(m => m.Message);

View file

@ -5,7 +5,6 @@ using Microsoft.Extensions.Options;
using ModelContextProtocol.Client;
using ModelContextProtocol.Protocol;
using ModelContextProtocol.Server;
using ModelContextProtocol.Tests.Utils;
using Moq;
using System.Collections;
using System.Collections.Concurrent;
@ -26,8 +25,6 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
{
}
private MockLoggerProvider _mockLoggerProvider = new();
protected override void ConfigureServices(ServiceCollection services, IMcpServerBuilder mcpServerBuilder)
{
mcpServerBuilder
@ -113,7 +110,6 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
.WithTools<EchoTool>(serializerOptions: BuilderToolsJsonContext.Default.Options);
services.AddSingleton(new ObjectWithId());
services.AddSingleton<ILoggerProvider>(_mockLoggerProvider);
}
[Fact]
@ -378,7 +374,7 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
Assert.NotEmpty(result.Content);
Assert.Contains("An error occurred", (result.Content[0] as TextContentBlock)?.Text);
var errorLog = Assert.Single(_mockLoggerProvider.LogMessages, m => m.LogLevel == LogLevel.Error);
var errorLog = Assert.Single(MockLoggerProvider.LogMessages, m => m.LogLevel == LogLevel.Error);
Assert.Equal($"\"throw_exception\" threw an unhandled exception.", errorLog.Message);
Assert.IsType<InvalidOperationException>(errorLog.Exception);
Assert.Equal("Test error", errorLog.Exception.Message);