Add WithXx overloads that take target instance (#706)

* Add WithXx overloads that take target instance
* Special-case enumerables
This commit is contained in:
Stephen Toub 2025-08-26 12:38:53 -04:00 • committed by GitHub
commit 2fa658e439
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -5,6 +5,8 @@ using Microsoft.Extensions.Options;
using ModelContextProtocol.Client;
using ModelContextProtocol.Protocol;
using ModelContextProtocol.Server;
using Moq;
using System.Collections;
using System.Collections.Concurrent;
using System.ComponentModel;
using System.IO.Pipelines;
@ -403,9 +405,11 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
Assert.Throws<ArgumentNullException>("tools", () => builder.WithTools((IEnumerable<McpServerTool>)null!));
Assert.Throws<ArgumentNullException>("toolTypes", () => builder.WithTools((IEnumerable<Type>)null!));
Assert.Throws<ArgumentNullException>("target", () => builder.WithTools<object>(target: null!));
IMcpServerBuilder nullBuilder = null!;
Assert.Throws<ArgumentNullException>("builder", () => nullBuilder.WithTools<object>());
Assert.Throws<ArgumentNullException>("builder", () => nullBuilder.WithTools(new object()));
Assert.Throws<ArgumentNullException>("builder", () => nullBuilder.WithTools(Array.Empty<Type>()));
Assert.Throws<ArgumentNullException>("builder", () => nullBuilder.WithToolsFromAssembly());
}
@ -503,6 +507,44 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
}
}
[Fact]
public async Task WithTools_TargetInstance_UsesTarget()
{
ServiceCollection sc = new();
var target = new EchoTool(new ObjectWithId());
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);
Assert.Equal(target.GetCtorParameter(), (result.Content[0] as TextContentBlock)?.Text);
}
[Fact]
public async Task WithTools_TargetInstance_UsesEnumerableImplementation()
{
ServiceCollection sc = new();
sc.AddMcpServer().WithTools(new MyToolProvider());
var tools = sc.BuildServiceProvider().GetServices<McpServerTool>().ToArray();
Assert.Equal(2, tools.Length);
Assert.Contains(tools, t => t.ProtocolTool.Name == "Returns42");
Assert.Contains(tools, t => t.ProtocolTool.Name == "Returns43");
}
private sealed class MyToolProvider : IEnumerable<McpServerTool>
{
public IEnumerator<McpServerTool> GetEnumerator()
{
yield return McpServerTool.Create(() => "42", new() { Name = "Returns42" });
yield return McpServerTool.Create(() => "43", new() { Name = "Returns43" });
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
[Fact]
public async Task Recognizes_Parameter_Types()
{