Move functionality out of IMcpClient/Server into extensions (#60)
This commit is contained in:
parent
cdcf134e7a
commit
67320b22d5
1 changed files with 1042 additions and 708 deletions
|
|
@ -10,13 +10,23 @@ using Microsoft.Extensions.Logging;
|
|||
namespace McpDotNet.Server;
|
||||
|
||||
/// <inheritdoc />
|
||||
internal class McpServer : McpJsonRpcEndpoint, IMcpServer
|
||||
internal sealed class McpServer : McpJsonRpcEndpoint, IMcpServer
|
||||
{
|
||||
private readonly IServerTransport _serverTransport;
|
||||
private readonly McpServerOptions _options;
|
||||
private volatile bool _isInitializing;
|
||||
private readonly ILogger<McpServer> _logger;
|
||||
|
||||
private Func<RequestContext<ListToolsRequestParams>, CancellationToken, Task<ListToolsResult>>? _listToolsHandler;
|
||||
private Func<RequestContext<CallToolRequestParams>, CancellationToken, Task<CallToolResponse>>? _callToolHandler;
|
||||
private Func<RequestContext<ListPromptsRequestParams>, CancellationToken, Task<ListPromptsResult>>? _listPromptsHandler;
|
||||
private Func<RequestContext<GetPromptRequestParams>, CancellationToken, Task<GetPromptResult>>? _getPromptHandler;
|
||||
private Func<RequestContext<ListResourcesRequestParams>, CancellationToken, Task<ListResourcesResult>>? _listResourcesHandler;
|
||||
private Func<RequestContext<ReadResourceRequestParams>, CancellationToken, Task<ReadResourceResult>>? _readResourceHandler;
|
||||
private Func<RequestContext<CompleteRequestParams>, CancellationToken, Task<CompleteResult>>? _getCompletionHandler;
|
||||
private Func<RequestContext<string>, CancellationToken, Task>? _subscribeToResourcesHandler;
|
||||
private Func<RequestContext<string>, CancellationToken, Task>? _unsubscribeFromResourcesHandler;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="McpServer"/>.
|
||||
/// </summary>
|
||||
|
|
@ -41,7 +51,7 @@ internal class McpServer : McpJsonRpcEndpoint, IMcpServer
|
|||
SetCompletionHandler();
|
||||
SetInitializeHandler(options);
|
||||
SetPingHandler();
|
||||
SetNotificationHandler();
|
||||
AddNotificationHandler();
|
||||
}
|
||||
|
||||
public ClientCapabilities? ClientCapabilities { get; set; }
|
||||
|
|
@ -56,38 +66,54 @@ internal class McpServer : McpJsonRpcEndpoint, IMcpServer
|
|||
public IServiceProvider? ServiceProvider { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Func<RequestContext<ListToolsRequestParams>, CancellationToken, Task<ListToolsResult>>? ListToolsHandler { get; set; }
|
||||
public override string EndpointName =>
|
||||
$"Server ({_options.ServerInfo.Name} {_options.ServerInfo.Version}), Client ({ClientInfo?.Name} {ClientInfo?.Version})";
|
||||
|
||||
/// <inheritdoc />
|
||||
public Func<RequestContext<CallToolRequestParams>, CancellationToken, Task<CallToolResponse>>? CallToolHandler { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Func<RequestContext<ListPromptsRequestParams>, CancellationToken, Task<ListPromptsResult>>? ListPromptsHandler { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Func<RequestContext<GetPromptRequestParams>, CancellationToken, Task<GetPromptResult>>? GetPromptHandler { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Func<RequestContext<ListResourcesRequestParams>, CancellationToken, Task<ListResourcesResult>>? ListResourcesHandler { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Func<RequestContext<ReadResourceRequestParams>, CancellationToken, Task<ReadResourceResult>>? ReadResourceHandler { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Func<RequestContext<CompleteRequestParams>, CancellationToken, Task<CompleteResult>>? GetCompletionHandler { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Func<RequestContext<string>, CancellationToken, Task>? SubscribeToResourcesHandler { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Func<RequestContext<string>, CancellationToken, Task>? UnsubscribeFromResourcesHandler { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string EndpointName
|
||||
public void SetOperationHandler(string operationName, Delegate handler)
|
||||
{
|
||||
get
|
||||
if (operationName is null)
|
||||
{
|
||||
return $"Server ({_options.ServerInfo.Name} {_options.ServerInfo.Version}), Client ({ClientInfo?.Name} {ClientInfo?.Version})";
|
||||
throw new ArgumentNullException(nameof(operationName));
|
||||
}
|
||||
|
||||
if (handler is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(handler));
|
||||
}
|
||||
|
||||
if (!TrySetOperationHandler(OperationNames.ListTools, operationName, handler, ref _listToolsHandler) &&
|
||||
!TrySetOperationHandler(OperationNames.CallTool, operationName, handler, ref _callToolHandler) &&
|
||||
!TrySetOperationHandler(OperationNames.ListPrompts, operationName, handler, ref _listPromptsHandler) &&
|
||||
!TrySetOperationHandler(OperationNames.GetPrompt, operationName, handler, ref _getPromptHandler) &&
|
||||
!TrySetOperationHandler(OperationNames.ListResources, operationName, handler, ref _listResourcesHandler) &&
|
||||
!TrySetOperationHandler(OperationNames.ReadResource, operationName, handler, ref _readResourceHandler) &&
|
||||
!TrySetOperationHandler(OperationNames.GetCompletion, operationName, handler, ref _getCompletionHandler) &&
|
||||
!TrySetOperationHandler(OperationNames.SubscribeToResources, operationName, handler, ref _subscribeToResourcesHandler) &&
|
||||
!TrySetOperationHandler(OperationNames.UnsubscribeFromResources, operationName, handler, ref _unsubscribeFromResourcesHandler))
|
||||
{
|
||||
throw new ArgumentException($"Unknown operation '{operationName}'", nameof(operationName));
|
||||
}
|
||||
|
||||
static bool TrySetOperationHandler<TFieldRequest, TFieldResponse>(
|
||||
string targetOperationName,
|
||||
string operationName,
|
||||
Delegate handler,
|
||||
ref Func<TFieldRequest, CancellationToken, TFieldResponse>? field)
|
||||
{
|
||||
if (operationName == targetOperationName)
|
||||
{
|
||||
if (handler is Func<TFieldRequest, CancellationToken, TFieldResponse> typed)
|
||||
{
|
||||
field = typed;
|
||||
return true;
|
||||
}
|
||||
|
||||
throw new ArgumentException(
|
||||
$"Handler must be of type {typeof(Func<TFieldRequest, CancellationToken, TFieldResponse>)}",
|
||||
nameof(handler));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -127,45 +153,9 @@ internal class McpServer : McpJsonRpcEndpoint, IMcpServer
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<CreateMessageResult> RequestSamplingAsync(CreateMessageRequestParams request, CancellationToken cancellationToken)
|
||||
private void AddNotificationHandler()
|
||||
{
|
||||
if (ClientCapabilities?.Sampling == null)
|
||||
{
|
||||
throw new McpServerException("Client does not support sampling");
|
||||
}
|
||||
|
||||
return await SendRequestAsync<CreateMessageResult>(
|
||||
new JsonRpcRequest
|
||||
{
|
||||
Method = "sampling/createMessage",
|
||||
Params = request
|
||||
},
|
||||
cancellationToken
|
||||
).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<ListRootsResult> RequestRootsAsync(ListRootsRequestParams request, CancellationToken cancellationToken)
|
||||
{
|
||||
if (ClientCapabilities?.Roots == null)
|
||||
{
|
||||
throw new McpServerException("Client does not support roots");
|
||||
}
|
||||
|
||||
return await SendRequestAsync<ListRootsResult>(
|
||||
new JsonRpcRequest
|
||||
{
|
||||
Method = "roots/list",
|
||||
Params = request
|
||||
},
|
||||
cancellationToken
|
||||
).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private void SetNotificationHandler()
|
||||
{
|
||||
OnNotification("notifications/initialized", (notification) =>
|
||||
AddNotificationHandler("notifications/initialized", (notification) =>
|
||||
{
|
||||
IsInitialized = true;
|
||||
return Task.CompletedTask;
|
||||
|
|
@ -174,11 +164,8 @@ internal class McpServer : McpJsonRpcEndpoint, IMcpServer
|
|||
|
||||
private void SetPingHandler()
|
||||
{
|
||||
SetRequestHandler<JsonNode, PingResult>("ping",
|
||||
(request) =>
|
||||
{
|
||||
return Task.FromResult(new PingResult());
|
||||
});
|
||||
SetRequestHandler<JsonNode, PingResult>("ping",
|
||||
request => Task.FromResult(new PingResult()));
|
||||
}
|
||||
|
||||
private void SetInitializeHandler(McpServerOptions options)
|
||||
|
|
@ -203,7 +190,7 @@ internal class McpServer : McpJsonRpcEndpoint, IMcpServer
|
|||
SetRequestHandler<CompleteRequestParams, CompleteResult>("completion/complete",
|
||||
async (request) =>
|
||||
{
|
||||
if (GetCompletionHandler == null)
|
||||
if (_getCompletionHandler is null)
|
||||
{
|
||||
// This capability is not optional, so return an empty result if there is no handler
|
||||
return new CompleteResult()
|
||||
|
|
@ -217,102 +204,102 @@ internal class McpServer : McpJsonRpcEndpoint, IMcpServer
|
|||
};
|
||||
}
|
||||
|
||||
return await GetCompletionHandler(new(this, request), CancellationTokenSource?.Token ?? CancellationToken.None).ConfigureAwait(false);
|
||||
return await _getCompletionHandler(new(this, request), CancellationTokenSource?.Token ?? CancellationToken.None).ConfigureAwait(false);
|
||||
});
|
||||
}
|
||||
|
||||
private void SetResourcesHandler(McpServerOptions options)
|
||||
{
|
||||
if (options.Capabilities?.Resources != null)
|
||||
if (options.Capabilities?.Resources is not null)
|
||||
{
|
||||
SetRequestHandler<ListResourcesRequestParams, ListResourcesResult>("resources/list",
|
||||
async (request) =>
|
||||
{
|
||||
if (ListResourcesHandler == null)
|
||||
if (_listResourcesHandler is null)
|
||||
{
|
||||
// Setting the capability, but not a handler means we have nothing to return to the server
|
||||
_logger.ListResourcesHandlerNotConfigured(EndpointName);
|
||||
throw new McpServerException("ListResources handler not configured");
|
||||
}
|
||||
|
||||
return await ListResourcesHandler(new(this, request), CancellationTokenSource?.Token ?? CancellationToken.None).ConfigureAwait(false);
|
||||
return await _listResourcesHandler(new(this, request), CancellationTokenSource?.Token ?? CancellationToken.None).ConfigureAwait(false);
|
||||
});
|
||||
|
||||
SetRequestHandler<ReadResourceRequestParams, ReadResourceResult>("resources/read",
|
||||
async (request) =>
|
||||
{
|
||||
if (ReadResourceHandler == null)
|
||||
if (_readResourceHandler is null)
|
||||
{
|
||||
// Setting the capability, but not a handler means we have nothing to return to the server
|
||||
_logger.ReadResourceHandlerNotConfigured(EndpointName);
|
||||
throw new McpServerException("ReadResource handler not configured");
|
||||
}
|
||||
|
||||
return await ReadResourceHandler(new(this, request), CancellationTokenSource?.Token ?? CancellationToken.None).ConfigureAwait(false);
|
||||
return await _readResourceHandler(new(this, request), CancellationTokenSource?.Token ?? CancellationToken.None).ConfigureAwait(false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void SetPromptsHandler(McpServerOptions options)
|
||||
{
|
||||
if (options.Capabilities?.Prompts != null)
|
||||
if (options.Capabilities?.Prompts is not null)
|
||||
{
|
||||
SetRequestHandler<ListPromptsRequestParams, ListPromptsResult>("prompts/list",
|
||||
async (request) =>
|
||||
{
|
||||
if (ListPromptsHandler == null)
|
||||
if (_listPromptsHandler is null)
|
||||
{
|
||||
// Setting the capability, but not a handler means we have nothing to return to the server
|
||||
_logger.ListPromptsHandlerNotConfigured(EndpointName);
|
||||
throw new McpServerException("ListPrompts handler not configured");
|
||||
}
|
||||
|
||||
return await ListPromptsHandler(new(this, request), CancellationTokenSource?.Token ?? CancellationToken.None).ConfigureAwait(false);
|
||||
return await _listPromptsHandler(new(this, request), CancellationTokenSource?.Token ?? CancellationToken.None).ConfigureAwait(false);
|
||||
});
|
||||
|
||||
SetRequestHandler<GetPromptRequestParams, GetPromptResult>("prompts/get",
|
||||
async (request) =>
|
||||
{
|
||||
if (GetPromptHandler == null)
|
||||
if (_getPromptHandler is null)
|
||||
{
|
||||
// Setting the capability, but not a handler means we have nothing to return to the server
|
||||
_logger.GetPromptHandlerNotConfigured(EndpointName);
|
||||
throw new McpServerException("GetPrompt handler not configured");
|
||||
}
|
||||
|
||||
return await GetPromptHandler(new(this, request), CancellationTokenSource?.Token ?? CancellationToken.None).ConfigureAwait(false);
|
||||
return await _getPromptHandler(new(this, request), CancellationTokenSource?.Token ?? CancellationToken.None).ConfigureAwait(false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void SetToolsHandler(McpServerOptions options)
|
||||
{
|
||||
if (options.Capabilities?.Tools != null)
|
||||
if (options.Capabilities?.Tools is not null)
|
||||
{
|
||||
SetRequestHandler<ListToolsRequestParams, ListToolsResult>("tools/list",
|
||||
async (request) =>
|
||||
{
|
||||
if (ListToolsHandler == null)
|
||||
if (_listToolsHandler is null)
|
||||
{
|
||||
// Setting the capability, but not a handler means we have nothing to return to the server
|
||||
_logger.ListToolsHandlerNotConfigured(EndpointName);
|
||||
throw new McpServerException("ListTools handler not configured");
|
||||
}
|
||||
|
||||
return await ListToolsHandler(new(this, request), CancellationTokenSource?.Token ?? CancellationToken.None).ConfigureAwait(false);
|
||||
return await _listToolsHandler(new(this, request), CancellationTokenSource?.Token ?? CancellationToken.None).ConfigureAwait(false);
|
||||
});
|
||||
|
||||
SetRequestHandler<CallToolRequestParams, CallToolResponse>("tools/call",
|
||||
async (request) =>
|
||||
{
|
||||
if (CallToolHandler == null)
|
||||
if (_callToolHandler is null)
|
||||
{
|
||||
// Setting the capability, but not a handler means we have nothing to return to the server
|
||||
_logger.CallToolHandlerNotConfigured(EndpointName);
|
||||
throw new McpServerException("CallTool handler not configured");
|
||||
}
|
||||
|
||||
return await CallToolHandler(new(this, request), CancellationTokenSource?.Token ?? CancellationToken.None).ConfigureAwait(false);
|
||||
return await _callToolHandler(new(this, request), CancellationTokenSource?.Token ?? CancellationToken.None).ConfigureAwait(false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue