Add McpServerPrompt / McpClientPrompt and friends (#126)

This commit is contained in:
Stephen Toub 2025-03-31 09:54:40 -04:00 • committed by GitHub
commit 5d3fb65b11
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 1950 additions and 550 deletions

View file

@ -1,12 +1,11 @@
using ModelContextProtocol.Utils;
using Microsoft.Extensions.DependencyInjection;
namespace ModelContextProtocol.Configuration;
namespace Microsoft.Extensions.DependencyInjection;
/// <summary>
/// Default implementation of <see cref="IMcpServerBuilder"/>.
/// </summary>
internal class DefaultMcpServerBuilder : IMcpServerBuilder
internal sealed class DefaultMcpServerBuilder : IMcpServerBuilder
{
/// <inheritdoc/>
public IServiceCollection Services { get; }

View file

@ -1,7 +1,6 @@
using ModelContextProtocol.Server;
using Microsoft.Extensions.DependencyInjection;
namespace ModelContextProtocol.Configuration;
namespace Microsoft.Extensions.DependencyInjection;
/// <summary>
/// Builder for configuring <see cref="IMcpServer"/> instances.

View file

@ -1,143 +0,0 @@
using ModelContextProtocol.Configuration;
using ModelContextProtocol.Protocol.Types;
using ModelContextProtocol.Server;
using ModelContextProtocol.Utils;
using Microsoft.Extensions.DependencyInjection;
namespace ModelContextProtocol;
/// <summary>
/// Extension to configure the MCP server with handlers
/// </summary>
public static partial class McpServerBuilderExtensions
{
/// <summary>
/// Sets the handler for list resource templates requests.
/// </summary>
/// <param name="builder">The builder instance.</param>
/// <param name="handler">The handler.</param>
public static IMcpServerBuilder WithListResourceTemplatesHandler(this IMcpServerBuilder builder, Func<RequestContext<ListResourceTemplatesRequestParams>, CancellationToken, Task<ListResourceTemplatesResult>> handler)
{
Throw.IfNull(builder);
builder.Services.Configure<McpServerHandlers>(s => s.ListResourceTemplatesHandler = handler);
return builder;
}
/// <summary>
/// Sets the handler for list tools requests.
/// </summary>
/// <param name="builder">The builder instance.</param>
/// <param name="handler">The handler.</param>
public static IMcpServerBuilder WithListToolsHandler(this IMcpServerBuilder builder, Func<RequestContext<ListToolsRequestParams>, CancellationToken, Task<ListToolsResult>> handler)
{
Throw.IfNull(builder);
builder.Services.Configure<McpServerHandlers>(s => s.ListToolsHandler = handler);
return builder;
}
/// <summary>
/// Sets the handler for call tool requests.
/// </summary>
/// <param name="builder">The builder instance.</param>
/// <param name="handler">The handler.</param>
public static IMcpServerBuilder WithCallToolHandler(this IMcpServerBuilder builder, Func<RequestContext<CallToolRequestParams>, CancellationToken, Task<CallToolResponse>> handler)
{
Throw.IfNull(builder);
builder.Services.Configure<McpServerHandlers>(s => s.CallToolHandler = handler);
return builder;
}
/// <summary>
/// Sets the handler for list prompts requests.
/// </summary>
/// <param name="builder">The builder instance.</param>
/// <param name="handler">The handler.</param>
public static IMcpServerBuilder WithListPromptsHandler(this IMcpServerBuilder builder, Func<RequestContext<ListPromptsRequestParams>, CancellationToken, Task<ListPromptsResult>> handler)
{
Throw.IfNull(builder);
builder.Services.Configure<McpServerHandlers>(s => s.ListPromptsHandler = handler);
return builder;
}
/// <summary>
/// Sets the handler for get prompt requests.
/// </summary>
/// <param name="builder">The builder instance.</param>
/// <param name="handler">The handler.</param>
public static IMcpServerBuilder WithGetPromptHandler(this IMcpServerBuilder builder, Func<RequestContext<GetPromptRequestParams>, CancellationToken, Task<GetPromptResult>> handler)
{
Throw.IfNull(builder);
builder.Services.Configure<McpServerHandlers>(s => s.GetPromptHandler = handler);
return builder;
}
/// <summary>
/// Sets the handler for list resources requests.
/// </summary>
/// <param name="builder">The builder instance.</param>
/// <param name="handler">The handler.</param>
public static IMcpServerBuilder WithListResourcesHandler(this IMcpServerBuilder builder, Func<RequestContext<ListResourcesRequestParams>, CancellationToken, Task<ListResourcesResult>> handler)
{
Throw.IfNull(builder);
builder.Services.Configure<McpServerHandlers>(s => s.ListResourcesHandler = handler);
return builder;
}
/// <summary>
/// Sets the handler for read resources requests.
/// </summary>
/// <param name="builder">The builder instance.</param>
/// <param name="handler">The handler.</param>
public static IMcpServerBuilder WithReadResourceHandler(this IMcpServerBuilder builder, Func<RequestContext<ReadResourceRequestParams>, CancellationToken, Task<ReadResourceResult>> handler)
{
Throw.IfNull(builder);
builder.Services.Configure<McpServerHandlers>(s => s.ReadResourceHandler = handler);
return builder;
}
/// <summary>
/// Sets the handler for get completion requests.
/// </summary>
/// <param name="builder">The builder instance.</param>
/// <param name="handler">The handler.</param>
public static IMcpServerBuilder WithGetCompletionHandler(this IMcpServerBuilder builder, Func<RequestContext<CompleteRequestParams>, CancellationToken, Task<CompleteResult>> handler)
{
Throw.IfNull(builder);
builder.Services.Configure<McpServerHandlers>(s => s.GetCompletionHandler = handler);
return builder;
}
/// <summary>
/// Sets the handler for subscribe to resources messages.
/// </summary>
/// <param name="builder">The builder instance.</param>
/// <param name="handler">The handler.</param>
public static IMcpServerBuilder WithSubscribeToResourcesHandler(this IMcpServerBuilder builder, Func<RequestContext<SubscribeRequestParams>, CancellationToken, Task<EmptyResult>> handler)
{
Throw.IfNull(builder);
builder.Services.Configure<McpServerHandlers>(s => s.SubscribeToResourcesHandler = handler);
return builder;
}
/// <summary>
/// Sets or sets the handler for subscribe to resources messages.
/// </summary>
/// <param name="builder">The builder instance.</param>
/// <param name="handler">The handler.</param>
public static IMcpServerBuilder WithUnsubscribeFromResourcesHandler(this IMcpServerBuilder builder, Func<RequestContext<UnsubscribeRequestParams>, CancellationToken, Task<EmptyResult>> handler)
{
Throw.IfNull(builder);
builder.Services.Configure<McpServerHandlers>(s => s.UnsubscribeFromResourcesHandler = handler);
return builder;
}
}

View file

@ -1,102 +0,0 @@
using ModelContextProtocol.Configuration;
using ModelContextProtocol.Server;
using ModelContextProtocol.Utils;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
namespace ModelContextProtocol;
/// <summary>
/// Extension to configure the MCP server with tools
/// </summary>
public static partial class McpServerBuilderExtensions
{
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.";
/// <summary>Adds <see cref="McpServerTool"/> instances to the service collection backing <paramref name="builder"/>.</summary>
/// <typeparam name="TTool">The tool type.</typeparam>
/// <param name="builder">The builder instance.</param>
/// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
/// <remarks>
/// This method discovers all instance and static methods (public and non-public) on the specified <typeparamref name="TTool"/>
/// type, where the methods are attributed as <see cref="McpServerToolAttribute"/>, and adds an <see cref="McpServerTool"/>
/// instance for each. For instance methods, an instance will be constructed for each invocation of the tool.
/// </remarks>
public static IMcpServerBuilder WithTools<[DynamicallyAccessedMembers(
DynamicallyAccessedMemberTypes.PublicMethods |
DynamicallyAccessedMemberTypes.NonPublicMethods |
DynamicallyAccessedMemberTypes.PublicConstructors)] TTool>(
this IMcpServerBuilder builder)
{
Throw.IfNull(builder);
foreach (var toolMethod in typeof(TTool).GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance))
{
if (toolMethod.GetCustomAttribute<McpServerToolAttribute>() is not null)
{
builder.Services.AddSingleton((Func<IServiceProvider, McpServerTool>)(toolMethod.IsStatic ?
services => McpServerTool.Create(toolMethod, options: new() { Services = services }) :
services => McpServerTool.Create(toolMethod, typeof(TTool), new() { Services = services })));
}
}
return builder;
}
/// <summary>Adds <see cref="McpServerTool"/> instances to the service collection backing <paramref name="builder"/>.</summary>
/// <param name="builder">The builder instance.</param>
/// <param name="toolTypes">Types with marked methods to add as tools to the server.</param>
/// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="toolTypes"/> is <see langword="null"/>.</exception>
/// <remarks>
/// This method discovers all instance and static methods (public and non-public) on the specified <paramref name="toolTypes"/>
/// types, where the methods are attributed as <see cref="McpServerToolAttribute"/>, and adds an <see cref="McpServerTool"/>
/// instance for each. For instance methods, an instance will be constructed for each invocation of the tool.
/// </remarks>
[RequiresUnreferencedCode(WithToolsRequiresUnreferencedCodeMessage)]
public static IMcpServerBuilder WithTools(this IMcpServerBuilder builder, params IEnumerable<Type> 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<McpServerToolAttribute>() is not null)
{
builder.Services.AddSingleton((Func<IServiceProvider, McpServerTool>)(toolMethod.IsStatic ?
services => McpServerTool.Create(toolMethod, options: new() { Services = services }) :
services => McpServerTool.Create(toolMethod, toolType, new() { Services = services })));
}
}
}
}
return builder;
}
/// <summary>
/// Adds types marked with the <see cref="McpServerToolTypeAttribute"/> attribute from the given assembly as tools to the server.
/// </summary>
/// <param name="builder">The builder instance.</param>
/// <param name="toolAssembly">The assembly to load the types from. Null to get the current assembly</param>
/// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
[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<McpServerToolTypeAttribute>() is not null
select t);
}
}

