using System.Text.Json.Nodes; using McpDotNet.Logging; using McpDotNet.Protocol.Transport; using McpDotNet.Protocol.Types; using McpDotNet.Shared; using McpDotNet.Utils; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; namespace McpDotNet.Server; /// internal sealed class McpServer : McpJsonRpcEndpoint, IMcpServer { private readonly IServerTransport? _serverTransport; private readonly McpServerOptions _options; private volatile bool _isInitializing; private readonly ILogger _logger; /// /// Creates a new instance of . /// /// Transport to use for the server /// Configuration options for this server, including capabilities. /// Make sure to accurately reflect exactly what capabilities the server supports and does not support. /// Logger factory to use for logging /// Optional service provider to use for dependency injection /// public McpServer(ITransport transport, McpServerOptions options, ILoggerFactory? loggerFactory, IServiceProvider? serviceProvider) : base(transport, loggerFactory) { Throw.IfNull(options); _serverTransport = transport as IServerTransport; _options = options; _logger = (ILogger?)loggerFactory?.CreateLogger() ?? NullLogger.Instance; ServerInstructions = options.ServerInstructions; ServiceProvider = serviceProvider; AddNotificationHandler("notifications/initialized", _ => { IsInitialized = true; return Task.CompletedTask; }); SetInitializeHandler(options); SetCompletionHandler(options); SetPingHandler(); SetToolsHandler(options); SetPromptsHandler(options); SetResourcesHandler(options); } public ClientCapabilities? ClientCapabilities { get; set; } /// public Implementation? ClientInfo { get; set; } /// public string? ServerInstructions { get; set; } /// public IServiceProvider? ServiceProvider { get; } /// public override string EndpointName => $"Server ({_options.ServerInfo.Name} {_options.ServerInfo.Version}), Client ({ClientInfo?.Name} {ClientInfo?.Version})"; /// public async Task StartAsync(CancellationToken cancellationToken = default) { if (_isInitializing) { _logger.ServerAlreadyInitializing(EndpointName); throw new InvalidOperationException("Server is already initializing"); } _isInitializing = true; if (IsInitialized) { _logger.ServerAlreadyInitializing(EndpointName); return; } try { CancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); if (_serverTransport is not null) { // Start listening for messages await _serverTransport.StartListeningAsync(CancellationTokenSource.Token).ConfigureAwait(false); } // Start processing messages MessageProcessingTask = ProcessMessagesAsync(CancellationTokenSource.Token); // Unlike McpClient, we're not done initializing until we've received a message from the client, so we don't set IsInitialized here } catch (Exception e) { _logger.ServerInitializationError(EndpointName, e); await CleanupAsync().ConfigureAwait(false); throw; } } private void SetPingHandler() { SetRequestHandler("ping", request => Task.FromResult(new PingResult())); } private void SetInitializeHandler(McpServerOptions options) { SetRequestHandler("initialize", request => { ClientCapabilities = request?.Capabilities ?? new(); ClientInfo = request?.ClientInfo; return Task.FromResult(new InitializeResult() { ProtocolVersion = options.ProtocolVersion, Instructions = ServerInstructions, ServerInfo = _options.ServerInfo, Capabilities = options.Capabilities ?? new ServerCapabilities(), }); }); } private void SetCompletionHandler(McpServerOptions options) { // This capability is not optional, so return an empty result if there is no handler. SetRequestHandler("completion/complete", options.GetCompletionHandler is { } handler ? request => handler(new(this, request), CancellationTokenSource?.Token ?? default) : request => Task.FromResult(new CompleteResult() { Completion = new() { Values = [], Total = 0, HasMore = false } })); } private void SetResourcesHandler(McpServerOptions options) { if (options.Capabilities?.Resources is not { } resourcesCapability) { return; } if (resourcesCapability.ListResourcesHandler is not { } listResourcesHandler || resourcesCapability.ReadResourceHandler is not { } readResourceHandler) { throw new McpServerException("Resources capability was enabled, but ListResources and/or ReadResource handlers were not specified."); } CancellationToken cancellationToken = CancellationTokenSource?.Token ?? default; SetRequestHandler("resources/list", request => listResourcesHandler(new(this, request), cancellationToken)); SetRequestHandler("resources/read", request => readResourceHandler(new(this, request), cancellationToken)); if (resourcesCapability.Subscribe is not true) { return; } var subscribeHandler = resourcesCapability.SubscribeToResourcesHandler; var unsubscribeHandler = resourcesCapability.UnsubscribeFromResourcesHandler; if (subscribeHandler is null || unsubscribeHandler is null) { throw new McpServerException("Resources capability was enabled with subscribe support, but SubscribeToResources and/or UnsubscribeFromResources handlers were not specified."); } SetRequestHandler("resources/subscribe", request => subscribeHandler(new(this, request), cancellationToken)); SetRequestHandler("resources/unsubscribe", request => unsubscribeHandler(new(this, request), cancellationToken)); } private void SetPromptsHandler(McpServerOptions options) { if (options.Capabilities?.Prompts is not { } promptsCapability) { return; } if (promptsCapability.ListPromptsHandler is not { } listPromptsHandler || promptsCapability.GetPromptHandler is not { } getPromptHandler) { throw new McpServerException("Prompts capability was enabled, but ListPrompts and/or GetPrompt handlers were not specified."); } CancellationToken cancellationToken = CancellationTokenSource?.Token ?? default; SetRequestHandler("prompts/list", request => listPromptsHandler(new(this, request), cancellationToken)); SetRequestHandler("prompts/get", request => getPromptHandler(new(this, request), cancellationToken)); } private void SetToolsHandler(McpServerOptions options) { if (options.Capabilities?.Tools is not { } toolsCapability) { return; } if (toolsCapability.ListToolsHandler is not { } listToolsHandler || toolsCapability.CallToolHandler is not { } callToolHandler) { throw new McpServerException("ListTools and/or CallTool handlers were specified but the Tools capability was not enabled."); } CancellationToken cancellationToken = CancellationTokenSource?.Token ?? default; SetRequestHandler("tools/list", request => listToolsHandler(new(this, request), cancellationToken)); SetRequestHandler("tools/call", request => callToolHandler(new(this, request), cancellationToken)); } }