using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using ModelContextProtocol.Hosting;
using ModelContextProtocol.Protocol.Transport;
using ModelContextProtocol.Protocol.Types;
using ModelContextProtocol.Server;
using ModelContextProtocol.Utils;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
namespace Microsoft.Extensions.DependencyInjection;
///
/// Provides methods for configuring MCP servers via dependency injection.
///
public static partial class McpServerBuilderExtensions
{
#region WithTools
private const string WithToolsRequiresUnreferencedCodeMessage =
$"The non-generic {nameof(WithTools)} and {nameof(WithToolsFromAssembly)} methods require dynamic lookup of method metadata" +
$"and may not work in Native AOT. Use the generic {nameof(WithTools)} method instead.";
/// Adds instances to the service collection backing .
/// The tool type.
/// The builder instance.
/// is .
///
/// This method discovers all instance and static methods (public and non-public) on the specified
/// type, where the methods are attributed as , and adds an
/// instance for each. For instance methods, an instance will be constructed for each invocation of the tool.
///
public static IMcpServerBuilder WithTools<[DynamicallyAccessedMembers(
DynamicallyAccessedMemberTypes.PublicMethods |
DynamicallyAccessedMemberTypes.NonPublicMethods |
DynamicallyAccessedMemberTypes.PublicConstructors)] TToolType>(
this IMcpServerBuilder builder)
{
Throw.IfNull(builder);
foreach (var toolMethod in typeof(TToolType).GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance))
{
if (toolMethod.GetCustomAttribute() is not null)
{
builder.Services.AddSingleton((Func)(toolMethod.IsStatic ?
services => McpServerTool.Create(toolMethod, options: new() { Services = services }) :
services => McpServerTool.Create(toolMethod, typeof(TToolType), new() { Services = services })));
}
}
return builder;
}
/// Adds instances to the service collection backing .
/// The builder instance.
/// Types with marked methods to add as tools to the server.
/// is .
/// 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 will be constructed for each invocation of the tool.
///
[RequiresUnreferencedCode(WithToolsRequiresUnreferencedCodeMessage)]
public static IMcpServerBuilder WithTools(this IMcpServerBuilder builder, params IEnumerable toolTypes)
{
Throw.IfNull(builder);
Throw.IfNull(toolTypes);
foreach (var toolType in toolTypes)
{
if (toolType is not null)
{
foreach (var toolMethod in toolType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance))
{
if (toolMethod.GetCustomAttribute() is not null)
{
builder.Services.AddSingleton((Func)(toolMethod.IsStatic ?
services => McpServerTool.Create(toolMethod, options: new() { Services = services }) :
services => McpServerTool.Create(toolMethod, toolType, new() { Services = services })));
}
}
}
}
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
/// is .
[RequiresUnreferencedCode(WithToolsRequiresUnreferencedCodeMessage)]
public static IMcpServerBuilder WithToolsFromAssembly(this IMcpServerBuilder builder, Assembly? toolAssembly = null)
{
Throw.IfNull(builder);
toolAssembly ??= Assembly.GetCallingAssembly();
return builder.WithTools(
from t in toolAssembly.GetTypes()
where t.GetCustomAttribute() is not null
select t);
}
#endregion
#region WithPrompts
private const string WithPromptsRequiresUnreferencedCodeMessage =
$"The non-generic {nameof(WithPrompts)} and {nameof(WithPromptsFromAssembly)} methods require dynamic lookup of method metadata" +
$"and may not work in Native AOT. Use the generic {nameof(WithPrompts)} method instead.";
/// Adds instances to the service collection backing .
/// The prompt type.
/// The builder instance.
/// is .
///
/// This method discovers all instance and static methods (public and non-public) on the specified
/// type, where the methods are attributed as , and adds an
/// instance for each. For instance methods, an instance will be constructed for each invocation of the prompt.
///
public static IMcpServerBuilder WithPrompts<[DynamicallyAccessedMembers(
DynamicallyAccessedMemberTypes.PublicMethods |
DynamicallyAccessedMemberTypes.NonPublicMethods |
DynamicallyAccessedMemberTypes.PublicConstructors)] TPromptType>(
this IMcpServerBuilder builder)
{
Throw.IfNull(builder);
foreach (var promptMethod in typeof(TPromptType).GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance))
{
if (promptMethod.GetCustomAttribute() is not null)
{
builder.Services.AddSingleton((Func)(promptMethod.IsStatic ?
services => McpServerPrompt.Create(promptMethod, options: new() { Services = services }) :
services => McpServerPrompt.Create(promptMethod, typeof(TPromptType), new() { Services = services })));
}
}
return builder;
}
/// Adds instances to the service collection backing .
/// The builder instance.
/// Types with marked methods to add as prompts to the server.
/// is .
/// 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 will be constructed for each invocation of the prompt.
///
[RequiresUnreferencedCode(WithPromptsRequiresUnreferencedCodeMessage)]
public static IMcpServerBuilder WithPrompts(this IMcpServerBuilder builder, params IEnumerable promptTypes)
{
Throw.IfNull(builder);
Throw.IfNull(promptTypes);
foreach (var promptType in promptTypes)
{
if (promptType is not null)
{
foreach (var promptMethod in promptType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance))
{
if (promptMethod.GetCustomAttribute() is not null)
{
builder.Services.AddSingleton((Func)(promptMethod.IsStatic ?
services => McpServerPrompt.Create(promptMethod, options: new() { Services = services }) :
services => McpServerPrompt.Create(promptMethod, promptType, new() { Services = services })));
}
}
}
}
return builder;
}
///
/// Adds types marked with the attribute from the given assembly as prompts to the server.
///
/// The builder instance.
/// The assembly to load the types from. Null to get the current assembly
/// is .
[RequiresUnreferencedCode(WithPromptsRequiresUnreferencedCodeMessage)]
public static IMcpServerBuilder WithPromptsFromAssembly(this IMcpServerBuilder builder, Assembly? promptAssembly = null)
{
Throw.IfNull(builder);
promptAssembly ??= Assembly.GetCallingAssembly();
return builder.WithPrompts(
from t in promptAssembly.GetTypes()
where t.GetCustomAttribute() is not null
select t);
}
#endregion
#region Handlers
///
/// Sets the handler for list resource templates requests.
///
/// The builder instance.
/// The handler.
public static IMcpServerBuilder WithListResourceTemplatesHandler(this IMcpServerBuilder builder, Func, CancellationToken, Task> handler)
{
Throw.IfNull(builder);
builder.Services.Configure(s => s.ListResourceTemplatesHandler = handler);
return builder;
}
///
/// Sets the handler for list tools requests.
///
/// The builder instance.
/// The handler.
public static IMcpServerBuilder WithListToolsHandler(this IMcpServerBuilder builder, Func, CancellationToken, Task> handler)
{
Throw.IfNull(builder);
builder.Services.Configure(s => s.ListToolsHandler = handler);
return builder;
}
///
/// Sets the handler for call tool requests.
///
/// The builder instance.
/// The handler.
public static IMcpServerBuilder WithCallToolHandler(this IMcpServerBuilder builder, Func, CancellationToken, Task> handler)
{
Throw.IfNull(builder);
builder.Services.Configure(s => s.CallToolHandler = handler);
return builder;
}
///
/// Sets the handler for list prompts requests.
///
/// The builder instance.
/// The handler.
public static IMcpServerBuilder WithListPromptsHandler(this IMcpServerBuilder builder, Func, CancellationToken, Task> handler)
{
Throw.IfNull(builder);
builder.Services.Configure(s => s.ListPromptsHandler = handler);
return builder;
}
///
/// Sets the handler for get prompt requests.
///
/// The builder instance.
/// The handler.
public static IMcpServerBuilder WithGetPromptHandler(this IMcpServerBuilder builder, Func, CancellationToken, Task> handler)
{
Throw.IfNull(builder);
builder.Services.Configure(s => s.GetPromptHandler = handler);
return builder;
}
///
/// Sets the handler for list resources requests.
///
/// The builder instance.
/// The handler.
public static IMcpServerBuilder WithListResourcesHandler(this IMcpServerBuilder builder, Func, CancellationToken, Task> handler)
{
Throw.IfNull(builder);
builder.Services.Configure(s => s.ListResourcesHandler = handler);
return builder;
}
///
/// Sets the handler for read resources requests.
///
/// The builder instance.
/// The handler.
public static IMcpServerBuilder WithReadResourceHandler(this IMcpServerBuilder builder, Func, CancellationToken, Task> handler)
{
Throw.IfNull(builder);
builder.Services.Configure(s => s.ReadResourceHandler = handler);
return builder;
}
///
/// Sets the handler for get completion requests.
///
/// The builder instance.
/// The handler.
public static IMcpServerBuilder WithGetCompletionHandler(this IMcpServerBuilder builder, Func, CancellationToken, Task> handler)
{
Throw.IfNull(builder);
builder.Services.Configure(s => s.GetCompletionHandler = handler);
return builder;
}
///
/// Sets the handler for subscribe to resources messages.
///
/// The builder instance.
/// The handler.
public static IMcpServerBuilder WithSubscribeToResourcesHandler(this IMcpServerBuilder builder, Func, CancellationToken, Task> handler)
{
Throw.IfNull(builder);
builder.Services.Configure(s => s.SubscribeToResourcesHandler = handler);
return builder;
}
///
/// Sets or sets the handler for subscribe to resources messages.
///
/// The builder instance.
/// The handler.
public static IMcpServerBuilder WithUnsubscribeFromResourcesHandler(this IMcpServerBuilder builder, Func, CancellationToken, Task> handler)
{
Throw.IfNull(builder);
builder.Services.Configure(s => s.UnsubscribeFromResourcesHandler = handler);
return builder;
}
#endregion
#region Transports
///
/// Adds a server transport that uses stdin/stdout for communication.
///
/// The builder instance.
public static IMcpServerBuilder WithStdioServerTransport(this IMcpServerBuilder builder)
{
Throw.IfNull(builder);
builder.Services.AddSingleton();
builder.Services.AddHostedService();
builder.Services.AddSingleton(services =>
{
ITransport serverTransport = services.GetRequiredService();
IOptions options = services.GetRequiredService>();
ILoggerFactory? loggerFactory = services.GetService();
return McpServerFactory.Create(serverTransport, options.Value, loggerFactory, services);
});
return builder;
}
#endregion
}