View file

@ -1,52 +0,0 @@
using ModelContextProtocol.Configuration;
using ModelContextProtocol.Hosting;
using ModelContextProtocol.Protocol.Transport;
using ModelContextProtocol.Utils;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using ModelContextProtocol.Server;
namespace ModelContextProtocol;
/// <summary>
/// Extension to configure the MCP server with transports
/// </summary>
public static partial class McpServerBuilderExtensions
{
/// <summary>
/// Adds a server transport that uses stdin/stdout for communication.
/// </summary>
/// <param name="builder">The builder instance.</param>
public static IMcpServerBuilder WithStdioServerTransport(this IMcpServerBuilder builder)
{
Throw.IfNull(builder);
builder.Services.AddSingleton<ITransport, StdioServerTransport>();
builder.Services.AddHostedService<McpServerSingleSessionHostedService>();
builder.Services.AddSingleton(services =>
{
ITransport serverTransport = services.GetRequiredService<ITransport>();
IOptions<McpServerOptions> options = services.GetRequiredService<IOptions<McpServerOptions>>();
ILoggerFactory? loggerFactory = services.GetService<ILoggerFactory>();
return McpServerFactory.Create(serverTransport, options.Value, loggerFactory, services);
});
return builder;
}
/// <summary>
/// Adds a server transport that uses SSE via a HttpListener for communication.
/// </summary>
/// <param name="builder">The builder instance.</param>
public static IMcpServerBuilder WithHttpListenerSseServerTransport(this IMcpServerBuilder builder)
{
Throw.IfNull(builder);
builder.Services.AddSingleton<IServerTransport, HttpListenerSseServerTransport>();
builder.Services.AddHostedService<McpServerMultiSessionHostedService>();
return builder;
}
}

