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 }