using System.Globalization;
using System.Reflection;
using System.Text.Json;
using McpDotNet.Configuration;
using McpDotNet.Protocol.Types;
using McpDotNet.Server;
using Microsoft.Extensions.DependencyInjection;
namespace McpDotNet;
///
/// Extension to configure the MCP server with tools
///
public static partial class McpServerBuilderExtensions
{
///
/// Adds a tool to the server.
///
/// The tool type.
/// The builder instance.
public static IMcpServerBuilder WithTool(this IMcpServerBuilder builder)
{
return WithTools(builder, typeof(TTool));
}
///
/// Adds all tools marked with from the current assembly to the server.
///
/// The builder instance.
public static IMcpServerBuilder WithTools(this IMcpServerBuilder builder)
{
return WithToolsFromAssembly(builder, Assembly.GetCallingAssembly());
}
///
/// Adds tools to the server.
///
/// The builder instance.
/// Types with marked methods to add as tools to the server.
public static IMcpServerBuilder WithTools(this IMcpServerBuilder builder, params Type[] toolTypes)
{
if (builder is null)
{
throw new ArgumentNullException(nameof(builder));
}
if (toolTypes is null || toolTypes.Length == 0)
throw new ArgumentException("At least one tool type must be provided.", nameof(toolTypes));
var tools = new List();
Dictionary, CancellationToken, Task>> callbacks = [];
foreach (var type in toolTypes)
{
var methods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static);
foreach (var method in methods)
{
var attribute = method.GetCustomAttribute();
if (attribute != null)
{
var tool = CreateTool(method, attribute);
tools.Add(tool);
callbacks.Add(tool.Name, async (request, cancellationToken) => await CallTool(request, method, cancellationToken).ConfigureAwait(false));
// register type because method is not static and so we need an instance
if (!method.IsStatic)
builder.Services.AddScoped(type);
}
}
}
builder.WithListToolsHandler((_, _) => Task.FromResult(new ListToolsResult() { Tools = tools }));
builder.WithCallToolHandler(async (request, cancellationToken) =>
{
if (request.Params != null && callbacks.TryGetValue(request.Params.Name, out var callback))
return await callback(request, cancellationToken).ConfigureAwait(false);
throw new McpServerException($"Unknown tool: {request.Params?.Name}");
});
return builder;
}
///
/// Adds types marked with the attribute from the given assembly as tools to the server.
///
/// The builder instance.
/// The assembly to load the types from. Null to get the current assembly
public static IMcpServerBuilder WithToolsFromAssembly(this IMcpServerBuilder builder, Assembly? assembly = null)
{
assembly ??= Assembly.GetCallingAssembly();
List toolTypes = [];
foreach (var type in assembly.GetTypes())
{
bool hasToolTypeAttribute = type.GetCustomAttribute() != null;
if (!hasToolTypeAttribute)
continue;
var methods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static);
foreach (var method in methods)
{
var attribute = method.GetCustomAttribute();
if (attribute != null)
{
toolTypes.Add(type);
break;
}
}
}
if (toolTypes.Count == 0)
throw new ArgumentException("No types with marked methods found in the assembly.", nameof(assembly));
return WithTools(builder, toolTypes.ToArray());
}
private static Tool CreateTool(MethodInfo method, McpToolAttribute attribute)
{
Dictionary properties = [];
List? requiredProperties = null;
foreach (var parameter in method.GetParameters())
{
if (parameter.ParameterType == typeof(CancellationToken))
continue;
var parameterAttribute = parameter.GetCustomAttribute();
properties.Add(parameter.Name ?? "NoName", new JsonSchemaProperty()
{
Type = GetParameterType(parameter.ParameterType),
Description = parameterAttribute?.Description
});
if (parameterAttribute?.Required == true)
{
requiredProperties ??= [];
requiredProperties.Add(parameter.Name ?? "NoName");
}
}
return new Tool()
{
Name = attribute.Name ?? method.Name,
Description = attribute.Description,
InputSchema = new JsonSchema()
{
Type = "object",
Properties = properties,
Required = requiredProperties
},
};
}
private static string GetParameterType(Type parameterType)
{
return parameterType switch
{
Type t when t == typeof(string) => "string",
Type t when t == typeof(int) || t == typeof(double) || t == typeof(float) => "number",
Type t when t == typeof(bool) => "boolean",
Type t when t.IsArray => "array",
Type t when t == typeof(DateTime) => "string",
_ => "object"
};
}
private static async Task CallTool(RequestContext request, MethodInfo method, CancellationToken cancellationToken)
{
var methodParameters = method.GetParameters();
List