Skip to content

Commit b0cd654

Browse files
authored
Add WithTools/Prompts overloads that accept McpServerTool/Prompts (#319)
To avoid folks needing to know how you should be registering tool instances into DI.
1 parent 227f440 commit b0cd654

File tree

4 files changed

+58
-6
lines changed

4 files changed

+58
-6
lines changed

src/ModelContextProtocol/Configuration/McpServerBuilderExtensions.cs

+45-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,29 @@ public static partial class McpServerBuilderExtensions
5757

5858
/// <summary>Adds <see cref="McpServerTool"/> instances to the service collection backing <paramref name="builder"/>.</summary>
5959
/// <param name="builder">The builder instance.</param>
60-
/// <param name="toolTypes">Types with marked methods to add as tools to the server.</param>
60+
/// <param name="tools">The <see cref="McpServerTool"/> instances to add to the server.</param>
61+
/// <returns>The builder provided in <paramref name="builder"/>.</returns>
62+
/// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
63+
/// <exception cref="ArgumentNullException"><paramref name="tools"/> is <see langword="null"/>.</exception>
64+
public static IMcpServerBuilder WithTools(this IMcpServerBuilder builder, IEnumerable<McpServerTool> tools)
65+
{
66+
Throw.IfNull(builder);
67+
Throw.IfNull(tools);
68+
69+
foreach (var tool in tools)
70+
{
71+
if (tool is not null)
72+
{
73+
builder.Services.AddSingleton(tool);
74+
}
75+
}
76+
77+
return builder;
78+
}
79+
80+
/// <summary>Adds <see cref="McpServerTool"/> instances to the service collection backing <paramref name="builder"/>.</summary>
81+
/// <param name="builder">The builder instance.</param>
82+
/// <param name="toolTypes">Types with <see cref="McpServerToolAttribute"/>-attributed methods to add as tools to the server.</param>
6183
/// <param name="serializerOptions">The serializer options governing tool parameter marshalling.</param>
6284
/// <returns>The builder provided in <paramref name="builder"/>.</returns>
6385
/// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
@@ -173,6 +195,28 @@ where t.GetCustomAttribute<McpServerToolTypeAttribute>() is not null
173195
return builder;
174196
}
175197

198+
/// <summary>Adds <see cref="McpServerPrompt"/> instances to the service collection backing <paramref name="builder"/>.</summary>
199+
/// <param name="builder">The builder instance.</param>
200+
/// <param name="prompts">The <see cref="McpServerPrompt"/> instances to add to the server.</param>
201+
/// <returns>The builder provided in <paramref name="builder"/>.</returns>
202+
/// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
203+
/// <exception cref="ArgumentNullException"><paramref name="prompts"/> is <see langword="null"/>.</exception>
204+
public static IMcpServerBuilder WithPrompts(this IMcpServerBuilder builder, IEnumerable<McpServerPrompt> prompts)
205+
{
206+
Throw.IfNull(builder);
207+
Throw.IfNull(prompts);
208+
209+
foreach (var prompt in prompts)
210+
{
211+
if (prompt is not null)
212+
{
213+
builder.Services.AddSingleton(prompt);
214+
}
215+
}
216+
217+
return builder;
218+
}
219+
176220
/// <summary>Adds <see cref="McpServerPrompt"/> instances to the service collection backing <paramref name="builder"/>.</summary>
177221
/// <param name="builder">The builder instance.</param>
178222
/// <param name="promptTypes">Types with marked methods to add as prompts to the server.</param>

tests/ModelContextProtocol.Tests/Client/McpClientExtensionsTests.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@ protected override void ConfigureServices(ServiceCollection services, IMcpServer
2525
for (int f = 0; f < 10; f++)
2626
{
2727
string name = $"Method{f}";
28-
services.AddSingleton(McpServerTool.Create((int i) => $"{name} Result {i}", new() { Name = name }));
28+
mcpServerBuilder.WithTools([McpServerTool.Create((int i) => $"{name} Result {i}", new() { Name = name })]);
2929
}
30-
services.AddSingleton(McpServerTool.Create([McpServerTool(Destructive = false, OpenWorld = true)] (string i) => $"{i} Result", new() { Name = "ValuesSetViaAttr" }));
31-
services.AddSingleton(McpServerTool.Create([McpServerTool(Destructive = false, OpenWorld = true)] (string i) => $"{i} Result", new() { Name = "ValuesSetViaOptions", Destructive = true, OpenWorld = false, ReadOnly = true }));
30+
mcpServerBuilder.WithTools([McpServerTool.Create([McpServerTool(Destructive = false, OpenWorld = true)] (string i) => $"{i} Result", new() { Name = "ValuesSetViaAttr" })]);
31+
mcpServerBuilder.WithTools([McpServerTool.Create([McpServerTool(Destructive = false, OpenWorld = true)] (string i) => $"{i} Result", new() { Name = "ValuesSetViaOptions", Destructive = true, OpenWorld = false, ReadOnly = true })]);
3232
}
3333

3434
[Theory]

tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsPromptsTests.cs

