csharp-sdk/tests/mcpdotnet.Tests/McpServerBuilderExtensionsToolsTests.cs
Toni Wenzel ec9bdfcf5d
Improve tool handling (#28)
* Improve tool handling

* Rework stuff with new hosting options

* Update readme

* Fix build

* Try run client tests

* Fix build

* Fix issues on linux

* Try to run testserver on linux

* Fix test issue

* Fix code smells
2025-03-07 16:48:55 +01:00

163 lines
6.1 KiB
C#

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<IMcpServerBuilder> _builder;
private readonly ServiceCollection _services;
public McpServerBuilderExtensionsToolsTests()
{
_services = new ServiceCollection();
_builder = new Mock<IMcpServerBuilder>();
_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<IOptions<McpServerDelegates>>().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<IOptions<McpServerDelegates>>().Value;
var result = await options.ListToolsHandler!(new(Mock.Of<IMcpServer>(), 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<IOptions<McpServerDelegates>>().Value;
var result = await options.CallToolHandler!(new(Mock.Of<IMcpServer>(), 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<IDependendService>(sp =>
{
dependencyInjectionCalled = true;
return Mock.Of<IDependendService>();
});
McpServerBuilderExtensions.WithTools(_builder.Object, typeof(EchoToolWithDi));
var serviceProvider = _services.BuildServiceProvider();
var options = serviceProvider.GetRequiredService<IOptions<McpServerDelegates>>().Value;
var mcpServer = new Mock<IMcpServer>();
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<IOptions<McpServerDelegates>>().Value;
var exception = await Assert.ThrowsAsync<McpServerException>(async () => await options.CallToolHandler!(new(Mock.Of<IMcpServer>(), 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<IOptions<McpServerDelegates>>().Value;
var exception = await Assert.ThrowsAsync<McpServerException>(async () => await options.CallToolHandler!(new(Mock.Of<IMcpServer>(), 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<string> Echo([McpParameter(true)] string message)
{
return Task.FromResult("hello " + message);
}
}
}