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

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,