+5-1
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ public void WithPrompts_InvalidArgs_Throws()
202202
{
203203
IMcpServerBuilder builder = new ServiceCollection().AddMcpServer();
204204

205+
Assert.Throws<ArgumentNullException>("prompts", () => builder.WithPrompts((IEnumerable<McpServerPrompt>)null!));
205206
Assert.Throws<ArgumentNullException>("promptTypes", () => builder.WithPrompts((IEnumerable<Type>)null!));
206207

207208
IMcpServerBuilder nullBuilder = null!;
@@ -215,6 +216,7 @@ public void Empty_Enumerables_Is_Allowed()
215216
{
216217
IMcpServerBuilder builder = new ServiceCollection().AddMcpServer();
217218

219+
builder.WithPrompts(prompts: []); // no exception
218220
builder.WithPrompts(promptTypes: []); // no exception
219221
builder.WithPrompts<object>(); // no exception even though no prompts exposed
220222
builder.WithPromptsFromAssembly(typeof(AIFunction).Assembly); // no exception even though no prompts exposed
@@ -236,13 +238,15 @@ public void Register_Prompts_From_Multiple_Sources()
236238
ServiceCollection sc = new();
237239
sc.AddMcpServer()
238240
.WithPrompts<SimplePrompts>()
239-
.WithPrompts<MorePrompts>(JsonContext4.Default.Options);
241+
.WithPrompts<MorePrompts>(JsonContext4.Default.Options)
242+
.WithPrompts([McpServerPrompt.Create(() => "42", new() { Name = "Returns42" })]);
240243
IServiceProvider services = sc.BuildServiceProvider();
241244

242245
Assert.Contains(services.GetServices<McpServerPrompt>(), t => t.ProtocolPrompt.Name == nameof(SimplePrompts.ReturnsChatMessages));
243246
Assert.Contains(services.GetServices<McpServerPrompt>(), t => t.ProtocolPrompt.Name == nameof(SimplePrompts.ThrowsException));
244247
Assert.Contains(services.GetServices<McpServerPrompt>(), t => t.ProtocolPrompt.Name == nameof(SimplePrompts.ReturnsString));
245248
Assert.Contains(services.GetServices<McpServerPrompt>(), t => t.ProtocolPrompt.Name == nameof(MorePrompts.AnotherPrompt));
249+
Assert.Contains(services.GetServices<McpServerPrompt>(), t => t.ProtocolPrompt.Name == "Returns42");
246250
}
247251

248252
[McpServerPromptType]

tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsToolsTests.cs

+5-1
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,7 @@ public void WithTools_InvalidArgs_Throws()
408408
{
409409
IMcpServerBuilder builder = new ServiceCollection().AddMcpServer();
410410

411+
Assert.Throws<ArgumentNullException>("tools", () => builder.WithTools((IEnumerable<McpServerTool>)null!));
411412
Assert.Throws<ArgumentNullException>("toolTypes", () => builder.WithTools((IEnumerable<Type>)null!));
412413

413414
IMcpServerBuilder nullBuilder = null!;
@@ -421,6 +422,7 @@ public void Empty_Enumerables_Is_Allowed()
421422
{
422423
IMcpServerBuilder builder = new ServiceCollection().AddMcpServer();
423424

425+
builder.WithTools(tools: []); // no exception
424426
builder.WithTools(toolTypes: []); // no exception
425427
builder.WithTools<object>(); // no exception even though no tools exposed
426428
builder.WithToolsFromAssembly(typeof(AIFunction).Assembly); // no exception even though no tools exposed
@@ -539,14 +541,16 @@ public void Register_Tools_From_Multiple_Sources()
539541
sc.AddMcpServer()
540542
.WithTools<EchoTool>(serializerOptions: BuilderToolsJsonContext.Default.Options)
541543
.WithTools<AnotherToolType>(serializerOptions: BuilderToolsJsonContext.Default.Options)
542-
.WithTools([typeof(ToolTypeWithNoAttribute)], BuilderToolsJsonContext.Default.Options);
544+
.WithTools([typeof(ToolTypeWithNoAttribute)], BuilderToolsJsonContext.Default.Options)
545+
.WithTools([McpServerTool.Create(() => "42", new() { Name = "Returns42" })]);
543546
IServiceProvider services = sc.BuildServiceProvider();
544547

545548
Assert.Contains(services.GetServices<McpServerTool>(), t => t.ProtocolTool.Name == "double_echo");
546549
Assert.Contains(services.GetServices<McpServerTool>(), t => t.ProtocolTool.Name == "DifferentName");
547550
Assert.Contains(services.GetServices<McpServerTool>(), t => t.ProtocolTool.Name == "MethodB");
548551
Assert.Contains(services.GetServices<McpServerTool>(), t => t.ProtocolTool.Name == "MethodC");
549552
Assert.Contains(services.GetServices<McpServerTool>(), t => t.ProtocolTool.Name == "MethodD");
553+
Assert.Contains(services.GetServices<McpServerTool>(), t => t.ProtocolTool.Name == "Returns42");
550554
}
551555

552556
[Fact]

0 commit comments

Comments
 (0)