From 3993661b7c913c60f52b45ed4d39dcf2954aa5a1 Mon Sep 17 00:00:00 2001 From: Florian Courtial Date: Wed, 4 Mar 2026 21:43:49 +0100 Subject: [PATCH 1/2] Add an optional `AIJsonSchemaCreateOptions` to `WithToolsFromAssembly`. --- .../McpServerBuilderExtensions.cs | 14 ++-- .../McpServerBuilderExtensionsToolsTests.cs | 64 ++++++++++++++++++- 2 files changed, 72 insertions(+), 6 deletions(-) diff --git a/src/ModelContextProtocol/McpServerBuilderExtensions.cs b/src/ModelContextProtocol/McpServerBuilderExtensions.cs index da63dc31d..9147e1682 100644 --- a/src/ModelContextProtocol/McpServerBuilderExtensions.cs +++ b/src/ModelContextProtocol/McpServerBuilderExtensions.cs @@ -7,6 +7,7 @@ using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Text.Json; +using Microsoft.Extensions.AI; namespace Microsoft.Extensions.DependencyInjection; @@ -125,6 +126,7 @@ public static IMcpServerBuilder WithTools(this IMcpServerBuilder builder, IEnume /// The builder instance. /// Types with -attributed methods to add as tools to the server. /// The serializer options governing tool parameter marshalling. + /// The schema creation options governing tool parameter/output schema generation. /// The builder provided in . /// or is . /// @@ -133,7 +135,7 @@ public static IMcpServerBuilder WithTools(this IMcpServerBuilder builder, IEnume /// instance for each. For instance methods, an instance is constructed for each invocation of the tool. /// [RequiresUnreferencedCode(WithToolsRequiresUnreferencedCodeMessage)] - public static IMcpServerBuilder WithTools(this IMcpServerBuilder builder, IEnumerable toolTypes, JsonSerializerOptions? serializerOptions = null) + public static IMcpServerBuilder WithTools(this IMcpServerBuilder builder, IEnumerable toolTypes, JsonSerializerOptions? serializerOptions = null, AIJsonSchemaCreateOptions? schemaCreateOptions = null) { Throw.IfNull(builder); Throw.IfNull(toolTypes); @@ -147,8 +149,8 @@ public static IMcpServerBuilder WithTools(this IMcpServerBuilder builder, IEnume if (toolMethod.GetCustomAttribute() is not null) { builder.Services.AddSingleton((Func)(toolMethod.IsStatic ? - services => McpServerTool.Create(toolMethod, options: new() { Services = services, SerializerOptions = serializerOptions }) : - services => McpServerTool.Create(toolMethod, r => CreateTarget(r.Services, toolType), new() { Services = services, SerializerOptions = serializerOptions }))); + services => McpServerTool.Create(toolMethod, options: new() { Services = services, SerializerOptions = serializerOptions, SchemaCreateOptions = schemaCreateOptions }) : + services => McpServerTool.Create(toolMethod, r => CreateTarget(r.Services, toolType), new() { Services = services, SerializerOptions = serializerOptions, SchemaCreateOptions = schemaCreateOptions }))); } } } @@ -163,6 +165,7 @@ public static IMcpServerBuilder WithTools(this IMcpServerBuilder builder, IEnume /// The builder instance. /// The serializer options governing tool parameter marshalling. /// The assembly to load the types from. If , the calling assembly is used. + /// The schema creation options governing tool parameter/output schema generation. /// The builder provided in . /// is . /// @@ -186,7 +189,7 @@ public static IMcpServerBuilder WithTools(this IMcpServerBuilder builder, IEnume /// /// [RequiresUnreferencedCode(WithToolsRequiresUnreferencedCodeMessage)] - public static IMcpServerBuilder WithToolsFromAssembly(this IMcpServerBuilder builder, Assembly? toolAssembly = null, JsonSerializerOptions? serializerOptions = null) + public static IMcpServerBuilder WithToolsFromAssembly(this IMcpServerBuilder builder, Assembly? toolAssembly = null, JsonSerializerOptions? serializerOptions = null, AIJsonSchemaCreateOptions? schemaCreateOptions = null) { Throw.IfNull(builder); @@ -196,7 +199,8 @@ public static IMcpServerBuilder WithToolsFromAssembly(this IMcpServerBuilder bui from t in toolAssembly.GetTypes() where t.GetCustomAttribute() is not null select t, - serializerOptions); + serializerOptions, + schemaCreateOptions); } #endregion diff --git a/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsToolsTests.cs b/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsToolsTests.cs index 518b70f00..f50504e7c 100644 --- a/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsToolsTests.cs +++ b/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsToolsTests.cs @@ -482,7 +482,7 @@ public void WithTools_InvalidArgs_Throws() IMcpServerBuilder builder = new ServiceCollection().AddMcpServer(); Assert.Throws("tools", () => builder.WithTools((IEnumerable)null!)); - Assert.Throws("toolTypes", () => builder.WithTools((IEnumerable)null!)); + Assert.Throws("toolTypes", () => builder.WithTools(toolTypes: (IEnumerable)null!)); Assert.Throws("target", () => builder.WithTools(target: null!)); IMcpServerBuilder nullBuilder = null!; @@ -664,6 +664,57 @@ public void Register_Tools_From_Multiple_Sources() Assert.Contains(services.GetServices(), t => t.ProtocolTool.Name == "method_d"); Assert.Contains(services.GetServices(), t => t.ProtocolTool.Name == "Returns42"); } + + [Fact] + public void Register_Static_Tools_With_Custom_Schema_Create_Options() + { + var jsonString = "{\"value\":42}"; + var schemaCreateOptions = new AIJsonSchemaCreateOptions + { + TransformSchemaNode = (context, node) => JsonNode.Parse(jsonString)! + }; + + ServiceCollection sc = new(); + sc.AddMcpServer().WithTools([typeof(ToolTypeWithSchemaCreateOptions)], schemaCreateOptions: schemaCreateOptions); + IServiceProvider services = sc.BuildServiceProvider(); + + McpServerTool tool = services.GetServices().First(t => t.ProtocolTool.Name == "static_tool"); + Assert.Contains("42", tool.ProtocolTool.InputSchema.GetProperty("properties").GetProperty("input").GetProperty("value").ToString()); + } + + [Fact] + public void Register_Instance_Tools_With_Custom_Schema_Create_Options() + { + var jsonString = "{\"value\":42}"; + var schemaCreateOptions = new AIJsonSchemaCreateOptions + { + TransformSchemaNode = (context, node) => JsonNode.Parse(jsonString)! + }; + + ServiceCollection sc = new(); + sc.AddMcpServer().WithTools([typeof(ToolTypeWithSchemaCreateOptions)], schemaCreateOptions: schemaCreateOptions); + IServiceProvider services = sc.BuildServiceProvider(); + + McpServerTool tool = services.GetServices().First(t => t.ProtocolTool.Name == "instance_tool"); + Assert.Contains("42", tool.ProtocolTool.InputSchema.GetProperty("properties").GetProperty("input").GetProperty("value").ToString()); + } + + [Fact] + public void WithToolsFromAssembly_With_Custom_Schema_Create_Options() + { + var jsonString = "{\"value\":42}"; + var schemaCreateOptions = new AIJsonSchemaCreateOptions + { + TransformSchemaNode = (context, node) => JsonNode.Parse(jsonString)! + }; + + ServiceCollection sc = new(); + sc.AddMcpServer().WithToolsFromAssembly(schemaCreateOptions: schemaCreateOptions); + IServiceProvider services = sc.BuildServiceProvider(); + + McpServerTool tool = services.GetServices().First(t => t.ProtocolTool.Name == "instance_tool"); + Assert.Contains("42", tool.ProtocolTool.InputSchema.GetProperty("properties").GetProperty("input").GetProperty("value").ToString()); + } [Fact] public void Create_ExtractsToolAnnotations_AllSet() @@ -951,6 +1002,17 @@ public class ComplexObject public string? Name { get; set; } public int Age { get; set; } } + + [McpServerToolType] + internal class ToolTypeWithSchemaCreateOptions + { + [McpServerTool] + public static string StaticTool(string input) => input; + + [McpServerTool] + public string InstanceTool(string input) => input; + } + [JsonSerializable(typeof(bool))] [JsonSerializable(typeof(int))] From 672e7a5256556ac0dfc2db0edd36659f972dcee4 Mon Sep 17 00:00:00 2001 From: Florian Courtial Date: Wed, 4 Mar 2026 22:42:44 +0100 Subject: [PATCH 2/2] Use overload to maintain binary compatibility. --- .../McpServerBuilderExtensions.cs | 68 +++++++++++++++++-- .../McpServerBuilderExtensionsToolsTests.cs | 6 +- 2 files changed, 66 insertions(+), 8 deletions(-) diff --git a/src/ModelContextProtocol/McpServerBuilderExtensions.cs b/src/ModelContextProtocol/McpServerBuilderExtensions.cs index 9147e1682..41bc4d60d 100644 --- a/src/ModelContextProtocol/McpServerBuilderExtensions.cs +++ b/src/ModelContextProtocol/McpServerBuilderExtensions.cs @@ -121,7 +121,22 @@ public static IMcpServerBuilder WithTools(this IMcpServerBuilder builder, IEnume return builder; } - + + /// Adds instances to the service collection backing . + /// The builder instance. + /// Types with -attributed methods to add as tools to the server. + /// The serializer options governing tool parameter marshalling. + /// The builder provided in . + /// or is . + /// + /// This method discovers all instance and static methods (public and non-public) on the specified + /// types, where the methods are attributed as , and adds an + /// instance for each. For instance methods, an instance is constructed for each invocation of the tool. + /// + [RequiresUnreferencedCode(WithToolsRequiresUnreferencedCodeMessage)] + public static IMcpServerBuilder WithTools(this IMcpServerBuilder builder, IEnumerable toolTypes, JsonSerializerOptions? serializerOptions = null) => + builder.WithTools(toolTypes, schemaCreateOptions: null, serializerOptions); + /// Adds instances to the service collection backing . /// The builder instance. /// Types with -attributed methods to add as tools to the server. @@ -135,7 +150,7 @@ public static IMcpServerBuilder WithTools(this IMcpServerBuilder builder, IEnume /// instance for each. For instance methods, an instance is constructed for each invocation of the tool. /// [RequiresUnreferencedCode(WithToolsRequiresUnreferencedCodeMessage)] - public static IMcpServerBuilder WithTools(this IMcpServerBuilder builder, IEnumerable toolTypes, JsonSerializerOptions? serializerOptions = null, AIJsonSchemaCreateOptions? schemaCreateOptions = null) + public static IMcpServerBuilder WithTools(this IMcpServerBuilder builder, IEnumerable toolTypes, AIJsonSchemaCreateOptions? schemaCreateOptions, JsonSerializerOptions? serializerOptions = null) { Throw.IfNull(builder); Throw.IfNull(toolTypes); @@ -189,7 +204,50 @@ public static IMcpServerBuilder WithTools(this IMcpServerBuilder builder, IEnum /// /// [RequiresUnreferencedCode(WithToolsRequiresUnreferencedCodeMessage)] - public static IMcpServerBuilder WithToolsFromAssembly(this IMcpServerBuilder builder, Assembly? toolAssembly = null, JsonSerializerOptions? serializerOptions = null, AIJsonSchemaCreateOptions? schemaCreateOptions = null) + public static IMcpServerBuilder WithToolsFromAssembly(this IMcpServerBuilder builder, AIJsonSchemaCreateOptions? schemaCreateOptions, Assembly? toolAssembly = null, JsonSerializerOptions? serializerOptions = null) + { + Throw.IfNull(builder); + + toolAssembly ??= Assembly.GetCallingAssembly(); + + return builder.WithTools( + from t in toolAssembly.GetTypes() + where t.GetCustomAttribute() is not null + select t, + schemaCreateOptions, + serializerOptions); + } + + /// + /// Adds types marked with the attribute from the given assembly as tools to the server. + /// + /// The builder instance. + /// The serializer options governing tool parameter marshalling. + /// The assembly to load the types from. If , the calling assembly is used. + /// The builder provided in . + /// is . + /// + /// + /// This method scans the specified assembly (or the calling assembly if none is provided) for classes + /// marked with the . It then discovers all methods within those + /// classes that are marked with the and registers them as s + /// in the 's . + /// + /// + /// The method automatically handles both static and instance methods. For instance methods, a new instance + /// of the containing class is constructed for each invocation of the tool. + /// + /// + /// Tools registered through this method can be discovered by clients using the list_tools request + /// and invoked using the call_tool request. + /// + /// + /// Note that this method performs reflection at runtime and might not work in Native AOT scenarios. For + /// Native AOT compatibility, consider using the generic method instead. + /// + /// + [RequiresUnreferencedCode(WithToolsRequiresUnreferencedCodeMessage)] + public static IMcpServerBuilder WithToolsFromAssembly(this IMcpServerBuilder builder, Assembly? toolAssembly = null, JsonSerializerOptions? serializerOptions = null) { Throw.IfNull(builder); @@ -199,8 +257,8 @@ public static IMcpServerBuilder WithToolsFromAssembly(this IMcpServerBuilder bui from t in toolAssembly.GetTypes() where t.GetCustomAttribute() is not null select t, - serializerOptions, - schemaCreateOptions); + schemaCreateOptions: null, + serializerOptions); } #endregion diff --git a/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsToolsTests.cs b/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsToolsTests.cs index f50504e7c..e3896f788 100644 --- a/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsToolsTests.cs +++ b/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsToolsTests.cs @@ -675,7 +675,7 @@ public void Register_Static_Tools_With_Custom_Schema_Create_Options() }; ServiceCollection sc = new(); - sc.AddMcpServer().WithTools([typeof(ToolTypeWithSchemaCreateOptions)], schemaCreateOptions: schemaCreateOptions); + sc.AddMcpServer().WithTools([typeof(ToolTypeWithSchemaCreateOptions)], schemaCreateOptions); IServiceProvider services = sc.BuildServiceProvider(); McpServerTool tool = services.GetServices().First(t => t.ProtocolTool.Name == "static_tool"); @@ -692,7 +692,7 @@ public void Register_Instance_Tools_With_Custom_Schema_Create_Options() }; ServiceCollection sc = new(); - sc.AddMcpServer().WithTools([typeof(ToolTypeWithSchemaCreateOptions)], schemaCreateOptions: schemaCreateOptions); + sc.AddMcpServer().WithTools([typeof(ToolTypeWithSchemaCreateOptions)], schemaCreateOptions); IServiceProvider services = sc.BuildServiceProvider(); McpServerTool tool = services.GetServices().First(t => t.ProtocolTool.Name == "instance_tool"); @@ -709,7 +709,7 @@ public void WithToolsFromAssembly_With_Custom_Schema_Create_Options() }; ServiceCollection sc = new(); - sc.AddMcpServer().WithToolsFromAssembly(schemaCreateOptions: schemaCreateOptions); + sc.AddMcpServer().WithToolsFromAssembly(schemaCreateOptions); IServiceProvider services = sc.BuildServiceProvider(); McpServerTool tool = services.GetServices().First(t => t.ProtocolTool.Name == "instance_tool");