using McpDotNet.Configuration; using McpDotNet.Server; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Moq; namespace McpDotNet.Tests; public class McpServerBuilderExtensionsToolsTests { private readonly Mock _builder; private readonly ServiceCollection _services; public McpServerBuilderExtensionsToolsTests() { _services = new ServiceCollection(); _builder = new Mock(); _builder.SetupGet(b => b.Services).Returns(_services); } [Fact] public void Adds_Tools_To_Server() { McpServerBuilderExtensions.WithTools(_builder.Object, typeof(EchoTool)); var serviceProvider = _services.BuildServiceProvider(); var options = serviceProvider.GetRequiredService>().Value; Assert.NotNull(options.ListToolsHandler); Assert.NotNull(options.CallToolHandler); } [Fact] public async Task Can_List_Registered_Tool() { McpServerBuilderExtensions.WithTools(_builder.Object, typeof(EchoTool)); var serviceProvider = _services.BuildServiceProvider(); var options = serviceProvider.GetRequiredService>().Value; var result = await options.ListToolsHandler!(new(Mock.Of(), new()), CancellationToken.None); Assert.NotNull(result); Assert.NotEmpty(result.Tools); var tool = result.Tools[0]; Assert.Equal("Echo", tool.Name); Assert.Equal("Echoes the input back to the client.", tool.Description); Assert.NotNull(tool.InputSchema); Assert.Equal("object", tool.InputSchema.Type); Assert.NotNull(tool.InputSchema.Properties); Assert.NotEmpty(tool.InputSchema.Properties); Assert.Contains("message", tool.InputSchema.Properties); Assert.Equal("string", tool.InputSchema.Properties["message"].Type); Assert.Equal("the echoes message", tool.InputSchema.Properties["message"].Description); Assert.NotNull(tool.InputSchema.Required); Assert.NotEmpty(tool.InputSchema.Required); Assert.Contains("message", tool.InputSchema.Required); tool = result.Tools[1]; Assert.Equal("double_echo", tool.Name); Assert.Equal("Echoes the input back to the client.", tool.Description); } [Fact] public async Task Can_Call_Registered_Tool() { McpServerBuilderExtensions.WithTools(_builder.Object, typeof(EchoTool)); var serviceProvider = _services.BuildServiceProvider(); var options = serviceProvider.GetRequiredService>().Value; var result = await options.CallToolHandler!(new(Mock.Of(), new() { Name = "Echo", Arguments = new() { { "message", "Peter" } } }), CancellationToken.None); Assert.NotNull(result); Assert.NotNull(result.Content); Assert.NotEmpty(result.Content); Assert.Equal("hello Peter", result.Content[0].Text); } [Fact] public async Task Can_Call_Registered_Tool_With_Dependency_Injection() { bool dependencyInjectionCalled = false; _services.AddTransient(sp => { dependencyInjectionCalled = true; return Mock.Of(); }); McpServerBuilderExtensions.WithTools(_builder.Object, typeof(EchoToolWithDi)); var serviceProvider = _services.BuildServiceProvider(); var options = serviceProvider.GetRequiredService>().Value; var mcpServer = new Mock(); mcpServer.SetupGet(s => s.ServiceProvider).Returns(serviceProvider); var result = await options.CallToolHandler!(new(mcpServer.Object, new() { Name = "Echo", Arguments = new() { { "message", "Peter" } } }), CancellationToken.None); Assert.NotNull(result); Assert.NotNull(result.Content); Assert.NotEmpty(result.Content); Assert.Equal("hello Peter", result.Content[0].Text); Assert.True(dependencyInjectionCalled); } [Fact] public async Task Throws_Exception_On_Unknown_Tool() { McpServerBuilderExtensions.WithTools(_builder.Object, typeof(EchoTool)); var serviceProvider = _services.BuildServiceProvider(); var options = serviceProvider.GetRequiredService>().Value; var exception = await Assert.ThrowsAsync(async () => await options.CallToolHandler!(new(Mock.Of(), new() { Name = "NotRegisteredTool" }), CancellationToken.None)); Assert.Equal("Unknown tool: NotRegisteredTool", exception.Message); } [Fact] public async Task Throws_Exception_Missing_Parameter() { McpServerBuilderExtensions.WithTools(_builder.Object, typeof(EchoTool)); var serviceProvider = _services.BuildServiceProvider(); var options = serviceProvider.GetRequiredService>().Value; var exception = await Assert.ThrowsAsync(async () => await options.CallToolHandler!(new(Mock.Of(), new() { Name = "Echo" }), CancellationToken.None)); Assert.Equal("Missing required argument 'message'.", exception.Message); } private static class EchoTool { [McpTool(Description = "Echoes the input back to the client.")] public static string Echo([McpParameter(true, Description = "the echoes message")] string message) { return "hello " + message; } [McpTool(Name = "double_echo", Description = "Echoes the input back to the client.")] public static string Echo2([McpParameter(true)] string message) { return "hello hello" + message; } } public interface IDependendService { } private class EchoToolWithDi { public EchoToolWithDi(IDependendService service) { } [McpTool(Description = "Echoes the input back to the client.")] public Task Echo([McpParameter(true)] string message) { return Task.FromResult("hello " + message); } } }