Skip to content

Commit c2310e8

Browse files
Copilothalter73
andcommitted
Create ModelContextProtocol.Hosting package with configuration infrastructure
Co-authored-by: halter73 <[email protected]>
1 parent 650c532 commit c2310e8

File tree

9 files changed

+1298
-0
lines changed

9 files changed

+1298
-0
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
namespace Microsoft.Extensions.DependencyInjection;
2+
3+
/// <summary>
4+
/// Default implementation of <see cref="IMcpServerBuilder"/> that enables fluent configuration
5+
/// of the Model Context Protocol (MCP) server. This builder is returned by the
6+
/// <see cref="McpServerServiceCollectionExtensions.AddMcpServer"/> extension method and
7+
/// provides access to the service collection for registering additional MCP components.
8+
/// </summary>
9+
internal sealed class DefaultMcpServerBuilder : IMcpServerBuilder
10+
{
11+
/// <inheritdoc/>
12+
public IServiceCollection Services { get; }
13+
14+
/// <summary>
15+
/// Initializes a new instance of the <see cref="DefaultMcpServerBuilder"/> class.
16+
/// </summary>
17+
/// <param name="services">The service collection to which MCP server services will be added. This collection
18+
/// is exposed through the <see cref="Services"/> property to allow additional configuration.</param>
19+
/// <exception cref="ArgumentNullException">Thrown when <paramref name="services"/> is null.</exception>
20+
public DefaultMcpServerBuilder(IServiceCollection services)
21+
{
22+
ArgumentNullException.ThrowIfNull(services);
23+
24+
Services = services;
25+
}
26+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using ModelContextProtocol.Server;
2+
3+
namespace Microsoft.Extensions.DependencyInjection;
4+
5+
/// <summary>
6+
/// Provides a builder for configuring <see cref="IMcpServer"/> instances.
7+
/// </summary>
8+
/// <remarks>
9+
/// <para>
10+
/// The <see cref="IMcpServerBuilder"/> interface provides a fluent API for configuring Model Context Protocol (MCP) servers
11+
/// when using dependency injection. It exposes methods for registering tools, prompts, custom request handlers,
12+
/// and server transports, allowing for comprehensive server configuration through a chain of method calls.
13+
/// </para>
14+
/// <para>
15+
/// The builder is obtained from the <see cref="McpServerServiceCollectionExtensions.AddMcpServer"/> extension
16+
/// method and provides access to the underlying service collection via the <see cref="Services"/> property.
17+
/// </para>
18+
/// </remarks>
19+
public interface IMcpServerBuilder
20+
{
21+
/// <summary>
22+
/// Gets the associated service collection.
23+
/// </summary>
24+
IServiceCollection Services { get; }
25+
}

src/ModelContextProtocol.Hosting/Configuration/McpServerBuilderExtensions.cs

Lines changed: 790 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
using Microsoft.Extensions.DependencyInjection;
2+
using ModelContextProtocol.Protocol;
3+
4+
namespace ModelContextProtocol.Server;
5+
6+
/// <summary>
7+
/// Provides a container for handlers used in the creation of an MCP server.
8+
/// </summary>
9+
/// <remarks>
10+
/// <para>
11+
/// This class provides a centralized collection of delegates that implement various capabilities of the Model Context Protocol.
12+
/// Each handler in this class corresponds to a specific endpoint in the Model Context Protocol and
13+
/// is responsible for processing a particular type of request. The handlers are used to customize
14+
/// the behavior of the MCP server by providing implementations for the various protocol operations.
15+
/// </para>
16+
/// <para>
17+
/// Handlers can be configured individually using the extension methods in McpServerBuilderExtensions
18+
/// such as WithListToolsHandler and WithCallToolHandler.
19+
/// </para>
20+
/// <para>
21+
/// When a client sends a request to the server, the appropriate handler is invoked to process the
22+
/// request and produce a response according to the protocol specification. Which handler is selected
23+
/// is done based on an ordinal, case-sensitive string comparison.
24+
/// </para>
25+
/// </remarks>
26+
public sealed class McpServerHandlers
27+
{
28+
/// <summary>
29+
/// Gets or sets the handler for <see cref="RequestMethods.ToolsList"/> requests.
30+
/// </summary>
31+
/// <remarks>
32+
/// <para>
33+
/// The handler should return a list of available tools when requested by a client.
34+
/// It supports pagination through the cursor mechanism, where the client can make
35+
/// repeated calls with the cursor returned by the previous call to retrieve more tools.
36+
/// </para>
37+
/// <para>
38+
/// This handler works alongside any tools defined in the <see cref="McpServerTool"/> collection.
39+
/// Tools from both sources will be combined when returning results to clients.
40+
/// </para>
41+
/// </remarks>
42+
public Func<RequestContext<ListToolsRequestParams>, CancellationToken, ValueTask<ListToolsResult>>? ListToolsHandler { get; set; }
43+
44+
/// <summary>
45+
/// Gets or sets the handler for <see cref="RequestMethods.ToolsCall"/> requests.
46+
/// </summary>
47+
/// <remarks>
48+
/// This handler is invoked when a client makes a call to a tool that isn't found in the <see cref="McpServerTool"/> collection.
49+
/// The handler should implement logic to execute the requested tool and return appropriate results.
50+
/// </remarks>
51+
public Func<RequestContext<CallToolRequestParams>, CancellationToken, ValueTask<CallToolResponse>>? CallToolHandler { get; set; }
52+
53+
/// <summary>
54+
/// Gets or sets the handler for <see cref="RequestMethods.PromptsList"/> requests.
55+
/// </summary>
56+
/// <remarks>
57+
/// <para>
58+
/// The handler should return a list of available prompts when requested by a client.
59+
/// It supports pagination through the cursor mechanism, where the client can make
60+
/// repeated calls with the cursor returned by the previous call to retrieve more prompts.
61+
/// </para>
62+
/// <para>
63+
/// This handler works alongside any prompts defined in the <see cref="McpServerPrompt"/> collection.
64+
/// Prompts from both sources will be combined when returning results to clients.
65+
/// </para>
66+
/// </remarks>
67+
public Func<RequestContext<ListPromptsRequestParams>, CancellationToken, ValueTask<ListPromptsResult>>? ListPromptsHandler { get; set; }
68+
69+
/// <summary>
70+
/// Gets or sets the handler for <see cref="RequestMethods.PromptsGet"/> requests.
71+
/// </summary>
72+
/// <remarks>
73+
/// This handler is invoked when a client requests details for a specific prompt that isn't found in the <see cref="McpServerPrompt"/> collection.
74+
/// The handler should implement logic to fetch or generate the requested prompt and return appropriate results.
75+
/// </remarks>
76+
public Func<RequestContext<GetPromptRequestParams>, CancellationToken, ValueTask<GetPromptResult>>? GetPromptHandler { get; set; }
77+
78+
/// <summary>
79+
/// Gets or sets the handler for <see cref="RequestMethods.ResourcesTemplatesList"/> requests.
80+
/// </summary>
81+
/// <remarks>
82+
/// The handler should return a list of available resource templates when requested by a client.
83+
/// It supports pagination through the cursor mechanism, where the client can make
84+
/// repeated calls with the cursor returned by the previous call to retrieve more resource templates.
85+
/// </remarks>
86+
public Func<RequestContext<ListResourceTemplatesRequestParams>, CancellationToken, ValueTask<ListResourceTemplatesResult>>? ListResourceTemplatesHandler { get; set; }
87+
88+
/// <summary>
89+
/// Gets or sets the handler for <see cref="RequestMethods.ResourcesList"/> requests.
90+
/// </summary>
91+
/// <remarks>
92+
/// The handler should return a list of available resources when requested by a client.
93+
/// It supports pagination through the cursor mechanism, where the client can make
94+
/// repeated calls with the cursor returned by the previous call to retrieve more resources.
95+
/// </remarks>
96+
public Func<RequestContext<ListResourcesRequestParams>, CancellationToken, ValueTask<ListResourcesResult>>? ListResourcesHandler { get; set; }
97+
98+
/// <summary>
99+
/// Gets or sets the handler for <see cref="RequestMethods.ResourcesRead"/> requests.
100+
/// </summary>
101+
/// <remarks>
102+
/// This handler is invoked when a client requests the content of a specific resource identified by its URI.
103+
/// The handler should implement logic to locate and retrieve the requested resource.
104+
/// </remarks>
105+
public Func<RequestContext<ReadResourceRequestParams>, CancellationToken, ValueTask<ReadResourceResult>>? ReadResourceHandler { get; set; }
106+
107+
/// <summary>
108+
/// Gets or sets the handler for <see cref="RequestMethods.CompletionComplete"/> requests.
109+
/// </summary>
110+
/// <remarks>
111+
/// This handler provides auto-completion suggestions for prompt arguments or resource references in the Model Context Protocol.
112+
/// The handler processes auto-completion requests, returning a list of suggestions based on the
113+
/// reference type and current argument value.
114+
/// </remarks>
115+
public Func<RequestContext<CompleteRequestParams>, CancellationToken, ValueTask<CompleteResult>>? CompleteHandler { get; set; }
116+
117+
/// <summary>
118+
/// Gets or sets the handler for <see cref="RequestMethods.ResourcesSubscribe"/> requests.
119+
/// </summary>
120+
/// <remarks>
121+
/// <para>
122+
/// This handler is invoked when a client wants to receive notifications about changes to specific resources or resource patterns.
123+
/// The handler should implement logic to register the client's interest in the specified resources
124+
/// and set up the necessary infrastructure to send notifications when those resources change.
125+
/// </para>
126+
/// <para>
127+
/// After a successful subscription, the server should send resource change notifications to the client
128+
/// whenever a relevant resource is created, updated, or deleted.
129+
/// </para>
130+
/// </remarks>
131+
public Func<RequestContext<SubscribeRequestParams>, CancellationToken, ValueTask<EmptyResult>>? SubscribeToResourcesHandler { get; set; }
132+
133+
/// <summary>
134+
/// Gets or sets the handler for <see cref="RequestMethods.ResourcesUnsubscribe"/> requests.
135+
/// </summary>
136+
/// <remarks>
137+
/// <para>
138+
/// This handler is invoked when a client wants to stop receiving notifications about previously subscribed resources.
139+
/// The handler should implement logic to remove the client's subscriptions to the specified resources
140+
/// and clean up any associated resources.
141+
/// </para>
142+
/// <para>
143+
/// After a successful unsubscription, the server should no longer send resource change notifications
144+
/// to the client for the specified resources.
145+
/// </para>
146+
/// </remarks>
147+
public Func<RequestContext<UnsubscribeRequestParams>, CancellationToken, ValueTask<EmptyResult>>? UnsubscribeFromResourcesHandler { get; set; }
148+
149+
/// <summary>
150+
/// Gets or sets the handler for <see cref="RequestMethods.LoggingSetLevel"/> requests.
151+
/// </summary>
152+
/// <remarks>
153+
/// <para>
154+
/// This handler processes <see cref="RequestMethods.LoggingSetLevel"/> requests from clients. When set, it enables
155+
/// clients to control which log messages they receive by specifying a minimum severity threshold.
156+
/// </para>
157+
/// <para>
158+
/// After handling a level change request, the server typically begins sending log messages
159+
/// at or above the specified level to the client as notifications/message notifications.
160+
/// </para>
161+
/// </remarks>
162+
public Func<RequestContext<SetLevelRequestParams>, CancellationToken, ValueTask<EmptyResult>>? SetLoggingLevelHandler { get; set; }
163+
164+
/// <summary>
165+
/// Overwrite any handlers in McpServerOptions with non-null handlers from this instance.
166+
/// </summary>
167+
/// <param name="options"></param>
168+
/// <returns></returns>
169+
internal void OverwriteWithSetHandlers(McpServerOptions options)
170+
{
171+
PromptsCapability? promptsCapability = options.Capabilities?.Prompts;
172+
if (ListPromptsHandler is not null || GetPromptHandler is not null)
173+
{
174+
promptsCapability ??= new();
175+
promptsCapability.ListPromptsHandler = ListPromptsHandler ?? promptsCapability.ListPromptsHandler;
176+
promptsCapability.GetPromptHandler = GetPromptHandler ?? promptsCapability.GetPromptHandler;
177+
}
178+
179+
ResourcesCapability? resourcesCapability = options.Capabilities?.Resources;
180+
if (ListResourcesHandler is not null ||
181+
ReadResourceHandler is not null)
182+
{
183+
resourcesCapability ??= new();
184+
resourcesCapability.ListResourceTemplatesHandler = ListResourceTemplatesHandler ?? resourcesCapability.ListResourceTemplatesHandler;
185+
resourcesCapability.ListResourcesHandler = ListResourcesHandler ?? resourcesCapability.ListResourcesHandler;
186+
resourcesCapability.ReadResourceHandler = ReadResourceHandler ?? resourcesCapability.ReadResourceHandler;
187+
188+
if (SubscribeToResourcesHandler is not null || UnsubscribeFromResourcesHandler is not null)
189+
{
190+
resourcesCapability.SubscribeToResourcesHandler = SubscribeToResourcesHandler ?? resourcesCapability.SubscribeToResourcesHandler;
191+
resourcesCapability.UnsubscribeFromResourcesHandler = UnsubscribeFromResourcesHandler ?? resourcesCapability.UnsubscribeFromResourcesHandler;
192+
resourcesCapability.Subscribe = true;
193+
}
194+
}
195+
196+
ToolsCapability? toolsCapability = options.Capabilities?.Tools;
197+
if (ListToolsHandler is not null || CallToolHandler is not null)
198+
{
199+
toolsCapability ??= new();
200+
toolsCapability.ListToolsHandler = ListToolsHandler ?? toolsCapability.ListToolsHandler;
201+
toolsCapability.CallToolHandler = CallToolHandler ?? toolsCapability.CallToolHandler;
202+
}
203+
204+
LoggingCapability? loggingCapability = options.Capabilities?.Logging;
205+
if (SetLoggingLevelHandler is not null)
206+
{
207+
loggingCapability ??= new();
208+
loggingCapability.SetLoggingLevelHandler = SetLoggingLevelHandler;
209+
}
210+
211+
CompletionsCapability? completionsCapability = options.Capabilities?.Completions;
212+
if (CompleteHandler is not null)
213+
{
214+
completionsCapability ??= new();
215+
completionsCapability.CompleteHandler = CompleteHandler;
216+
}
217+
218+
options.Capabilities ??= new();
219+
options.Capabilities.Prompts = promptsCapability;
220+
options.Capabilities.Resources = resourcesCapability;
221+
options.Capabilities.Tools = toolsCapability;
222+
options.Capabilities.Logging = loggingCapability;
223+
options.Capabilities.Completions = completionsCapability;
224+
}
225+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
using Microsoft.Extensions.Options;
2+
using ModelContextProtocol.Server;
3+
4+
namespace ModelContextProtocol.Hosting.Configuration;
5+
6+
/// <summary>
7+
/// Configures the McpServerOptions using addition services from DI.
8+
/// </summary>
9+
/// <param name="serverHandlers">The server handlers configuration options.</param>
10+
/// <param name="serverTools">Tools individually registered.</param>
11+
/// <param name="serverPrompts">Prompts individually registered.</param>
12+
/// <param name="serverResources">Resources individually registered.</param>
13+
internal sealed class McpServerOptionsSetup(
14+
IOptions<McpServerHandlers> serverHandlers,
15+
IEnumerable<McpServerTool> serverTools,
16+
IEnumerable<McpServerPrompt> serverPrompts,
17+
IEnumerable<McpServerResource> serverResources) : IConfigureOptions<McpServerOptions>
18+
{
19+
/// <summary>
20+
/// Configures the given McpServerOptions instance by setting server information
21+
/// and applying custom server handlers and tools.
22+
/// </summary>
23+
/// <param name="options">The options instance to be configured.</param>
24+
public void Configure(McpServerOptions options)
25+
{
26+
ArgumentNullException.ThrowIfNull(options);
27+
28+
// Collect all of the provided tools into a tools collection. If the options already has
29+
// a collection, add to it, otherwise create a new one. We want to maintain the identity
30+
// of an existing collection in case someone has provided their own derived type, wants
31+
// change notifications, etc.
32+
McpServerPrimitiveCollection<McpServerTool> toolCollection = options.Capabilities?.Tools?.ToolCollection ?? [];
33+
foreach (var tool in serverTools)
34+
{
35+
toolCollection.TryAdd(tool);
36+
}
37+
38+
if (!toolCollection.IsEmpty)
39+
{
40+
options.Capabilities ??= new();
41+
options.Capabilities.Tools ??= new();
42+
options.Capabilities.Tools.ToolCollection = toolCollection;
43+
}
44+
45+
// Collect all of the provided prompts into a prompts collection. If the options already has
46+
// a collection, add to it, otherwise create a new one. We want to maintain the identity
47+
// of an existing collection in case someone has provided their own derived type, wants
48+
// change notifications, etc.
49+
McpServerPrimitiveCollection<McpServerPrompt> promptCollection = options.Capabilities?.Prompts?.PromptCollection ?? [];
50+
foreach (var prompt in serverPrompts)
51+
{
52+
promptCollection.TryAdd(prompt);
53+
}
54+
55+
if (!promptCollection.IsEmpty)
56+
{
57+
options.Capabilities ??= new();
58+
options.Capabilities.Prompts ??= new();
59+
options.Capabilities.Prompts.PromptCollection = promptCollection;
60+
}
61+
62+
// Collect all of the provided resources into a resources collection. If the options already has
63+
// a collection, add to it, otherwise create a new one. We want to maintain the identity
64+
// of an existing collection in case someone has provided their own derived type, wants
65+
// change notifications, etc.
66+
McpServerPrimitiveCollection<McpServerResource> resourceCollection = options.Capabilities?.Resources?.ResourceCollection ?? [];
67+
foreach (var resource in serverResources)
68+
{
69+
resourceCollection.TryAdd(resource);
70+
}
71+
72+
if (!resourceCollection.IsEmpty)
73+
{
74+
options.Capabilities ??= new();
75+
options.Capabilities.Resources ??= new();
76+
options.Capabilities.Resources.ResourceCollection = resourceCollection;
77+
}
78+
79+
// Apply custom server handlers.
80+
serverHandlers.Value.OverwriteWithSetHandlers(options);
81+
}
82+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using Microsoft.Extensions.DependencyInjection.Extensions;
2+
using Microsoft.Extensions.Options;
3+
using ModelContextProtocol.Hosting.Configuration;
4+
using ModelContextProtocol.Server;
5+
6+
namespace Microsoft.Extensions.DependencyInjection;
7+
8+
/// <summary>
9+
/// Provides extension methods for configuring MCP servers with dependency injection.
10+
/// </summary>
11+
public static class McpServerServiceCollectionExtensions
12+
{
13+
/// <summary>
14+
/// Adds the Model Context Protocol (MCP) server to the service collection with default options.
15+
/// </summary>
16+
/// <param name="services">The <see cref="IServiceCollection"/> to add the server to.</param>
17+
/// <param name="configureOptions">Optional callback to configure the <see cref="McpServerOptions"/>.</param>
18+
/// <returns>An <see cref="IMcpServerBuilder"/> that can be used to further configure the MCP server.</returns>
19+
20+
public static IMcpServerBuilder AddMcpServer(this IServiceCollection services, Action<McpServerOptions>? configureOptions = null)
21+
{
22+
services.AddOptions();
23+
services.TryAddEnumerable(ServiceDescriptor.Transient<IConfigureOptions<McpServerOptions>, McpServerOptionsSetup>());
24+
if (configureOptions is not null)
25+
{
26+
services.Configure(configureOptions);
27+
}
28+
29+
return new DefaultMcpServerBuilder(services);
30+
}
31+
}

0 commit comments

Comments
 (0)