using Microsoft.Extensions.DependencyInjection.Extensions; 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; using System.Text.Json; 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. /// The serializer options governing tool parameter marshalling. /// The builder provided in . /// 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, JsonSerializerOptions? serializerOptions = null) { 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, SerializerOptions = serializerOptions }) : services => McpServerTool.Create(toolMethod, typeof(TToolType), new() { Services = services, SerializerOptions = serializerOptions }))); } } return builder; } /// Adds instances to the service collection backing . /// The builder instance. /// The instances to add to the server. /// The builder provided in . /// is . /// is . public static IMcpServerBuilder WithTools(this IMcpServerBuilder builder, IEnumerable tools) { Throw.IfNull(builder); Throw.IfNull(tools); foreach (var tool in tools) { if (tool is not null) { builder.Services.AddSingleton(tool); } } return builder; } /// Adds instances to the service collection backing . /// The builder instance. /// Types with -attributed methods to add as tools to the server. /// The serializer options governing tool parameter marshalling. /// The builder provided in . /// 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, IEnumerable toolTypes, JsonSerializerOptions? serializerOptions = null) { 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 , SerializerOptions = serializerOptions }) : services => McpServerTool.Create(toolMethod, toolType, new() { Services = services , SerializerOptions = serializerOptions }))); } } } } return builder; } /// /// Adds types marked with the attribute from the given assembly as tools to the server. /// /// The builder instance. /// The serializer options governing tool parameter marshalling. /// The assembly to load the types from. If , the calling assembly will be used. /// The builder provided in . /// is . /// /// /// This method scans the specified assembly (or the calling assembly if none is provided) for classes /// marked with the . It then discovers all methods within those /// classes that are marked with the and registers them as s /// in the 's . /// /// /// The method automatically handles both static and instance methods. For instance methods, a new instance /// of the containing class will be constructed for each invocation of the tool. /// /// /// Tools registered through this method can be discovered by clients using the list_tools request /// and invoked using the call_tool request. /// /// /// Note that this method performs reflection at runtime and may not work in Native AOT scenarios. For /// Native AOT compatibility, consider using the generic method instead. /// /// [RequiresUnreferencedCode(WithToolsRequiresUnreferencedCodeMessage)] public static IMcpServerBuilder WithToolsFromAssembly(this IMcpServerBuilder builder, Assembly? toolAssembly = null, JsonSerializerOptions? serializerOptions = null) { Throw.IfNull(builder); toolAssembly ??= Assembly.GetCallingAssembly(); return builder.WithTools( from t in toolAssembly.GetTypes() where t.GetCustomAttribute() is not null select t, serializerOptions); } #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. /// The serializer options governing prompt parameter marshalling. /// The builder provided in . /// 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, JsonSerializerOptions? serializerOptions = null) { 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, SerializerOptions = serializerOptions }) : services => McpServerPrompt.Create(promptMethod, typeof(TPromptType), new() { Services = services, SerializerOptions = serializerOptions }))); } } return builder; } /// Adds instances to the service collection backing . /// The builder instance. /// The instances to add to the server. /// The builder provided in . /// is . /// is . public static IMcpServerBuilder WithPrompts(this IMcpServerBuilder builder, IEnumerable prompts) { Throw.IfNull(builder); Throw.IfNull(prompts); foreach (var prompt in prompts) { if (prompt is not null) { builder.Services.AddSingleton(prompt); } } return builder; } /// Adds instances to the service collection backing . /// The builder instance. /// Types with marked methods to add as prompts to the server. /// The serializer options governing prompt parameter marshalling. /// The builder provided in . /// 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, IEnumerable promptTypes, JsonSerializerOptions? serializerOptions = null) { 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, SerializerOptions = serializerOptions }) : services => McpServerPrompt.Create(promptMethod, promptType, new() { Services = services, SerializerOptions = serializerOptions }))); } } } } return builder; } /// /// Adds types marked with the attribute from the given assembly as prompts to the server. /// /// The builder instance. /// The serializer options governing prompt parameter marshalling. /// The assembly to load the types from. If , the calling assembly will be used. /// The builder provided in . /// is . /// /// /// This method scans the specified assembly (or the calling assembly if none is provided) for classes /// marked with the . It then discovers all methods within those /// classes that are marked with the and registers them as s /// in the 's . /// /// /// The method automatically handles both static and instance methods. For instance methods, a new instance /// of the containing class will be constructed for each invocation of the prompt. /// /// /// Prompts registered through this method can be discovered by clients using the list_prompts request /// and invoked using the call_prompt request. /// /// /// Note that this method performs reflection at runtime and may not work in Native AOT scenarios. For /// Native AOT compatibility, consider using the generic method instead. /// /// [RequiresUnreferencedCode(WithPromptsRequiresUnreferencedCodeMessage)] public static IMcpServerBuilder WithPromptsFromAssembly(this IMcpServerBuilder builder, Assembly? promptAssembly = null, JsonSerializerOptions? serializerOptions = null) { Throw.IfNull(builder); promptAssembly ??= Assembly.GetCallingAssembly(); return builder.WithPrompts( from t in promptAssembly.GetTypes() where t.GetCustomAttribute() is not null select t, serializerOptions); } #endregion #region WithResources private const string WithResourcesRequiresUnreferencedCodeMessage = $"The non-generic {nameof(WithResources)} and {nameof(WithResourcesFromAssembly)} methods require dynamic lookup of member metadata" + $"and may not work in Native AOT. Use the generic {nameof(WithResources)} method instead."; /// Adds instances to the service collection backing . /// The resource type. /// The builder instance. /// The builder provided in . /// is . /// /// This method discovers all instance and static methods (public and non-public) on the specified /// type, where the members are attributed as , and adds an /// instance for each. For instance members, an instance will be constructed for each invocation of the resource. /// public static IMcpServerBuilder WithResources<[DynamicallyAccessedMembers( DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods | DynamicallyAccessedMemberTypes.PublicConstructors)] TResourceType>( this IMcpServerBuilder builder) { Throw.IfNull(builder); foreach (var resourceTemplateMethod in typeof(TResourceType).GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance)) { if (resourceTemplateMethod.GetCustomAttribute() is not null) { builder.Services.AddSingleton((Func)(resourceTemplateMethod.IsStatic ? services => McpServerResource.Create(resourceTemplateMethod, options: new() { Services = services }) : services => McpServerResource.Create(resourceTemplateMethod, typeof(TResourceType), new() { Services = services }))); } } return builder; } /// Adds instances to the service collection backing . /// The builder instance. /// The instances to add to the server. /// The builder provided in . /// is . /// is . public static IMcpServerBuilder WithResources(this IMcpServerBuilder builder, IEnumerable resourcetemplates) { Throw.IfNull(builder); Throw.IfNull(resourcetemplates); foreach (var resourceTemplate in resourcetemplates) { if (resourceTemplate is not null) { builder.Services.AddSingleton(resourceTemplate); } } return builder; } /// Adds instances to the service collection backing . /// The builder instance. /// Types with marked methods to add as resources to the server. /// The builder provided in . /// 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 resource. /// [RequiresUnreferencedCode(WithResourcesRequiresUnreferencedCodeMessage)] public static IMcpServerBuilder WithResources(this IMcpServerBuilder builder, IEnumerable resourceTemplateTypes) { Throw.IfNull(builder); Throw.IfNull(resourceTemplateTypes); foreach (var resourceTemplateType in resourceTemplateTypes) { if (resourceTemplateType is not null) { foreach (var resourceTemplateMethod in resourceTemplateType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance)) { if (resourceTemplateMethod.GetCustomAttribute() is not null) { builder.Services.AddSingleton((Func)(resourceTemplateMethod.IsStatic ? services => McpServerResource.Create(resourceTemplateMethod, options: new() { Services = services }) : services => McpServerResource.Create(resourceTemplateMethod, resourceTemplateType, new() { Services = services }))); } } } } return builder; } /// /// Adds types marked with the attribute from the given assembly as resources to the server. /// /// The builder instance. /// The assembly to load the types from. If , the calling assembly will be used. /// The builder provided in . /// is . /// /// /// This method scans the specified assembly (or the calling assembly if none is provided) for classes /// marked with the . It then discovers all members within those /// classes that are marked with the and registers them as s /// in the 's . /// /// /// The method automatically handles both static and instance members. For instance members, a new instance /// of the containing class will be constructed for each invocation of the resource. /// /// /// Resource templates registered through this method can be discovered by clients using the list_resourcetemplates request /// and invoked using the read_resource request. /// /// /// Note that this method performs reflection at runtime and may not work in Native AOT scenarios. For /// Native AOT compatibility, consider using the generic method instead. /// /// [RequiresUnreferencedCode(WithResourcesRequiresUnreferencedCodeMessage)] public static IMcpServerBuilder WithResourcesFromAssembly(this IMcpServerBuilder builder, Assembly? resourceAssembly = null) { Throw.IfNull(builder); resourceAssembly ??= Assembly.GetCallingAssembly(); return builder.WithResources( from t in resourceAssembly.GetTypes() where t.GetCustomAttribute() is not null select t); } #endregion #region Handlers /// /// Configures a handler for listing resource templates available from the Model Context Protocol server. /// /// The builder instance. /// The handler function that processes resource template list requests. /// The builder provided in . /// is . /// /// /// This handler is responsible for providing clients with information about available resource templates /// that can be used to construct resource URIs. /// /// /// Resource templates describe the structure of resource URIs that the server can handle. They include /// URI templates (according to RFC 6570) that clients can use to construct valid resource URIs. /// /// /// This handler is typically paired with to provide a complete /// resource system where templates define the URI patterns and the read handler provides the actual content. /// /// public static IMcpServerBuilder WithListResourceTemplatesHandler(this IMcpServerBuilder builder, Func, CancellationToken, ValueTask> handler) { Throw.IfNull(builder); builder.Services.Configure(s => s.ListResourceTemplatesHandler = handler); return builder; } /// /// Configures a handler for listing tools available from the Model Context Protocol server. /// /// The builder instance. /// The handler that processes list tools requests. /// The builder provided in . /// is . /// /// /// This handler is called when a client requests a list of available tools. It should return all tools /// that can be invoked through the server, including their names, descriptions, and parameter specifications. /// The handler can optionally support pagination via the cursor mechanism for large or dynamically-generated /// tool collections. /// /// /// When tools are also defined using collection, both sets of tools /// will be combined in the response to clients. This allows for a mix of programmatically defined /// tools and dynamically generated tools. /// /// /// This method is typically paired with to provide a complete tools implementation, /// where advertises available tools and /// executes them when invoked by clients. /// /// public static IMcpServerBuilder WithListToolsHandler(this IMcpServerBuilder builder, Func, CancellationToken, ValueTask> handler) { Throw.IfNull(builder); builder.Services.Configure(s => s.ListToolsHandler = handler); return builder; } /// /// Configures a handler for calling tools available from the Model Context Protocol server. /// /// The builder instance. /// The handler function that processes tool calls. /// The builder provided in . /// is . /// /// The call tool handler is responsible for executing custom tools and returning their results to clients. /// This method is typically paired with to provide a complete tools implementation, /// where advertises available tools and this handler executes them. /// public static IMcpServerBuilder WithCallToolHandler(this IMcpServerBuilder builder, Func, CancellationToken, ValueTask> handler) { Throw.IfNull(builder); builder.Services.Configure(s => s.CallToolHandler = handler); return builder; } /// /// Configures a handler for listing prompts available from the Model Context Protocol server. /// /// The builder instance. /// The handler that processes list prompts requests. /// The builder provided in . /// is . /// /// /// This handler is called when a client requests a list of available prompts. It should return all prompts /// that can be invoked through the server, including their names, descriptions, and parameter specifications. /// The handler can optionally support pagination via the cursor mechanism for large or dynamically-generated /// prompt collections. /// /// /// When prompts are also defined using collection, both sets of prompts /// will be combined in the response to clients. This allows for a mix of programmatically defined /// prompts and dynamically generated prompts. /// /// /// This method is typically paired with to provide a complete prompts implementation, /// where advertises available prompts and /// produces them when invoked by clients. /// /// public static IMcpServerBuilder WithListPromptsHandler(this IMcpServerBuilder builder, Func, CancellationToken, ValueTask> handler) { Throw.IfNull(builder); builder.Services.Configure(s => s.ListPromptsHandler = handler); return builder; } /// /// Configures a handler for getting a prompt available from the Model Context Protocol server. /// /// The builder instance. /// The handler function that processes prompt requests. /// The builder provided in . /// is . public static IMcpServerBuilder WithGetPromptHandler(this IMcpServerBuilder builder, Func, CancellationToken, ValueTask> handler) { Throw.IfNull(builder); builder.Services.Configure(s => s.GetPromptHandler = handler); return builder; } /// /// Configures a handler for listing resources available from the Model Context Protocol server. /// /// The builder instance. /// The handler function that processes resource list requests. /// The builder provided in . /// is . /// /// /// This handler is typically paired with to provide a complete resources implementation, /// where this handler advertises available resources and the read handler provides their content when requested. /// /// public static IMcpServerBuilder WithListResourcesHandler(this IMcpServerBuilder builder, Func, CancellationToken, ValueTask> handler) { Throw.IfNull(builder); builder.Services.Configure(s => s.ListResourcesHandler = handler); return builder; } /// /// Configures a handler for reading a resource available from the Model Context Protocol server. /// /// The builder instance. /// The handler function that processes resource read requests. /// The builder provided in . /// is . /// /// This handler is typically paired with to provide a complete resources implementation, /// where the list handler advertises available resources and the read handler provides their content when requested. /// public static IMcpServerBuilder WithReadResourceHandler(this IMcpServerBuilder builder, Func, CancellationToken, ValueTask> handler) { Throw.IfNull(builder); builder.Services.Configure(s => s.ReadResourceHandler = handler); return builder; } /// /// Configures a handler for auto-completion suggestions for prompt arguments or resource references available from the Model Context Protocol server. /// /// The builder instance. /// The handler function that processes completion requests. /// The builder provided in . /// is . /// /// The completion handler is invoked when clients request suggestions for argument values. /// This enables auto-complete functionality for both prompt arguments and resource references. /// public static IMcpServerBuilder WithCompleteHandler(this IMcpServerBuilder builder, Func, CancellationToken, ValueTask> handler) { Throw.IfNull(builder); builder.Services.Configure(s => s.CompleteHandler = handler); return builder; } /// /// Configures a handler for resource subscription requests. /// /// The builder instance. /// The handler function that processes resource subscription requests. /// The builder provided in . /// is . /// /// /// The subscribe handler is responsible for registering client interest in specific resources. When a resource /// changes, the server can notify all subscribed clients about the change. /// /// /// This handler is typically paired with to provide a complete /// subscription management system. Resource subscriptions allow clients to maintain up-to-date information without /// needing to poll resources constantly. /// /// /// After registering a subscription, it's the server's responsibility to track which client is subscribed to which /// resources and to send appropriate notifications through the connection when resources change. /// /// public static IMcpServerBuilder WithSubscribeToResourcesHandler(this IMcpServerBuilder builder, Func, CancellationToken, ValueTask> handler) { Throw.IfNull(builder); builder.Services.Configure(s => s.SubscribeToResourcesHandler = handler); return builder; } /// /// Configures a handler for resource unsubscription requests. /// /// The builder instance. /// The handler function that processes resource unsubscription requests. /// The builder provided in . /// is . /// /// /// The unsubscribe handler is responsible for removing client interest in specific resources. When a client /// no longer needs to receive notifications about resource changes, it can send an unsubscribe request. /// /// /// This handler is typically paired with to provide a complete /// subscription management system. The unsubscribe operation is idempotent, meaning it can be called multiple /// times for the same resource without causing errors, even if there is no active subscription. /// /// /// After removing a subscription, the server should stop sending notifications to the client about changes /// to the specified resource. /// /// public static IMcpServerBuilder WithUnsubscribeFromResourcesHandler(this IMcpServerBuilder builder, Func, CancellationToken, ValueTask> handler) { Throw.IfNull(builder); builder.Services.Configure(s => s.UnsubscribeFromResourcesHandler = handler); return builder; } /// /// Configures a handler for processing logging level change requests from clients. /// /// The server builder instance. /// The handler that processes requests to change the logging level. /// The builder provided in . /// is . /// /// /// When a client sends a logging/setLevel request, this handler will be invoked to process /// the requested level change. The server typically adjusts its internal logging level threshold /// and may begin sending log messages at or above the specified level to the client. /// /// /// Regardless of whether a handler is provided, an should itself handle /// such notifications by updating its property to return the /// most recently set level. /// /// public static IMcpServerBuilder WithSetLoggingLevelHandler(this IMcpServerBuilder builder, Func, CancellationToken, ValueTask> handler) { Throw.IfNull(builder); builder.Services.Configure(s => s.SetLoggingLevelHandler = handler); return builder; } #endregion #region Transports /// /// Adds a server transport that uses standard input (stdin) and standard output (stdout) for communication. /// /// The builder instance. /// The builder provided in . /// is . /// /// /// This method configures the server to communicate using the standard input and output streams, /// which is commonly used when the Model Context Protocol server is launched locally by a client process. /// /// /// When using this transport, the server runs as a single-session service that exits when the /// stdin stream is closed. This makes it suitable for scenarios where the server should terminate /// when the parent process disconnects. /// /// /// is . public static IMcpServerBuilder WithStdioServerTransport(this IMcpServerBuilder builder) { Throw.IfNull(builder); AddSingleSessionServerDependencies(builder.Services); builder.Services.AddSingleton(); return builder; } /// /// Adds a server transport that uses the specified input and output streams for communication. /// /// The builder instance. /// The input to use as standard input. /// The output to use as standard output. /// The builder provided in . /// is . /// is . /// is . public static IMcpServerBuilder WithStreamServerTransport( this IMcpServerBuilder builder, Stream inputStream, Stream outputStream) { Throw.IfNull(builder); Throw.IfNull(inputStream); Throw.IfNull(outputStream); AddSingleSessionServerDependencies(builder.Services); builder.Services.AddSingleton(new StreamServerTransport(inputStream, outputStream)); return builder; } private static void AddSingleSessionServerDependencies(IServiceCollection services) { services.AddHostedService(); services.TryAddSingleton(services => { ITransport serverTransport = services.GetRequiredService(); IOptions options = services.GetRequiredService>(); ILoggerFactory? loggerFactory = services.GetService(); return McpServerFactory.Create(serverTransport, options.Value, loggerFactory, services); }); } #endregion }