View file

@ -0,0 +1,367 @@
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;
/// <summary>
/// Provides methods for configuring MCP servers via dependency injection.
/// </summary>
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.";
/// <summary>Adds <see cref="McpServerTool"/> instances to the service collection backing <paramref name="builder"/>.</summary>
/// <typeparam name="TToolType">The tool type.</typeparam>
/// <param name="builder">The builder instance.</param>
/// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
/// <remarks>
/// This method discovers all instance and static methods (public and non-public) on the specified <typeparamref name="TToolType"/>
/// type, where the methods are attributed as <see cref="McpServerToolAttribute"/>, and adds an <see cref="McpServerTool"/>
/// instance for each. For instance methods, an instance will be constructed for each invocation of the tool.
/// </remarks>
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<McpServerToolAttribute>() is not null)
{
builder.Services.AddSingleton((Func<IServiceProvider, McpServerTool>)(toolMethod.IsStatic ?
services => McpServerTool.Create(toolMethod, options: new() { Services = services }) :
services => McpServerTool.Create(toolMethod, typeof(TToolType), new() { Services = services })));
}
}
return builder;
}
/// <summary>Adds <see cref="McpServerTool"/> instances to the service collection backing <paramref name="builder"/>.</summary>
/// <param name="builder">The builder instance.</param>
/// <param name="toolTypes">Types with marked methods to add as tools to the server.</param>
/// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="toolTypes"/> is <see langword="null"/>.</exception>
/// <remarks>
/// This method discovers all instance and static methods (public and non-public) on the specified <paramref name="toolTypes"/>
/// types, where the methods are attributed as <see cref="McpServerToolAttribute"/>, and adds an <see cref="McpServerTool"/>
/// instance for each. For instance methods, an instance will be constructed for each invocation of the tool.
/// </remarks>
[RequiresUnreferencedCode(WithToolsRequiresUnreferencedCodeMessage)]
public static IMcpServerBuilder WithTools(this IMcpServerBuilder builder, params IEnumerable<Type> 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<McpServerToolAttribute>() is not null)
{
builder.Services.AddSingleton((Func<IServiceProvider, McpServerTool>)(toolMethod.IsStatic ?
services => McpServerTool.Create(toolMethod, options: new() { Services = services }) :
services => McpServerTool.Create(toolMethod, toolType, new() { Services = services })));
}
}
}
}
return builder;
}
/// <summary>
/// Adds types marked with the <see cref="McpServerToolTypeAttribute"/> attribute from the given assembly as tools to the server.
/// </summary>
/// <param name="builder">The builder instance.</param>
/// <param name="toolAssembly">The assembly to load the types from. Null to get the current assembly</param>
/// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
[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<McpServerToolTypeAttribute>() 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.";
/// <summary>Adds <see cref="McpServerPrompt"/> instances to the service collection backing <paramref name="builder"/>.</summary>
/// <typeparam name="TPromptType">The prompt type.</typeparam>
/// <param name="builder">The builder instance.</param>
/// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
/// <remarks>
/// This method discovers all instance and static methods (public and non-public) on the specified <typeparamref name="TPromptType"/>
/// type, where the methods are attributed as <see cref="McpServerPromptAttribute"/>, and adds an <see cref="McpServerPrompt"/>
/// instance for each. For instance methods, an instance will be constructed for each invocation of the prompt.
/// </remarks>
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<McpServerPromptAttribute>() is not null)
{
builder.Services.AddSingleton((Func<IServiceProvider, McpServerPrompt>)(promptMethod.IsStatic ?
services => McpServerPrompt.Create(promptMethod, options: new() { Services = services }) :
services => McpServerPrompt.Create(promptMethod, typeof(TPromptType), new() { Services = services })));
}
}
return builder;
}
/// <summary>Adds <see cref="McpServerPrompt"/> instances to the service collection backing <paramref name="builder"/>.</summary>
/// <param name="builder">The builder instance.</param>
/// <param name="promptTypes">Types with marked methods to add as prompts to the server.</param>
/// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="promptTypes"/> is <see langword="null"/>.</exception>
/// <remarks>
/// This method discovers all instance and static methods (public and non-public) on the specified <paramref name="promptTypes"/>
/// types, where the methods are attributed as <see cref="McpServerPromptAttribute"/>, and adds an <see cref="McpServerPrompt"/>
/// instance for each. For instance methods, an instance will be constructed for each invocation of the prompt.
/// </remarks>
[RequiresUnreferencedCode(WithPromptsRequiresUnreferencedCodeMessage)]
public static IMcpServerBuilder WithPrompts(this IMcpServerBuilder builder, params IEnumerable<Type> 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<McpServerPromptAttribute>() is not null)
{
builder.Services.AddSingleton((Func<IServiceProvider, McpServerPrompt>)(promptMethod.IsStatic ?
services => McpServerPrompt.Create(promptMethod, options: new() { Services = services }) :
services => McpServerPrompt.Create(promptMethod, promptType, new() { Services = services })));
}
}
}
}
return builder;
}
/// <summary>
/// Adds types marked with the <see cref="McpServerToolTypeAttribute"/> attribute from the given assembly as prompts to the server.
/// </summary>
/// <param name="builder">The builder instance.</param>
/// <param name="promptAssembly">The assembly to load the types from. Null to get the current assembly</param>
/// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
[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<McpServerToolTypeAttribute>() is not null
select t);
}
#endregion
#region Handlers
/// <summary>
/// Sets the handler for list resource templates requests.
/// </summary>
/// <param name="builder">The builder instance.</param>
/// <param name="handler">The handler.</param>
public static IMcpServerBuilder WithListResourceTemplatesHandler(this IMcpServerBuilder builder, Func<RequestContext<ListResourceTemplatesRequestParams>, CancellationToken, Task<ListResourceTemplatesResult>> handler)
{
Throw.IfNull(builder);
builder.Services.Configure<McpServerHandlers>(s => s.ListResourceTemplatesHandler = handler);
return builder;
}
/// <summary>
/// Sets the handler for list tools requests.
/// </summary>
/// <param name="builder">The builder instance.</param>
/// <param name="handler">The handler.</param>
public static IMcpServerBuilder WithListToolsHandler(this IMcpServerBuilder builder, Func<RequestContext<ListToolsRequestParams>, CancellationToken, Task<ListToolsResult>> handler)
{
Throw.IfNull(builder);
builder.Services.Configure<McpServerHandlers>(s => s.ListToolsHandler = handler);
return builder;
}
/// <summary>
/// Sets the handler for call tool requests.
/// </summary>
/// <param name="builder">The builder instance.</param>
/// <param name="handler">The handler.</param>
public static IMcpServerBuilder WithCallToolHandler(this IMcpServerBuilder builder, Func<RequestContext<CallToolRequestParams>, CancellationToken, Task<CallToolResponse>> handler)
{
Throw.IfNull(builder);
builder.Services.Configure<McpServerHandlers>(s => s.CallToolHandler = handler);
return builder;
}
/// <summary>
/// Sets the handler for list prompts requests.
/// </summary>
/// <param name="builder">The builder instance.</param>
/// <param name="handler">The handler.</param>
public static IMcpServerBuilder WithListPromptsHandler(this IMcpServerBuilder builder, Func<RequestContext<ListPromptsRequestParams>, CancellationToken, Task<ListPromptsResult>> handler)
{
Throw.IfNull(builder);
builder.Services.Configure<McpServerHandlers>(s => s.ListPromptsHandler = handler);
return builder;
}
/// <summary>
/// Sets the handler for get prompt requests.
/// </summary>
/// <param name="builder">The builder instance.</param>
/// <param name="handler">The handler.</param>
public static IMcpServerBuilder WithGetPromptHandler(this IMcpServerBuilder builder, Func<RequestContext<GetPromptRequestParams>, CancellationToken, Task<GetPromptResult>> handler)
{
Throw.IfNull(builder);
builder.Services.Configure<McpServerHandlers>(s => s.GetPromptHandler = handler);
return builder;
}
/// <summary>
/// Sets the handler for list resources requests.
/// </summary>
/// <param name="builder">The builder instance.</param>
/// <param name="handler">The handler.</param>
public static IMcpServerBuilder WithListResourcesHandler(this IMcpServerBuilder builder, Func<RequestContext<ListResourcesRequestParams>, CancellationToken, Task<ListResourcesResult>> handler)
{
Throw.IfNull(builder);
builder.Services.Configure<McpServerHandlers>(s => s.ListResourcesHandler = handler);
return builder;
}
/// <summary>
/// Sets the handler for read resources requests.
/// </summary>
/// <param name="builder">The builder instance.</param>
/// <param name="handler">The handler.</param>
public static IMcpServerBuilder WithReadResourceHandler(this IMcpServerBuilder builder, Func<RequestContext<ReadResourceRequestParams>, CancellationToken, Task<ReadResourceResult>> handler)
{
Throw.IfNull(builder);
builder.Services.Configure<McpServerHandlers>(s => s.ReadResourceHandler = handler);
return builder;
}
/// <summary>
/// Sets the handler for get completion requests.
/// </summary>
/// <param name="builder">The builder instance.</param>
/// <param name="handler">The handler.</param>
public static IMcpServerBuilder WithGetCompletionHandler(this IMcpServerBuilder builder, Func<RequestContext<CompleteRequestParams>, CancellationToken, Task<CompleteResult>> handler)
{
Throw.IfNull(builder);
builder.Services.Configure<McpServerHandlers>(s => s.GetCompletionHandler = handler);
return builder;
}
/// <summary>
/// Sets the handler for subscribe to resources messages.
/// </summary>
/// <param name="builder">The builder instance.</param>
/// <param name="handler">The handler.</param>
public static IMcpServerBuilder WithSubscribeToResourcesHandler(this IMcpServerBuilder builder, Func<RequestContext<SubscribeRequestParams>, CancellationToken, Task<EmptyResult>> handler)
{
Throw.IfNull(builder);
builder.Services.Configure<McpServerHandlers>(s => s.SubscribeToResourcesHandler = handler);
return builder;
}
/// <summary>
/// Sets or sets the handler for subscribe to resources messages.
/// </summary>
/// <param name="builder">The builder instance.</param>
/// <param name="handler">The handler.</param>
public static IMcpServerBuilder WithUnsubscribeFromResourcesHandler(this IMcpServerBuilder builder, Func<RequestContext<UnsubscribeRequestParams>, CancellationToken, Task<EmptyResult>> handler)
{
Throw.IfNull(builder);
builder.Services.Configure<McpServerHandlers>(s => s.UnsubscribeFromResourcesHandler = handler);
return builder;
}
#endregion
#region Transports
/// <summary>
/// Adds a server transport that uses stdin/stdout for communication.
/// </summary>
/// <param name="builder">The builder instance.</param>
public static IMcpServerBuilder WithStdioServerTransport(this IMcpServerBuilder builder)
{
Throw.IfNull(builder);
builder.Services.AddSingleton<ITransport, StdioServerTransport>();
builder.Services.AddHostedService<McpServerSingleSessionHostedService>();
builder.Services.AddSingleton(services =>
{
ITransport serverTransport = services.GetRequiredService<ITransport>();
IOptions<McpServerOptions> options = services.GetRequiredService<IOptions<McpServerOptions>>();
ILoggerFactory? loggerFactory = services.GetService<ILoggerFactory>();
return McpServerFactory.Create(serverTransport, options.Value, loggerFactory, services);
});
return builder;
}
/// <summary>
/// Adds a server transport that uses SSE via a HttpListener for communication.
/// </summary>
/// <param name="builder">The builder instance.</param>
public static IMcpServerBuilder WithHttpListenerSseServerTransport(this IMcpServerBuilder builder)
{
Throw.IfNull(builder);
builder.Services.AddSingleton<IServerTransport, HttpListenerSseServerTransport>();
builder.Services.AddHostedService<McpServerMultiSessionHostedService>();
return builder;
}
#endregion
}

