From 33d2b0d76272b6a01e04afbc2fce7735e63bbe88 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Sun, 9 Mar 2025 06:16:00 -0400 Subject: [PATCH] Use DescriptionAttribute and HasDefaultValue in server builder extensions (#46) --- .../TestServerWithHosting/Tools/EchoTool.cs | 5 ++-- .../Tools/SampleLlmTool.cs | 7 +++-- .../McpServerBuilderExtensions.Tools.cs | 21 +++++++------- src/mcpdotnet/Server/McpParameterAttribute.cs | 29 ------------------- src/mcpdotnet/Server/McpToolAttribute.cs | 13 ++------- .../McpServerBuilderExtensionsToolsTests.cs | 13 +++++---- 6 files changed, 27 insertions(+), 61 deletions(-) delete mode 100644 src/mcpdotnet/Server/McpParameterAttribute.cs diff --git a/samples/TestServerWithHosting/Tools/EchoTool.cs b/samples/TestServerWithHosting/Tools/EchoTool.cs index c215746..25c9f22 100644 --- a/samples/TestServerWithHosting/Tools/EchoTool.cs +++ b/samples/TestServerWithHosting/Tools/EchoTool.cs @@ -1,12 +1,13 @@ using McpDotNet.Server; +using System.ComponentModel; namespace TestServerWithHosting.Tools; [McpToolType] public static class EchoTool { - [McpTool(description: "Echoes the input back to the client.")] - public static string Echo([McpParameter(true)] string message) + [McpTool, Description("Echoes the input back to the client.")] + public static string Echo(string message) { return "hello " + message; } diff --git a/samples/TestServerWithHosting/Tools/SampleLlmTool.cs b/samples/TestServerWithHosting/Tools/SampleLlmTool.cs index 3bd2fed..a49669f 100644 --- a/samples/TestServerWithHosting/Tools/SampleLlmTool.cs +++ b/samples/TestServerWithHosting/Tools/SampleLlmTool.cs @@ -1,5 +1,6 @@ using McpDotNet.Protocol.Types; using McpDotNet.Server; +using System.ComponentModel; namespace TestServerWithHosting.Tools; @@ -16,10 +17,10 @@ public class SampleLlmTool _server = server ?? throw new ArgumentNullException(nameof(server)); } - [McpTool(name: "sampleLLM", description: "Samples from an LLM using MCP's sampling feature")] + [McpTool("sampleLLM"), Description("Samples from an LLM using MCP's sampling feature")] public async Task SampleLLM( - [McpParameter(true, description: "The prompt to send to the LLM")] string prompt, - [McpParameter(true, description: "Maximum number of tokens to generate")] int maxTokens, + [Description("The prompt to send to the LLM")] string prompt, + [Description("Maximum number of tokens to generate")] int maxTokens, CancellationToken cancellationToken) { var samplingParams = CreateRequestSamplingParams(prompt ?? string.Empty, "sampleLLM", maxTokens); diff --git a/src/mcpdotnet/Configuration/McpServerBuilderExtensions.Tools.cs b/src/mcpdotnet/Configuration/McpServerBuilderExtensions.Tools.cs index a272b5a..639db23 100644 --- a/src/mcpdotnet/Configuration/McpServerBuilderExtensions.Tools.cs +++ b/src/mcpdotnet/Configuration/McpServerBuilderExtensions.Tools.cs @@ -1,4 +1,5 @@ -using System.Globalization; +using System.ComponentModel; +using System.Globalization; using System.Reflection; using System.Text.Json; using McpDotNet.Configuration; @@ -117,7 +118,7 @@ public static partial class McpServerBuilderExtensions return WithTools(builder, toolTypes.ToArray()); } - private static Tool CreateTool(MethodInfo method, McpToolAttribute attribute) + private static Tool CreateTool(MethodInfo method, McpToolAttribute mcpAttribute) { Dictionary properties = []; List? requiredProperties = null; @@ -127,15 +128,15 @@ public static partial class McpServerBuilderExtensions if (parameter.ParameterType == typeof(CancellationToken)) continue; - var parameterAttribute = parameter.GetCustomAttribute(); + var parameterDescriptionAttr = parameter.GetCustomAttribute(); properties.Add(parameter.Name ?? "NoName", new JsonSchemaProperty() { Type = GetParameterType(parameter.ParameterType), - Description = parameterAttribute?.Description + Description = parameterDescriptionAttr?.Description, }); - if (parameterAttribute?.Required == true) + if (!parameter.HasDefaultValue) { requiredProperties ??= []; requiredProperties.Add(parameter.Name ?? "NoName"); @@ -144,8 +145,8 @@ public static partial class McpServerBuilderExtensions return new Tool() { - Name = attribute.Name ?? method.Name, - Description = attribute.Description, + Name = mcpAttribute.Name ?? method.Name, + Description = method.GetCustomAttribute()?.Description, InputSchema = new JsonSchema() { Type = "object", @@ -229,12 +230,10 @@ public static partial class McpServerBuilderExtensions } else { - var parameterAttribute = parameter.GetCustomAttribute(); - - if (parameterAttribute?.Required == true) + if (!parameter.HasDefaultValue) throw new McpServerException($"Missing required argument '{parameter.Name}'."); - parameters.Add(parameter.HasDefaultValue ? parameter.DefaultValue : null); + parameters.Add(parameter.DefaultValue); } } diff --git a/src/mcpdotnet/Server/McpParameterAttribute.cs b/src/mcpdotnet/Server/McpParameterAttribute.cs deleted file mode 100644 index 3d8f0a6..0000000 --- a/src/mcpdotnet/Server/McpParameterAttribute.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace McpDotNet.Server; - -/// -/// Attribute to mark a method parameter -/// -[AttributeUsage(AttributeTargets.Parameter)] -public sealed class McpParameterAttribute : Attribute -{ - /// - /// Defines if the parameter is required. - /// - public bool Required { get; } - - /// - /// A description of the parameter. - /// - public string? Description { get; } - - /// - /// Attribute to mark a method as an MCP tool. - /// - /// True if the parameter is mandatory. - /// A description of the tool. - public McpParameterAttribute(bool required = false, string? description = null) - { - Required = required; - Description = description; - } -} diff --git a/src/mcpdotnet/Server/McpToolAttribute.cs b/src/mcpdotnet/Server/McpToolAttribute.cs index 9f14a36..a84e2da 100644 --- a/src/mcpdotnet/Server/McpToolAttribute.cs +++ b/src/mcpdotnet/Server/McpToolAttribute.cs @@ -6,22 +6,15 @@ [AttributeUsage(AttributeTargets.Method)] public sealed class McpToolAttribute : Attribute { - /// The name of the tool. If not provided, the class name will be used. + /// Gets the name of the tool. + /// If not provided, the method name will be used. public string? Name { get; } - - /// - /// A description of the tool. - /// - public string? Description { get; } - /// /// Attribute to mark a method as an MCP tool. /// /// The name of the tool. If not provided, the class name will be used. - /// A description of the tool. - public McpToolAttribute(string? name = null, string? description = null) + public McpToolAttribute(string? name = null) { Name = name; - Description = description; } } diff --git a/tests/mcpdotnet.Tests/McpServerBuilderExtensionsToolsTests.cs b/tests/mcpdotnet.Tests/McpServerBuilderExtensionsToolsTests.cs index b9b9df7..0c2eca6 100644 --- a/tests/mcpdotnet.Tests/McpServerBuilderExtensionsToolsTests.cs +++ b/tests/mcpdotnet.Tests/McpServerBuilderExtensionsToolsTests.cs @@ -3,6 +3,7 @@ using McpDotNet.Server; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Moq; +using System.ComponentModel; namespace McpDotNet.Tests; @@ -131,14 +132,14 @@ public class McpServerBuilderExtensionsToolsTests private static class EchoTool { - [McpTool(description: "Echoes the input back to the client.")] - public static string Echo([McpParameter(true, description: "the echoes message")] string message) + [McpTool, Description("Echoes the input back to the client.")] + public static string Echo([Description("the echoes message")] string message) { return "hello " + message; } - [McpTool(name: "double_echo", description: "Echoes the input back to the client.")] - public static string Echo2([McpParameter(true)] string message) + [McpTool("double_echo"), Description("Echoes the input back to the client.")] + public static string Echo2(string message) { return "hello hello" + message; } @@ -154,8 +155,8 @@ public class McpServerBuilderExtensionsToolsTests { } - [McpTool(description: "Echoes the input back to the client.")] - public Task Echo([McpParameter(true)] string message) + [McpTool, Description("Echoes the input back to the client.")] + public Task Echo(string message) { return Task.FromResult("hello " + message); }