Add middleware and authz support for server-side handlers (#733)
This commit is contained in:
parent
0f0aad020b
commit
d344c65120
9 changed files with 2871 additions and 435 deletions
|
|
@ -20,7 +20,8 @@ public abstract class ClientServerTestBase : LoggedTest, IAsyncDisposable
|
|||
: base(testOutputHelper)
|
||||
{
|
||||
ServiceCollection sc = new();
|
||||
sc.AddSingleton(LoggerFactory);
|
||||
sc.AddLogging();
|
||||
sc.AddSingleton(XunitLoggerProvider);
|
||||
_builder = sc
|
||||
.AddMcpServer()
|
||||
.WithStreamServerTransport(_clientToServerPipe.Reader.AsStream(), _serverToClientPipe.Writer.AsStream());
|
||||
|
|
|
|||
|
|
@ -0,0 +1,314 @@
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ModelContextProtocol.Client;
|
||||
using ModelContextProtocol.Protocol;
|
||||
using ModelContextProtocol.Server;
|
||||
using ModelContextProtocol.Tests.Utils;
|
||||
|
||||
namespace ModelContextProtocol.Tests.Configuration;
|
||||
|
||||
public class McpServerBuilderExtensionsFilterTests : ClientServerTestBase
|
||||
{
|
||||
public McpServerBuilderExtensionsFilterTests(ITestOutputHelper testOutputHelper)
|
||||
: base(testOutputHelper)
|
||||
{
|
||||
}
|
||||
|
||||
private MockLoggerProvider _mockLoggerProvider = new();
|
||||
|
||||
private static ILogger GetLogger(IServiceProvider? services, string categoryName)
|
||||
{
|
||||
var loggerFactory = services?.GetRequiredService<ILoggerFactory>() ?? throw new InvalidOperationException("LoggerFactory not available");
|
||||
return loggerFactory.CreateLogger(categoryName);
|
||||
}
|
||||
|
||||
protected override void ConfigureServices(ServiceCollection services, IMcpServerBuilder mcpServerBuilder)
|
||||
{
|
||||
mcpServerBuilder
|
||||
.AddListResourceTemplatesFilter((next) => async (request, cancellationToken) =>
|
||||
{
|
||||
var logger = GetLogger(request.Services, "ListResourceTemplatesFilter");
|
||||
logger.LogInformation("ListResourceTemplatesFilter executed");
|
||||
return await next(request, cancellationToken);
|
||||
})
|
||||
.AddListToolsFilter((next) => async (request, cancellationToken) =>
|
||||
{
|
||||
var logger = GetLogger(request.Services, "ListToolsFilter");
|
||||
logger.LogInformation("ListToolsFilter executed");
|
||||
return await next(request, cancellationToken);
|
||||
})
|
||||
.AddListToolsFilter((next) => async (request, cancellationToken) =>
|
||||
{
|
||||
var logger = GetLogger(request.Services, "ListToolsOrder1");
|
||||
logger.LogInformation("ListToolsOrder1 before");
|
||||
var result = await next(request, cancellationToken);
|
||||
logger.LogInformation("ListToolsOrder1 after");
|
||||
return result;
|
||||
})
|
||||
.AddListToolsFilter((next) => async (request, cancellationToken) =>
|
||||
{
|
||||
var logger = GetLogger(request.Services, "ListToolsOrder2");
|
||||
logger.LogInformation("ListToolsOrder2 before");
|
||||
var result = await next(request, cancellationToken);
|
||||
logger.LogInformation("ListToolsOrder2 after");
|
||||
return result;
|
||||
})
|
||||
.AddCallToolFilter((next) => async (request, cancellationToken) =>
|
||||
{
|
||||
var logger = GetLogger(request.Services, "CallToolFilter");
|
||||
var primitiveId = request.MatchedPrimitive?.Id ?? "unknown";
|
||||
logger.LogInformation($"CallToolFilter executed for tool: {primitiveId}");
|
||||
return await next(request, cancellationToken);
|
||||
})
|
||||
.AddListPromptsFilter((next) => async (request, cancellationToken) =>
|
||||
{
|
||||
var logger = GetLogger(request.Services, "ListPromptsFilter");
|
||||
logger.LogInformation("ListPromptsFilter executed");
|
||||
return await next(request, cancellationToken);
|
||||
})
|
||||
.AddGetPromptFilter((next) => async (request, cancellationToken) =>
|
||||
{
|
||||
var logger = GetLogger(request.Services, "GetPromptFilter");
|
||||
var primitiveId = request.MatchedPrimitive?.Id ?? "unknown";
|
||||
logger.LogInformation($"GetPromptFilter executed for prompt: {primitiveId}");
|
||||
return await next(request, cancellationToken);
|
||||
})
|
||||
.AddListResourcesFilter((next) => async (request, cancellationToken) =>
|
||||
{
|
||||
var logger = GetLogger(request.Services, "ListResourcesFilter");
|
||||
logger.LogInformation("ListResourcesFilter executed");
|
||||
return await next(request, cancellationToken);
|
||||
})
|
||||
.AddReadResourceFilter((next) => async (request, cancellationToken) =>
|
||||
{
|
||||
var logger = GetLogger(request.Services, "ReadResourceFilter");
|
||||
var primitiveId = request.MatchedPrimitive?.Id ?? "unknown";
|
||||
logger.LogInformation($"ReadResourceFilter executed for resource: {primitiveId}");
|
||||
return await next(request, cancellationToken);
|
||||
})
|
||||
.AddCompleteFilter((next) => async (request, cancellationToken) =>
|
||||
{
|
||||
var logger = GetLogger(request.Services, "CompleteFilter");
|
||||
logger.LogInformation("CompleteFilter executed");
|
||||
return await next(request, cancellationToken);
|
||||
})
|
||||
.AddSubscribeToResourcesFilter((next) => async (request, cancellationToken) =>
|
||||
{
|
||||
var logger = GetLogger(request.Services, "SubscribeToResourcesFilter");
|
||||
logger.LogInformation("SubscribeToResourcesFilter executed");
|
||||
return await next(request, cancellationToken);
|
||||
})
|
||||
.AddUnsubscribeFromResourcesFilter((next) => async (request, cancellationToken) =>
|
||||
{
|
||||
var logger = GetLogger(request.Services, "UnsubscribeFromResourcesFilter");
|
||||
logger.LogInformation("UnsubscribeFromResourcesFilter executed");
|
||||
return await next(request, cancellationToken);
|
||||
})
|
||||
.AddSetLoggingLevelFilter((next) => async (request, cancellationToken) =>
|
||||
{
|
||||
var logger = GetLogger(request.Services, "SetLoggingLevelFilter");
|
||||
logger.LogInformation("SetLoggingLevelFilter executed");
|
||||
return await next(request, cancellationToken);
|
||||
})
|
||||
.WithTools<TestTool>()
|
||||
.WithPrompts<TestPrompt>()
|
||||
.WithResources<TestResource>()
|
||||
.WithSetLoggingLevelHandler(async (request, cancellationToken) => new EmptyResult())
|
||||
.WithListResourceTemplatesHandler(async (request, cancellationToken) => new ListResourceTemplatesResult
|
||||
{
|
||||
ResourceTemplates = [new() { Name = "test", UriTemplate = "test://resource/{id}" }]
|
||||
})
|
||||
.WithCompleteHandler(async (request, cancellationToken) => new CompleteResult
|
||||
{
|
||||
Completion = new() { Values = ["test"] }
|
||||
});
|
||||
|
||||
services.AddSingleton<ILoggerProvider>(_mockLoggerProvider);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AddListResourceTemplatesFilter_Logs_When_ListResourceTemplates_Called()
|
||||
{
|
||||
await using IMcpClient client = await CreateMcpClientForServer();
|
||||
|
||||
await client.ListResourceTemplatesAsync(cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
var logMessage = Assert.Single(_mockLoggerProvider.LogMessages, m => m.Message == "ListResourceTemplatesFilter executed");
|
||||
Assert.Equal(LogLevel.Information, logMessage.LogLevel);
|
||||
Assert.Equal("ListResourceTemplatesFilter", logMessage.Category);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AddListToolsFilter_Logs_When_ListTools_Called()
|
||||
{
|
||||
await using IMcpClient client = await CreateMcpClientForServer();
|
||||
|
||||
await client.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
var logMessage = Assert.Single(_mockLoggerProvider.LogMessages, m => m.Message == "ListToolsFilter executed");
|
||||
Assert.Equal(LogLevel.Information, logMessage.LogLevel);
|
||||
Assert.Equal("ListToolsFilter", logMessage.Category);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AddCallToolFilter_Logs_When_CallTool_Called()
|
||||
{
|
||||
await using IMcpClient client = await CreateMcpClientForServer();
|
||||
|
||||
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");
|
||||
Assert.Equal(LogLevel.Information, logMessage.LogLevel);
|
||||
Assert.Equal("CallToolFilter", logMessage.Category);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AddListPromptsFilter_Logs_When_ListPrompts_Called()
|
||||
{
|
||||
await using IMcpClient client = await CreateMcpClientForServer();
|
||||
|
||||
await client.ListPromptsAsync(cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
var logMessage = Assert.Single(_mockLoggerProvider.LogMessages, m => m.Message == "ListPromptsFilter executed");
|
||||
Assert.Equal(LogLevel.Information, logMessage.LogLevel);
|
||||
Assert.Equal("ListPromptsFilter", logMessage.Category);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AddGetPromptFilter_Logs_When_GetPrompt_Called()
|
||||
{
|
||||
await using IMcpClient client = await CreateMcpClientForServer();
|
||||
|
||||
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");
|
||||
Assert.Equal(LogLevel.Information, logMessage.LogLevel);
|
||||
Assert.Equal("GetPromptFilter", logMessage.Category);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AddListResourcesFilter_Logs_When_ListResources_Called()
|
||||
{
|
||||
await using IMcpClient client = await CreateMcpClientForServer();
|
||||
|
||||
await client.ListResourcesAsync(cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
var logMessage = Assert.Single(_mockLoggerProvider.LogMessages, m => m.Message == "ListResourcesFilter executed");
|
||||
Assert.Equal(LogLevel.Information, logMessage.LogLevel);
|
||||
Assert.Equal("ListResourcesFilter", logMessage.Category);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AddReadResourceFilter_Logs_When_ReadResource_Called()
|
||||
{
|
||||
await using IMcpClient client = await CreateMcpClientForServer();
|
||||
|
||||
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}");
|
||||
Assert.Equal(LogLevel.Information, logMessage.LogLevel);
|
||||
Assert.Equal("ReadResourceFilter", logMessage.Category);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AddCompleteFilter_Logs_When_Complete_Called()
|
||||
{
|
||||
await using IMcpClient client = await CreateMcpClientForServer();
|
||||
|
||||
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");
|
||||
Assert.Equal(LogLevel.Information, logMessage.LogLevel);
|
||||
Assert.Equal("CompleteFilter", logMessage.Category);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AddSubscribeToResourcesFilter_Logs_When_SubscribeToResources_Called()
|
||||
{
|
||||
await using IMcpClient client = await CreateMcpClientForServer();
|
||||
|
||||
await client.SubscribeToResourceAsync("test://resource/123", cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
var logMessage = Assert.Single(_mockLoggerProvider.LogMessages, m => m.Message == "SubscribeToResourcesFilter executed");
|
||||
Assert.Equal(LogLevel.Information, logMessage.LogLevel);
|
||||
Assert.Equal("SubscribeToResourcesFilter", logMessage.Category);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AddUnsubscribeFromResourcesFilter_Logs_When_UnsubscribeFromResources_Called()
|
||||
{
|
||||
await using IMcpClient client = await CreateMcpClientForServer();
|
||||
|
||||
await client.UnsubscribeFromResourceAsync("test://resource/123", cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
var logMessage = Assert.Single(_mockLoggerProvider.LogMessages, m => m.Message == "UnsubscribeFromResourcesFilter executed");
|
||||
Assert.Equal(LogLevel.Information, logMessage.LogLevel);
|
||||
Assert.Equal("UnsubscribeFromResourcesFilter", logMessage.Category);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AddSetLoggingLevelFilter_Logs_When_SetLoggingLevel_Called()
|
||||
{
|
||||
await using IMcpClient client = await CreateMcpClientForServer();
|
||||
|
||||
await client.SetLoggingLevel(LoggingLevel.Info, cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
var logMessage = Assert.Single(_mockLoggerProvider.LogMessages, m => m.Message == "SetLoggingLevelFilter executed");
|
||||
Assert.Equal(LogLevel.Information, logMessage.LogLevel);
|
||||
Assert.Equal("SetLoggingLevelFilter", logMessage.Category);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AddListToolsFilter_Multiple_Filters_Log_In_Expected_Order()
|
||||
{
|
||||
await using IMcpClient client = await CreateMcpClientForServer();
|
||||
|
||||
await client.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
var logMessages = _mockLoggerProvider.LogMessages
|
||||
.Where(m => m.Category.StartsWith("ListToolsOrder"))
|
||||
.Select(m => m.Message);
|
||||
|
||||
Assert.Collection(logMessages,
|
||||
m => Assert.Equal("ListToolsOrder1 before", m),
|
||||
m => Assert.Equal("ListToolsOrder2 before", m),
|
||||
m => Assert.Equal("ListToolsOrder2 after", m),
|
||||
m => Assert.Equal("ListToolsOrder1 after", m)
|
||||
);
|
||||
}
|
||||
|
||||
[McpServerToolType]
|
||||
public sealed class TestTool
|
||||
{
|
||||
[McpServerTool]
|
||||
public static string TestToolMethod()
|
||||
{
|
||||
return "test result";
|
||||
}
|
||||
}
|
||||
|
||||
[McpServerPromptType]
|
||||
public sealed class TestPrompt
|
||||
{
|
||||
[McpServerPrompt]
|
||||
public static Task<GetPromptResult> TestPromptMethod()
|
||||
{
|
||||
return Task.FromResult(new GetPromptResult
|
||||
{
|
||||
Description = "Test prompt",
|
||||
Messages = [new() { Role = Role.User, Content = new TextContentBlock { Text = "Test" } }]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[McpServerResourceType]
|
||||
public sealed class TestResource
|
||||
{
|
||||
[McpServerResource(UriTemplate = "test://resource/{id}")]
|
||||
public static string TestResourceMethod(string id)
|
||||
{
|
||||
return $"Test resource for ID: {id}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -21,7 +21,7 @@ public class McpServerBuilderExtensionsHandlerTests
|
|||
[Fact]
|
||||
public void WithListToolsHandler_Sets_Handler()
|
||||
{
|
||||
Func<RequestContext<ListToolsRequestParams>, CancellationToken, ValueTask<ListToolsResult>> handler = async (context, token) => new ListToolsResult();
|
||||
McpRequestHandler<ListToolsRequestParams, ListToolsResult> handler = async (context, token) => new ListToolsResult();
|
||||
|
||||
_builder.Object.WithListToolsHandler(handler);
|
||||
|
||||
|
|
@ -34,7 +34,7 @@ public class McpServerBuilderExtensionsHandlerTests
|
|||
[Fact]
|
||||
public void WithCallToolHandler_Sets_Handler()
|
||||
{
|
||||
Func<RequestContext<CallToolRequestParams>, CancellationToken, ValueTask<CallToolResult>> handler = async (context, token) => new CallToolResult();
|
||||
McpRequestHandler<CallToolRequestParams, CallToolResult> handler = async (context, token) => new CallToolResult();
|
||||
|
||||
_builder.Object.WithCallToolHandler(handler);
|
||||
|
||||
|
|
@ -47,7 +47,7 @@ public class McpServerBuilderExtensionsHandlerTests
|
|||
[Fact]
|
||||
public void WithListPromptsHandler_Sets_Handler()
|
||||
{
|
||||
Func<RequestContext<ListPromptsRequestParams>, CancellationToken, ValueTask<ListPromptsResult>> handler = async (context, token) => new ListPromptsResult();
|
||||
McpRequestHandler<ListPromptsRequestParams, ListPromptsResult> handler = async (context, token) => new ListPromptsResult();
|
||||
|
||||
_builder.Object.WithListPromptsHandler(handler);
|
||||
|
||||
|
|
@ -60,7 +60,7 @@ public class McpServerBuilderExtensionsHandlerTests
|
|||
[Fact]
|
||||
public void WithGetPromptHandler_Sets_Handler()
|
||||
{
|
||||
Func<RequestContext<GetPromptRequestParams>, CancellationToken, ValueTask<GetPromptResult>> handler = async (context, token) => new GetPromptResult();
|
||||
McpRequestHandler<GetPromptRequestParams, GetPromptResult> handler = async (context, token) => new GetPromptResult();
|
||||
|
||||
_builder.Object.WithGetPromptHandler(handler);
|
||||
|
||||
|
|
@ -73,7 +73,7 @@ public class McpServerBuilderExtensionsHandlerTests
|
|||
[Fact]
|
||||
public void WithListResourceTemplatesHandler_Sets_Handler()
|
||||
{
|
||||
Func<RequestContext<ListResourceTemplatesRequestParams>, CancellationToken, ValueTask<ListResourceTemplatesResult>> handler = async (context, token) => new ListResourceTemplatesResult();
|
||||
McpRequestHandler<ListResourceTemplatesRequestParams, ListResourceTemplatesResult> handler = async (context, token) => new ListResourceTemplatesResult();
|
||||
|
||||
_builder.Object.WithListResourceTemplatesHandler(handler);
|
||||
|
||||
|
|
@ -86,7 +86,7 @@ public class McpServerBuilderExtensionsHandlerTests
|
|||
[Fact]
|
||||
public void WithListResourcesHandler_Sets_Handler()
|
||||
{
|
||||
Func<RequestContext<ListResourcesRequestParams>, CancellationToken, ValueTask<ListResourcesResult>> handler = async (context, token) => new ListResourcesResult();
|
||||
McpRequestHandler<ListResourcesRequestParams, ListResourcesResult> handler = async (context, token) => new ListResourcesResult();
|
||||
|
||||
_builder.Object.WithListResourcesHandler(handler);
|
||||
|
||||
|
|
@ -99,7 +99,7 @@ public class McpServerBuilderExtensionsHandlerTests
|
|||
[Fact]
|
||||
public void WithReadResourceHandler_Sets_Handler()
|
||||
{
|
||||
Func<RequestContext<ReadResourceRequestParams>, CancellationToken, ValueTask<ReadResourceResult>> handler = async (context, token) => new ReadResourceResult();
|
||||
McpRequestHandler<ReadResourceRequestParams, ReadResourceResult> handler = async (context, token) => new ReadResourceResult();
|
||||
|
||||
_builder.Object.WithReadResourceHandler(handler);
|
||||
|
||||
|
|
@ -112,7 +112,7 @@ public class McpServerBuilderExtensionsHandlerTests
|
|||
[Fact]
|
||||
public void WithCompleteHandler_Sets_Handler()
|
||||
{
|
||||
Func<RequestContext<CompleteRequestParams>, CancellationToken, ValueTask<CompleteResult>> handler = async (context, token) => new CompleteResult();
|
||||
McpRequestHandler<CompleteRequestParams, CompleteResult> handler = async (context, token) => new CompleteResult();
|
||||
|
||||
_builder.Object.WithCompleteHandler(handler);
|
||||
|
||||
|
|
@ -125,7 +125,7 @@ public class McpServerBuilderExtensionsHandlerTests
|
|||
[Fact]
|
||||
public void WithSubscribeToResourcesHandler_Sets_Handler()
|
||||
{
|
||||
Func<RequestContext<SubscribeRequestParams>, CancellationToken, ValueTask<EmptyResult>> handler = async (context, token) => new EmptyResult();
|
||||
McpRequestHandler<SubscribeRequestParams, EmptyResult> handler = async (context, token) => new EmptyResult();
|
||||
|
||||
_builder.Object.WithSubscribeToResourcesHandler(handler);
|
||||
|
||||
|
|
@ -138,7 +138,7 @@ public class McpServerBuilderExtensionsHandlerTests
|
|||
[Fact]
|
||||
public void WithUnsubscribeFromResourcesHandler_Sets_Handler()
|
||||
{
|
||||
Func<RequestContext<UnsubscribeRequestParams>, CancellationToken, ValueTask<EmptyResult>> handler = async (context, token) => new EmptyResult();
|
||||
McpRequestHandler<UnsubscribeRequestParams, EmptyResult> handler = async (context, token) => new EmptyResult();
|
||||
|
||||
_builder.Object.WithUnsubscribeFromResourcesHandler(handler);
|
||||
|
||||
|
|
|
|||
|
|
@ -238,7 +238,7 @@ public partial class McpServerBuilderExtensionsPromptsTests : ClientServerTestBa
|
|||
sc.AddMcpServer().WithPrompts(target);
|
||||
|
||||
McpServerPrompt prompt = sc.BuildServiceProvider().GetServices<McpServerPrompt>().First(t => t.ProtocolPrompt.Name == "returns_string");
|
||||
var result = await prompt.GetAsync(new RequestContext<GetPromptRequestParams>(new Mock<IMcpServer>().Object)
|
||||
var result = await prompt.GetAsync(new RequestContext<GetPromptRequestParams>(new Mock<IMcpServer>().Object, new JsonRpcRequest { Method = "test", Id = new RequestId("1") })
|
||||
{
|
||||
Params = new GetPromptRequestParams
|
||||
{
|
||||
|
|
|
|||
|
|
@ -265,7 +265,7 @@ public partial class McpServerBuilderExtensionsResourcesTests : ClientServerTest
|
|||
sc.AddMcpServer().WithResources(target);
|
||||
|
||||
McpServerResource resource = sc.BuildServiceProvider().GetServices<McpServerResource>().First(t => t.ProtocolResource?.Name == "returns_string");
|
||||
var result = await resource.ReadAsync(new RequestContext<ReadResourceRequestParams>(new Mock<IMcpServer>().Object)
|
||||
var result = await resource.ReadAsync(new RequestContext<ReadResourceRequestParams>(new Mock<IMcpServer>().Object, new JsonRpcRequest { Method = "test", Id = new RequestId("1") })
|
||||
{
|
||||
Params = new()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ 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;
|
||||
|
|
@ -24,6 +25,8 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
|||
{
|
||||
}
|
||||
|
||||
private MockLoggerProvider _mockLoggerProvider = new();
|
||||
|
||||
protected override void ConfigureServices(ServiceCollection services, IMcpServerBuilder mcpServerBuilder)
|
||||
{
|
||||
mcpServerBuilder
|
||||
|
|
@ -109,6 +112,7 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
|||
.WithTools<EchoTool>(serializerOptions: BuilderToolsJsonContext.Default.Options);
|
||||
|
||||
services.AddSingleton(new ObjectWithId());
|
||||
services.AddSingleton<ILoggerProvider>(_mockLoggerProvider);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -157,8 +161,8 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
|||
|
||||
await using (var client = await McpClientFactory.CreateAsync(
|
||||
new StreamClientTransport(
|
||||
serverInput: stdinPipe.Writer.AsStream(),
|
||||
serverOutput: stdoutPipe.Reader.AsStream(),
|
||||
serverInput: stdinPipe.Writer.AsStream(),
|
||||
serverOutput: stdoutPipe.Reader.AsStream(),
|
||||
LoggerFactory),
|
||||
loggerFactory: LoggerFactory,
|
||||
cancellationToken: TestContext.Current.CancellationToken))
|
||||
|
|
@ -232,7 +236,7 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
|||
|
||||
var result = await client.CallToolAsync(
|
||||
"echo",
|
||||
new Dictionary<string, object?>() { ["message"] = "Peter" },
|
||||
new Dictionary<string, object?>() { ["message"] = "Peter" },
|
||||
cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.NotNull(result);
|
||||
|
|
@ -353,14 +357,14 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
|||
string random1 = parts[0][0];
|
||||
string random2 = parts[1][0];
|
||||
Assert.NotEqual(random1, random2);
|
||||
|
||||
|
||||
string id1 = parts[0][1];
|
||||
string id2 = parts[1][1];
|
||||
Assert.Equal(id1, id2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Returns_IsError_Content_When_Tool_Fails()
|
||||
public async Task Returns_IsError_Content_And_Logs_Error_When_Tool_Fails()
|
||||
{
|
||||
await using IMcpClient client = await CreateMcpClientForServer();
|
||||
|
||||
|
|
@ -372,6 +376,11 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
|||
Assert.NotNull(result.Content);
|
||||
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);
|
||||
Assert.Equal($"\"throw_exception\" threw an unhandled exception.", errorLog.Message);
|
||||
Assert.IsType<InvalidOperationException>(errorLog.Exception);
|
||||
Assert.Equal("Test error", errorLog.Exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -516,7 +525,7 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
|||
sc.AddMcpServer().WithTools(target, BuilderToolsJsonContext.Default.Options);
|
||||
|
||||
McpServerTool tool = sc.BuildServiceProvider().GetServices<McpServerTool>().First(t => t.ProtocolTool.Name == "get_ctor_parameter");
|
||||
var result = await tool.InvokeAsync(new RequestContext<CallToolRequestParams>(new Mock<IMcpServer>().Object), TestContext.Current.CancellationToken);
|
||||
var result = await tool.InvokeAsync(new RequestContext<CallToolRequestParams>(new Mock<IMcpServer>().Object, new JsonRpcRequest { Method = "test", Id = new RequestId("1") }), TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Equal(target.GetCtorParameter(), (result.Content[0] as TextContentBlock)?.Text);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using ModelContextProtocol.Protocol;
|
||||
|
|
@ -15,6 +15,16 @@ namespace ModelContextProtocol.Tests.Server;
|
|||
|
||||
public class McpServerPromptTests
|
||||
{
|
||||
private static JsonRpcRequest CreateTestJsonRpcRequest()
|
||||
{
|
||||
return new JsonRpcRequest
|
||||
{
|
||||
Id = new RequestId("test-id"),
|
||||
Method = "test/method",
|
||||
Params = null
|
||||
};
|
||||
}
|
||||
|
||||
public McpServerPromptTests()
|
||||
{
|
||||
#if !NET
|
||||
|
|
@ -46,7 +56,7 @@ public class McpServerPromptTests
|
|||
Assert.DoesNotContain("server", prompt.ProtocolPrompt.Arguments?.Select(a => a.Name) ?? []);
|
||||
|
||||
var result = await prompt.GetAsync(
|
||||
new RequestContext<GetPromptRequestParams>(mockServer.Object),
|
||||
new RequestContext<GetPromptRequestParams>(mockServer.Object, CreateTestJsonRpcRequest()),
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(result);
|
||||
Assert.NotNull(result.Messages);
|
||||
|
|
@ -75,7 +85,7 @@ public class McpServerPromptTests
|
|||
}, new() { Services = services });
|
||||
|
||||
var result = await prompt.GetAsync(
|
||||
new RequestContext<GetPromptRequestParams>(mockServer.Object),
|
||||
new RequestContext<GetPromptRequestParams>(mockServer.Object, CreateTestJsonRpcRequest()),
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(result);
|
||||
Assert.NotNull(result.Messages);
|
||||
|
|
@ -125,11 +135,11 @@ public class McpServerPromptTests
|
|||
Assert.DoesNotContain("actualMyService", prompt.ProtocolPrompt.Arguments?.Select(a => a.Name) ?? []);
|
||||
|
||||
await Assert.ThrowsAnyAsync<ArgumentException>(async () => await prompt.GetAsync(
|
||||
new RequestContext<GetPromptRequestParams>(new Mock<IMcpServer>().Object),
|
||||
new RequestContext<GetPromptRequestParams>(new Mock<IMcpServer>().Object, CreateTestJsonRpcRequest()),
|
||||
TestContext.Current.CancellationToken));
|
||||
|
||||
var result = await prompt.GetAsync(
|
||||
new RequestContext<GetPromptRequestParams>(new Mock<IMcpServer>().Object) { Services = services },
|
||||
new RequestContext<GetPromptRequestParams>(new Mock<IMcpServer>().Object, CreateTestJsonRpcRequest()) { Services = services },
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.Equal("Hello", Assert.IsType<TextContentBlock>(result.Messages[0].Content).Text);
|
||||
}
|
||||
|
|
@ -150,7 +160,7 @@ public class McpServerPromptTests
|
|||
}, new() { Services = services });
|
||||
|
||||
var result = await prompt.GetAsync(
|
||||
new RequestContext<GetPromptRequestParams>(new Mock<IMcpServer>().Object),
|
||||
new RequestContext<GetPromptRequestParams>(new Mock<IMcpServer>().Object, CreateTestJsonRpcRequest()),
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.Equal("Hello", Assert.IsType<TextContentBlock>(result.Messages[0].Content).Text);
|
||||
}
|
||||
|
|
@ -163,7 +173,7 @@ public class McpServerPromptTests
|
|||
_ => new DisposablePromptType());
|
||||
|
||||
var result = await prompt1.GetAsync(
|
||||
new RequestContext<GetPromptRequestParams>(new Mock<IMcpServer>().Object),
|
||||
new RequestContext<GetPromptRequestParams>(new Mock<IMcpServer>().Object, CreateTestJsonRpcRequest()),
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.Equal("disposals:1", Assert.IsType<TextContentBlock>(result.Messages[0].Content).Text);
|
||||
}
|
||||
|
|
@ -176,7 +186,7 @@ public class McpServerPromptTests
|
|||
_ => new AsyncDisposablePromptType());
|
||||
|
||||
var result = await prompt1.GetAsync(
|
||||
new RequestContext<GetPromptRequestParams>(new Mock<IMcpServer>().Object),
|
||||
new RequestContext<GetPromptRequestParams>(new Mock<IMcpServer>().Object, CreateTestJsonRpcRequest()),
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.Equal("asyncDisposals:1", Assert.IsType<TextContentBlock>(result.Messages[0].Content).Text);
|
||||
}
|
||||
|
|
@ -189,7 +199,7 @@ public class McpServerPromptTests
|
|||
_ => new AsyncDisposableAndDisposablePromptType());
|
||||
|
||||
var result = await prompt1.GetAsync(
|
||||
new RequestContext<GetPromptRequestParams>(new Mock<IMcpServer>().Object),
|
||||
new RequestContext<GetPromptRequestParams>(new Mock<IMcpServer>().Object, CreateTestJsonRpcRequest()),
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.Equal("disposals:0, asyncDisposals:1", Assert.IsType<TextContentBlock>(result.Messages[0].Content).Text);
|
||||
}
|
||||
|
|
@ -205,7 +215,7 @@ public class McpServerPromptTests
|
|||
});
|
||||
|
||||
var actual = await prompt.GetAsync(
|
||||
new RequestContext<GetPromptRequestParams>(new Mock<IMcpServer>().Object),
|
||||
new RequestContext<GetPromptRequestParams>(new Mock<IMcpServer>().Object, CreateTestJsonRpcRequest()),
|
||||
TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Same(expected, actual);
|
||||
|
|
@ -222,7 +232,7 @@ public class McpServerPromptTests
|
|||
});
|
||||
|
||||
var actual = await prompt.GetAsync(
|
||||
new RequestContext<GetPromptRequestParams>(new Mock<IMcpServer>().Object),
|
||||
new RequestContext<GetPromptRequestParams>(new Mock<IMcpServer>().Object, CreateTestJsonRpcRequest()),
|
||||
TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.NotNull(actual);
|
||||
|
|
@ -248,7 +258,7 @@ public class McpServerPromptTests
|
|||
});
|
||||
|
||||
var actual = await prompt.GetAsync(
|
||||
new RequestContext<GetPromptRequestParams>(new Mock<IMcpServer>().Object),
|
||||
new RequestContext<GetPromptRequestParams>(new Mock<IMcpServer>().Object, CreateTestJsonRpcRequest()),
|
||||
TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.NotNull(actual);
|
||||
|
|
@ -260,7 +270,7 @@ public class McpServerPromptTests
|
|||
[Fact]
|
||||
public async Task CanReturnPromptMessages()
|
||||
{
|
||||
IList<PromptMessage> expected =
|
||||
IList<PromptMessage> expected =
|
||||
[
|
||||
new()
|
||||
{
|
||||
|
|
@ -280,7 +290,7 @@ public class McpServerPromptTests
|
|||
});
|
||||
|
||||
var actual = await prompt.GetAsync(
|
||||
new RequestContext<GetPromptRequestParams>(new Mock<IMcpServer>().Object),
|
||||
new RequestContext<GetPromptRequestParams>(new Mock<IMcpServer>().Object, CreateTestJsonRpcRequest()),
|
||||
TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.NotNull(actual);
|
||||
|
|
@ -307,7 +317,7 @@ public class McpServerPromptTests
|
|||
});
|
||||
|
||||
var actual = await prompt.GetAsync(
|
||||
new RequestContext<GetPromptRequestParams>(new Mock<IMcpServer>().Object),
|
||||
new RequestContext<GetPromptRequestParams>(new Mock<IMcpServer>().Object, CreateTestJsonRpcRequest()),
|
||||
TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.NotNull(actual);
|
||||
|
|
@ -339,7 +349,7 @@ public class McpServerPromptTests
|
|||
});
|
||||
|
||||
var actual = await prompt.GetAsync(
|
||||
new RequestContext<GetPromptRequestParams>(new Mock<IMcpServer>().Object),
|
||||
new RequestContext<GetPromptRequestParams>(new Mock<IMcpServer>().Object, CreateTestJsonRpcRequest()),
|
||||
TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.NotNull(actual);
|
||||
|
|
@ -360,7 +370,7 @@ public class McpServerPromptTests
|
|||
});
|
||||
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(async () => await prompt.GetAsync(
|
||||
new RequestContext<GetPromptRequestParams>(new Mock<IMcpServer>().Object),
|
||||
new RequestContext<GetPromptRequestParams>(new Mock<IMcpServer>().Object, CreateTestJsonRpcRequest()),
|
||||
TestContext.Current.CancellationToken));
|
||||
}
|
||||
|
||||
|
|
@ -373,7 +383,7 @@ public class McpServerPromptTests
|
|||
});
|
||||
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(async () => await prompt.GetAsync(
|
||||
new RequestContext<GetPromptRequestParams>(new Mock<IMcpServer>().Object),
|
||||
new RequestContext<GetPromptRequestParams>(new Mock<IMcpServer>().Object, CreateTestJsonRpcRequest()),
|
||||
TestContext.Current.CancellationToken));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ModelContextProtocol.Protocol;
|
||||
using ModelContextProtocol.Server;
|
||||
|
|
@ -11,6 +11,16 @@ namespace ModelContextProtocol.Tests.Server;
|
|||
|
||||
public partial class McpServerResourceTests
|
||||
{
|
||||
private static JsonRpcRequest CreateTestJsonRpcRequest()
|
||||
{
|
||||
return new JsonRpcRequest
|
||||
{
|
||||
Id = new RequestId("test-id"),
|
||||
Method = "test/method",
|
||||
Params = null
|
||||
};
|
||||
}
|
||||
|
||||
public McpServerResourceTests()
|
||||
{
|
||||
#if !NET
|
||||
|
|
@ -138,7 +148,7 @@ public partial class McpServerResourceTests
|
|||
t = McpServerResource.Create(() => "42", new() { Name = Name });
|
||||
Assert.Equal("resource://mcp/Hello", t.ProtocolResourceTemplate.UriTemplate);
|
||||
result = await t.ReadAsync(
|
||||
new RequestContext<ReadResourceRequestParams>(server) { Params = new() { Uri = "resource://mcp/Hello" } },
|
||||
new RequestContext<ReadResourceRequestParams>(server, CreateTestJsonRpcRequest()) { Params = new() { Uri = "resource://mcp/Hello" } },
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("42", ((TextResourceContents)result.Contents[0]).Text);
|
||||
|
|
@ -146,7 +156,7 @@ public partial class McpServerResourceTests
|
|||
t = McpServerResource.Create((IMcpServer server) => "42", new() { Name = Name });
|
||||
Assert.Equal("resource://mcp/Hello", t.ProtocolResourceTemplate.UriTemplate);
|
||||
result = await t.ReadAsync(
|
||||
new RequestContext<ReadResourceRequestParams>(server) { Params = new() { Uri = "resource://mcp/Hello" } },
|
||||
new RequestContext<ReadResourceRequestParams>(server, CreateTestJsonRpcRequest()) { Params = new() { Uri = "resource://mcp/Hello" } },
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("42", ((TextResourceContents)result.Contents[0]).Text);
|
||||
|
|
@ -154,7 +164,7 @@ public partial class McpServerResourceTests
|
|||
t = McpServerResource.Create((string arg1) => arg1, new() { Name = Name });
|
||||
Assert.Equal($"resource://mcp/Hello{{?arg1}}", t.ProtocolResourceTemplate.UriTemplate);
|
||||
result = await t.ReadAsync(
|
||||
new RequestContext<ReadResourceRequestParams>(server) { Params = new() { Uri = $"resource://mcp/Hello?arg1=wOrLd" } },
|
||||
new RequestContext<ReadResourceRequestParams>(server, CreateTestJsonRpcRequest()) { Params = new() { Uri = $"resource://mcp/Hello?arg1=wOrLd" } },
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("wOrLd", ((TextResourceContents)result.Contents[0]).Text);
|
||||
|
|
@ -162,7 +172,7 @@ public partial class McpServerResourceTests
|
|||
t = McpServerResource.Create((string arg1, string? arg2 = null) => arg1 + arg2, new() { Name = Name });
|
||||
Assert.Equal($"resource://mcp/Hello{{?arg1,arg2}}", t.ProtocolResourceTemplate.UriTemplate);
|
||||
result = await t.ReadAsync(
|
||||
new RequestContext<ReadResourceRequestParams>(server) { Params = new() { Uri = $"resource://mcp/Hello?arg1=wo&arg2=rld" } },
|
||||
new RequestContext<ReadResourceRequestParams>(server, CreateTestJsonRpcRequest()) { Params = new() { Uri = $"resource://mcp/Hello?arg1=wo&arg2=rld" } },
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("world", ((TextResourceContents)result.Contents[0]).Text);
|
||||
|
|
@ -170,7 +180,7 @@ public partial class McpServerResourceTests
|
|||
t = McpServerResource.Create((object a1, bool a2, char a3, byte a4, sbyte a5) => a1.ToString() + a2 + a3 + a4 + a5, new() { Name = Name });
|
||||
Assert.Equal($"resource://mcp/Hello{{?a1,a2,a3,a4,a5}}", t.ProtocolResourceTemplate.UriTemplate);
|
||||
result = await t.ReadAsync(
|
||||
new RequestContext<ReadResourceRequestParams>(server) { Params = new() { Uri = $"resource://mcp/Hello?a1=hi&a2=true&a3=s&a4=12&a5=34" } },
|
||||
new RequestContext<ReadResourceRequestParams>(server, CreateTestJsonRpcRequest()) { Params = new() { Uri = $"resource://mcp/Hello?a1=hi&a2=true&a3=s&a4=12&a5=34" } },
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("hiTrues1234", ((TextResourceContents)result.Contents[0]).Text);
|
||||
|
|
@ -178,7 +188,7 @@ public partial class McpServerResourceTests
|
|||
t = McpServerResource.Create((ushort a1, short a2, uint a3, int a4, ulong a5) => (a1 + a2 + a3 + a4 + (long)a5).ToString(), new() { Name = Name });
|
||||
Assert.Equal($"resource://mcp/Hello{{?a1,a2,a3,a4,a5}}", t.ProtocolResourceTemplate.UriTemplate);
|
||||
result = await t.ReadAsync(
|
||||
new RequestContext<ReadResourceRequestParams>(server) { Params = new() { Uri = $"resource://mcp/Hello?a1=10&a2=20&a3=30&a4=40&a5=50" } },
|
||||
new RequestContext<ReadResourceRequestParams>(server, CreateTestJsonRpcRequest()) { Params = new() { Uri = $"resource://mcp/Hello?a1=10&a2=20&a3=30&a4=40&a5=50" } },
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("150", ((TextResourceContents)result.Contents[0]).Text);
|
||||
|
|
@ -186,7 +196,7 @@ public partial class McpServerResourceTests
|
|||
t = McpServerResource.Create((long a1, float a2, double a3, decimal a4, TimeSpan a5) => a5.ToString(), new() { Name = Name });
|
||||
Assert.Equal($"resource://mcp/Hello{{?a1,a2,a3,a4,a5}}", t.ProtocolResourceTemplate.UriTemplate);
|
||||
result = await t.ReadAsync(
|
||||
new RequestContext<ReadResourceRequestParams>(server) { Params = new() { Uri = $"resource://mcp/Hello?a1=1&a2=2&a3=3&a4=4&a5=5" } },
|
||||
new RequestContext<ReadResourceRequestParams>(server, CreateTestJsonRpcRequest()) { Params = new() { Uri = $"resource://mcp/Hello?a1=1&a2=2&a3=3&a4=4&a5=5" } },
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("5.00:00:00", ((TextResourceContents)result.Contents[0]).Text);
|
||||
|
|
@ -194,7 +204,7 @@ public partial class McpServerResourceTests
|
|||
t = McpServerResource.Create((DateTime a1, DateTimeOffset a2, Uri a3, Guid a4, Version a5) => a4.ToString("N") + a5, new() { Name = Name });
|
||||
Assert.Equal($"resource://mcp/Hello{{?a1,a2,a3,a4,a5}}", t.ProtocolResourceTemplate.UriTemplate);
|
||||
result = await t.ReadAsync(
|
||||
new RequestContext<ReadResourceRequestParams>(server) { Params = new() { Uri = $"resource://mcp/Hello?a1={DateTime.UtcNow:r}&a2={DateTimeOffset.UtcNow:r}&a3=http%3A%2F%2Ftest&a4=14e5f43d-0d41-47d6-8207-8249cf669e41&a5=1.2.3.4" } },
|
||||
new RequestContext<ReadResourceRequestParams>(server, CreateTestJsonRpcRequest()) { Params = new() { Uri = $"resource://mcp/Hello?a1={DateTime.UtcNow:r}&a2={DateTimeOffset.UtcNow:r}&a3=http%3A%2F%2Ftest&a4=14e5f43d-0d41-47d6-8207-8249cf669e41&a5=1.2.3.4" } },
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("14e5f43d0d4147d682078249cf669e411.2.3.4", ((TextResourceContents)result.Contents[0]).Text);
|
||||
|
|
@ -203,7 +213,7 @@ public partial class McpServerResourceTests
|
|||
t = McpServerResource.Create((Half a2, Int128 a3, UInt128 a4, IntPtr a5) => (a3 + (Int128)a4 + a5).ToString(), new() { Name = Name });
|
||||
Assert.Equal($"resource://mcp/Hello{{?a2,a3,a4,a5}}", t.ProtocolResourceTemplate.UriTemplate);
|
||||
result = await t.ReadAsync(
|
||||
new RequestContext<ReadResourceRequestParams>(server) { Params = new() { Uri = $"resource://mcp/Hello?a2=1.0&a3=3&a4=4&a5=5" } },
|
||||
new RequestContext<ReadResourceRequestParams>(server, CreateTestJsonRpcRequest()) { Params = new() { Uri = $"resource://mcp/Hello?a2=1.0&a3=3&a4=4&a5=5" } },
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("12", ((TextResourceContents)result.Contents[0]).Text);
|
||||
|
|
@ -211,7 +221,7 @@ public partial class McpServerResourceTests
|
|||
t = McpServerResource.Create((UIntPtr a1, DateOnly a2, TimeOnly a3) => a1.ToString(), new() { Name = Name });
|
||||
Assert.Equal($"resource://mcp/Hello{{?a1,a2,a3}}", t.ProtocolResourceTemplate.UriTemplate);
|
||||
result = await t.ReadAsync(
|
||||
new RequestContext<ReadResourceRequestParams>(server) { Params = new() { Uri = $"resource://mcp/Hello?a1=123&a2=0001-02-03&a3=01%3A02%3A03" } },
|
||||
new RequestContext<ReadResourceRequestParams>(server, CreateTestJsonRpcRequest()) { Params = new() { Uri = $"resource://mcp/Hello?a1=123&a2=0001-02-03&a3=01%3A02%3A03" } },
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("123", ((TextResourceContents)result.Contents[0]).Text);
|
||||
|
|
@ -220,7 +230,7 @@ public partial class McpServerResourceTests
|
|||
t = McpServerResource.Create((bool? a2, char? a3, byte? a4, sbyte? a5) => a2?.ToString() + a3 + a4 + a5, new() { Name = Name });
|
||||
Assert.Equal($"resource://mcp/Hello{{?a2,a3,a4,a5}}", t.ProtocolResourceTemplate.UriTemplate);
|
||||
result = await t.ReadAsync(
|
||||
new RequestContext<ReadResourceRequestParams>(server) { Params = new() { Uri = $"resource://mcp/Hello?a2=true&a3=s&a4=12&a5=34" } },
|
||||
new RequestContext<ReadResourceRequestParams>(server, CreateTestJsonRpcRequest()) { Params = new() { Uri = $"resource://mcp/Hello?a2=true&a3=s&a4=12&a5=34" } },
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("Trues1234", ((TextResourceContents)result.Contents[0]).Text);
|
||||
|
|
@ -228,7 +238,7 @@ public partial class McpServerResourceTests
|
|||
t = McpServerResource.Create((ushort? a1, short? a2, uint? a3, int? a4, ulong? a5) => (a1 + a2 + a3 + a4 + (long?)a5).ToString(), new() { Name = Name });
|
||||
Assert.Equal($"resource://mcp/Hello{{?a1,a2,a3,a4,a5}}", t.ProtocolResourceTemplate.UriTemplate);
|
||||
result = await t.ReadAsync(
|
||||
new RequestContext<ReadResourceRequestParams>(server) { Params = new() { Uri = $"resource://mcp/Hello?a1=10&a2=20&a3=30&a4=40&a5=50" } },
|
||||
new RequestContext<ReadResourceRequestParams>(server, CreateTestJsonRpcRequest()) { Params = new() { Uri = $"resource://mcp/Hello?a1=10&a2=20&a3=30&a4=40&a5=50" } },
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("150", ((TextResourceContents)result.Contents[0]).Text);
|
||||
|
|
@ -236,7 +246,7 @@ public partial class McpServerResourceTests
|
|||
t = McpServerResource.Create((long? a1, float? a2, double? a3, decimal? a4, TimeSpan? a5) => a5?.ToString(), new() { Name = Name });
|
||||
Assert.Equal($"resource://mcp/Hello{{?a1,a2,a3,a4,a5}}", t.ProtocolResourceTemplate.UriTemplate);
|
||||
result = await t.ReadAsync(
|
||||
new RequestContext<ReadResourceRequestParams>(server) { Params = new() { Uri = $"resource://mcp/Hello?a1=1&a2=2&a3=3&a4=4&a5=5" } },
|
||||
new RequestContext<ReadResourceRequestParams>(server, CreateTestJsonRpcRequest()) { Params = new() { Uri = $"resource://mcp/Hello?a1=1&a2=2&a3=3&a4=4&a5=5" } },
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("5.00:00:00", ((TextResourceContents)result.Contents[0]).Text);
|
||||
|
|
@ -244,7 +254,7 @@ public partial class McpServerResourceTests
|
|||
t = McpServerResource.Create((DateTime? a1, DateTimeOffset? a2, Guid? a4) => a4?.ToString("N"), new() { Name = Name });
|
||||
Assert.Equal($"resource://mcp/Hello{{?a1,a2,a4}}", t.ProtocolResourceTemplate.UriTemplate);
|
||||
result = await t.ReadAsync(
|
||||
new RequestContext<ReadResourceRequestParams>(server) { Params = new() { Uri = $"resource://mcp/Hello?a1={DateTime.UtcNow:r}&a2={DateTimeOffset.UtcNow:r}&a4=14e5f43d-0d41-47d6-8207-8249cf669e41" } },
|
||||
new RequestContext<ReadResourceRequestParams>(server, CreateTestJsonRpcRequest()) { Params = new() { Uri = $"resource://mcp/Hello?a1={DateTime.UtcNow:r}&a2={DateTimeOffset.UtcNow:r}&a4=14e5f43d-0d41-47d6-8207-8249cf669e41" } },
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("14e5f43d0d4147d682078249cf669e41", ((TextResourceContents)result.Contents[0]).Text);
|
||||
|
|
@ -253,7 +263,7 @@ public partial class McpServerResourceTests
|
|||
t = McpServerResource.Create((Half? a2, Int128? a3, UInt128? a4, IntPtr? a5) => (a3 + (Int128?)a4 + a5).ToString(), new() { Name = Name });
|
||||
Assert.Equal($"resource://mcp/Hello{{?a2,a3,a4,a5}}", t.ProtocolResourceTemplate.UriTemplate);
|
||||
result = await t.ReadAsync(
|
||||
new RequestContext<ReadResourceRequestParams>(server) { Params = new() { Uri = $"resource://mcp/Hello?a2=1.0&a3=3&a4=4&a5=5" } },
|
||||
new RequestContext<ReadResourceRequestParams>(server, CreateTestJsonRpcRequest()) { Params = new() { Uri = $"resource://mcp/Hello?a2=1.0&a3=3&a4=4&a5=5" } },
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("12", ((TextResourceContents)result.Contents[0]).Text);
|
||||
|
|
@ -261,7 +271,7 @@ public partial class McpServerResourceTests
|
|||
t = McpServerResource.Create((UIntPtr? a1, DateOnly? a2, TimeOnly? a3) => a1?.ToString(), new() { Name = Name });
|
||||
Assert.Equal($"resource://mcp/Hello{{?a1,a2,a3}}", t.ProtocolResourceTemplate.UriTemplate);
|
||||
result = await t.ReadAsync(
|
||||
new RequestContext<ReadResourceRequestParams>(server) { Params = new() { Uri = $"resource://mcp/Hello?a1=123&a2=0001-02-03&a3=01%3A02%3A03" } },
|
||||
new RequestContext<ReadResourceRequestParams>(server, CreateTestJsonRpcRequest()) { Params = new() { Uri = $"resource://mcp/Hello?a1=123&a2=0001-02-03&a3=01%3A02%3A03" } },
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("123", ((TextResourceContents)result.Contents[0]).Text);
|
||||
|
|
@ -277,7 +287,7 @@ public partial class McpServerResourceTests
|
|||
McpServerResource t = McpServerResource.Create((string arg1) => arg1, new() { Name = "Hello" });
|
||||
Assert.Equal("resource://mcp/Hello{?arg1}", t.ProtocolResourceTemplate.UriTemplate);
|
||||
Assert.Null(await t.ReadAsync(
|
||||
new RequestContext<ReadResourceRequestParams>(new Mock<IMcpServer>().Object) { Params = new() { Uri = uri } },
|
||||
new RequestContext<ReadResourceRequestParams>(new Mock<IMcpServer>().Object, CreateTestJsonRpcRequest()) { Params = new() { Uri = uri } },
|
||||
TestContext.Current.CancellationToken));
|
||||
}
|
||||
|
||||
|
|
@ -288,7 +298,7 @@ public partial class McpServerResourceTests
|
|||
{
|
||||
McpServerResource t = McpServerResource.Create(() => "resource", new() { UriTemplate = actualUri });
|
||||
Assert.NotNull(await t.ReadAsync(
|
||||
new RequestContext<ReadResourceRequestParams>(new Mock<IMcpServer>().Object) { Params = new() { Uri = queriedUri } },
|
||||
new RequestContext<ReadResourceRequestParams>(new Mock<IMcpServer>().Object, CreateTestJsonRpcRequest()) { Params = new() { Uri = queriedUri } },
|
||||
TestContext.Current.CancellationToken));
|
||||
}
|
||||
|
||||
|
|
@ -317,7 +327,7 @@ public partial class McpServerResourceTests
|
|||
McpServerResource t = McpServerResource.Create((string arg1, int arg2) => arg1, new() { Name = "Hello" });
|
||||
Assert.Equal("resource://mcp/Hello{?arg1,arg2}", t.ProtocolResourceTemplate.UriTemplate);
|
||||
await Assert.ThrowsAsync<ArgumentException>(async () => await t.ReadAsync(
|
||||
new RequestContext<ReadResourceRequestParams>(new Mock<IMcpServer>().Object) { Params = new() { Uri = uri } },
|
||||
new RequestContext<ReadResourceRequestParams>(new Mock<IMcpServer>().Object, CreateTestJsonRpcRequest()) { Params = new() { Uri = uri } },
|
||||
TestContext.Current.CancellationToken));
|
||||
}
|
||||
|
||||
|
|
@ -330,25 +340,25 @@ public partial class McpServerResourceTests
|
|||
ReadResourceResult? result;
|
||||
|
||||
result = await t.ReadAsync(
|
||||
new RequestContext<ReadResourceRequestParams>(new Mock<IMcpServer>().Object) { Params = new() { Uri = "resource://mcp/Hello" } },
|
||||
new RequestContext<ReadResourceRequestParams>(new Mock<IMcpServer>().Object, CreateTestJsonRpcRequest()) { Params = new() { Uri = "resource://mcp/Hello" } },
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("", ((TextResourceContents)result.Contents[0]).Text);
|
||||
|
||||
result = await t.ReadAsync(
|
||||
new RequestContext<ReadResourceRequestParams>(new Mock<IMcpServer>().Object) { Params = new() { Uri = "resource://mcp/Hello?arg1=first" } },
|
||||
new RequestContext<ReadResourceRequestParams>(new Mock<IMcpServer>().Object, CreateTestJsonRpcRequest()) { Params = new() { Uri = "resource://mcp/Hello?arg1=first" } },
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("first", ((TextResourceContents)result.Contents[0]).Text);
|
||||
|
||||
result = await t.ReadAsync(
|
||||
new RequestContext<ReadResourceRequestParams>(new Mock<IMcpServer>().Object) { Params = new() { Uri = "resource://mcp/Hello?arg2=42" } },
|
||||
new RequestContext<ReadResourceRequestParams>(new Mock<IMcpServer>().Object, CreateTestJsonRpcRequest()) { Params = new() { Uri = "resource://mcp/Hello?arg2=42" } },
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("42", ((TextResourceContents)result.Contents[0]).Text);
|
||||
|
||||
result = await t.ReadAsync(
|
||||
new RequestContext<ReadResourceRequestParams>(new Mock<IMcpServer>().Object) { Params = new() { Uri = "resource://mcp/Hello?arg1=first&arg2=42" } },
|
||||
new RequestContext<ReadResourceRequestParams>(new Mock<IMcpServer>().Object, CreateTestJsonRpcRequest()) { Params = new() { Uri = "resource://mcp/Hello?arg1=first&arg2=42" } },
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("first42", ((TextResourceContents)result.Contents[0]).Text);
|
||||
|
|
@ -366,7 +376,7 @@ public partial class McpServerResourceTests
|
|||
}, new() { Name = "Test" });
|
||||
|
||||
var result = await resource.ReadAsync(
|
||||
new RequestContext<ReadResourceRequestParams>(mockServer.Object) { Params = new() { Uri = "resource://mcp/Test" } },
|
||||
new RequestContext<ReadResourceRequestParams>(mockServer.Object, CreateTestJsonRpcRequest()) { Params = new() { Uri = "resource://mcp/Test" } },
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("42", ((TextResourceContents)result.Contents[0]).Text);
|
||||
|
|
@ -393,7 +403,7 @@ public partial class McpServerResourceTests
|
|||
}, new() { Services = services });
|
||||
|
||||
var result = await tool.ReadAsync(
|
||||
new RequestContext<ReadResourceRequestParams>(mockServer.Object) { Params = new() { Uri = "https://something" } },
|
||||
new RequestContext<ReadResourceRequestParams>(mockServer.Object, CreateTestJsonRpcRequest()) { Params = new() { Uri = "https://something" } },
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(result);
|
||||
Assert.NotNull(result.Contents);
|
||||
|
|
@ -470,11 +480,11 @@ public partial class McpServerResourceTests
|
|||
Mock<IMcpServer> mockServer = new();
|
||||
|
||||
await Assert.ThrowsAnyAsync<ArgumentException>(async () => await resource.ReadAsync(
|
||||
new RequestContext<ReadResourceRequestParams>(mockServer.Object) { Params = new() { Uri = "resource://mcp/Test" } },
|
||||
new RequestContext<ReadResourceRequestParams>(mockServer.Object, CreateTestJsonRpcRequest()) { Params = new() { Uri = "resource://mcp/Test" } },
|
||||
TestContext.Current.CancellationToken));
|
||||
|
||||
var result = await resource.ReadAsync(
|
||||
new RequestContext<ReadResourceRequestParams>(mockServer.Object) { Services = services, Params = new() { Uri = "resource://mcp/Test" } },
|
||||
new RequestContext<ReadResourceRequestParams>(mockServer.Object, CreateTestJsonRpcRequest()) { Services = services, Params = new() { Uri = "resource://mcp/Test" } },
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("42", ((TextResourceContents)result.Contents[0]).Text);
|
||||
|
|
@ -496,7 +506,7 @@ public partial class McpServerResourceTests
|
|||
}, new() { Services = services, Name = "Test" });
|
||||
|
||||
var result = await resource.ReadAsync(
|
||||
new RequestContext<ReadResourceRequestParams>(new Mock<IMcpServer>().Object) { Params = new() { Uri = "resource://mcp/Test" } },
|
||||
new RequestContext<ReadResourceRequestParams>(new Mock<IMcpServer>().Object, CreateTestJsonRpcRequest()) { Params = new() { Uri = "resource://mcp/Test" } },
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("42", ((TextResourceContents)result.Contents[0]).Text);
|
||||
|
|
@ -512,7 +522,7 @@ public partial class McpServerResourceTests
|
|||
_ => new DisposableResourceType());
|
||||
|
||||
var result = await resource1.ReadAsync(
|
||||
new RequestContext<ReadResourceRequestParams>(new Mock<IMcpServer>().Object) { Params = new() { Uri = "test://static/resource/instanceMethod" } },
|
||||
new RequestContext<ReadResourceRequestParams>(new Mock<IMcpServer>().Object, CreateTestJsonRpcRequest()) { Params = new() { Uri = "test://static/resource/instanceMethod" } },
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("0", ((TextResourceContents)result.Contents[0]).Text);
|
||||
|
|
@ -530,7 +540,7 @@ public partial class McpServerResourceTests
|
|||
return new ReadResourceResult { Contents = new List<ResourceContents> { new TextResourceContents { Text = "hello" } } };
|
||||
}, new() { Name = "Test" });
|
||||
var result = await resource.ReadAsync(
|
||||
new RequestContext<ReadResourceRequestParams>(mockServer.Object) { Params = new() { Uri = "resource://mcp/Test" } },
|
||||
new RequestContext<ReadResourceRequestParams>(mockServer.Object, CreateTestJsonRpcRequest()) { Params = new() { Uri = "resource://mcp/Test" } },
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(result);
|
||||
Assert.Single(result.Contents);
|
||||
|
|
@ -547,7 +557,7 @@ public partial class McpServerResourceTests
|
|||
return new TextResourceContents { Text = "hello" };
|
||||
}, new() { Name = "Test", SerializerOptions = JsonContext6.Default.Options });
|
||||
var result = await resource.ReadAsync(
|
||||
new RequestContext<ReadResourceRequestParams>(mockServer.Object) { Params = new() { Uri = "resource://mcp/Test" } },
|
||||
new RequestContext<ReadResourceRequestParams>(mockServer.Object, CreateTestJsonRpcRequest()) { Params = new() { Uri = "resource://mcp/Test" } },
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(result);
|
||||
Assert.Single(result.Contents);
|
||||
|
|
@ -568,7 +578,7 @@ public partial class McpServerResourceTests
|
|||
];
|
||||
}, new() { Name = "Test" });
|
||||
var result = await resource.ReadAsync(
|
||||
new RequestContext<ReadResourceRequestParams>(mockServer.Object) { Params = new() { Uri = "resource://mcp/Test" } },
|
||||
new RequestContext<ReadResourceRequestParams>(mockServer.Object, CreateTestJsonRpcRequest()) { Params = new() { Uri = "resource://mcp/Test" } },
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(2, result.Contents.Count);
|
||||
|
|
@ -586,7 +596,7 @@ public partial class McpServerResourceTests
|
|||
return "42";
|
||||
}, new() { Name = "Test" });
|
||||
var result = await resource.ReadAsync(
|
||||
new RequestContext<ReadResourceRequestParams>(mockServer.Object) { Params = new() { Uri = "resource://mcp/Test" } },
|
||||
new RequestContext<ReadResourceRequestParams>(mockServer.Object, CreateTestJsonRpcRequest()) { Params = new() { Uri = "resource://mcp/Test" } },
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(result);
|
||||
Assert.Single(result.Contents);
|
||||
|
|
@ -603,7 +613,7 @@ public partial class McpServerResourceTests
|
|||
return new List<string> { "42", "43" };
|
||||
}, new() { Name = "Test", SerializerOptions = JsonContext6.Default.Options });
|
||||
var result = await resource.ReadAsync(
|
||||
new RequestContext<ReadResourceRequestParams>(mockServer.Object) { Params = new() { Uri = "resource://mcp/Test" } },
|
||||
new RequestContext<ReadResourceRequestParams>(mockServer.Object, CreateTestJsonRpcRequest()) { Params = new() { Uri = "resource://mcp/Test" } },
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(2, result.Contents.Count);
|
||||
|
|
@ -621,7 +631,7 @@ public partial class McpServerResourceTests
|
|||
return new DataContent(new byte[] { 0, 1, 2 }, "application/octet-stream");
|
||||
}, new() { Name = "Test" });
|
||||
var result = await resource.ReadAsync(
|
||||
new RequestContext<ReadResourceRequestParams>(mockServer.Object) { Params = new() { Uri = "resource://mcp/Test" } },
|
||||
new RequestContext<ReadResourceRequestParams>(mockServer.Object, CreateTestJsonRpcRequest()) { Params = new() { Uri = "resource://mcp/Test" } },
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(result);
|
||||
Assert.Single(result.Contents);
|
||||
|
|
@ -643,7 +653,7 @@ public partial class McpServerResourceTests
|
|||
};
|
||||
}, new() { Name = "Test", SerializerOptions = JsonContext6.Default.Options });
|
||||
var result = await resource.ReadAsync(
|
||||
new RequestContext<ReadResourceRequestParams>(mockServer.Object) { Params = new() { Uri = "resource://mcp/Test" } },
|
||||
new RequestContext<ReadResourceRequestParams>(mockServer.Object, CreateTestJsonRpcRequest()) { Params = new() { Uri = "resource://mcp/Test" } },
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(2, result.Contents.Count);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
using Json.Schema;
|
||||
using Json.Schema;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ModelContextProtocol.Protocol;
|
||||
using ModelContextProtocol.Server;
|
||||
using ModelContextProtocol.Tests.Utils;
|
||||
using Moq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
|
@ -18,6 +16,16 @@ namespace ModelContextProtocol.Tests.Server;
|
|||
|
||||
public partial class McpServerToolTests
|
||||
{
|
||||
private static JsonRpcRequest CreateTestJsonRpcRequest()
|
||||
{
|
||||
return new JsonRpcRequest
|
||||
{
|
||||
Id = new RequestId("test-id"),
|
||||
Method = "test/method",
|
||||
Params = null
|
||||
};
|
||||
}
|
||||
|
||||
public McpServerToolTests()
|
||||
{
|
||||
#if !NET
|
||||
|
|
@ -53,7 +61,7 @@ public partial class McpServerToolTests
|
|||
Assert.DoesNotContain("server", JsonSerializer.Serialize(tool.ProtocolTool.InputSchema, McpJsonUtilities.DefaultOptions));
|
||||
|
||||
var result = await tool.InvokeAsync(
|
||||
new RequestContext<CallToolRequestParams>(mockServer.Object),
|
||||
new RequestContext<CallToolRequestParams>(mockServer.Object, CreateTestJsonRpcRequest()),
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.Equal("42", (result.Content[0] as TextContentBlock)?.Text);
|
||||
}
|
||||
|
|
@ -79,7 +87,7 @@ public partial class McpServerToolTests
|
|||
}, new() { Services = services });
|
||||
|
||||
var result = await tool.InvokeAsync(
|
||||
new RequestContext<CallToolRequestParams>(mockServer.Object),
|
||||
new RequestContext<CallToolRequestParams>(mockServer.Object, CreateTestJsonRpcRequest()),
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(result);
|
||||
Assert.NotNull(result.Content);
|
||||
|
|
@ -156,13 +164,14 @@ public partial class McpServerToolTests
|
|||
|
||||
Mock<IMcpServer> mockServer = new();
|
||||
|
||||
var result = await tool.InvokeAsync(
|
||||
new RequestContext<CallToolRequestParams>(mockServer.Object),
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.True(result.IsError);
|
||||
var ex = await Assert.ThrowsAsync<ArgumentException>(async () => await tool.InvokeAsync(
|
||||
new RequestContext<CallToolRequestParams>(mockServer.Object, CreateTestJsonRpcRequest()),
|
||||
TestContext.Current.CancellationToken));
|
||||
|
||||
result = await tool.InvokeAsync(
|
||||
new RequestContext<CallToolRequestParams>(mockServer.Object) { Services = services },
|
||||
mockServer.SetupGet(s => s.Services).Returns(services);
|
||||
|
||||
var result = await tool.InvokeAsync(
|
||||
new RequestContext<CallToolRequestParams>(mockServer.Object, CreateTestJsonRpcRequest()) { Services = services },
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.Equal("42", (result.Content[0] as TextContentBlock)?.Text);
|
||||
}
|
||||
|
|
@ -183,7 +192,7 @@ public partial class McpServerToolTests
|
|||
}, new() { Services = services });
|
||||
|
||||
var result = await tool.InvokeAsync(
|
||||
new RequestContext<CallToolRequestParams>(new Mock<IMcpServer>().Object),
|
||||
new RequestContext<CallToolRequestParams>(new Mock<IMcpServer>().Object, CreateTestJsonRpcRequest()),
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.Equal("42", (result.Content[0] as TextContentBlock)?.Text);
|
||||
}
|
||||
|
|
@ -198,7 +207,7 @@ public partial class McpServerToolTests
|
|||
options);
|
||||
|
||||
var result = await tool1.InvokeAsync(
|
||||
new RequestContext<CallToolRequestParams>(new Mock<IMcpServer>().Object),
|
||||
new RequestContext<CallToolRequestParams>(new Mock<IMcpServer>().Object, CreateTestJsonRpcRequest()),
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.Equal("""{"disposals":1}""", (result.Content[0] as TextContentBlock)?.Text);
|
||||
}
|
||||
|
|
@ -213,7 +222,7 @@ public partial class McpServerToolTests
|
|||
options);
|
||||
|
||||
var result = await tool1.InvokeAsync(
|
||||
new RequestContext<CallToolRequestParams>(new Mock<IMcpServer>().Object),
|
||||
new RequestContext<CallToolRequestParams>(new Mock<IMcpServer>().Object, CreateTestJsonRpcRequest()),
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.Equal("""{"asyncDisposals":1}""", (result.Content[0] as TextContentBlock)?.Text);
|
||||
}
|
||||
|
|
@ -232,7 +241,7 @@ public partial class McpServerToolTests
|
|||
options);
|
||||
|
||||
var result = await tool1.InvokeAsync(
|
||||
new RequestContext<CallToolRequestParams>(new Mock<IMcpServer>().Object) { Services = services },
|
||||
new RequestContext<CallToolRequestParams>(new Mock<IMcpServer>().Object, CreateTestJsonRpcRequest()) { Services = services },
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.Equal("""{"asyncDisposals":1,"disposals":0}""", (result.Content[0] as TextContentBlock)?.Text);
|
||||
}
|
||||
|
|
@ -253,7 +262,7 @@ public partial class McpServerToolTests
|
|||
}, new() { SerializerOptions = JsonContext2.Default.Options });
|
||||
|
||||
var result = await tool.InvokeAsync(
|
||||
new RequestContext<CallToolRequestParams>(mockServer.Object),
|
||||
new RequestContext<CallToolRequestParams>(mockServer.Object, CreateTestJsonRpcRequest()),
|
||||
TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Equal(3, result.Content.Count);
|
||||
|
|
@ -287,7 +296,7 @@ public partial class McpServerToolTests
|
|||
});
|
||||
|
||||
var result = await tool.InvokeAsync(
|
||||
new RequestContext<CallToolRequestParams>(mockServer.Object),
|
||||
new RequestContext<CallToolRequestParams>(mockServer.Object, CreateTestJsonRpcRequest()),
|
||||
TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Single(result.Content);
|
||||
|
|
@ -323,7 +332,7 @@ public partial class McpServerToolTests
|
|||
return (string?)null;
|
||||
});
|
||||
var result = await tool.InvokeAsync(
|
||||
new RequestContext<CallToolRequestParams>(mockServer.Object),
|
||||
new RequestContext<CallToolRequestParams>(mockServer.Object, CreateTestJsonRpcRequest()),
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.Empty(result.Content);
|
||||
}
|
||||
|
|
@ -338,7 +347,7 @@ public partial class McpServerToolTests
|
|||
return "42";
|
||||
});
|
||||
var result = await tool.InvokeAsync(
|
||||
new RequestContext<CallToolRequestParams>(mockServer.Object),
|
||||
new RequestContext<CallToolRequestParams>(mockServer.Object, CreateTestJsonRpcRequest()),
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.Single(result.Content);
|
||||
Assert.Equal("42", Assert.IsType<TextContentBlock>(result.Content[0]).Text);
|
||||
|
|
@ -354,7 +363,7 @@ public partial class McpServerToolTests
|
|||
return new List<string> { "42", "43" };
|
||||
}, new() { SerializerOptions = JsonContext2.Default.Options });
|
||||
var result = await tool.InvokeAsync(
|
||||
new RequestContext<CallToolRequestParams>(mockServer.Object),
|
||||
new RequestContext<CallToolRequestParams>(mockServer.Object, CreateTestJsonRpcRequest()),
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.Single(result.Content);
|
||||
Assert.Equal("""["42","43"]""", Assert.IsType<TextContentBlock>(result.Content[0]).Text);
|
||||
|
|
@ -370,7 +379,7 @@ public partial class McpServerToolTests
|
|||
return new TextContentBlock { Text = "42" };
|
||||
});
|
||||
var result = await tool.InvokeAsync(
|
||||
new RequestContext<CallToolRequestParams>(mockServer.Object),
|
||||
new RequestContext<CallToolRequestParams>(mockServer.Object, CreateTestJsonRpcRequest()),
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.Single(result.Content);
|
||||
Assert.Equal("42", Assert.IsType<TextContentBlock>(result.Content[0]).Text);
|
||||
|
|
@ -386,12 +395,12 @@ public partial class McpServerToolTests
|
|||
Assert.Same(mockServer.Object, server);
|
||||
return (IList<ContentBlock>)
|
||||
[
|
||||
new TextContentBlock { Text = "42" },
|
||||
new ImageContentBlock { Data = "1234", MimeType = "image/png" }
|
||||
new TextContentBlock { Text = "42" },
|
||||
new ImageContentBlock { Data = "1234", MimeType = "image/png" }
|
||||
];
|
||||
});
|
||||
var result = await tool.InvokeAsync(
|
||||
new RequestContext<CallToolRequestParams>(mockServer.Object),
|
||||
new RequestContext<CallToolRequestParams>(mockServer.Object, CreateTestJsonRpcRequest()),
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.Equal(2, result.Content.Count);
|
||||
Assert.Equal("42", Assert.IsType<TextContentBlock>(result.Content[0]).Text);
|
||||
|
|
@ -414,7 +423,7 @@ public partial class McpServerToolTests
|
|||
return response;
|
||||
});
|
||||
var result = await tool.InvokeAsync(
|
||||
new RequestContext<CallToolRequestParams>(mockServer.Object),
|
||||
new RequestContext<CallToolRequestParams>(mockServer.Object, CreateTestJsonRpcRequest()),
|
||||
TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Same(response, result);
|
||||
|
|
@ -447,45 +456,6 @@ public partial class McpServerToolTests
|
|||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ToolCallError_LogsErrorMessage()
|
||||
{
|
||||
// Arrange
|
||||
var mockLoggerProvider = new MockLoggerProvider();
|
||||
var loggerFactory = new LoggerFactory(new[] { mockLoggerProvider });
|
||||
var services = new ServiceCollection();
|
||||
services.AddSingleton<ILoggerFactory>(loggerFactory);
|
||||
var serviceProvider = services.BuildServiceProvider();
|
||||
|
||||
var toolName = "tool-that-throws";
|
||||
var exceptionMessage = "Test exception message";
|
||||
|
||||
McpServerTool tool = McpServerTool.Create(() =>
|
||||
{
|
||||
throw new InvalidOperationException(exceptionMessage);
|
||||
}, new() { Name = toolName, Services = serviceProvider });
|
||||
|
||||
var mockServer = new Mock<IMcpServer>();
|
||||
var request = new RequestContext<CallToolRequestParams>(mockServer.Object)
|
||||
{
|
||||
Params = new CallToolRequestParams { Name = toolName },
|
||||
Services = serviceProvider
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = await tool.InvokeAsync(request, TestContext.Current.CancellationToken);
|
||||
|
||||
// Assert
|
||||
Assert.True(result.IsError);
|
||||
Assert.Single(result.Content);
|
||||
Assert.Equal($"An error occurred invoking '{toolName}'.", Assert.IsType<TextContentBlock>(result.Content[0]).Text);
|
||||
|
||||
var errorLog = Assert.Single(mockLoggerProvider.LogMessages, m => m.LogLevel == LogLevel.Error);
|
||||
Assert.Equal($"\"{toolName}\" threw an unhandled exception.", errorLog.Message);
|
||||
Assert.IsType<InvalidOperationException>(errorLog.Exception);
|
||||
Assert.Equal(exceptionMessage, errorLog.Exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(StructuredOutput_ReturnsExpectedSchema_Inputs))]
|
||||
public async Task StructuredOutput_Enabled_ReturnsExpectedSchema<T>(T value)
|
||||
|
|
@ -493,7 +463,7 @@ public partial class McpServerToolTests
|
|||
JsonSerializerOptions options = new() { TypeInfoResolver = new DefaultJsonTypeInfoResolver() };
|
||||
McpServerTool tool = McpServerTool.Create(() => value, new() { Name = "tool", UseStructuredContent = true, SerializerOptions = options });
|
||||
var mockServer = new Mock<IMcpServer>();
|
||||
var request = new RequestContext<CallToolRequestParams>(mockServer.Object)
|
||||
var request = new RequestContext<CallToolRequestParams>(mockServer.Object, CreateTestJsonRpcRequest())
|
||||
{
|
||||
Params = new CallToolRequestParams { Name = "tool" },
|
||||
};
|
||||
|
|
@ -511,7 +481,7 @@ public partial class McpServerToolTests
|
|||
{
|
||||
McpServerTool tool = McpServerTool.Create(() => { });
|
||||
var mockServer = new Mock<IMcpServer>();
|
||||
var request = new RequestContext<CallToolRequestParams>(mockServer.Object)
|
||||
var request = new RequestContext<CallToolRequestParams>(mockServer.Object, CreateTestJsonRpcRequest())
|
||||
{
|
||||
Params = new CallToolRequestParams { Name = "tool" },
|
||||
};
|
||||
|
|
@ -522,7 +492,7 @@ public partial class McpServerToolTests
|
|||
Assert.Null(result.StructuredContent);
|
||||
|
||||
tool = McpServerTool.Create(() => Task.CompletedTask);
|
||||
request = new RequestContext<CallToolRequestParams>(mockServer.Object)
|
||||
request = new RequestContext<CallToolRequestParams>(mockServer.Object, CreateTestJsonRpcRequest())
|
||||
{
|
||||
Params = new CallToolRequestParams { Name = "tool" },
|
||||
};
|
||||
|
|
@ -533,7 +503,7 @@ public partial class McpServerToolTests
|
|||
Assert.Null(result.StructuredContent);
|
||||
|
||||
tool = McpServerTool.Create(() => default(ValueTask));
|
||||
request = new RequestContext<CallToolRequestParams>(mockServer.Object)
|
||||
request = new RequestContext<CallToolRequestParams>(mockServer.Object, CreateTestJsonRpcRequest())
|
||||
{
|
||||
Params = new CallToolRequestParams { Name = "tool" },
|
||||
};
|
||||
|
|
@ -551,7 +521,7 @@ public partial class McpServerToolTests
|
|||
JsonSerializerOptions options = new() { TypeInfoResolver = new DefaultJsonTypeInfoResolver() };
|
||||
McpServerTool tool = McpServerTool.Create(() => value, new() { UseStructuredContent = false, SerializerOptions = options });
|
||||
var mockServer = new Mock<IMcpServer>();
|
||||
var request = new RequestContext<CallToolRequestParams>(mockServer.Object)
|
||||
var request = new RequestContext<CallToolRequestParams>(mockServer.Object, CreateTestJsonRpcRequest())
|
||||
{
|
||||
Params = new CallToolRequestParams { Name = "tool" },
|
||||
};
|
||||
|
|
@ -592,7 +562,7 @@ public partial class McpServerToolTests
|
|||
yield return new object[] { new() };
|
||||
yield return new object[] { new List<string> { "item1", "item2" } };
|
||||
yield return new object[] { new Dictionary<string, int> { ["key1"] = 1, ["key2"] = 2 } };
|
||||
yield return new object[] { new Person("John", 27) };
|
||||
yield return new object[] { new Person("John", 27) };
|
||||
}
|
||||
|
||||
private sealed class MyService;
|
||||
|
|
|
|||
Loading…
Reference in a new issue