Decouple Handlers and Collections from protocol types (#765)

* Decouple Handlers and Collections (e.g. ToolsHandler and ToolsCollection) from Capabilities by promoting them to the options types.

Also, group all handlers in container classes, for server, I decided to move McpServerHandlers down to Mcp.Core and move the NotificationHandlers there too.

Also, Fix ListResourceTemplatesHandler issue.

* Add remarks for empty protocol capability types

* Keep JsonIgnore properties and mark them as Obsolete

* Continue supporting Obsolete members

* Update Obsolete messages to indicate the member will be removed

* Add EditorBrowsable Never to the remaining obsolete members
This commit is contained in:
David Cantú 2025-09-24 09:42:03 -05:00 • committed by GitHub
commit 5f992994c3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 1588 additions and 1161 deletions

View file

@ -1,11 +1,13 @@
using ModelContextProtocol.Protocol;
using System.ComponentModel;
namespace ModelContextProtocol.Server;
/// <summary>
/// Represents an instance of a Model Context Protocol (MCP) server that connects to and communicates with an MCP client.
/// </summary>
[Obsolete($"Use {nameof(McpServer)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774
[Obsolete($"Use {nameof(McpServer)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774
[EditorBrowsable(EditorBrowsableState.Never)]
public interface IMcpServer : IMcpEndpoint
{
/// <summary>

View file

@ -1,6 +1,7 @@
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Logging;
using ModelContextProtocol.Protocol;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Text.Json;
@ -26,7 +27,8 @@ public static class McpServerExtensions
/// It allows detailed control over sampling parameters including messages, system prompt, temperature,
/// and token limits.
/// </remarks>
[Obsolete($"Use {nameof(McpServer)}.{nameof(McpServer.SampleAsync)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774
[Obsolete($"Use {nameof(McpServer)}.{nameof(McpServer.SampleAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774
[EditorBrowsable(EditorBrowsableState.Never)]
public static ValueTask<CreateMessageResult> SampleAsync(
this IMcpServer server, CreateMessageRequestParams request, CancellationToken cancellationToken = default)
=> AsServerOrThrow(server).SampleAsync(request, cancellationToken);
@ -46,7 +48,8 @@ public static class McpServerExtensions
/// This method converts the provided chat messages into a format suitable for the sampling API,
/// handling different content types such as text, images, and audio.
/// </remarks>
[Obsolete($"Use {nameof(McpServer)}.{nameof(McpServer.SampleAsync)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774]
[Obsolete($"Use {nameof(McpServer)}.{nameof(McpServer.SampleAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774
[EditorBrowsable(EditorBrowsableState.Never)]
public static Task<ChatResponse> SampleAsync(
this IMcpServer server,
IEnumerable<ChatMessage> messages, ChatOptions? options = default, CancellationToken cancellationToken = default)
@ -59,14 +62,16 @@ public static class McpServerExtensions
/// <returns>The <see cref="IChatClient"/> that can be used to issue sampling requests to the client.</returns>
/// <exception cref="ArgumentNullException"><paramref name="server"/> is <see langword="null"/>.</exception>
/// <exception cref="InvalidOperationException">The client does not support sampling.</exception>
[Obsolete($"Use {nameof(McpServer)}.{nameof(McpServer.AsSamplingChatClient)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774
[Obsolete($"Use {nameof(McpServer)}.{nameof(McpServer.AsSamplingChatClient)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774
[EditorBrowsable(EditorBrowsableState.Never)]
public static IChatClient AsSamplingChatClient(this IMcpServer server)
=> AsServerOrThrow(server).AsSamplingChatClient();
/// <summary>Gets an <see cref="ILogger"/> on which logged messages will be sent as notifications to the client.</summary>
/// <param name="server">The server to wrap as an <see cref="ILogger"/>.</param>
/// <returns>An <see cref="ILogger"/> that can be used to log to the client..</returns>
[Obsolete($"Use {nameof(McpServer)}.{nameof(McpServer.AsSamplingChatClient)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774
[Obsolete($"Use {nameof(McpServer)}.{nameof(McpServer.AsSamplingChatClient)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774
[EditorBrowsable(EditorBrowsableState.Never)]
public static ILoggerProvider AsClientLoggerProvider(this IMcpServer server)
=> AsServerOrThrow(server).AsClientLoggerProvider();
@ -85,7 +90,8 @@ public static class McpServerExtensions
/// navigated and accessed by the server. These resources might include file systems, databases,
/// or other structured data sources that the client makes available through the protocol.
/// </remarks>
[Obsolete($"Use {nameof(McpServer)}.{nameof(McpServer.RequestRootsAsync)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774
[Obsolete($"Use {nameof(McpServer)}.{nameof(McpServer.RequestRootsAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774
[EditorBrowsable(EditorBrowsableState.Never)]
public static ValueTask<ListRootsResult> RequestRootsAsync(
this IMcpServer server, ListRootsRequestParams request, CancellationToken cancellationToken = default)
=> AsServerOrThrow(server).RequestRootsAsync(request, cancellationToken);
@ -102,7 +108,8 @@ public static class McpServerExtensions
/// <remarks>
/// This method requires the client to support the elicitation capability.
/// </remarks>
[Obsolete($"Use {nameof(McpServer)}.{nameof(McpServer.ElicitAsync)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774
[Obsolete($"Use {nameof(McpServer)}.{nameof(McpServer.ElicitAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774
[EditorBrowsable(EditorBrowsableState.Never)]
public static ValueTask<ElicitResult> ElicitAsync(
this IMcpServer server, ElicitRequestParams request, CancellationToken cancellationToken = default)
=> AsServerOrThrow(server).ElicitAsync(request, cancellationToken);

View file

@ -1,5 +1,6 @@
using Microsoft.Extensions.Logging;
using ModelContextProtocol.Protocol;
using System.ComponentModel;
namespace ModelContextProtocol.Server;
@ -10,7 +11,8 @@ namespace ModelContextProtocol.Server;
/// This is the recommended way to create <see cref="IMcpServer"/> instances.
/// The factory handles proper initialization of server instances with the required dependencies.
/// </remarks>
[Obsolete($"Use {nameof(McpServer)}.{nameof(McpServer.Create)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774
[Obsolete($"Use {nameof(McpServer)}.{nameof(McpServer.Create)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774
[EditorBrowsable(EditorBrowsableState.Never)]
public static class McpServerFactory
{
/// <summary>

View file

@ -0,0 +1,177 @@
using ModelContextProtocol.Protocol;
namespace ModelContextProtocol.Server;
/// <summary>
/// Provides a container for handlers used in the creation of an MCP server.
/// </summary>
/// <remarks>
/// <para>
/// This class provides a centralized collection of delegates that implement various capabilities of the Model Context Protocol.
/// Each handler in this class corresponds to a specific endpoint in the Model Context Protocol and
/// is responsible for processing a particular type of message. The handlers are used to customize
/// the behavior of the MCP server by providing implementations for the various protocol operations.
/// </para>
/// <para>
/// When a client sends a message to the server, the appropriate handler is invoked to process it
/// according to the protocol specification. Which handler is selected
/// is done based on an ordinal, case-sensitive string comparison.
/// </para>
/// </remarks>
public sealed class McpServerHandlers
{
/// <summary>
/// Gets or sets the handler for <see cref="RequestMethods.ToolsList"/> requests.
/// </summary>
/// <remarks>
/// <para>
/// The handler should return a list of available tools when requested by a client.
/// It supports pagination through the cursor mechanism, where the client can make
/// repeated calls with the cursor returned by the previous call to retrieve more tools.
/// </para>
/// <para>
/// This handler works alongside any tools defined in the <see cref="McpServerTool"/> collection.
/// Tools from both sources will be combined when returning results to clients.
/// </para>
/// </remarks>
public McpRequestHandler<ListToolsRequestParams, ListToolsResult>? ListToolsHandler { get; set; }
/// <summary>
/// Gets or sets the handler for <see cref="RequestMethods.ToolsCall"/> requests.
/// </summary>
/// <remarks>
/// This handler is invoked when a client makes a call to a tool that isn't found in the <see cref="McpServerTool"/> collection.
/// The handler should implement logic to execute the requested tool and return appropriate results.
/// </remarks>
public McpRequestHandler<CallToolRequestParams, CallToolResult>? CallToolHandler { get; set; }
/// <summary>
/// Gets or sets the handler for <see cref="RequestMethods.PromptsList"/> requests.
/// </summary>
/// <remarks>
/// <para>
/// The handler should return a list of available prompts when requested by a client.
/// It supports pagination through the cursor mechanism, where the client can make
/// repeated calls with the cursor returned by the previous call to retrieve more prompts.
/// </para>
/// <para>
/// This handler works alongside any prompts defined in the <see cref="McpServerPrompt"/> collection.
/// Prompts from both sources will be combined when returning results to clients.
/// </para>
/// </remarks>
public McpRequestHandler<ListPromptsRequestParams, ListPromptsResult>? ListPromptsHandler { get; set; }
/// <summary>
/// Gets or sets the handler for <see cref="RequestMethods.PromptsGet"/> requests.
/// </summary>
/// <remarks>
/// This handler is invoked when a client requests details for a specific prompt that isn't found in the <see cref="McpServerPrompt"/> collection.
/// The handler should implement logic to fetch or generate the requested prompt and return appropriate results.
/// </remarks>
public McpRequestHandler<GetPromptRequestParams, GetPromptResult>? GetPromptHandler { get; set; }
/// <summary>
/// Gets or sets the handler for <see cref="RequestMethods.ResourcesTemplatesList"/> requests.
/// </summary>
/// <remarks>
/// The handler should return a list of available resource templates when requested by a client.
/// It supports pagination through the cursor mechanism, where the client can make
/// repeated calls with the cursor returned by the previous call to retrieve more resource templates.
/// </remarks>
public McpRequestHandler<ListResourceTemplatesRequestParams, ListResourceTemplatesResult>? ListResourceTemplatesHandler { get; set; }
/// <summary>
/// Gets or sets the handler for <see cref="RequestMethods.ResourcesList"/> requests.
/// </summary>
/// <remarks>
/// The handler should return a list of available resources when requested by a client.
/// It supports pagination through the cursor mechanism, where the client can make
/// repeated calls with the cursor returned by the previous call to retrieve more resources.
/// </remarks>
public McpRequestHandler<ListResourcesRequestParams, ListResourcesResult>? ListResourcesHandler { get; set; }
/// <summary>
/// Gets or sets the handler for <see cref="RequestMethods.ResourcesRead"/> requests.
/// </summary>
/// <remarks>
/// This handler is invoked when a client requests the content of a specific resource identified by its URI.
/// The handler should implement logic to locate and retrieve the requested resource.
/// </remarks>
public McpRequestHandler<ReadResourceRequestParams, ReadResourceResult>? ReadResourceHandler { get; set; }
/// <summary>
/// Gets or sets the handler for <see cref="RequestMethods.CompletionComplete"/> requests.
/// </summary>
/// <remarks>
/// This handler provides auto-completion suggestions for prompt arguments or resource references in the Model Context Protocol.
/// The handler processes auto-completion requests, returning a list of suggestions based on the
/// reference type and current argument value.
/// </remarks>
public McpRequestHandler<CompleteRequestParams, CompleteResult>? CompleteHandler { get; set; }
/// <summary>
/// Gets or sets the handler for <see cref="RequestMethods.ResourcesSubscribe"/> requests.
/// </summary>
/// <remarks>
/// <para>
/// This handler is invoked when a client wants to receive notifications about changes to specific resources or resource patterns.
/// The handler should implement logic to register the client's interest in the specified resources
/// and set up the necessary infrastructure to send notifications when those resources change.
/// </para>
/// <para>
/// After a successful subscription, the server should send resource change notifications to the client
/// whenever a relevant resource is created, updated, or deleted.
/// </para>
/// </remarks>
public McpRequestHandler<SubscribeRequestParams, EmptyResult>? SubscribeToResourcesHandler { get; set; }
/// <summary>
/// Gets or sets the handler for <see cref="RequestMethods.ResourcesUnsubscribe"/> requests.
/// </summary>
/// <remarks>
/// <para>
/// This handler is invoked when a client wants to stop receiving notifications about previously subscribed resources.
/// The handler should implement logic to remove the client's subscriptions to the specified resources
/// and clean up any associated resources.
/// </para>
/// <para>
/// After a successful unsubscription, the server should no longer send resource change notifications
/// to the client for the specified resources.
/// </para>
/// </remarks>
public McpRequestHandler<UnsubscribeRequestParams, EmptyResult>? UnsubscribeFromResourcesHandler { get; set; }
/// <summary>
/// Gets or sets the handler for <see cref="RequestMethods.LoggingSetLevel"/> requests.
/// </summary>
/// <remarks>
/// <para>
/// This handler processes <see cref="RequestMethods.LoggingSetLevel"/> requests from clients. When set, it enables
/// clients to control which log messages they receive by specifying a minimum severity threshold.
/// </para>
/// <para>
/// After handling a level change request, the server typically begins sending log messages
/// at or above the specified level to the client as notifications/message notifications.
/// </para>
/// </remarks>
public McpRequestHandler<SetLevelRequestParams, EmptyResult>? SetLoggingLevelHandler { get; set; }
/// <summary>Gets or sets notification handlers to register with the server.</summary>
/// <remarks>
/// <para>
/// When constructed, the server will enumerate these handlers once, which may contain multiple handlers per notification method key.
/// The server will not re-enumerate the sequence after initialization.
/// </para>
/// <para>
/// Notification handlers allow the server to respond to client-sent notifications for specific methods.
/// Each key in the collection is a notification method name, and each value is a callback that will be invoked
/// when a notification with that method is received.
/// </para>
/// <para>
/// Handlers provided via <see cref="NotificationHandlers"/> will be registered with the server for the lifetime of the server.
/// For transient handlers, <see cref="IMcpEndpoint.RegisterNotificationHandler"/> may be used to register a handler that can
/// then be unregistered by disposing of the <see cref="IAsyncDisposable"/> returned from the method.
/// </para>
/// </remarks>
public IEnumerable<KeyValuePair<string, Func<JsonRpcNotification, CancellationToken, ValueTask>>>? NotificationHandlers { get; set; }
}

View file

@ -84,7 +84,7 @@ internal sealed partial class McpServerImpl : McpServer
ConfigurePing();
// Register any notification handlers that were provided.
if (options.Capabilities?.NotificationHandlers is { } notificationHandlers)
if (options.Handlers.NotificationHandlers is { } notificationHandlers)
{
_notificationHandlers.RegisterRange(notificationHandlers);
}
@ -92,9 +92,9 @@ internal sealed partial class McpServerImpl : McpServer
// Now that everything has been configured, subscribe to any necessary notifications.
if (transport is not StreamableHttpServerTransport streamableHttpTransport || streamableHttpTransport.Stateless is false)
{
Register(ServerOptions.Capabilities?.Tools?.ToolCollection, NotificationMethods.ToolListChangedNotification);
Register(ServerOptions.Capabilities?.Prompts?.PromptCollection, NotificationMethods.PromptListChangedNotification);
Register(ServerOptions.Capabilities?.Resources?.ResourceCollection, NotificationMethods.ResourceListChangedNotification);
Register(ServerOptions.ToolCollection, NotificationMethods.ToolListChangedNotification);
Register(ServerOptions.PromptCollection, NotificationMethods.PromptListChangedNotification);
Register(ServerOptions.ResourceCollection, NotificationMethods.ResourceListChangedNotification);
void Register<TPrimitive>(McpServerPrimitiveCollection<TPrimitive>? collection, string notificationMethod)
where TPrimitive : IMcpServerPrimitive
@ -230,22 +230,26 @@ internal sealed partial class McpServerImpl : McpServer
private void ConfigureCompletion(McpServerOptions options)
{
if (options.Capabilities?.Completions is not { } completionsCapability)
var completeHandler = options.Handlers.CompleteHandler;
var completionsCapability = options.Capabilities?.Completions;
#pragma warning disable CS0618 // Type or member is obsolete
completeHandler ??= completionsCapability?.CompleteHandler;
#pragma warning restore CS0618 // Type or member is obsolete
if (completeHandler is null && completionsCapability is null)
{
return;
}
var completeHandler = completionsCapability.CompleteHandler ?? (static async (_, __) => new CompleteResult());
completeHandler ??= (static async (_, __) => new CompleteResult());
completeHandler = BuildFilterPipeline(completeHandler, options.Filters.CompleteFilters);
ServerCapabilities.Completions = new()
{
CompleteHandler = completeHandler
};
ServerCapabilities.Completions = new();
SetHandler(
RequestMethods.CompletionComplete,
ServerCapabilities.Completions.CompleteHandler,
completeHandler,
McpJsonUtilities.JsonContext.Default.CompleteRequestParams,
McpJsonUtilities.JsonContext.Default.CompleteResult);
}
@ -257,21 +261,38 @@ internal sealed partial class McpServerImpl : McpServer
private void ConfigureResources(McpServerOptions options)
{
if (options.Capabilities?.Resources is not { } resourcesCapability)
var listResourcesHandler = options.Handlers.ListResourcesHandler;
var listResourceTemplatesHandler = options.Handlers.ListResourceTemplatesHandler;
var readResourceHandler = options.Handlers.ReadResourceHandler;
var subscribeHandler = options.Handlers.SubscribeToResourcesHandler;
var unsubscribeHandler = options.Handlers.UnsubscribeFromResourcesHandler;
var resources = options.ResourceCollection;
var resourcesCapability = options.Capabilities?.Resources;
#pragma warning disable CS0618 // Type or member is obsolete
listResourcesHandler ??= resourcesCapability?.ListResourcesHandler;
listResourceTemplatesHandler ??= resourcesCapability?.ListResourceTemplatesHandler;
readResourceHandler ??= resourcesCapability?.ReadResourceHandler;
subscribeHandler ??= resourcesCapability?.SubscribeToResourcesHandler;
unsubscribeHandler ??= resourcesCapability?.UnsubscribeFromResourcesHandler;
#pragma warning restore CS0618 // Type or member is obsolete
if (listResourcesHandler is null && listResourceTemplatesHandler is null && readResourceHandler is null &&
subscribeHandler is null && unsubscribeHandler is null && resources is null &&
resourcesCapability is null)
{
return;
}
ServerCapabilities.Resources = new();
var listResourcesHandler = resourcesCapability.ListResourcesHandler ?? (static async (_, __) => new ListResourcesResult());
var listResourceTemplatesHandler = resourcesCapability.ListResourceTemplatesHandler ?? (static async (_, __) => new ListResourceTemplatesResult());
var readResourceHandler = resourcesCapability.ReadResourceHandler ?? (static async (request, _) => throw new McpException($"Unknown resource URI: '{request.Params?.Uri}'", McpErrorCode.InvalidParams));
var subscribeHandler = resourcesCapability.SubscribeToResourcesHandler ?? (static async (_, __) => new EmptyResult());
var unsubscribeHandler = resourcesCapability.UnsubscribeFromResourcesHandler ?? (static async (_, __) => new EmptyResult());
var resources = resourcesCapability.ResourceCollection;
var listChanged = resourcesCapability.ListChanged;
var subscribe = resourcesCapability.Subscribe;
listResourcesHandler ??= (static async (_, __) => new ListResourcesResult());
listResourceTemplatesHandler ??= (static async (_, __) => new ListResourceTemplatesResult());
readResourceHandler ??= (static async (request, _) => throw new McpException($"Unknown resource URI: '{request.Params?.Uri}'", McpErrorCode.InvalidParams));
subscribeHandler ??= (static async (_, __) => new EmptyResult());
unsubscribeHandler ??= (static async (_, __) => new EmptyResult());
var listChanged = resourcesCapability?.ListChanged;
var subscribe = resourcesCapability?.Subscribe;
// Handle resources provided via DI.
if (resources is { IsEmpty: false })
@ -376,12 +397,6 @@ internal sealed partial class McpServerImpl : McpServer
subscribeHandler = BuildFilterPipeline(subscribeHandler, options.Filters.SubscribeToResourcesFilters);
unsubscribeHandler = BuildFilterPipeline(unsubscribeHandler, options.Filters.UnsubscribeFromResourcesFilters);
ServerCapabilities.Resources.ListResourcesHandler = listResourcesHandler;
ServerCapabilities.Resources.ListResourceTemplatesHandler = listResourceTemplatesHandler;
ServerCapabilities.Resources.ReadResourceHandler = readResourceHandler;
ServerCapabilities.Resources.ResourceCollection = resources;
ServerCapabilities.Resources.SubscribeToResourcesHandler = subscribeHandler;
ServerCapabilities.Resources.UnsubscribeFromResourcesHandler = unsubscribeHandler;
ServerCapabilities.Resources.ListChanged = listChanged;
ServerCapabilities.Resources.Subscribe = subscribe;
@ -418,17 +433,27 @@ internal sealed partial class McpServerImpl : McpServer
private void ConfigurePrompts(McpServerOptions options)
{
if (options.Capabilities?.Prompts is not { } promptsCapability)
var listPromptsHandler = options.Handlers.ListPromptsHandler;
var getPromptHandler = options.Handlers.GetPromptHandler;
var prompts = options.PromptCollection;
var promptsCapability = options.Capabilities?.Prompts;
#pragma warning disable CS0618 // Type or member is obsolete
listPromptsHandler ??= promptsCapability?.ListPromptsHandler;
getPromptHandler ??= promptsCapability?.GetPromptHandler;
#pragma warning restore CS0618 // Type or member is obsolete
if (listPromptsHandler is null && getPromptHandler is null && prompts is null &&
promptsCapability is null)
{
return;
}
ServerCapabilities.Prompts = new();
var listPromptsHandler = promptsCapability.ListPromptsHandler ?? (static async (_, __) => new ListPromptsResult());
var getPromptHandler = promptsCapability.GetPromptHandler ?? (static async (request, _) => throw new McpException($"Unknown prompt: '{request.Params?.Name}'", McpErrorCode.InvalidParams));
var prompts = promptsCapability.PromptCollection;
var listChanged = promptsCapability.ListChanged;
listPromptsHandler ??= (static async (_, __) => new ListPromptsResult());
getPromptHandler ??= (static async (request, _) => throw new McpException($"Unknown prompt: '{request.Params?.Name}'", McpErrorCode.InvalidParams));
var listChanged = promptsCapability?.ListChanged;
// Handle tools provided via DI by augmenting the handlers to incorporate them.
if (prompts is { IsEmpty: false })
@ -479,9 +504,6 @@ internal sealed partial class McpServerImpl : McpServer
return handler(request, cancellationToken);
});
ServerCapabilities.Prompts.ListPromptsHandler = listPromptsHandler;
ServerCapabilities.Prompts.GetPromptHandler = getPromptHandler;
ServerCapabilities.Prompts.PromptCollection = prompts;
ServerCapabilities.Prompts.ListChanged = listChanged;
SetHandler(
@ -499,17 +521,27 @@ internal sealed partial class McpServerImpl : McpServer
private void ConfigureTools(McpServerOptions options)
{
if (options.Capabilities?.Tools is not { } toolsCapability)
var listToolsHandler = options.Handlers.ListToolsHandler;
var callToolHandler = options.Handlers.CallToolHandler;
var tools = options.ToolCollection;
var toolsCapability = options.Capabilities?.Tools;
#pragma warning disable CS0618 // Type or member is obsolete
listToolsHandler ??= toolsCapability?.ListToolsHandler;
callToolHandler ??= toolsCapability?.CallToolHandler;
#pragma warning restore CS0618 // Type or member is obsolete
if (listToolsHandler is null && callToolHandler is null && tools is null &&
toolsCapability is null)
{
return;
}
ServerCapabilities.Tools = new();
var listToolsHandler = toolsCapability.ListToolsHandler ?? (static async (_, __) => new ListToolsResult());
var callToolHandler = toolsCapability.CallToolHandler ?? (static async (request, _) => throw new McpException($"Unknown tool: '{request.Params?.Name}'", McpErrorCode.InvalidParams));
var tools = toolsCapability.ToolCollection;
var listChanged = toolsCapability.ListChanged;
listToolsHandler ??= (static async (_, __) => new ListToolsResult());
callToolHandler ??= (static async (request, _) => throw new McpException($"Unknown tool: '{request.Params?.Name}'", McpErrorCode.InvalidParams));
var listChanged = toolsCapability?.ListChanged;
// Handle tools provided via DI by augmenting the handlers to incorporate them.
if (tools is { IsEmpty: false })
@ -591,9 +623,6 @@ internal sealed partial class McpServerImpl : McpServer
}
});
ServerCapabilities.Tools.ListToolsHandler = listToolsHandler;
ServerCapabilities.Tools.CallToolHandler = callToolHandler;
ServerCapabilities.Tools.ToolCollection = tools;
ServerCapabilities.Tools.ListChanged = listChanged;
SetHandler(
@ -612,7 +641,11 @@ internal sealed partial class McpServerImpl : McpServer
private void ConfigureLogging(McpServerOptions options)
{
// We don't require that the handler be provided, as we always store the provided log level to the server.
var setLoggingLevelHandler = options.Capabilities?.Logging?.SetLoggingLevelHandler;
var setLoggingLevelHandler = options.Handlers.SetLoggingLevelHandler;
#pragma warning disable CS0618 // Type or member is obsolete
setLoggingLevelHandler ??= options.Capabilities?.Logging?.SetLoggingLevelHandler;
#pragma warning restore CS0618 // Type or member is obsolete
// Apply filters to the handler
if (setLoggingLevelHandler is not null)
@ -621,7 +654,6 @@ internal sealed partial class McpServerImpl : McpServer
}
ServerCapabilities.Logging = new();
ServerCapabilities.Logging.SetLoggingLevelHandler = setLoggingLevelHandler;
_requestHandlers.Set(
RequestMethods.LoggingSetLevel,

View file

@ -7,6 +7,8 @@ namespace ModelContextProtocol.Server;
/// </summary>
public sealed class McpServerOptions
{
private McpServerHandlers? _handlers;
/// <summary>
/// Gets or sets information about this server implementation, including its name and version.
/// </summary>
@ -89,4 +91,67 @@ public sealed class McpServerOptions
/// added will be the outermost (first to execute).
/// </remarks>
public McpServerFilters Filters { get; } = new();
/// <summary>
/// Gets or sets the container of handlers used by the server for processing protocol messages.
/// </summary>
public McpServerHandlers Handlers
{
get => _handlers ??= new();
set
{
Throw.IfNull(value);
_handlers = value;
}
}
/// <summary>
/// Gets or sets a collection of tools served by the server.
/// </summary>
/// <remarks>
/// Tools specified via <see cref="ToolCollection"/> augment the <see cref="McpServerHandlers.ListToolsHandler"/> and
/// <see cref="McpServerHandlers.CallToolHandler"/>, if provided. ListTools requests will output information about every tool
/// in <see cref="ToolCollection"/> and then also any tools output by <see cref="McpServerHandlers.ListToolsHandler"/>, if it's
/// non-<see langword="null"/>. CallTool requests will first check <see cref="ToolCollection"/> for the tool
/// being requested, and if the tool is not found in the <see cref="ToolCollection"/>, any specified <see cref="McpServerHandlers.CallToolHandler"/>
/// will be invoked as a fallback.
/// </remarks>
public McpServerPrimitiveCollection<McpServerTool>? ToolCollection { get; set; }
/// <summary>
/// Gets or sets a collection of resources served by the server.
/// </summary>
/// <remarks>
/// <para>
/// Resources specified via <see cref="ResourceCollection"/> augment the <see cref="McpServerHandlers.ListResourcesHandler"/>, <see cref="McpServerHandlers.ListResourceTemplatesHandler"/>
/// and <see cref="McpServerHandlers.ReadResourceHandler"/> handlers, if provided. Resources with template expressions in their URI templates are considered resource templates
/// and are listed via ListResourceTemplate, whereas resources without template parameters are considered static resources and are listed with ListResources.
/// </para>
/// <para>
/// ReadResource requests will first check the <see cref="ResourceCollection"/> for the exact resource being requested. If no match is found, they'll proceed to
/// try to match the resource against each resource template in <see cref="ResourceCollection"/>. If no match is still found, the request will fall back to
/// any handler registered for <see cref="McpServerHandlers.ReadResourceHandler"/>.
/// </para>
/// </remarks>
public McpServerResourceCollection? ResourceCollection { get; set; }
/// <summary>
/// Gets or sets a collection of prompts that will be served by the server.
/// </summary>
/// <remarks>
/// <para>
/// The <see cref="PromptCollection"/> contains the predefined prompts that clients can request from the server.
/// This collection works in conjunction with <see cref="McpServerHandlers.ListPromptsHandler"/> and <see cref="McpServerHandlers.GetPromptHandler"/>
/// when those are provided:
/// </para>
/// <para>
/// - For <see cref="RequestMethods.PromptsList"/> requests: The server returns all prompts from this collection
/// plus any additional prompts provided by the <see cref="McpServerHandlers.ListPromptsHandler"/> if it's set.
/// </para>
/// <para>
/// - For <see cref="RequestMethods.PromptsGet"/> requests: The server first checks this collection for the requested prompt.
/// If not found, it will invoke the <see cref="McpServerHandlers.GetPromptHandler"/> as a fallback if one is set.
/// </para>
/// </remarks>
public McpServerPrimitiveCollection<McpServerPrompt>? PromptCollection { get; set; }
}