View file

@ -1,4 +1,4 @@
namespace ModelContextProtocol.Configuration;
namespace ModelContextProtocol;
/// <summary>
/// Configuration for an MCP server connection.

View file

@ -3,16 +3,18 @@ using ModelContextProtocol.Server;
using Microsoft.Extensions.Options;
using ModelContextProtocol.Utils;
namespace ModelContextProtocol.Configuration;
namespace Microsoft.Extensions.DependencyInjection;
/// <summary>
/// Configures the McpServerOptions using addition services from DI.
/// </summary>
/// <param name="serverHandlers">The server handlers configuration options.</param>
/// <param name="serverTools">Tools individually registered.</param>
/// <param name="serverPrompts">Prompts individually registered.</param>
internal sealed class McpServerOptionsSetup(
IOptions<McpServerHandlers> serverHandlers,
IEnumerable<McpServerTool> serverTools) : IConfigureOptions<McpServerOptions>
IEnumerable<McpServerTool> serverTools,
IEnumerable<McpServerPrompt> serverPrompts) : IConfigureOptions<McpServerOptions>
{
/// <summary>
/// Configures the given McpServerOptions instance by setting server information
@ -39,17 +41,34 @@ internal sealed class McpServerOptionsSetup(
// a collection, add to it, otherwise create a new one. We want to maintain the identity
// of an existing collection in case someone has provided their own derived type, wants
// change notifications, etc.
McpServerToolCollection toolsCollection = options.Capabilities?.Tools?.ToolCollection ?? [];
McpServerPrimitiveCollection<McpServerTool> toolCollection = options.Capabilities?.Tools?.ToolCollection ?? [];
foreach (var tool in serverTools)
{
toolsCollection.TryAdd(tool);
toolCollection.TryAdd(tool);
}
if (!toolsCollection.IsEmpty)
if (!toolCollection.IsEmpty)
{
options.Capabilities ??= new();
options.Capabilities.Tools ??= new();
options.Capabilities.Tools.ToolCollection = toolsCollection;
options.Capabilities.Tools.ToolCollection = toolCollection;
}
// Collect all of the provided prompts into a prompts collection. If the options already has
// a collection, add to it, otherwise create a new one. We want to maintain the identity
// of an existing collection in case someone has provided their own derived type, wants
// change notifications, etc.
McpServerPrimitiveCollection<McpServerPrompt> promptCollection = options.Capabilities?.Prompts?.PromptCollection ?? [];
foreach (var prompt in serverPrompts)
{
promptCollection.TryAdd(prompt);
}
if (!promptCollection.IsEmpty)
{
options.Capabilities ??= new();
options.Capabilities.Prompts ??= new();
options.Capabilities.Prompts.PromptCollection = promptCollection;
}
// Apply custom server handlers.

View file

@ -0,0 +1,28 @@
using ModelContextProtocol.Server;
using Microsoft.Extensions.Options;
namespace Microsoft.Extensions.DependencyInjection;
/// <summary>
/// Extension to host an MCP server
/// </summary>
public static class McpServerServiceCollectionExtensions
{
/// <summary>
/// Adds the MCP server to the service collection with default options.
/// </summary>
/// <param name="services"></param>
/// <param name="configureOptions"></param>
/// <returns></returns>
public static IMcpServerBuilder AddMcpServer(this IServiceCollection services, Action<McpServerOptions>? configureOptions = null)
{
services.AddOptions();
services.AddTransient<IConfigureOptions<McpServerOptions>, McpServerOptionsSetup>();
if (configureOptions is not null)
{
services.Configure(configureOptions);
}
return new DefaultMcpServerBuilder(services);
}
}