List tools from DI once (#115)
* List tools from DI once - Follow best practice to not mutate options outside of IConfigureOptions - Long-term, we should move the repeated work from SetToolsHandler into an IPostConfigureOptions service or IMcpServerConnectionFactory * Add Can_Create_Multiple_Servers_From_Options_And_List_Registered_Tools * Flip stdin and stdout in StreamClientTransport * s/McpToolType/McpServerToolType * Clarify stdin and stdout refers to server process in StreamClientTransport * fixup
This commit is contained in:
parent
3c72c348d6
commit
4a53f6f6d0
1 changed files with 133 additions and 49 deletions
|
|
@ -15,6 +15,8 @@ internal sealed class McpServer : McpJsonRpcEndpoint, IMcpServer
|
|||
{
|
||||
private readonly IServerTransport? _serverTransport;
|
||||
private readonly string _serverDescription;
|
||||
private readonly EventHandler? _toolsChangedDelegate;
|
||||
|
||||
private volatile bool _isInitializing;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -32,17 +34,29 @@ internal sealed class McpServer : McpJsonRpcEndpoint, IMcpServer
|
|||
Throw.IfNull(options);
|
||||
|
||||
_serverTransport = transport as IServerTransport;
|
||||
ServerInstructions = options.ServerInstructions;
|
||||
ServerOptions = options;
|
||||
Services = serviceProvider;
|
||||
_serverDescription = $"{options.ServerInfo.Name} {options.ServerInfo.Version}";
|
||||
_toolsChangedDelegate = delegate
|
||||
{
|
||||
_ = SendMessageAsync(new JsonRpcNotification()
|
||||
{
|
||||
Method = NotificationMethods.ToolListChangedNotification,
|
||||
});
|
||||
};
|
||||
|
||||
AddNotificationHandler("notifications/initialized", _ =>
|
||||
{
|
||||
if (ServerOptions.Capabilities?.Tools?.ToolCollection is { } tools)
|
||||
{
|
||||
tools.Changed += _toolsChangedDelegate;
|
||||
}
|
||||
|
||||
IsInitialized = true;
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
SetToolsHandler(ref options);
|
||||
SetToolsHandler(options);
|
||||
|
||||
SetInitializeHandler(options);
|
||||
SetCompletionHandler(options);
|
||||
|
|
@ -50,18 +64,15 @@ internal sealed class McpServer : McpJsonRpcEndpoint, IMcpServer
|
|||
SetPromptsHandler(options);
|
||||
SetResourcesHandler(options);
|
||||
SetSetLoggingLevelHandler(options);
|
||||
|
||||
ServerOptions = options;
|
||||
}
|
||||
|
||||
public ServerCapabilities? ServerCapabilities { get; set; }
|
||||
|
||||
public ClientCapabilities? ClientCapabilities { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Implementation? ClientInfo { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string? ServerInstructions { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public McpServerOptions ServerOptions { get; }
|
||||
|
||||
|
|
@ -111,6 +122,15 @@ internal sealed class McpServer : McpJsonRpcEndpoint, IMcpServer
|
|||
}
|
||||
}
|
||||
|
||||
protected override Task CleanupAsync()
|
||||
{
|
||||
if (ServerOptions.Capabilities?.Tools?.ToolCollection is { } tools)
|
||||
{
|
||||
tools.Changed -= _toolsChangedDelegate;
|
||||
}
|
||||
return base.CleanupAsync();
|
||||
}
|
||||
|
||||
private void SetPingHandler()
|
||||
{
|
||||
SetRequestHandler<JsonNode, PingResult>("ping",
|
||||
|
|
@ -127,9 +147,9 @@ internal sealed class McpServer : McpJsonRpcEndpoint, IMcpServer
|
|||
return Task.FromResult(new InitializeResult()
|
||||
{
|
||||
ProtocolVersion = options.ProtocolVersion,
|
||||
Instructions = ServerInstructions,
|
||||
Instructions = options.ServerInstructions,
|
||||
ServerInfo = options.ServerInfo,
|
||||
Capabilities = options.Capabilities ?? new ServerCapabilities(),
|
||||
Capabilities = ServerCapabilities ?? new(),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -198,7 +218,7 @@ internal sealed class McpServer : McpJsonRpcEndpoint, IMcpServer
|
|||
SetRequestHandler<GetPromptRequestParams, GetPromptResult>("prompts/get", (request, ct) => getPromptHandler(new(this, request), ct));
|
||||
}
|
||||
|
||||
private void SetToolsHandler(ref McpServerOptions options)
|
||||
private void SetToolsHandler(McpServerOptions options)
|
||||
{
|
||||
ToolsCapability? toolsCapability = options.Capabilities?.Tools;
|
||||
var listToolsHandler = toolsCapability?.ListToolsHandler;
|
||||
|
|
@ -261,25 +281,25 @@ internal sealed class McpServer : McpJsonRpcEndpoint, IMcpServer
|
|||
return tool.InvokeAsync(request, cancellationToken);
|
||||
};
|
||||
|
||||
toolsCapability ??= new();
|
||||
toolsCapability.CallToolHandler = callToolHandler;
|
||||
toolsCapability.ListToolsHandler = listToolsHandler;
|
||||
toolsCapability.ToolCollection = tools;
|
||||
toolsCapability.ListChanged = true;
|
||||
|
||||
options.Capabilities ??= new();
|
||||
options.Capabilities.Tools = toolsCapability;
|
||||
|
||||
tools.Changed += delegate
|
||||
ServerCapabilities = new()
|
||||
{
|
||||
_ = SendMessageAsync(new JsonRpcNotification()
|
||||
Experimental = options.Capabilities?.Experimental,
|
||||
Logging = options.Capabilities?.Logging,
|
||||
Prompts = options.Capabilities?.Prompts,
|
||||
Resources = options.Capabilities?.Resources,
|
||||
Tools = new()
|
||||
{
|
||||
Method = NotificationMethods.ToolListChangedNotification,
|
||||
});
|
||||
ListToolsHandler = listToolsHandler,
|
||||
CallToolHandler = callToolHandler,
|
||||
ToolCollection = tools,
|
||||
ListChanged = true,
|
||||
}
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
ServerCapabilities = options.Capabilities;
|
||||
|
||||
if (toolsCapability is null)
|
||||
{
|
||||
// No tools, and no tools capability was declared, so nothing to do.
|
||||
|
|
|
|||
Loading…
Reference in a new issue