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:
parent
39b0ed3c18
commit
5f992994c3
2 changed files with 1588 additions and 1161 deletions
|
|
@ -1,226 +0,0 @@
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
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 request. The handlers are used to customize
|
||||
/// the behavior of the MCP server by providing implementations for the various protocol operations.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Handlers can be configured individually using the extension methods in <see cref="McpServerBuilderExtensions"/>
|
||||
/// such as <see cref="McpServerBuilderExtensions.WithListToolsHandler"/> and
|
||||
/// <see cref="McpServerBuilderExtensions.WithCallToolHandler"/>.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// When a client sends a request to the server, the appropriate handler is invoked to process the
|
||||
/// request and produce a response 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>
|
||||
/// Overwrite any handlers in McpServerOptions with non-null handlers from this instance.
|
||||
/// </summary>
|
||||
/// <param name="options"></param>
|
||||
/// <returns></returns>
|
||||
internal void OverwriteWithSetHandlers(McpServerOptions options)
|
||||
{
|
||||
PromptsCapability? promptsCapability = options.Capabilities?.Prompts;
|
||||
if (ListPromptsHandler is not null || GetPromptHandler is not null)
|
||||
{
|
||||
promptsCapability ??= new();
|
||||
promptsCapability.ListPromptsHandler = ListPromptsHandler ?? promptsCapability.ListPromptsHandler;
|
||||
promptsCapability.GetPromptHandler = GetPromptHandler ?? promptsCapability.GetPromptHandler;
|
||||
}
|
||||
|
||||
ResourcesCapability? resourcesCapability = options.Capabilities?.Resources;
|
||||
if (ListResourcesHandler is not null ||
|
||||
ReadResourceHandler is not null)
|
||||
{
|
||||
resourcesCapability ??= new();
|
||||
resourcesCapability.ListResourceTemplatesHandler = ListResourceTemplatesHandler ?? resourcesCapability.ListResourceTemplatesHandler;
|
||||
resourcesCapability.ListResourcesHandler = ListResourcesHandler ?? resourcesCapability.ListResourcesHandler;
|
||||
resourcesCapability.ReadResourceHandler = ReadResourceHandler ?? resourcesCapability.ReadResourceHandler;
|
||||
|
||||
if (SubscribeToResourcesHandler is not null || UnsubscribeFromResourcesHandler is not null)
|
||||
{
|
||||
resourcesCapability.SubscribeToResourcesHandler = SubscribeToResourcesHandler ?? resourcesCapability.SubscribeToResourcesHandler;
|
||||
resourcesCapability.UnsubscribeFromResourcesHandler = UnsubscribeFromResourcesHandler ?? resourcesCapability.UnsubscribeFromResourcesHandler;
|
||||
resourcesCapability.Subscribe = true;
|
||||
}
|
||||
}
|
||||
|
||||
ToolsCapability? toolsCapability = options.Capabilities?.Tools;
|
||||
if (ListToolsHandler is not null || CallToolHandler is not null)
|
||||
{
|
||||
toolsCapability ??= new();
|
||||
toolsCapability.ListToolsHandler = ListToolsHandler ?? toolsCapability.ListToolsHandler;
|
||||
toolsCapability.CallToolHandler = CallToolHandler ?? toolsCapability.CallToolHandler;
|
||||
}
|
||||
|
||||
LoggingCapability? loggingCapability = options.Capabilities?.Logging;
|
||||
if (SetLoggingLevelHandler is not null)
|
||||
{
|
||||
loggingCapability ??= new();
|
||||
loggingCapability.SetLoggingLevelHandler = SetLoggingLevelHandler;
|
||||
}
|
||||
|
||||
CompletionsCapability? completionsCapability = options.Capabilities?.Completions;
|
||||
if (CompleteHandler is not null)
|
||||
{
|
||||
completionsCapability ??= new();
|
||||
completionsCapability.CompleteHandler = CompleteHandler;
|
||||
}
|
||||
|
||||
options.Capabilities ??= new();
|
||||
options.Capabilities.Prompts = promptsCapability;
|
||||
options.Capabilities.Resources = resourcesCapability;
|
||||
options.Capabilities.Tools = toolsCapability;
|
||||
options.Capabilities.Logging = loggingCapability;
|
||||
options.Capabilities.Completions = completionsCapability;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
using Microsoft.Extensions.Options;
|
||||
using ModelContextProtocol.Protocol;
|
||||
using ModelContextProtocol.Server;
|
||||
|
||||
namespace ModelContextProtocol;
|
||||
|
|
@ -29,7 +30,7 @@ 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.
|
||||
McpServerPrimitiveCollection<McpServerTool> toolCollection = options.Capabilities?.Tools?.ToolCollection ?? [];
|
||||
McpServerPrimitiveCollection<McpServerTool> toolCollection = options.ToolCollection ?? [];
|
||||
foreach (var tool in serverTools)
|
||||
{
|
||||
toolCollection.TryAdd(tool);
|
||||
|
|
@ -37,16 +38,14 @@ internal sealed class McpServerOptionsSetup(
|
|||
|
||||
if (!toolCollection.IsEmpty)
|
||||
{
|
||||
options.Capabilities ??= new();
|
||||
options.Capabilities.Tools ??= new();
|
||||
options.Capabilities.Tools.ToolCollection = toolCollection;
|
||||
options.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 ?? [];
|
||||
McpServerPrimitiveCollection<McpServerPrompt> promptCollection = options.PromptCollection ?? [];
|
||||
foreach (var prompt in serverPrompts)
|
||||
{
|
||||
promptCollection.TryAdd(prompt);
|
||||
|
|
@ -54,16 +53,14 @@ internal sealed class McpServerOptionsSetup(
|
|||
|
||||
if (!promptCollection.IsEmpty)
|
||||
{
|
||||
options.Capabilities ??= new();
|
||||
options.Capabilities.Prompts ??= new();
|
||||
options.Capabilities.Prompts.PromptCollection = promptCollection;
|
||||
options.PromptCollection = promptCollection;
|
||||
}
|
||||
|
||||
// Collect all of the provided resources into a resources 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.
|
||||
McpServerResourceCollection resourceCollection = options.Capabilities?.Resources?.ResourceCollection ?? [];
|
||||
McpServerResourceCollection resourceCollection = options.ResourceCollection ?? [];
|
||||
foreach (var resource in serverResources)
|
||||
{
|
||||
resourceCollection.TryAdd(resource);
|
||||
|
|
@ -71,12 +68,71 @@ internal sealed class McpServerOptionsSetup(
|
|||
|
||||
if (!resourceCollection.IsEmpty)
|
||||
{
|
||||
options.Capabilities ??= new();
|
||||
options.Capabilities.Resources ??= new();
|
||||
options.Capabilities.Resources.ResourceCollection = resourceCollection;
|
||||
options.ResourceCollection = resourceCollection;
|
||||
}
|
||||
|
||||
// Apply custom server handlers.
|
||||
serverHandlers.Value.OverwriteWithSetHandlers(options);
|
||||
OverwriteWithSetHandlers(serverHandlers.Value, options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overwrite any handlers in McpServerOptions with non-null handlers from this instance.
|
||||
/// </summary>
|
||||
private static void OverwriteWithSetHandlers(McpServerHandlers handlers, McpServerOptions options)
|
||||
{
|
||||
McpServerHandlers optionsHandlers = options.Handlers;
|
||||
|
||||
PromptsCapability? promptsCapability = options.Capabilities?.Prompts;
|
||||
if (handlers.ListPromptsHandler is not null || handlers.GetPromptHandler is not null)
|
||||
{
|
||||
promptsCapability ??= new();
|
||||
optionsHandlers.ListPromptsHandler = handlers.ListPromptsHandler ?? optionsHandlers.ListPromptsHandler;
|
||||
optionsHandlers.GetPromptHandler = handlers.GetPromptHandler ?? optionsHandlers.GetPromptHandler;
|
||||
}
|
||||
|
||||
ResourcesCapability? resourcesCapability = options.Capabilities?.Resources;
|
||||
if (handlers.ListResourceTemplatesHandler is not null || handlers.ListResourcesHandler is not null || handlers.ReadResourceHandler is not null)
|
||||
{
|
||||
resourcesCapability ??= new();
|
||||
optionsHandlers.ListResourceTemplatesHandler = handlers.ListResourceTemplatesHandler ?? optionsHandlers.ListResourceTemplatesHandler;
|
||||
optionsHandlers.ListResourcesHandler = handlers.ListResourcesHandler ?? optionsHandlers.ListResourcesHandler;
|
||||
optionsHandlers.ReadResourceHandler = handlers.ReadResourceHandler ?? optionsHandlers.ReadResourceHandler;
|
||||
|
||||
if (handlers.SubscribeToResourcesHandler is not null || handlers.UnsubscribeFromResourcesHandler is not null)
|
||||
{
|
||||
optionsHandlers.SubscribeToResourcesHandler = handlers.SubscribeToResourcesHandler ?? optionsHandlers.SubscribeToResourcesHandler;
|
||||
optionsHandlers.UnsubscribeFromResourcesHandler = handlers.UnsubscribeFromResourcesHandler ?? optionsHandlers.UnsubscribeFromResourcesHandler;
|
||||
resourcesCapability.Subscribe = true;
|
||||
}
|
||||
}
|
||||
|
||||
ToolsCapability? toolsCapability = options.Capabilities?.Tools;
|
||||
if (handlers.ListToolsHandler is not null || handlers.CallToolHandler is not null)
|
||||
{
|
||||
toolsCapability ??= new();
|
||||
optionsHandlers.ListToolsHandler = handlers.ListToolsHandler ?? optionsHandlers.ListToolsHandler;
|
||||
optionsHandlers.CallToolHandler = handlers.CallToolHandler ?? optionsHandlers.CallToolHandler;
|
||||
}
|
||||
|
||||
LoggingCapability? loggingCapability = options.Capabilities?.Logging;
|
||||
if (handlers.SetLoggingLevelHandler is not null)
|
||||
{
|
||||
loggingCapability ??= new();
|
||||
optionsHandlers.SetLoggingLevelHandler = handlers.SetLoggingLevelHandler;
|
||||
}
|
||||
|
||||
CompletionsCapability? completionsCapability = options.Capabilities?.Completions;
|
||||
if (handlers.CompleteHandler is not null)
|
||||
{
|
||||
completionsCapability ??= new();
|
||||
optionsHandlers.CompleteHandler = handlers.CompleteHandler;
|
||||
}
|
||||
|
||||
options.Capabilities ??= new();
|
||||
options.Capabilities.Prompts = promptsCapability;
|
||||
options.Capabilities.Resources = resourcesCapability;
|
||||
options.Capabilities.Tools = toolsCapability;
|
||||
options.Capabilities.Logging = loggingCapability;
|
||||
options.Capabilities.Completions = completionsCapability;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue