diff --git a/src/ModelContextProtocol/McpServerBuilderExtensions.cs b/src/ModelContextProtocol/McpServerBuilderExtensions.cs index da63dc31d..41bc4d60d 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; @@ -120,11 +121,27 @@ 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. /// 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 +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) + public static IMcpServerBuilder WithTools(this IMcpServerBuilder builder, IEnumerable toolTypes, AIJsonSchemaCreateOptions? schemaCreateOptions, JsonSerializerOptions? serializerOptions = null) { Throw.IfNull(builder); Throw.IfNull(toolTypes); @@ -147,8 +164,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 }))); } } } @@ -157,6 +174,50 @@ public static IMcpServerBuilder WithTools(this IMcpServerBuilder builder, IEnume return builder; } + /// + /// 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 schema creation options governing tool parameter/output schema generation. + /// 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, 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. /// @@ -196,6 +257,7 @@ public static IMcpServerBuilder WithToolsFromAssembly(this IMcpServerBuilder bui from t in toolAssembly.GetTypes() where t.GetCustomAttribute() is not null select t, + schemaCreateOptions: null, serializerOptions); } #endregion diff --git a/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsToolsTests.cs b/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsToolsTests.cs index 518b70f00..e3896f788 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); + 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); + 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); + 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))]