Use DescriptionAttribute and HasDefaultValue in server builder extensions (#46)

This commit is contained in:
Stephen Toub 2025-03-09 06:16:00 -04:00 • committed by GitHub
commit 33d2b0d762
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 27 additions and 61 deletions

View file

@ -1,12 +1,13 @@
using McpDotNet.Server; using McpDotNet.Server;
using System.ComponentModel;
namespace TestServerWithHosting.Tools; namespace TestServerWithHosting.Tools;
[McpToolType] [McpToolType]
public static class EchoTool public static class EchoTool
{ {
[McpTool(description: "Echoes the input back to the client.")] [McpTool, Description("Echoes the input back to the client.")]
public static string Echo([McpParameter(true)] string message) public static string Echo(string message)
{ {
return "hello " + message; return "hello " + message;
} }

View file

@ -1,5 +1,6 @@
using McpDotNet.Protocol.Types; using McpDotNet.Protocol.Types;
using McpDotNet.Server; using McpDotNet.Server;
using System.ComponentModel;
namespace TestServerWithHosting.Tools; namespace TestServerWithHosting.Tools;
@ -16,10 +17,10 @@ public class SampleLlmTool
_server = server ?? throw new ArgumentNullException(nameof(server)); _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<string> SampleLLM( public async Task<string> SampleLLM(
[McpParameter(true, description: "The prompt to send to the LLM")] string prompt, [Description("The prompt to send to the LLM")] string prompt,
[McpParameter(true, description: "Maximum number of tokens to generate")] int maxTokens, [Description("Maximum number of tokens to generate")] int maxTokens,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
var samplingParams = CreateRequestSamplingParams(prompt ?? string.Empty, "sampleLLM", maxTokens); var samplingParams = CreateRequestSamplingParams(prompt ?? string.Empty, "sampleLLM", maxTokens);

View file

@ -1,4 +1,5 @@
using System.Globalization; using System.ComponentModel;
using System.Globalization;
using System.Reflection; using System.Reflection;
using System.Text.Json; using System.Text.Json;
using McpDotNet.Configuration; using McpDotNet.Configuration;
@ -117,7 +118,7 @@ public static partial class McpServerBuilderExtensions
return WithTools(builder, toolTypes.ToArray()); return WithTools(builder, toolTypes.ToArray());
} }
private static Tool CreateTool(MethodInfo method, McpToolAttribute attribute) private static Tool CreateTool(MethodInfo method, McpToolAttribute mcpAttribute)
{ {
Dictionary<string, JsonSchemaProperty> properties = []; Dictionary<string, JsonSchemaProperty> properties = [];
List<string>? requiredProperties = null; List<string>? requiredProperties = null;
@ -127,15 +128,15 @@ public static partial class McpServerBuilderExtensions
if (parameter.ParameterType == typeof(CancellationToken)) if (parameter.ParameterType == typeof(CancellationToken))
continue; continue;
var parameterAttribute = parameter.GetCustomAttribute<McpParameterAttribute>(); var parameterDescriptionAttr = parameter.GetCustomAttribute<DescriptionAttribute>();
properties.Add(parameter.Name ?? "NoName", new JsonSchemaProperty() properties.Add(parameter.Name ?? "NoName", new JsonSchemaProperty()
{ {
Type = GetParameterType(parameter.ParameterType), Type = GetParameterType(parameter.ParameterType),
Description = parameterAttribute?.Description Description = parameterDescriptionAttr?.Description,
}); });
if (parameterAttribute?.Required == true) if (!parameter.HasDefaultValue)
{ {
requiredProperties ??= []; requiredProperties ??= [];
requiredProperties.Add(parameter.Name ?? "NoName"); requiredProperties.Add(parameter.Name ?? "NoName");
@ -144,8 +145,8 @@ public static partial class McpServerBuilderExtensions
return new Tool() return new Tool()
{ {
Name = attribute.Name ?? method.Name, Name = mcpAttribute.Name ?? method.Name,
Description = attribute.Description, Description = method.GetCustomAttribute<DescriptionAttribute>()?.Description,
InputSchema = new JsonSchema() InputSchema = new JsonSchema()
{ {
Type = "object", Type = "object",
@ -229,12 +230,10 @@ public static partial class McpServerBuilderExtensions
} }
else else
{ {
var parameterAttribute = parameter.GetCustomAttribute<McpParameterAttribute>(); if (!parameter.HasDefaultValue)
if (parameterAttribute?.Required == true)
throw new McpServerException($"Missing required argument '{parameter.Name}'."); throw new McpServerException($"Missing required argument '{parameter.Name}'.");
parameters.Add(parameter.HasDefaultValue ? parameter.DefaultValue : null); parameters.Add(parameter.DefaultValue);
} }
} }

View file

@ -1,29 +0,0 @@
namespace McpDotNet.Server;
/// <summary>
/// Attribute to mark a method parameter
/// </summary>
[AttributeUsage(AttributeTargets.Parameter)]
public sealed class McpParameterAttribute : Attribute
{
/// <summary>
/// Defines if the parameter is required.
/// </summary>
public bool Required { get; }
/// <summary>
/// A description of the parameter.
/// </summary>
public string? Description { get; }
/// <summary>
/// Attribute to mark a method as an MCP tool.
/// </summary>
/// <param name="required">True if the parameter is mandatory.</param>
/// <param name="description">A description of the tool.</param>
public McpParameterAttribute(bool required = false, string? description = null)
{
Required = required;
Description = description;
}
}

View file

@ -6,22 +6,15 @@
[AttributeUsage(AttributeTargets.Method)] [AttributeUsage(AttributeTargets.Method)]
public sealed class McpToolAttribute : Attribute public sealed class McpToolAttribute : Attribute
{ {
/// The name of the tool. If not provided, the class name will be used. /// <summary>Gets the name of the tool.</summary>
/// <remarks>If not provided, the method name will be used.</remarks>
public string? Name { get; } public string? Name { get; }
/// <summary>
/// A description of the tool.
/// </summary>
public string? Description { get; }
/// <summary> /// <summary>
/// Attribute to mark a method as an MCP tool. /// Attribute to mark a method as an MCP tool.
/// </summary> /// </summary>
/// <param name="name">The name of the tool. If not provided, the class name will be used.</param> /// <param name="name">The name of the tool. If not provided, the class name will be used.</param>
/// <param name="description">A description of the tool.</param> public McpToolAttribute(string? name = null)
public McpToolAttribute(string? name = null, string? description = null)
{ {
Name = name; Name = name;
Description = description;
} }
} }

View file

@ -3,6 +3,7 @@ using McpDotNet.Server;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Moq; using Moq;
using System.ComponentModel;
namespace McpDotNet.Tests; namespace McpDotNet.Tests;
@ -131,14 +132,14 @@ public class McpServerBuilderExtensionsToolsTests
private static class EchoTool private static class EchoTool
{ {
[McpTool(description: "Echoes the input back to the client.")] [McpTool, Description("Echoes the input back to the client.")]
public static string Echo([McpParameter(true, description: "the echoes message")] string message) public static string Echo([Description("the echoes message")] string message)
{ {
return "hello " + message; return "hello " + message;
} }
[McpTool(name: "double_echo", description: "Echoes the input back to the client.")] [McpTool("double_echo"), Description("Echoes the input back to the client.")]
public static string Echo2([McpParameter(true)] string message) public static string Echo2(string message)
{ {
return "hello hello" + message; return "hello hello" + message;
} }
@ -154,8 +155,8 @@ public class McpServerBuilderExtensionsToolsTests
{ {
} }
[McpTool(description: "Echoes the input back to the client.")] [McpTool, Description("Echoes the input back to the client.")]
public Task<string> Echo([McpParameter(true)] string message) public Task<string> Echo(string message)
{ {
return Task.FromResult("hello " + message); return Task.FromResult("hello " + message);
} }