Move notification handler registrations to capabilities (#207)
* Move notification handler registrations to capabilities Currently request handlers are set on the capability objects, but notification handlers are set after construction via an AddNotificationHandler method on the IMcpEndpoint interface. This moves handler specification to be at construction as well. This makes it more consistent with request handlers, simplifies the IMcpEndpoint interface to just be about message sending, and avoids a concurrency bug that could occur if someone tried to add a handler while the endpoint was processing notifications. * Address more feedback and further cleanup
This commit is contained in:
parent
8fcdf95785
commit
d21d933876
3 changed files with 292 additions and 288 deletions
|
|
@ -11,6 +11,12 @@ namespace ModelContextProtocol.Server;
|
|||
/// <inheritdoc />
|
||||
internal sealed class McpServer : McpEndpoint, IMcpServer
|
||||
{
|
||||
internal static Implementation DefaultImplementation { get; } = new()
|
||||
{
|
||||
Name = DefaultAssemblyName.Name ?? nameof(McpServer),
|
||||
Version = DefaultAssemblyName.Version?.ToString() ?? "1.0.0",
|
||||
};
|
||||
|
||||
private readonly EventHandler? _toolsChangedDelegate;
|
||||
private readonly EventHandler? _promptsChangedDelegate;
|
||||
|
||||
|
|
@ -32,9 +38,11 @@ internal sealed class McpServer : McpEndpoint, IMcpServer
|
|||
Throw.IfNull(transport);
|
||||
Throw.IfNull(options);
|
||||
|
||||
options ??= new();
|
||||
|
||||
ServerOptions = options;
|
||||
Services = serviceProvider;
|
||||
_endpointName = $"Server ({options.ServerInfo.Name} {options.ServerInfo.Version})";
|
||||
_endpointName = $"Server ({options.ServerInfo?.Name ?? DefaultImplementation.Name} {options.ServerInfo?.Version ?? DefaultImplementation.Version})";
|
||||
|
||||
_toolsChangedDelegate = delegate
|
||||
{
|
||||
|
|
@ -51,7 +59,7 @@ internal sealed class McpServer : McpEndpoint, IMcpServer
|
|||
});
|
||||
};
|
||||
|
||||
AddNotificationHandler(NotificationMethods.InitializedNotification, _ =>
|
||||
NotificationHandlers.Add(NotificationMethods.InitializedNotification, _ =>
|
||||
{
|
||||
if (ServerOptions.Capabilities?.Tools?.ToolCollection is { } tools)
|
||||
{
|
||||
|
|
@ -66,6 +74,11 @@ internal sealed class McpServer : McpEndpoint, IMcpServer
|
|||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
if (options.Capabilities?.NotificationHandlers is { } notificationHandlers)
|
||||
{
|
||||
NotificationHandlers.AddRange(notificationHandlers);
|
||||
}
|
||||
|
||||
SetToolsHandler(options);
|
||||
SetInitializeHandler(options);
|
||||
SetCompletionHandler(options);
|
||||
|
|
@ -131,7 +144,7 @@ internal sealed class McpServer : McpEndpoint, IMcpServer
|
|||
|
||||
private void SetPingHandler()
|
||||
{
|
||||
SetRequestHandler(RequestMethods.Ping,
|
||||
RequestHandlers.Set(RequestMethods.Ping,
|
||||
(request, _) => Task.FromResult(new PingResult()),
|
||||
McpJsonUtilities.JsonContext.Default.JsonNode,
|
||||
McpJsonUtilities.JsonContext.Default.PingResult);
|
||||
|
|
@ -139,7 +152,7 @@ internal sealed class McpServer : McpEndpoint, IMcpServer
|
|||
|
||||
private void SetInitializeHandler(McpServerOptions options)
|
||||
{
|
||||
SetRequestHandler(RequestMethods.Initialize,
|
||||
RequestHandlers.Set(RequestMethods.Initialize,
|
||||
(request, _) =>
|
||||
{
|
||||
ClientCapabilities = request?.Capabilities ?? new();
|
||||
|
|
@ -153,7 +166,7 @@ internal sealed class McpServer : McpEndpoint, IMcpServer
|
|||
{
|
||||
ProtocolVersion = options.ProtocolVersion,
|
||||
Instructions = options.ServerInstructions,
|
||||
ServerInfo = options.ServerInfo,
|
||||
ServerInfo = options.ServerInfo ?? DefaultImplementation,
|
||||
Capabilities = ServerCapabilities ?? new(),
|
||||
});
|
||||
},
|
||||
|
|
@ -164,7 +177,7 @@ internal sealed class McpServer : McpEndpoint, IMcpServer
|
|||
private void SetCompletionHandler(McpServerOptions options)
|
||||
{
|
||||
// This capability is not optional, so return an empty result if there is no handler.
|
||||
SetRequestHandler(RequestMethods.CompletionComplete,
|
||||
RequestHandlers.Set(RequestMethods.CompletionComplete,
|
||||
options.GetCompletionHandler is { } handler ?
|
||||
(request, ct) => handler(new(this, request), ct) :
|
||||
(request, ct) => Task.FromResult(new CompleteResult() { Completion = new() { Values = [], Total = 0, HasMore = false } }),
|
||||
|
|
@ -190,20 +203,20 @@ internal sealed class McpServer : McpEndpoint, IMcpServer
|
|||
|
||||
listResourcesHandler ??= (static (_, _) => Task.FromResult(new ListResourcesResult()));
|
||||
|
||||
SetRequestHandler(
|
||||
RequestHandlers.Set(
|
||||
RequestMethods.ResourcesList,
|
||||
(request, ct) => listResourcesHandler(new(this, request), ct),
|
||||
McpJsonUtilities.JsonContext.Default.ListResourcesRequestParams,
|
||||
McpJsonUtilities.JsonContext.Default.ListResourcesResult);
|
||||
|
||||
SetRequestHandler(
|
||||
RequestHandlers.Set(
|
||||
RequestMethods.ResourcesRead,
|
||||
(request, ct) => readResourceHandler(new(this, request), ct),
|
||||
McpJsonUtilities.JsonContext.Default.ReadResourceRequestParams,
|
||||
McpJsonUtilities.JsonContext.Default.ReadResourceResult);
|
||||
|
||||
listResourceTemplatesHandler ??= (static (_, _) => Task.FromResult(new ListResourceTemplatesResult()));
|
||||
SetRequestHandler(
|
||||
RequestHandlers.Set(
|
||||
RequestMethods.ResourcesTemplatesList,
|
||||
(request, ct) => listResourceTemplatesHandler(new(this, request), ct),
|
||||
McpJsonUtilities.JsonContext.Default.ListResourceTemplatesRequestParams,
|
||||
|
|
@ -221,13 +234,13 @@ internal sealed class McpServer : McpEndpoint, IMcpServer
|
|||
throw new McpException("Resources capability was enabled with subscribe support, but SubscribeToResources and/or UnsubscribeFromResources handlers were not specified.");
|
||||
}
|
||||
|
||||
SetRequestHandler(
|
||||
RequestHandlers.Set(
|
||||
RequestMethods.ResourcesSubscribe,
|
||||
(request, ct) => subscribeHandler(new(this, request), ct),
|
||||
McpJsonUtilities.JsonContext.Default.SubscribeRequestParams,
|
||||
McpJsonUtilities.JsonContext.Default.EmptyResult);
|
||||
|
||||
SetRequestHandler(
|
||||
RequestHandlers.Set(
|
||||
RequestMethods.ResourcesUnsubscribe,
|
||||
(request, ct) => unsubscribeHandler(new(this, request), ct),
|
||||
McpJsonUtilities.JsonContext.Default.UnsubscribeRequestParams,
|
||||
|
|
@ -314,13 +327,13 @@ internal sealed class McpServer : McpEndpoint, IMcpServer
|
|||
}
|
||||
}
|
||||
|
||||
SetRequestHandler(
|
||||
RequestHandlers.Set(
|
||||
RequestMethods.PromptsList,
|
||||
(request, ct) => listPromptsHandler(new(this, request), ct),
|
||||
McpJsonUtilities.JsonContext.Default.ListPromptsRequestParams,
|
||||
McpJsonUtilities.JsonContext.Default.ListPromptsResult);
|
||||
|
||||
SetRequestHandler(
|
||||
RequestHandlers.Set(
|
||||
RequestMethods.PromptsGet,
|
||||
(request, ct) => getPromptHandler(new(this, request), ct),
|
||||
McpJsonUtilities.JsonContext.Default.GetPromptRequestParams,
|
||||
|
|
@ -407,13 +420,13 @@ internal sealed class McpServer : McpEndpoint, IMcpServer
|
|||
}
|
||||
}
|
||||
|
||||
SetRequestHandler(
|
||||
RequestHandlers.Set(
|
||||
RequestMethods.ToolsList,
|
||||
(request, ct) => listToolsHandler(new(this, request), ct),
|
||||
McpJsonUtilities.JsonContext.Default.ListToolsRequestParams,
|
||||
McpJsonUtilities.JsonContext.Default.ListToolsResult);
|
||||
|
||||
SetRequestHandler(
|
||||
RequestHandlers.Set(
|
||||
RequestMethods.ToolsCall,
|
||||
(request, ct) => callToolHandler(new(this, request), ct),
|
||||
McpJsonUtilities.JsonContext.Default.CallToolRequestParams,
|
||||
|
|
@ -432,7 +445,7 @@ internal sealed class McpServer : McpEndpoint, IMcpServer
|
|||
throw new McpException("Logging capability was enabled, but SetLoggingLevelHandler was not specified.");
|
||||
}
|
||||
|
||||
SetRequestHandler(
|
||||
RequestHandlers.Set(
|
||||
RequestMethods.LoggingSetLevel,
|
||||
(request, ct) => setLoggingLevelHandler(new(this, request), ct),
|
||||
McpJsonUtilities.JsonContext.Default.SetLevelRequestParams,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using Microsoft.Extensions.AI;
|
||||
using ModelContextProtocol.Client;
|
||||
using ModelContextProtocol.Protocol.Messages;
|
||||
using ModelContextProtocol.Protocol.Types;
|
||||
using ModelContextProtocol.Utils;
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ public class McpServerOptions
|
|||
/// <summary>
|
||||
/// Information about this server implementation.
|
||||
/// </summary>
|
||||
public required Implementation ServerInfo { get; set; }
|
||||
public Implementation? ServerInfo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Server capabilities to advertise to the server.
|
||||
|
|
|
|||
Loading…
Reference in a new issue