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

@ -4,7 +4,10 @@ using Microsoft.Extensions.Options;
using ModelContextProtocol.Client;
using ModelContextProtocol.Protocol;
using ModelContextProtocol.Server;
using Moq;
using System.Collections;
using System.ComponentModel;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Channels;
@ -217,13 +220,63 @@ public partial class McpServerBuilderExtensionsPromptsTests : ClientServerTestBa
Assert.Throws<ArgumentNullException>("prompts", () => builder.WithPrompts((IEnumerable<McpServerPrompt>)null!));
Assert.Throws<ArgumentNullException>("promptTypes", () => builder.WithPrompts((IEnumerable<Type>)null!));
Assert.Throws<ArgumentNullException>("target", () => builder.WithPrompts<object>(target: null!));
IMcpServerBuilder nullBuilder = null!;
Assert.Throws<ArgumentNullException>("builder", () => nullBuilder.WithPrompts<object>());
Assert.Throws<ArgumentNullException>("builder", () => nullBuilder.WithPrompts(new object()));
Assert.Throws<ArgumentNullException>("builder", () => nullBuilder.WithPrompts(Array.Empty<Type>()));
Assert.Throws<ArgumentNullException>("builder", () => nullBuilder.WithPromptsFromAssembly());
}
[Fact]
public async Task WithPrompts_TargetInstance_UsesTarget()
{
ServiceCollection sc = new();
var target = new SimplePrompts(new ObjectWithId() { Id = "42" });
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)
{
Params = new GetPromptRequestParams
{
Name = "returns_string",
Arguments = new Dictionary<string, JsonElement>
{
["message"] = JsonSerializer.SerializeToElement("hello", AIJsonUtilities.DefaultOptions),
}
}
}, TestContext.Current.CancellationToken);
Assert.Equal(target.ReturnsString("hello"), (result.Messages[0].Content as TextContentBlock)?.Text);
}
[Fact]
public async Task WithPrompts_TargetInstance_UsesEnumerableImplementation()
{
ServiceCollection sc = new();
sc.AddMcpServer().WithPrompts(new MyPromptProvider());
var prompts = sc.BuildServiceProvider().GetServices<McpServerPrompt>().ToArray();
Assert.Equal(2, prompts.Length);
Assert.Contains(prompts, t => t.ProtocolPrompt.Name == "Returns42");
Assert.Contains(prompts, t => t.ProtocolPrompt.Name == "Returns43");
}
private sealed class MyPromptProvider : IEnumerable<McpServerPrompt>
{
public IEnumerator<McpServerPrompt> GetEnumerator()
{
yield return McpServerPrompt.Create(() => "42", new() { Name = "Returns42" });
yield return McpServerPrompt.Create(() => "43", new() { Name = "Returns43" });
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
[Fact]
public void Empty_Enumerables_Is_Allowed()
{