From 0a0ff5973639f12457193f5f60872782c0f18e1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Cant=C3=BA?= Date: Mon, 22 Sep 2025 19:28:18 -0500 Subject: [PATCH 1/6] 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. --- README.md | 72 ++- .../elicitation/samples/client/Program.cs | 7 +- .../AspNetCoreMcpPerSessionTools/Program.cs | 2 +- samples/ChatWithTools/Program.cs | 5 +- samples/InMemoryTransport/Program.cs | 8 +- .../Client/McpClient.Methods.cs | 4 +- .../Client/McpClientExtensions.cs | 4 +- .../Client/McpClientHandlers.cs | 89 +++ .../Client/McpClientImpl.cs | 41 +- .../Client/McpClientOptions.cs | 15 + .../Protocol/ClientCapabilities.cs | 2 +- .../Protocol/CompletionsCapability.cs | 14 - .../Protocol/ElicitationCapability.cs | 20 +- .../Protocol/LoggingCapability.cs | 9 - .../Protocol/PromptsCapability.cs | 51 +- .../Protocol/ResourcesCapability.cs | 76 --- .../Protocol/RootsCapability.cs | 10 - .../Protocol/SamplingCapability.cs | 27 +- .../Protocol/ServerCapabilities.cs | 20 - .../Protocol/ToolsCapability.cs | 40 -- .../Server}/McpServerHandlers.cs | 91 +-- .../Server/McpServerImpl.cs | 93 +-- .../Server/McpServerOptions.cs | 65 ++ .../McpServerOptionsSetup.cs | 82 ++- .../HttpServerIntegrationTests.cs | 4 +- .../MapMcpTests.cs | 35 +- .../StatelessServerTests.cs | 12 +- .../Program.cs | 553 +++++++++--------- .../Program.cs | 461 +++++++-------- .../Client/McpClientCreationTests.cs | 23 +- .../Client/McpClientTests.cs | 2 +- .../ClientIntegrationTests.cs | 42 +- .../McpServerBuilderExtensionsPromptsTests.cs | 4 +- ...cpServerBuilderExtensionsResourcesTests.cs | 4 +- .../McpServerBuilderExtensionsToolsTests.cs | 4 +- .../McpServerOptionsSetupTests.cs | 194 ++++++ .../DiagnosticTests.cs | 14 +- .../DockerEverythingServerTests.cs | 25 +- .../Protocol/ElicitationTests.cs | 113 ++-- .../Protocol/ElicitationTypedTests.cs | 239 ++++---- .../Server/McpServerLoggingLevelTests.cs | 2 +- .../Server/McpServerTests.cs | 180 +++--- 42 files changed, 1390 insertions(+), 1368 deletions(-) create mode 100644 src/ModelContextProtocol.Core/Client/McpClientHandlers.cs rename src/{ModelContextProtocol => ModelContextProtocol.Core/Server}/McpServerHandlers.cs (68%) create mode 100644 tests/ModelContextProtocol.Tests/Configuration/McpServerOptionsSetupTests.cs diff --git a/README.md b/README.md index cd3c53928..4aa1f5e94 100644 --- a/README.md +++ b/README.md @@ -174,54 +174,50 @@ using System.Text.Json; McpServerOptions options = new() { ServerInfo = new Implementation { Name = "MyServer", Version = "1.0.0" }, - Capabilities = new ServerCapabilities + Handlers = new McpServerHandlers() { - Tools = new ToolsCapability + ListToolsHandler = (request, cancellationToken) => + ValueTask.FromResult(new ListToolsResult { - ListToolsHandler = (request, cancellationToken) => - ValueTask.FromResult(new ListToolsResult + Tools = + [ + new Tool { - Tools = - [ - new Tool + Name = "echo", + Description = "Echoes the input back to the client.", + InputSchema = JsonSerializer.Deserialize(""" { - Name = "echo", - Description = "Echoes the input back to the client.", - InputSchema = JsonSerializer.Deserialize(""" - { - "type": "object", - "properties": { - "message": { - "type": "string", - "description": "The input to echo back" - } - }, - "required": ["message"] - } - """), + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "The input to echo back" + } + }, + "required": ["message"] } - ] - }), - - CallToolHandler = (request, cancellationToken) => + """), + } + ] + }), + CallToolHandler = (request, cancellationToken) => + { + if (request.Params?.Name == "echo") { - if (request.Params?.Name == "echo") + if (request.Params.Arguments?.TryGetValue("message", out var message) is not true) { - if (request.Params.Arguments?.TryGetValue("message", out var message) is not true) - { - throw new McpException("Missing required argument 'message'"); - } - - return ValueTask.FromResult(new CallToolResult - { - Content = [new TextContentBlock { Text = $"Echo: {message}", Type = "text" }] - }); + throw new McpException("Missing required argument 'message'"); } - throw new McpException($"Unknown tool: '{request.Params?.Name}'"); - }, + return ValueTask.FromResult(new CallToolResult + { + Content = [new TextContentBlock { Text = $"Echo: {message}", Type = "text" }] + }); + } + + throw new McpException($"Unknown tool: '{request.Params?.Name}'"); } - }, + } }; await using McpServer server = McpServer.Create(new StdioServerTransport("MyServer"), options); diff --git a/docs/concepts/elicitation/samples/client/Program.cs b/docs/concepts/elicitation/samples/client/Program.cs index b56960dc7..b2a91ca4b 100644 --- a/docs/concepts/elicitation/samples/client/Program.cs +++ b/docs/concepts/elicitation/samples/client/Program.cs @@ -18,12 +18,9 @@ Name = "ElicitationClient", Version = "1.0.0" }, - Capabilities = new() + Handlers = new() { - Elicitation = new() - { - ElicitationHandler = HandleElicitationAsync - } + ElicitationHandler = HandleElicitationAsync } }; diff --git a/samples/AspNetCoreMcpPerSessionTools/Program.cs b/samples/AspNetCoreMcpPerSessionTools/Program.cs index 3a52b93ed..3484978ec 100644 --- a/samples/AspNetCoreMcpPerSessionTools/Program.cs +++ b/samples/AspNetCoreMcpPerSessionTools/Program.cs @@ -24,7 +24,7 @@ { mcpOptions.Capabilities = new(); mcpOptions.Capabilities.Tools = new(); - var toolCollection = mcpOptions.Capabilities.Tools.ToolCollection = new(); + var toolCollection = mcpOptions.ToolCollection = new(); foreach (var tool in tools) { diff --git a/samples/ChatWithTools/Program.cs b/samples/ChatWithTools/Program.cs index a84393e15..c6fca0493 100644 --- a/samples/ChatWithTools/Program.cs +++ b/samples/ChatWithTools/Program.cs @@ -41,7 +41,10 @@ }), clientOptions: new() { - Capabilities = new() { Sampling = new() { SamplingHandler = samplingClient.CreateSamplingHandler() } }, + Handlers = new() + { + SamplingHandler = samplingClient.CreateSamplingHandler() + } }, loggerFactory: loggerFactory); diff --git a/samples/InMemoryTransport/Program.cs b/samples/InMemoryTransport/Program.cs index 141692fe9..dbffaa34d 100644 --- a/samples/InMemoryTransport/Program.cs +++ b/samples/InMemoryTransport/Program.cs @@ -10,13 +10,7 @@ new StreamServerTransport(clientToServerPipe.Reader.AsStream(), serverToClientPipe.Writer.AsStream()), new McpServerOptions() { - Capabilities = new() - { - Tools = new() - { - ToolCollection = [McpServerTool.Create((string arg) => $"Echo: {arg}", new() { Name = "Echo" })] - } - } + ToolCollection = [McpServerTool.Create((string arg) => $"Echo: {arg}", new() { Name = "Echo" })] }); _ = server.RunAsync(); diff --git a/src/ModelContextProtocol.Core/Client/McpClient.Methods.cs b/src/ModelContextProtocol.Core/Client/McpClient.Methods.cs index 560ce31dc..5550e786e 100644 --- a/src/ModelContextProtocol.Core/Client/McpClient.Methods.cs +++ b/src/ModelContextProtocol.Core/Client/McpClient.Methods.cs @@ -631,11 +631,11 @@ internal static CreateMessageResult ToCreateMessageResult(ChatResponse chatRespo } /// - /// Creates a sampling handler for use with that will + /// Creates a sampling handler for use with that will /// satisfy sampling requests using the specified . /// /// The with which to satisfy sampling requests. - /// The created handler delegate that can be assigned to . + /// The created handler delegate that can be assigned to . /// is . public static Func, CancellationToken, ValueTask> CreateSamplingHandler( IChatClient chatClient) diff --git a/src/ModelContextProtocol.Core/Client/McpClientExtensions.cs b/src/ModelContextProtocol.Core/Client/McpClientExtensions.cs index e987f30f6..817202a40 100644 --- a/src/ModelContextProtocol.Core/Client/McpClientExtensions.cs +++ b/src/ModelContextProtocol.Core/Client/McpClientExtensions.cs @@ -19,11 +19,11 @@ namespace ModelContextProtocol.Client; public static class McpClientExtensions { /// - /// Creates a sampling handler for use with that will + /// Creates a sampling handler for use with that will /// satisfy sampling requests using the specified . /// /// The with which to satisfy sampling requests. - /// The created handler delegate that can be assigned to . + /// The created handler delegate that can be assigned to . /// /// /// This method creates a function that converts MCP message requests into chat client calls, enabling diff --git a/src/ModelContextProtocol.Core/Client/McpClientHandlers.cs b/src/ModelContextProtocol.Core/Client/McpClientHandlers.cs new file mode 100644 index 000000000..b03d20746 --- /dev/null +++ b/src/ModelContextProtocol.Core/Client/McpClientHandlers.cs @@ -0,0 +1,89 @@ +using Microsoft.Extensions.AI; +using ModelContextProtocol.Protocol; + +namespace ModelContextProtocol.Client; + +/// +/// Provides a container for handlers used in the creation of an MCP client. +/// +/// +/// +/// This class provides a centralized collection of delegates that implement various capabilities of the Model Context Protocol. +/// +/// +/// Each handler in this class corresponds to a specific client endpoint in the Model Context Protocol and +/// is responsible for processing a particular type of message. The handlers are used to customize +/// the behavior of the MCP server by providing implementations for the various protocol operations. +/// +/// +/// When a server sends a message to the client, the appropriate handler is invoked to process it +/// according to the protocol specification. Which handler is selected +/// is done based on an ordinal, case-sensitive string comparison. +/// +/// +public class McpClientHandlers +{ + + /// Gets or sets notification handlers to register with the client. + /// + /// + /// When constructed, the client will enumerate these handlers once, which may contain multiple handlers per notification method key. + /// The client will not re-enumerate the sequence after initialization. + /// + /// + /// Notification handlers allow the client to respond to server-sent notifications for specific methods. + /// Each key in the collection is a notification method name, and each value is a callback that will be invoked + /// when a notification with that method is received. + /// + /// + /// Handlers provided via will be registered with the client for the lifetime of the client. + /// For transient handlers, may be used to register a handler that can + /// then be unregistered by disposing of the returned from the method. + /// + /// + public IEnumerable>>? NotificationHandlers { get; set; } + + /// + /// Gets or sets the handler for requests. + /// + /// + /// This handler is invoked when a client sends a request to retrieve available roots. + /// The handler receives request parameters and should return a containing the collection of available roots. + /// + public Func>? RootsHandler { get; set; } + + /// + /// Gets or sets the handler for processing requests. + /// + /// + /// + /// This handler function is called when an MCP server requests the client to provide additional + /// information during interactions. The client must set this property for the elicitation capability to work. + /// + /// + /// The handler receives message parameters and a cancellation token. + /// It should return a containing the response to the elicitation request. + /// + /// + public Func>? ElicitationHandler { get; set; } + + /// + /// Gets or sets the handler for processing requests. + /// + /// + /// + /// This handler function is called when an MCP server requests the client to generate content + /// using an AI model. The client must set this property for the sampling capability to work. + /// + /// + /// The handler receives message parameters, a progress reporter for updates, and a + /// cancellation token. It should return a containing the + /// generated content. + /// + /// + /// You can create a handler using the extension + /// method with any implementation of . + /// + /// + public Func, CancellationToken, ValueTask>? SamplingHandler { get; set; } +} diff --git a/src/ModelContextProtocol.Core/Client/McpClientImpl.cs b/src/ModelContextProtocol.Core/Client/McpClientImpl.cs index 729185a3e..7e664baeb 100644 --- a/src/ModelContextProtocol.Core/Client/McpClientImpl.cs +++ b/src/ModelContextProtocol.Core/Client/McpClientImpl.cs @@ -49,28 +49,20 @@ internal McpClientImpl(ITransport transport, string endpointName, McpClientOptio var notificationHandlers = new NotificationHandlers(); var requestHandlers = new RequestHandlers(); - if (options.Capabilities is { } capabilities) - { - RegisterHandlers(capabilities, notificationHandlers, requestHandlers); - } + RegisterHandlers(options.Handlers, notificationHandlers, requestHandlers); _sessionHandler = new McpSessionHandler(isServer: false, transport, endpointName, requestHandlers, notificationHandlers, _logger); } - private void RegisterHandlers(ClientCapabilities capabilities, NotificationHandlers notificationHandlers, RequestHandlers requestHandlers) + private void RegisterHandlers(McpClientHandlers handlers, NotificationHandlers notificationHandlers, RequestHandlers requestHandlers) { - if (capabilities.NotificationHandlers is { } notificationHandlersFromCapabilities) + if (handlers.NotificationHandlers is { } notificationHandlersFromOptions) { - notificationHandlers.RegisterRange(notificationHandlersFromCapabilities); + notificationHandlers.RegisterRange(notificationHandlersFromOptions); } - if (capabilities.Sampling is { } samplingCapability) + if (handlers.SamplingHandler is { } samplingHandler) { - if (samplingCapability.SamplingHandler is not { } samplingHandler) - { - throw new InvalidOperationException("Sampling capability was set but it did not provide a handler."); - } - requestHandlers.Set( RequestMethods.SamplingCreateMessage, (request, _, cancellationToken) => samplingHandler( @@ -79,34 +71,33 @@ private void RegisterHandlers(ClientCapabilities capabilities, NotificationHandl cancellationToken), McpJsonUtilities.JsonContext.Default.CreateMessageRequestParams, McpJsonUtilities.JsonContext.Default.CreateMessageResult); + + _options.Capabilities ??= new(); + _options.Capabilities.Sampling ??= new(); } - if (capabilities.Roots is { } rootsCapability) + if (handlers.RootsHandler is { } rootsHandler) { - if (rootsCapability.RootsHandler is not { } rootsHandler) - { - throw new InvalidOperationException("Roots capability was set but it did not provide a handler."); - } - requestHandlers.Set( RequestMethods.RootsList, (request, _, cancellationToken) => rootsHandler(request, cancellationToken), McpJsonUtilities.JsonContext.Default.ListRootsRequestParams, McpJsonUtilities.JsonContext.Default.ListRootsResult); + + _options.Capabilities ??= new(); + _options.Capabilities.Roots ??= new(); } - if (capabilities.Elicitation is { } elicitationCapability) + if (handlers.ElicitationHandler is { } elicitationHandler) { - if (elicitationCapability.ElicitationHandler is not { } elicitationHandler) - { - throw new InvalidOperationException("Elicitation capability was set but it did not provide a handler."); - } - requestHandlers.Set( RequestMethods.ElicitationCreate, (request, _, cancellationToken) => elicitationHandler(request, cancellationToken), McpJsonUtilities.JsonContext.Default.ElicitRequestParams, McpJsonUtilities.JsonContext.Default.ElicitResult); + + _options.Capabilities ??= new(); + _options.Capabilities.Elicitation ??= new(); } } diff --git a/src/ModelContextProtocol.Core/Client/McpClientOptions.cs b/src/ModelContextProtocol.Core/Client/McpClientOptions.cs index d4ed41db4..ff71f5899 100644 --- a/src/ModelContextProtocol.Core/Client/McpClientOptions.cs +++ b/src/ModelContextProtocol.Core/Client/McpClientOptions.cs @@ -11,6 +11,8 @@ namespace ModelContextProtocol.Client; /// public sealed class McpClientOptions { + private McpClientHandlers? _handlers; + /// /// Gets or sets information about this client implementation, including its name and version. /// @@ -63,4 +65,17 @@ public sealed class McpClientOptions /// The default value is 60 seconds. /// public TimeSpan InitializationTimeout { get; set; } = TimeSpan.FromSeconds(60); + + /// + /// Gets or sets the container of handlers used by the client for processing protocol messages. + /// + public McpClientHandlers Handlers + { + get => _handlers ??= new(); + set + { + Throw.IfNull(value); + _handlers = value; + } + } } diff --git a/src/ModelContextProtocol.Core/Protocol/ClientCapabilities.cs b/src/ModelContextProtocol.Core/Protocol/ClientCapabilities.cs index c065ed6cb..058ea3c34 100644 --- a/src/ModelContextProtocol.Core/Protocol/ClientCapabilities.cs +++ b/src/ModelContextProtocol.Core/Protocol/ClientCapabilities.cs @@ -45,7 +45,7 @@ public sealed class ClientCapabilities /// /// /// The server can use to request the list of - /// available roots from the client, which will trigger the client's . + /// available roots from the client, which will trigger the client's . /// /// [JsonPropertyName("roots")] diff --git a/src/ModelContextProtocol.Core/Protocol/CompletionsCapability.cs b/src/ModelContextProtocol.Core/Protocol/CompletionsCapability.cs index 86363351a..e8d5932c9 100644 --- a/src/ModelContextProtocol.Core/Protocol/CompletionsCapability.cs +++ b/src/ModelContextProtocol.Core/Protocol/CompletionsCapability.cs @@ -1,6 +1,3 @@ -using ModelContextProtocol.Server; -using System.Text.Json.Serialization; - namespace ModelContextProtocol.Protocol; /// @@ -23,15 +20,4 @@ namespace ModelContextProtocol.Protocol; public sealed class CompletionsCapability { // Currently empty in the spec, but may be extended in the future. - - /// - /// Gets or sets the handler for completion requests. - /// - /// - /// This handler provides auto-completion suggestions for prompt arguments or resource references in the Model Context Protocol. - /// The handler receives a reference type (e.g., "ref/prompt" or "ref/resource") and the current argument value, - /// and should return appropriate completion suggestions. - /// - [JsonIgnore] - public McpRequestHandler? CompleteHandler { get; set; } } \ No newline at end of file diff --git a/src/ModelContextProtocol.Core/Protocol/ElicitationCapability.cs b/src/ModelContextProtocol.Core/Protocol/ElicitationCapability.cs index d88247d2d..39d76ad05 100644 --- a/src/ModelContextProtocol.Core/Protocol/ElicitationCapability.cs +++ b/src/ModelContextProtocol.Core/Protocol/ElicitationCapability.cs @@ -1,5 +1,3 @@ -using System.Text.Json.Serialization; - namespace ModelContextProtocol.Protocol; /// @@ -11,26 +9,10 @@ namespace ModelContextProtocol.Protocol; /// /// /// When this capability is enabled, an MCP server can request the client to provide additional information -/// during interactions. The client must set a to process these requests. +/// during interactions. The client must set a to process these requests. /// /// public sealed class ElicitationCapability { // Currently empty in the spec, but may be extended in the future. - - /// - /// Gets or sets the handler for processing requests. - /// - /// - /// - /// This handler function is called when an MCP server requests the client to provide additional - /// information during interactions. The client must set this property for the elicitation capability to work. - /// - /// - /// The handler receives message parameters and a cancellation token. - /// It should return a containing the response to the elicitation request. - /// - /// - [JsonIgnore] - public Func>? ElicitationHandler { get; set; } } \ No newline at end of file diff --git a/src/ModelContextProtocol.Core/Protocol/LoggingCapability.cs b/src/ModelContextProtocol.Core/Protocol/LoggingCapability.cs index 07803c1ac..3cc771b0c 100644 --- a/src/ModelContextProtocol.Core/Protocol/LoggingCapability.cs +++ b/src/ModelContextProtocol.Core/Protocol/LoggingCapability.cs @@ -1,6 +1,3 @@ -using ModelContextProtocol.Server; -using System.Text.Json.Serialization; - namespace ModelContextProtocol.Protocol; /// @@ -13,10 +10,4 @@ namespace ModelContextProtocol.Protocol; public sealed class LoggingCapability { // Currently empty in the spec, but may be extended in the future - - /// - /// Gets or sets the handler for set logging level requests from clients. - /// - [JsonIgnore] - public McpRequestHandler? SetLoggingLevelHandler { get; set; } } \ No newline at end of file diff --git a/src/ModelContextProtocol.Core/Protocol/PromptsCapability.cs b/src/ModelContextProtocol.Core/Protocol/PromptsCapability.cs index fdfa3d43c..50e5a6f56 100644 --- a/src/ModelContextProtocol.Core/Protocol/PromptsCapability.cs +++ b/src/ModelContextProtocol.Core/Protocol/PromptsCapability.cs @@ -9,7 +9,7 @@ namespace ModelContextProtocol.Protocol; /// /// /// The prompts capability allows a server to expose a collection of predefined prompt templates that clients -/// can discover and use. These prompts can be static (defined in the ) or +/// can discover and use. These prompts can be static (defined in the ) or /// dynamically generated through handlers. /// /// @@ -30,53 +30,4 @@ public sealed class PromptsCapability /// [JsonPropertyName("listChanged")] public bool? ListChanged { get; set; } - - /// - /// Gets or sets the handler for requests. - /// - /// - /// This handler is invoked when a client requests a list of available prompts from the server - /// via a request. Results from this handler are returned - /// along with any prompts defined in . - /// - [JsonIgnore] - public McpRequestHandler? ListPromptsHandler { get; set; } - - /// - /// Gets or sets the handler for requests. - /// - /// - /// - /// This handler is invoked when a client requests details for a specific prompt by name and provides arguments - /// for the prompt if needed. The handler receives the request context containing the prompt name and any arguments, - /// and should return a with the prompt messages and other details. - /// - /// - /// This handler will be invoked if the requested prompt name is not found in the , - /// allowing for dynamic prompt generation or retrieval from external sources. - /// - /// - [JsonIgnore] - public McpRequestHandler? GetPromptHandler { get; set; } - - /// - /// Gets or sets a collection of prompts that will be served by the server. - /// - /// - /// - /// The contains the predefined prompts that clients can request from the server. - /// This collection works in conjunction with and - /// when those are provided: - /// - /// - /// - For requests: The server returns all prompts from this collection - /// plus any additional prompts provided by the if it's set. - /// - /// - /// - For requests: The server first checks this collection for the requested prompt. - /// If not found, it will invoke the as a fallback if one is set. - /// - /// - [JsonIgnore] - public McpServerPrimitiveCollection? PromptCollection { get; set; } } \ No newline at end of file diff --git a/src/ModelContextProtocol.Core/Protocol/ResourcesCapability.cs b/src/ModelContextProtocol.Core/Protocol/ResourcesCapability.cs index b5336b207..cf1648de2 100644 --- a/src/ModelContextProtocol.Core/Protocol/ResourcesCapability.cs +++ b/src/ModelContextProtocol.Core/Protocol/ResourcesCapability.cs @@ -28,80 +28,4 @@ public sealed class ResourcesCapability /// [JsonPropertyName("listChanged")] public bool? ListChanged { get; set; } - - /// - /// Gets or sets the handler for requests. - /// - /// - /// This handler is called when clients request available resource templates that can be used - /// to create resources within the Model Context Protocol server. - /// Resource templates define the structure and URI patterns for resources accessible in the system, - /// allowing clients to discover available resource types and their access patterns. - /// - [JsonIgnore] - public McpRequestHandler? ListResourceTemplatesHandler { get; set; } - - /// - /// Gets or sets the handler for requests. - /// - /// - /// This handler responds to client requests for available resources and returns information about resources accessible through the server. - /// The implementation should return a with the matching resources. - /// - [JsonIgnore] - public McpRequestHandler? ListResourcesHandler { get; set; } - - /// - /// Gets or sets the handler for requests. - /// - /// - /// This handler is responsible for retrieving the content of a specific resource identified by its URI in the Model Context Protocol. - /// When a client sends a resources/read request, this handler is invoked with the resource URI. - /// The handler should implement logic to locate and retrieve the requested resource, then return - /// its contents in a ReadResourceResult object. - /// - [JsonIgnore] - public McpRequestHandler? ReadResourceHandler { get; set; } - - /// - /// Gets or sets the handler for requests. - /// - /// - /// When a client sends a request, this handler is invoked with the resource URI - /// to be subscribed to. The implementation should register the client's interest in receiving updates - /// for the specified resource. - /// Subscriptions allow clients to receive real-time notifications when resources change, without - /// requiring polling. - /// - [JsonIgnore] - public McpRequestHandler? SubscribeToResourcesHandler { get; set; } - - /// - /// Gets or sets the handler for requests. - /// - /// - /// When a client sends a request, this handler is invoked with the resource URI - /// to be unsubscribed from. The implementation should remove the client's registration for receiving updates - /// about the specified resource. - /// - [JsonIgnore] - public McpRequestHandler? UnsubscribeFromResourcesHandler { get; set; } - - /// - /// Gets or sets a collection of resources served by the server. - /// - /// - /// - /// Resources specified via augment the , - /// and handlers, if provided. Resources with template expressions in their URI templates are considered resource templates - /// and are listed via ListResourceTemplate, whereas resources without template parameters are considered static resources and are listed with ListResources. - /// - /// - /// ReadResource requests will first check the for the exact resource being requested. If no match is found, they'll proceed to - /// try to match the resource against each resource template in . If no match is still found, the request will fall back to - /// any handler registered for . - /// - /// - [JsonIgnore] - public McpServerResourceCollection? ResourceCollection { get; set; } } \ No newline at end of file diff --git a/src/ModelContextProtocol.Core/Protocol/RootsCapability.cs b/src/ModelContextProtocol.Core/Protocol/RootsCapability.cs index 60d20b94f..9f0b0b812 100644 --- a/src/ModelContextProtocol.Core/Protocol/RootsCapability.cs +++ b/src/ModelContextProtocol.Core/Protocol/RootsCapability.cs @@ -31,14 +31,4 @@ public sealed class RootsCapability /// [JsonPropertyName("listChanged")] public bool? ListChanged { get; set; } - - /// - /// Gets or sets the handler for requests. - /// - /// - /// This handler is invoked when a client sends a request to retrieve available roots. - /// The handler receives request parameters and should return a containing the collection of available roots. - /// - [JsonIgnore] - public Func>? RootsHandler { get; set; } } \ No newline at end of file diff --git a/src/ModelContextProtocol.Core/Protocol/SamplingCapability.cs b/src/ModelContextProtocol.Core/Protocol/SamplingCapability.cs index 7828ce290..ad82c7957 100644 --- a/src/ModelContextProtocol.Core/Protocol/SamplingCapability.cs +++ b/src/ModelContextProtocol.Core/Protocol/SamplingCapability.cs @@ -1,7 +1,3 @@ -using Microsoft.Extensions.AI; -using ModelContextProtocol.Client; -using System.Text.Json.Serialization; - namespace ModelContextProtocol.Protocol; /// @@ -13,31 +9,10 @@ namespace ModelContextProtocol.Protocol; /// /// /// When this capability is enabled, an MCP server can request the client to generate content -/// using an AI model. The client must set a to process these requests. +/// using an AI model. The client must set a to process these requests. /// /// public sealed class SamplingCapability { // Currently empty in the spec, but may be extended in the future - - /// - /// Gets or sets the handler for processing requests. - /// - /// - /// - /// This handler function is called when an MCP server requests the client to generate content - /// using an AI model. The client must set this property for the sampling capability to work. - /// - /// - /// The handler receives message parameters, a progress reporter for updates, and a - /// cancellation token. It should return a containing the - /// generated content. - /// - /// - /// You can create a handler using the extension - /// method with any implementation of . - /// - /// - [JsonIgnore] - public Func, CancellationToken, ValueTask>? SamplingHandler { get; set; } } \ No newline at end of file diff --git a/src/ModelContextProtocol.Core/Protocol/ServerCapabilities.cs b/src/ModelContextProtocol.Core/Protocol/ServerCapabilities.cs index 023a869a4..ed2f1f2af 100644 --- a/src/ModelContextProtocol.Core/Protocol/ServerCapabilities.cs +++ b/src/ModelContextProtocol.Core/Protocol/ServerCapabilities.cs @@ -63,24 +63,4 @@ public sealed class ServerCapabilities /// [JsonPropertyName("completions")] public CompletionsCapability? Completions { get; set; } - - /// Gets or sets notification handlers to register with the server. - /// - /// - /// When constructed, the server will enumerate these handlers once, which may contain multiple handlers per notification method key. - /// The server will not re-enumerate the sequence after initialization. - /// - /// - /// Notification handlers allow the server to respond to client-sent notifications for specific methods. - /// Each key in the collection is a notification method name, and each value is a callback that will be invoked - /// when a notification with that method is received. - /// - /// - /// Handlers provided via will be registered with the server for the lifetime of the server. - /// For transient handlers, may be used to register a handler that can - /// then be unregistered by disposing of the returned from the method. - /// - /// - [JsonIgnore] - public IEnumerable>>? NotificationHandlers { get; set; } } diff --git a/src/ModelContextProtocol.Core/Protocol/ToolsCapability.cs b/src/ModelContextProtocol.Core/Protocol/ToolsCapability.cs index 0ea955314..dbbb9462f 100644 --- a/src/ModelContextProtocol.Core/Protocol/ToolsCapability.cs +++ b/src/ModelContextProtocol.Core/Protocol/ToolsCapability.cs @@ -1,4 +1,3 @@ -using ModelContextProtocol.Server; using System.Text.Json.Serialization; namespace ModelContextProtocol.Protocol; @@ -21,43 +20,4 @@ public sealed class ToolsCapability /// [JsonPropertyName("listChanged")] public bool? ListChanged { get; set; } - - /// - /// Gets or sets the handler for requests. - /// - /// - /// The handler should return a list of available tools when requested by a client. - /// It supports pagination through the cursor mechanism, where the client can make - /// repeated calls with the cursor returned by the previous call to retrieve more tools. - /// When used in conjunction with , both the tools from this handler - /// and the tools from the collection will be combined to form the complete list of available tools. - /// - [JsonIgnore] - public McpRequestHandler? ListToolsHandler { get; set; } - - /// - /// Gets or sets the handler for requests. - /// - /// - /// This handler is invoked when a client makes a call to a tool that isn't found in the . - /// The handler should implement logic to execute the requested tool and return appropriate results. - /// It receives a containing information about the tool - /// being called and its arguments, and should return a with the execution results. - /// - [JsonIgnore] - public McpRequestHandler? CallToolHandler { get; set; } - - /// - /// Gets or sets a collection of tools served by the server. - /// - /// - /// Tools will specified via augment the and - /// , if provided. ListTools requests will output information about every tool - /// in and then also any tools output by , if it's - /// non-. CallTool requests will first check for the tool - /// being requested, and if the tool is not found in the , any specified - /// will be invoked as a fallback. - /// - [JsonIgnore] - public McpServerPrimitiveCollection? ToolCollection { get; set; } } \ No newline at end of file diff --git a/src/ModelContextProtocol/McpServerHandlers.cs b/src/ModelContextProtocol.Core/Server/McpServerHandlers.cs similarity index 68% rename from src/ModelContextProtocol/McpServerHandlers.cs rename to src/ModelContextProtocol.Core/Server/McpServerHandlers.cs index 34504e928..0d8deba13 100644 --- a/src/ModelContextProtocol/McpServerHandlers.cs +++ b/src/ModelContextProtocol.Core/Server/McpServerHandlers.cs @@ -1,4 +1,3 @@ -using Microsoft.Extensions.DependencyInjection; using ModelContextProtocol.Protocol; namespace ModelContextProtocol.Server; @@ -10,17 +9,12 @@ namespace ModelContextProtocol.Server; /// /// This class provides a centralized collection of delegates that implement various capabilities of the Model Context Protocol. /// Each handler in this class corresponds to a specific endpoint in the Model Context Protocol and -/// is responsible for processing a particular type of request. The handlers are used to customize +/// is responsible for processing a particular type of message. The handlers are used to customize /// the behavior of the MCP server by providing implementations for the various protocol operations. /// /// -/// Handlers can be configured individually using the extension methods in -/// such as and -/// . -/// -/// -/// When a client sends a request to the server, the appropriate handler is invoked to process the -/// request and produce a response according to the protocol specification. Which handler is selected +/// When a client sends a message to the server, the appropriate handler is invoked to process it +/// according to the protocol specification. Which handler is selected /// is done based on an ordinal, case-sensitive string comparison. /// /// @@ -162,65 +156,22 @@ public sealed class McpServerHandlers /// public McpRequestHandler? SetLoggingLevelHandler { get; set; } - /// - /// Overwrite any handlers in McpServerOptions with non-null handlers from this instance. - /// - /// - /// - internal void OverwriteWithSetHandlers(McpServerOptions options) - { - PromptsCapability? promptsCapability = options.Capabilities?.Prompts; - if (ListPromptsHandler is not null || GetPromptHandler is not null) - { - promptsCapability ??= new(); - promptsCapability.ListPromptsHandler = ListPromptsHandler ?? promptsCapability.ListPromptsHandler; - promptsCapability.GetPromptHandler = GetPromptHandler ?? promptsCapability.GetPromptHandler; - } - - ResourcesCapability? resourcesCapability = options.Capabilities?.Resources; - if (ListResourcesHandler is not null || - ReadResourceHandler is not null) - { - resourcesCapability ??= new(); - resourcesCapability.ListResourceTemplatesHandler = ListResourceTemplatesHandler ?? resourcesCapability.ListResourceTemplatesHandler; - resourcesCapability.ListResourcesHandler = ListResourcesHandler ?? resourcesCapability.ListResourcesHandler; - resourcesCapability.ReadResourceHandler = ReadResourceHandler ?? resourcesCapability.ReadResourceHandler; - - if (SubscribeToResourcesHandler is not null || UnsubscribeFromResourcesHandler is not null) - { - resourcesCapability.SubscribeToResourcesHandler = SubscribeToResourcesHandler ?? resourcesCapability.SubscribeToResourcesHandler; - resourcesCapability.UnsubscribeFromResourcesHandler = UnsubscribeFromResourcesHandler ?? resourcesCapability.UnsubscribeFromResourcesHandler; - resourcesCapability.Subscribe = true; - } - } - - ToolsCapability? toolsCapability = options.Capabilities?.Tools; - if (ListToolsHandler is not null || CallToolHandler is not null) - { - toolsCapability ??= new(); - toolsCapability.ListToolsHandler = ListToolsHandler ?? toolsCapability.ListToolsHandler; - toolsCapability.CallToolHandler = CallToolHandler ?? toolsCapability.CallToolHandler; - } - - LoggingCapability? loggingCapability = options.Capabilities?.Logging; - if (SetLoggingLevelHandler is not null) - { - loggingCapability ??= new(); - loggingCapability.SetLoggingLevelHandler = SetLoggingLevelHandler; - } - - CompletionsCapability? completionsCapability = options.Capabilities?.Completions; - if (CompleteHandler is not null) - { - completionsCapability ??= new(); - completionsCapability.CompleteHandler = CompleteHandler; - } - - options.Capabilities ??= new(); - options.Capabilities.Prompts = promptsCapability; - options.Capabilities.Resources = resourcesCapability; - options.Capabilities.Tools = toolsCapability; - options.Capabilities.Logging = loggingCapability; - options.Capabilities.Completions = completionsCapability; - } + /// Gets or sets notification handlers to register with the server. + /// + /// + /// When constructed, the server will enumerate these handlers once, which may contain multiple handlers per notification method key. + /// The server will not re-enumerate the sequence after initialization. + /// + /// + /// Notification handlers allow the server to respond to client-sent notifications for specific methods. + /// Each key in the collection is a notification method name, and each value is a callback that will be invoked + /// when a notification with that method is received. + /// + /// + /// Handlers provided via will be registered with the server for the lifetime of the server. + /// For transient handlers, may be used to register a handler that can + /// then be unregistered by disposing of the returned from the method. + /// + /// + public IEnumerable>>? NotificationHandlers { get; set; } } diff --git a/src/ModelContextProtocol.Core/Server/McpServerImpl.cs b/src/ModelContextProtocol.Core/Server/McpServerImpl.cs index f4f9e8b32..0771000cf 100644 --- a/src/ModelContextProtocol.Core/Server/McpServerImpl.cs +++ b/src/ModelContextProtocol.Core/Server/McpServerImpl.cs @@ -84,7 +84,7 @@ public McpServerImpl(ITransport transport, McpServerOptions options, ILoggerFact 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 @@ public McpServerImpl(ITransport transport, McpServerOptions options, ILoggerFact // 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(McpServerPrimitiveCollection? collection, string notificationMethod) where TPrimitive : IMcpServerPrimitive @@ -230,22 +230,21 @@ private void ConfigureInitialize(McpServerOptions options) private void ConfigureCompletion(McpServerOptions options) { - if (options.Capabilities?.Completions is not { } completionsCapability) + var completeHandler = options.Handlers.CompleteHandler; + + if (completeHandler is null && options.Capabilities?.Completions 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 +256,30 @@ private void ConfigureExperimental(McpServerOptions options) 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; + + 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 +384,6 @@ await originalListResourceTemplatesHandler(request, cancellationToken).Configure 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 +420,22 @@ await originalListResourceTemplatesHandler(request, cancellationToken).Configure 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; + + 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 +486,6 @@ await originalListPromptsHandler(request, cancellationToken).ConfigureAwait(fals return handler(request, cancellationToken); }); - ServerCapabilities.Prompts.ListPromptsHandler = listPromptsHandler; - ServerCapabilities.Prompts.GetPromptHandler = getPromptHandler; - ServerCapabilities.Prompts.PromptCollection = prompts; ServerCapabilities.Prompts.ListChanged = listChanged; SetHandler( @@ -499,17 +503,22 @@ await originalListPromptsHandler(request, cancellationToken).ConfigureAwait(fals 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; + + 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 +600,6 @@ await originalListToolsHandler(request, cancellationToken).ConfigureAwait(false) } }); - ServerCapabilities.Tools.ListToolsHandler = listToolsHandler; - ServerCapabilities.Tools.CallToolHandler = callToolHandler; - ServerCapabilities.Tools.ToolCollection = tools; ServerCapabilities.Tools.ListChanged = listChanged; SetHandler( @@ -612,7 +618,7 @@ await originalListToolsHandler(request, cancellationToken).ConfigureAwait(false) 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; // Apply filters to the handler if (setLoggingLevelHandler is not null) @@ -621,7 +627,6 @@ private void ConfigureLogging(McpServerOptions options) } ServerCapabilities.Logging = new(); - ServerCapabilities.Logging.SetLoggingLevelHandler = setLoggingLevelHandler; _requestHandlers.Set( RequestMethods.LoggingSetLevel, diff --git a/src/ModelContextProtocol.Core/Server/McpServerOptions.cs b/src/ModelContextProtocol.Core/Server/McpServerOptions.cs index 1c981b77f..833c852e3 100644 --- a/src/ModelContextProtocol.Core/Server/McpServerOptions.cs +++ b/src/ModelContextProtocol.Core/Server/McpServerOptions.cs @@ -7,6 +7,8 @@ namespace ModelContextProtocol.Server; /// public sealed class McpServerOptions { + private McpServerHandlers? _handlers; + /// /// Gets or sets information about this server implementation, including its name and version. /// @@ -89,4 +91,67 @@ public sealed class McpServerOptions /// added will be the outermost (first to execute). /// public McpServerFilters Filters { get; } = new(); + + /// + /// Gets or sets the container of handlers used by the server for processing protocol messages. + /// + public McpServerHandlers Handlers + { + get => _handlers ??= new(); + set + { + Throw.IfNull(value); + _handlers = value; + } + } + + /// + /// Gets or sets a collection of tools served by the server. + /// + /// + /// Tools specified via augment the and + /// , if provided. ListTools requests will output information about every tool + /// in and then also any tools output by , if it's + /// non-. CallTool requests will first check for the tool + /// being requested, and if the tool is not found in the , any specified + /// will be invoked as a fallback. + /// + public McpServerPrimitiveCollection? ToolCollection { get; set; } + + /// + /// Gets or sets a collection of resources served by the server. + /// + /// + /// + /// Resources specified via augment the , + /// and handlers, if provided. Resources with template expressions in their URI templates are considered resource templates + /// and are listed via ListResourceTemplate, whereas resources without template parameters are considered static resources and are listed with ListResources. + /// + /// + /// ReadResource requests will first check the for the exact resource being requested. If no match is found, they'll proceed to + /// try to match the resource against each resource template in . If no match is still found, the request will fall back to + /// any handler registered for . + /// + /// + public McpServerResourceCollection? ResourceCollection { get; set; } + + /// + /// Gets or sets a collection of prompts that will be served by the server. + /// + /// + /// + /// The contains the predefined prompts that clients can request from the server. + /// This collection works in conjunction with and + /// when those are provided: + /// + /// + /// - For requests: The server returns all prompts from this collection + /// plus any additional prompts provided by the if it's set. + /// + /// + /// - For requests: The server first checks this collection for the requested prompt. + /// If not found, it will invoke the as a fallback if one is set. + /// + /// + public McpServerPrimitiveCollection? PromptCollection { get; set; } } diff --git a/src/ModelContextProtocol/McpServerOptionsSetup.cs b/src/ModelContextProtocol/McpServerOptionsSetup.cs index 7fe4f61cb..030c3012a 100644 --- a/src/ModelContextProtocol/McpServerOptionsSetup.cs +++ b/src/ModelContextProtocol/McpServerOptionsSetup.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.Options; +using ModelContextProtocol.Protocol; using ModelContextProtocol.Server; namespace ModelContextProtocol; @@ -29,7 +30,7 @@ public void Configure(McpServerOptions options) // a collection, add to it, otherwise create a new one. We want to maintain the identity // of an existing collection in case someone has provided their own derived type, wants // change notifications, etc. - McpServerPrimitiveCollection toolCollection = options.Capabilities?.Tools?.ToolCollection ?? []; + McpServerPrimitiveCollection toolCollection = options.ToolCollection ?? []; foreach (var tool in serverTools) { toolCollection.TryAdd(tool); @@ -37,16 +38,14 @@ public void Configure(McpServerOptions options) if (!toolCollection.IsEmpty) { - options.Capabilities ??= new(); - options.Capabilities.Tools ??= new(); - options.Capabilities.Tools.ToolCollection = toolCollection; + options.ToolCollection = toolCollection; } // Collect all of the provided prompts into a prompts collection. If the options already has // a collection, add to it, otherwise create a new one. We want to maintain the identity // of an existing collection in case someone has provided their own derived type, wants // change notifications, etc. - McpServerPrimitiveCollection promptCollection = options.Capabilities?.Prompts?.PromptCollection ?? []; + McpServerPrimitiveCollection promptCollection = options.PromptCollection ?? []; foreach (var prompt in serverPrompts) { promptCollection.TryAdd(prompt); @@ -54,16 +53,14 @@ public void Configure(McpServerOptions options) if (!promptCollection.IsEmpty) { - options.Capabilities ??= new(); - options.Capabilities.Prompts ??= new(); - options.Capabilities.Prompts.PromptCollection = promptCollection; + options.PromptCollection = promptCollection; } // Collect all of the provided resources into a resources collection. If the options already has // a collection, add to it, otherwise create a new one. We want to maintain the identity // of an existing collection in case someone has provided their own derived type, wants // change notifications, etc. - McpServerResourceCollection resourceCollection = options.Capabilities?.Resources?.ResourceCollection ?? []; + McpServerResourceCollection resourceCollection = options.ResourceCollection ?? []; foreach (var resource in serverResources) { resourceCollection.TryAdd(resource); @@ -71,12 +68,71 @@ public void Configure(McpServerOptions options) if (!resourceCollection.IsEmpty) { - options.Capabilities ??= new(); - options.Capabilities.Resources ??= new(); - options.Capabilities.Resources.ResourceCollection = resourceCollection; + options.ResourceCollection = resourceCollection; } // Apply custom server handlers. - serverHandlers.Value.OverwriteWithSetHandlers(options); + OverwriteWithSetHandlers(serverHandlers.Value, options); + } + + /// + /// Overwrite any handlers in McpServerOptions with non-null handlers from this instance. + /// + private static void OverwriteWithSetHandlers(McpServerHandlers handlers, McpServerOptions options) + { + McpServerHandlers optionsHandlers = options.Handlers; + + PromptsCapability? promptsCapability = options.Capabilities?.Prompts; + if (handlers.ListPromptsHandler is not null || handlers.GetPromptHandler is not null) + { + promptsCapability ??= new(); + optionsHandlers.ListPromptsHandler = handlers.ListPromptsHandler ?? optionsHandlers.ListPromptsHandler; + optionsHandlers.GetPromptHandler = handlers.GetPromptHandler ?? optionsHandlers.GetPromptHandler; + } + + ResourcesCapability? resourcesCapability = options.Capabilities?.Resources; + if (handlers.ListResourceTemplatesHandler is not null || handlers.ListResourcesHandler is not null || handlers.ReadResourceHandler is not null) + { + resourcesCapability ??= new(); + optionsHandlers.ListResourceTemplatesHandler = handlers.ListResourceTemplatesHandler ?? optionsHandlers.ListResourceTemplatesHandler; + optionsHandlers.ListResourcesHandler = handlers.ListResourcesHandler ?? optionsHandlers.ListResourcesHandler; + optionsHandlers.ReadResourceHandler = handlers.ReadResourceHandler ?? optionsHandlers.ReadResourceHandler; + + if (handlers.SubscribeToResourcesHandler is not null || handlers.UnsubscribeFromResourcesHandler is not null) + { + optionsHandlers.SubscribeToResourcesHandler = handlers.SubscribeToResourcesHandler ?? optionsHandlers.SubscribeToResourcesHandler; + optionsHandlers.UnsubscribeFromResourcesHandler = handlers.UnsubscribeFromResourcesHandler ?? optionsHandlers.UnsubscribeFromResourcesHandler; + resourcesCapability.Subscribe = true; + } + } + + ToolsCapability? toolsCapability = options.Capabilities?.Tools; + if (handlers.ListToolsHandler is not null || handlers.CallToolHandler is not null) + { + toolsCapability ??= new(); + optionsHandlers.ListToolsHandler = handlers.ListToolsHandler ?? optionsHandlers.ListToolsHandler; + optionsHandlers.CallToolHandler = handlers.CallToolHandler ?? optionsHandlers.CallToolHandler; + } + + LoggingCapability? loggingCapability = options.Capabilities?.Logging; + if (handlers.SetLoggingLevelHandler is not null) + { + loggingCapability ??= new(); + optionsHandlers.SetLoggingLevelHandler = handlers.SetLoggingLevelHandler; + } + + CompletionsCapability? completionsCapability = options.Capabilities?.Completions; + if (handlers.CompleteHandler is not null) + { + completionsCapability ??= new(); + optionsHandlers.CompleteHandler = handlers.CompleteHandler; + } + + options.Capabilities ??= new(); + options.Capabilities.Prompts = promptsCapability; + options.Capabilities.Resources = resourcesCapability; + options.Capabilities.Tools = toolsCapability; + options.Capabilities.Logging = loggingCapability; + options.Capabilities.Completions = completionsCapability; } } diff --git a/tests/ModelContextProtocol.AspNetCore.Tests/HttpServerIntegrationTests.cs b/tests/ModelContextProtocol.AspNetCore.Tests/HttpServerIntegrationTests.cs index 5da37146a..1d27a219e 100644 --- a/tests/ModelContextProtocol.AspNetCore.Tests/HttpServerIntegrationTests.cs +++ b/tests/ModelContextProtocol.AspNetCore.Tests/HttpServerIntegrationTests.cs @@ -251,9 +251,7 @@ public async Task Sampling_Sse_TestServer() int samplingHandlerCalls = 0; #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously McpClientOptions options = new(); - options.Capabilities = new(); - options.Capabilities.Sampling ??= new(); - options.Capabilities.Sampling.SamplingHandler = async (_, _, _) => + options.Handlers.SamplingHandler = async (_, _, _) => { samplingHandlerCalls++; return new CreateMessageResult diff --git a/tests/ModelContextProtocol.AspNetCore.Tests/MapMcpTests.cs b/tests/ModelContextProtocol.AspNetCore.Tests/MapMcpTests.cs index bb9746ed7..341171c51 100644 --- a/tests/ModelContextProtocol.AspNetCore.Tests/MapMcpTests.cs +++ b/tests/ModelContextProtocol.AspNetCore.Tests/MapMcpTests.cs @@ -161,29 +161,26 @@ public async Task Sampling_DoesNotCloseStream_Prematurely() await app.StartAsync(TestContext.Current.CancellationToken); var sampleCount = 0; - var clientOptions = new McpClientOptions + var clientOptions = new McpClientOptions() { - Capabilities = new() + Handlers = new() { - Sampling = new() + SamplingHandler = async (parameters, _, _) => { - SamplingHandler = async (parameters, _, _) => + Assert.NotNull(parameters?.Messages); + var message = Assert.Single(parameters.Messages); + Assert.Equal(Role.User, message.Role); + Assert.Equal("Test prompt for sampling", Assert.IsType(message.Content).Text); + + sampleCount++; + return new CreateMessageResult { - Assert.NotNull(parameters?.Messages); - var message = Assert.Single(parameters.Messages); - Assert.Equal(Role.User, message.Role); - Assert.Equal("Test prompt for sampling", Assert.IsType(message.Content).Text); - - sampleCount++; - return new CreateMessageResult - { - Model = "test-model", - Role = Role.Assistant, - Content = new TextContentBlock { Text = "Sampling response from client" }, - }; - }, - }, - }, + Model = "test-model", + Role = Role.Assistant, + Content = new TextContentBlock { Text = "Sampling response from client" }, + }; + } + } }; await using var mcpClient = await ConnectAsync(clientOptions: clientOptions); diff --git a/tests/ModelContextProtocol.AspNetCore.Tests/StatelessServerTests.cs b/tests/ModelContextProtocol.AspNetCore.Tests/StatelessServerTests.cs index 3c200bb61..199d815eb 100644 --- a/tests/ModelContextProtocol.AspNetCore.Tests/StatelessServerTests.cs +++ b/tests/ModelContextProtocol.AspNetCore.Tests/StatelessServerTests.cs @@ -102,9 +102,7 @@ public async Task SamplingRequest_Fails_WithInvalidOperationException() await StartAsync(); var mcpClientOptions = new McpClientOptions(); - mcpClientOptions.Capabilities = new(); - mcpClientOptions.Capabilities.Sampling ??= new(); - mcpClientOptions.Capabilities.Sampling.SamplingHandler = (_, _, _) => + mcpClientOptions.Handlers.SamplingHandler = (_, _, _) => { throw new UnreachableException(); }; @@ -122,9 +120,7 @@ public async Task RootsRequest_Fails_WithInvalidOperationException() await StartAsync(); var mcpClientOptions = new McpClientOptions(); - mcpClientOptions.Capabilities = new(); - mcpClientOptions.Capabilities.Roots ??= new(); - mcpClientOptions.Capabilities.Roots.RootsHandler = (_, _) => + mcpClientOptions.Handlers.RootsHandler = (_, _) => { throw new UnreachableException(); }; @@ -142,9 +138,7 @@ public async Task ElicitRequest_Fails_WithInvalidOperationException() await StartAsync(); var mcpClientOptions = new McpClientOptions(); - mcpClientOptions.Capabilities = new(); - mcpClientOptions.Capabilities.Elicitation ??= new(); - mcpClientOptions.Capabilities.Elicitation.ElicitationHandler = (_, _) => + mcpClientOptions.Handlers.ElicitationHandler = (_, _) => { throw new UnreachableException(); }; diff --git a/tests/ModelContextProtocol.TestServer/Program.cs b/tests/ModelContextProtocol.TestServer/Program.cs index cbfd828c8..75271e348 100644 --- a/tests/ModelContextProtocol.TestServer/Program.cs +++ b/tests/ModelContextProtocol.TestServer/Program.cs @@ -1,10 +1,10 @@ -using Microsoft.Extensions.Logging; +using System.Collections.Concurrent; +using System.Text; +using System.Text.Json; +using Microsoft.Extensions.Logging; using ModelContextProtocol.Protocol; using ModelContextProtocol.Server; using Serilog; -using System.Collections.Concurrent; -using System.Text; -using System.Text.Json; #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously @@ -38,17 +38,16 @@ private static async Task Main(string[] args) McpServerOptions options = new() { - Capabilities = new ServerCapabilities - { - Tools = ConfigureTools(), - Resources = ConfigureResources(), - Prompts = ConfigurePrompts(), - Logging = ConfigureLogging(), - Completions = ConfigureCompletions(), - }, + Capabilities = new ServerCapabilities(), ServerInstructions = "This is a test server with only stub functionality", }; + ConfigureTools(options); + ConfigureResources(options); + ConfigurePrompts(options); + ConfigureLogging(options); + ConfigureCompletions(options); + using var loggerFactory = CreateLoggerFactory(); await using var stdioTransport = new StdioServerTransport("TestServer", loggerFactory); await using McpServer server = McpServer.Create(stdioTransport, options, loggerFactory); @@ -105,222 +104,215 @@ await server.SendMessageAsync(new JsonRpcNotification } } - private static ToolsCapability ConfigureTools() + private static void ConfigureTools(McpServerOptions options) { - return new() + options.Handlers.ListToolsHandler = async (request, cancellationToken) => { - ListToolsHandler = async (request, cancellationToken) => + return new ListToolsResult { - return new ListToolsResult - { - Tools = - [ - new Tool - { - Name = "echo", - Description = "Echoes the input back to the client.", - InputSchema = JsonSerializer.Deserialize(""" - { - "type": "object", - "properties": { - "message": { - "type": "string", - "description": "The input to echo back." - } - }, - "required": ["message"] - } - """), - }, - new Tool - { - Name = "echoSessionId", - Description = "Echoes the session id back to the client.", - InputSchema = JsonSerializer.Deserialize(""" - { - "type": "object" - } - """, McpJsonUtilities.DefaultOptions), - }, - new Tool - { - Name = "sampleLLM", - Description = "Samples from an LLM using MCP's sampling feature.", - InputSchema = JsonSerializer.Deserialize(""" - { - "type": "object", - "properties": { - "prompt": { - "type": "string", - "description": "The prompt to send to the LLM" - }, - "maxTokens": { - "type": "number", - "description": "Maximum number of tokens to generate" - } + Tools = + [ + new Tool + { + Name = "echo", + Description = "Echoes the input back to the client.", + InputSchema = JsonSerializer.Deserialize(""" + { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "The input to echo back." + } + }, + "required": ["message"] + } + """), + }, + new Tool + { + Name = "echoSessionId", + Description = "Echoes the session id back to the client.", + InputSchema = JsonSerializer.Deserialize(""" + { + "type": "object" + } + """, McpJsonUtilities.DefaultOptions), + }, + new Tool + { + Name = "sampleLLM", + Description = "Samples from an LLM using MCP's sampling feature.", + InputSchema = JsonSerializer.Deserialize(""" + { + "type": "object", + "properties": { + "prompt": { + "type": "string", + "description": "The prompt to send to the LLM" }, - "required": ["prompt", "maxTokens"] - } - """), - } - ] - }; - }, - - CallToolHandler = async (request, cancellationToken) => + "maxTokens": { + "type": "number", + "description": "Maximum number of tokens to generate" + } + }, + "required": ["prompt", "maxTokens"] + } + """), + } + ] + }; + }; + options.Handlers.CallToolHandler = async (request, cancellationToken) => + { + if (request.Params?.Name == "echo") { - if (request.Params?.Name == "echo") + if (request.Params?.Arguments is null || !request.Params.Arguments.TryGetValue("message", out var message)) { - if (request.Params?.Arguments is null || !request.Params.Arguments.TryGetValue("message", out var message)) - { - throw new McpException("Missing required argument 'message'", McpErrorCode.InvalidParams); - } - return new CallToolResult - { - Content = [new TextContentBlock { Text = $"Echo: {message}" }] - }; + throw new McpException("Missing required argument 'message'", McpErrorCode.InvalidParams); } - else if (request.Params?.Name == "echoSessionId") + return new CallToolResult { - return new CallToolResult - { - Content = [new TextContentBlock { Text = request.Server.SessionId ?? string.Empty }] - }; - } - else if (request.Params?.Name == "sampleLLM") + Content = [new TextContentBlock { Text = $"Echo: {message}" }] + }; + } + else if (request.Params?.Name == "echoSessionId") + { + return new CallToolResult { - if (request.Params?.Arguments is null || - !request.Params.Arguments.TryGetValue("prompt", out var prompt) || - !request.Params.Arguments.TryGetValue("maxTokens", out var maxTokens)) - { - throw new McpException("Missing required arguments 'prompt' and 'maxTokens'", McpErrorCode.InvalidParams); - } - var sampleResult = await request.Server.SampleAsync(CreateRequestSamplingParams(prompt.ToString(), "sampleLLM", Convert.ToInt32(maxTokens.GetRawText())), - cancellationToken); - - return new CallToolResult - { - Content = [new TextContentBlock { Text = $"LLM sampling result: {(sampleResult.Content as TextContentBlock)?.Text}" }] - }; - } - else + Content = [new TextContentBlock { Text = request.Server.SessionId ?? string.Empty }] + }; + } + else if (request.Params?.Name == "sampleLLM") + { + if (request.Params?.Arguments is null || + !request.Params.Arguments.TryGetValue("prompt", out var prompt) || + !request.Params.Arguments.TryGetValue("maxTokens", out var maxTokens)) { - throw new McpException($"Unknown tool: {request.Params?.Name}", McpErrorCode.InvalidParams); + throw new McpException("Missing required arguments 'prompt' and 'maxTokens'", McpErrorCode.InvalidParams); } + var sampleResult = await request.Server.SampleAsync(CreateRequestSamplingParams(prompt.ToString(), "sampleLLM", Convert.ToInt32(maxTokens.GetRawText())), + cancellationToken); + + return new CallToolResult + { + Content = [new TextContentBlock { Text = $"LLM sampling result: {(sampleResult.Content as TextContentBlock)?.Text}" }] + }; + } + else + { + throw new McpException($"Unknown tool: {request.Params?.Name}", McpErrorCode.InvalidParams); } }; } - private static PromptsCapability ConfigurePrompts() + private static void ConfigurePrompts(McpServerOptions options) { - return new() + options.Handlers.ListPromptsHandler = async (request, cancellationToken) => { - ListPromptsHandler = async (request, cancellationToken) => + return new ListPromptsResult { - return new ListPromptsResult - { - Prompts = [ - new Prompt - { - Name = "simple_prompt", - Description = "A prompt without arguments" - }, - new Prompt - { - Name = "complex_prompt", - Description = "A prompt with arguments", - Arguments = - [ - new PromptArgument - { - Name = "temperature", - Description = "Temperature setting", - Required = true - }, - new PromptArgument - { - Name = "style", - Description = "Output style", - Required = false - } - ] - } - ] - }; - }, + Prompts = [ + new Prompt + { + Name = "simple_prompt", + Description = "A prompt without arguments" + }, + new Prompt + { + Name = "complex_prompt", + Description = "A prompt with arguments", + Arguments = + [ + new PromptArgument + { + Name = "temperature", + Description = "Temperature setting", + Required = true + }, + new PromptArgument + { + Name = "style", + Description = "Output style", + Required = false + } + ] + } + ] + }; + }; - GetPromptHandler = async (request, cancellationToken) => + options.Handlers.GetPromptHandler = async (request, cancellationToken) => + { + List messages = []; + if (request.Params?.Name == "simple_prompt") { - List messages = []; - if (request.Params?.Name == "simple_prompt") + messages.Add(new PromptMessage { - messages.Add(new PromptMessage - { - Role = Role.User, - Content = new TextContentBlock { Text = "This is a simple prompt without arguments." }, - }); - } - else if (request.Params?.Name == "complex_prompt") + Role = Role.User, + Content = new TextContentBlock { Text = "This is a simple prompt without arguments." }, + }); + } + else if (request.Params?.Name == "complex_prompt") + { + string temperature = request.Params.Arguments?["temperature"].ToString() ?? "unknown"; + string style = request.Params.Arguments?["style"].ToString() ?? "unknown"; + messages.Add(new PromptMessage { - string temperature = request.Params.Arguments?["temperature"].ToString() ?? "unknown"; - string style = request.Params.Arguments?["style"].ToString() ?? "unknown"; - messages.Add(new PromptMessage - { - Role = Role.User, - Content = new TextContentBlock { Text = $"This is a complex prompt with arguments: temperature={temperature}, style={style}" }, - }); - messages.Add(new PromptMessage - { - Role = Role.Assistant, - Content = new TextContentBlock { Text = "I understand. You've provided a complex prompt with temperature and style arguments. How would you like me to proceed?" }, - }); - messages.Add(new PromptMessage - { - Role = Role.User, - Content = new ImageContentBlock - { - Data = MCP_TINY_IMAGE, - MimeType = "image/png" - } - }); - } - else + Role = Role.User, + Content = new TextContentBlock { Text = $"This is a complex prompt with arguments: temperature={temperature}, style={style}" }, + }); + messages.Add(new PromptMessage { - throw new McpException($"Unknown prompt: {request.Params?.Name}", McpErrorCode.InvalidParams); - } - - return new GetPromptResult + Role = Role.Assistant, + Content = new TextContentBlock { Text = "I understand. You've provided a complex prompt with temperature and style arguments. How would you like me to proceed?" }, + }); + messages.Add(new PromptMessage { - Messages = messages - }; + Role = Role.User, + Content = new ImageContentBlock + { + Data = MCP_TINY_IMAGE, + MimeType = "image/png" + } + }); } + else + { + throw new McpException($"Unknown prompt: {request.Params?.Name}", McpErrorCode.InvalidParams); + } + + return new GetPromptResult + { + Messages = messages + }; }; } private static LoggingLevel? _minimumLoggingLevel = null; - private static LoggingCapability ConfigureLogging() + private static void ConfigureLogging(McpServerOptions options) { - return new() + options.Handlers.SetLoggingLevelHandler = async (request, cancellationToken) => { - SetLoggingLevelHandler = async (request, cancellationToken) => + if (request.Params?.Level is null) { - if (request.Params?.Level is null) - { - throw new McpException("Missing required argument 'level'", McpErrorCode.InvalidParams); - } + throw new McpException("Missing required argument 'level'", McpErrorCode.InvalidParams); + } - _minimumLoggingLevel = request.Params.Level; + _minimumLoggingLevel = request.Params.Level; - return new EmptyResult(); - } + return new EmptyResult(); }; } private static readonly ConcurrentDictionary _subscribedResources = new(); - private static ResourcesCapability ConfigureResources() + private static void ConfigureResources(McpServerOptions options) { + var capabilities = options.Capabilities ??= new(); + capabilities.Resources = new() { Subscribe = true }; + List resources = []; List resourceContents = []; for (int i = 0; i < 100; ++i) @@ -361,128 +353,123 @@ private static ResourcesCapability ConfigureResources() const int pageSize = 10; - return new() + options.Handlers.ListResourceTemplatesHandler = async (request, cancellationToken) => { - ListResourceTemplatesHandler = async (request, cancellationToken) => - { - return new ListResourceTemplatesResult - { - ResourceTemplates = [ - new ResourceTemplate - { - UriTemplate = "test://dynamic/resource/{id}", - Name = "Dynamic Resource", - } - ] - }; - }, - - ListResourcesHandler = async (request, cancellationToken) => + return new ListResourceTemplatesResult { - int startIndex = 0; - if (request.Params?.Cursor is not null) - { - try + ResourceTemplates = [ + new ResourceTemplate { - var startIndexAsString = Encoding.UTF8.GetString(Convert.FromBase64String(request.Params.Cursor)); - startIndex = Convert.ToInt32(startIndexAsString); + UriTemplate = "test://dynamic/resource/{id}", + Name = "Dynamic Resource", } - catch (Exception e) - { - throw new McpException($"Invalid cursor: '{request.Params.Cursor}'", e, McpErrorCode.InvalidParams); - } - } - - int endIndex = Math.Min(startIndex + pageSize, resources.Count); - string? nextCursor = null; + ] + }; + }; - if (endIndex < resources.Count) + options.Handlers.ListResourcesHandler = async (request, cancellationToken) => + { + int startIndex = 0; + if (request.Params?.Cursor is not null) + { + try { - nextCursor = Convert.ToBase64String(Encoding.UTF8.GetBytes(endIndex.ToString())); + var startIndexAsString = Encoding.UTF8.GetString(Convert.FromBase64String(request.Params.Cursor)); + startIndex = Convert.ToInt32(startIndexAsString); } - return new ListResourcesResult + catch (Exception e) { - NextCursor = nextCursor, - Resources = resources.GetRange(startIndex, endIndex - startIndex) - }; - }, + throw new McpException($"Invalid cursor: '{request.Params.Cursor}'", e, McpErrorCode.InvalidParams); + } + } + + int endIndex = Math.Min(startIndex + pageSize, resources.Count); + string? nextCursor = null; - ReadResourceHandler = async (request, cancellationToken) => + if (endIndex < resources.Count) { - if (request.Params?.Uri is null) - { - throw new McpException("Missing required argument 'uri'", McpErrorCode.InvalidParams); - } + nextCursor = Convert.ToBase64String(Encoding.UTF8.GetBytes(endIndex.ToString())); + } + return new ListResourcesResult + { + NextCursor = nextCursor, + Resources = resources.GetRange(startIndex, endIndex - startIndex) + }; + }; - if (request.Params.Uri.StartsWith("test://dynamic/resource/")) - { - var id = request.Params.Uri.Split('/').LastOrDefault(); - if (string.IsNullOrEmpty(id)) - { - throw new McpException($"Invalid resource URI: '{request.Params.Uri}'", McpErrorCode.InvalidParams); - } + options.Handlers.ReadResourceHandler = async (request, cancellationToken) => + { + if (request.Params?.Uri is null) + { + throw new McpException("Missing required argument 'uri'", McpErrorCode.InvalidParams); + } - return new ReadResourceResult - { - Contents = [ - new TextResourceContents - { - Uri = request.Params.Uri, - MimeType = "text/plain", - Text = $"Dynamic resource {id}: This is a plaintext resource" - } - ] - }; + if (request.Params.Uri.StartsWith("test://dynamic/resource/")) + { + var id = request.Params.Uri.Split('/').LastOrDefault(); + if (string.IsNullOrEmpty(id)) + { + throw new McpException($"Invalid resource URI: '{request.Params.Uri}'", McpErrorCode.InvalidParams); } - ResourceContents contents = resourceContents.FirstOrDefault(r => r.Uri == request.Params.Uri) - ?? throw new McpException($"Resource not found: '{request.Params.Uri}'", McpErrorCode.InvalidParams); - return new ReadResourceResult { - Contents = [contents] + Contents = [ + new TextResourceContents + { + Uri = request.Params.Uri, + MimeType = "text/plain", + Text = $"Dynamic resource {id}: This is a plaintext resource" + } + ] }; - }, + } - SubscribeToResourcesHandler = async (request, cancellationToken) => + ResourceContents contents = resourceContents.FirstOrDefault(r => r.Uri == request.Params.Uri) + ?? throw new McpException($"Resource not found: '{request.Params.Uri}'", McpErrorCode.InvalidParams); + + return new ReadResourceResult { - if (request?.Params?.Uri is null) - { - throw new McpException("Missing required argument 'uri'", McpErrorCode.InvalidParams); - } - if (!request.Params.Uri.StartsWith("test://static/resource/") - && !request.Params.Uri.StartsWith("test://dynamic/resource/")) - { - throw new McpException($"Invalid resource URI: '{request.Params.Uri}'", McpErrorCode.InvalidParams); - } + Contents = [contents] + }; + }; - _subscribedResources.TryAdd(request.Params.Uri, true); + options.Handlers.SubscribeToResourcesHandler = async (request, cancellationToken) => + { + if (request?.Params?.Uri is null) + { + throw new McpException("Missing required argument 'uri'", McpErrorCode.InvalidParams); + } + if (!request.Params.Uri.StartsWith("test://static/resource/") + && !request.Params.Uri.StartsWith("test://dynamic/resource/")) + { + throw new McpException($"Invalid resource URI: '{request.Params.Uri}'", McpErrorCode.InvalidParams); + } - return new EmptyResult(); - }, + _subscribedResources.TryAdd(request.Params.Uri, true); - UnsubscribeFromResourcesHandler = async (request, cancellationToken) => - { - if (request?.Params?.Uri is null) - { - throw new McpException("Missing required argument 'uri'", McpErrorCode.InvalidParams); - } - if (!request.Params.Uri.StartsWith("test://static/resource/") - && !request.Params.Uri.StartsWith("test://dynamic/resource/")) - { - throw new McpException($"Invalid resource URI: '{request.Params.Uri}'", McpErrorCode.InvalidParams); - } + return new EmptyResult(); + }; - _subscribedResources.TryRemove(request.Params.Uri, out _); + options.Handlers.UnsubscribeFromResourcesHandler = async (request, cancellationToken) => + { + if (request?.Params?.Uri is null) + { + throw new McpException("Missing required argument 'uri'", McpErrorCode.InvalidParams); + } + if (!request.Params.Uri.StartsWith("test://static/resource/") + && !request.Params.Uri.StartsWith("test://dynamic/resource/")) + { + throw new McpException($"Invalid resource URI: '{request.Params.Uri}'", McpErrorCode.InvalidParams); + } - return new EmptyResult(); - }, + _subscribedResources.TryRemove(request.Params.Uri, out _); - Subscribe = true + return new EmptyResult(); }; } - private static CompletionsCapability ConfigureCompletions() + private static void ConfigureCompletions(McpServerOptions options) { List sampleResourceIds = ["1", "2", "3", "4", "5"]; Dictionary> exampleCompletions = new() @@ -491,7 +478,7 @@ private static CompletionsCapability ConfigureCompletions() {"temperature", ["0", "0.5", "0.7", "1.0"]}, }; - McpRequestHandler handler = async (request, cancellationToken) => + options.Handlers.CompleteHandler = async (request, cancellationToken) => { string[]? values; switch (request.Params?.Ref) @@ -517,8 +504,6 @@ private static CompletionsCapability ConfigureCompletions() throw new McpException($"Unknown reference type: '{request.Params?.Ref.Type}'", McpErrorCode.InvalidParams); } }; - - return new() { CompleteHandler = handler }; } static CreateMessageRequestParams CreateRequestSamplingParams(string context, string uri, int maxTokens = 100) diff --git a/tests/ModelContextProtocol.TestSseServer/Program.cs b/tests/ModelContextProtocol.TestSseServer/Program.cs index d4abf81f9..cf78c0896 100644 --- a/tests/ModelContextProtocol.TestSseServer/Program.cs +++ b/tests/ModelContextProtocol.TestSseServer/Program.cs @@ -95,284 +95,273 @@ static CreateMessageRequestParams CreateRequestSamplingParams(string context, st } const int pageSize = 10; - - options.Capabilities = new() + options.Handlers = new() { - Tools = new() + ListToolsHandler = async (request, cancellationToken) => { - ListToolsHandler = async (request, cancellationToken) => - { - return new ListToolsResult - { - Tools = - [ - new Tool - { - Name = "echo", - Description = "Echoes the input back to the client.", - InputSchema = JsonSerializer.Deserialize(""" - { - "type": "object", - "properties": { - "message": { - "type": "string", - "description": "The input to echo back." - } - }, - "required": ["message"] - } - """, McpJsonUtilities.DefaultOptions), - }, - new Tool - { - Name = "echoSessionId", - Description = "Echoes the session id back to the client.", - InputSchema = JsonSerializer.Deserialize(""" - { - "type": "object" - } - """, McpJsonUtilities.DefaultOptions), - }, - new Tool - { - Name = "sampleLLM", - Description = "Samples from an LLM using MCP's sampling feature.", - InputSchema = JsonSerializer.Deserialize(""" - { - "type": "object", - "properties": { - "prompt": { - "type": "string", - "description": "The prompt to send to the LLM" - }, - "maxTokens": { - "type": "number", - "description": "Maximum number of tokens to generate" - } - }, - "required": ["prompt", "maxTokens"] - } - """, McpJsonUtilities.DefaultOptions), - } - ] - }; - }, - CallToolHandler = async (request, cancellationToken) => + return new ListToolsResult { - if (request.Params is null) - { - throw new McpException("Missing required parameter 'name'", McpErrorCode.InvalidParams); - } - if (request.Params.Name == "echo") - { - if (request.Params.Arguments is null || !request.Params.Arguments.TryGetValue("message", out var message)) + Tools = + [ + new Tool { - throw new McpException("Missing required argument 'message'", McpErrorCode.InvalidParams); - } - return new CallToolResult - { - Content = [new TextContentBlock { Text = $"Echo: {message}" }] - }; - } - else if (request.Params.Name == "echoSessionId") - { - return new CallToolResult + Name = "echo", + Description = "Echoes the input back to the client.", + InputSchema = JsonSerializer.Deserialize(""" + { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "The input to echo back." + } + }, + "required": ["message"] + } + """, McpJsonUtilities.DefaultOptions), + }, + new Tool { - Content = [new TextContentBlock { Text = request.Server.SessionId ?? string.Empty }] - }; - } - else if (request.Params.Name == "sampleLLM") - { - if (request.Params.Arguments is null || - !request.Params.Arguments.TryGetValue("prompt", out var prompt) || - !request.Params.Arguments.TryGetValue("maxTokens", out var maxTokens)) + Name = "echoSessionId", + Description = "Echoes the session id back to the client.", + InputSchema = JsonSerializer.Deserialize(""" + { + "type": "object" + } + """, McpJsonUtilities.DefaultOptions), + }, + new Tool { - throw new McpException("Missing required arguments 'prompt' and 'maxTokens'", McpErrorCode.InvalidParams); + Name = "sampleLLM", + Description = "Samples from an LLM using MCP's sampling feature.", + InputSchema = JsonSerializer.Deserialize(""" + { + "type": "object", + "properties": { + "prompt": { + "type": "string", + "description": "The prompt to send to the LLM" + }, + "maxTokens": { + "type": "number", + "description": "Maximum number of tokens to generate" + } + }, + "required": ["prompt", "maxTokens"] + } + """, McpJsonUtilities.DefaultOptions), } - var sampleResult = await request.Server.SampleAsync(CreateRequestSamplingParams(prompt.ToString(), "sampleLLM", Convert.ToInt32(maxTokens.ToString())), - cancellationToken); - - return new CallToolResult - { - Content = [new TextContentBlock { Text = $"LLM sampling result: {(sampleResult.Content as TextContentBlock)?.Text}" }] - }; - } - else + ] + }; + }, + CallToolHandler = async (request, cancellationToken) => + { + if (request.Params is null) + { + throw new McpException("Missing required parameter 'name'", McpErrorCode.InvalidParams); + } + if (request.Params.Name == "echo") + { + if (request.Params.Arguments is null || !request.Params.Arguments.TryGetValue("message", out var message)) { - throw new McpException($"Unknown tool: '{request.Params.Name}'", McpErrorCode.InvalidParams); + throw new McpException("Missing required argument 'message'", McpErrorCode.InvalidParams); } + return new CallToolResult + { + Content = [new TextContentBlock { Text = $"Echo: {message}" }] + }; } - }, - Resources = new() - { - ListResourceTemplatesHandler = async (request, cancellationToken) => + else if (request.Params.Name == "echoSessionId") { - - return new ListResourceTemplatesResult + return new CallToolResult { - ResourceTemplates = [ - new ResourceTemplate - { - UriTemplate = "test://dynamic/resource/{id}", - Name = "Dynamic Resource", - } - ] + Content = [new TextContentBlock { Text = request.Server.SessionId ?? string.Empty }] }; - }, - - ListResourcesHandler = async (request, cancellationToken) => + } + else if (request.Params.Name == "sampleLLM") { - int startIndex = 0; - var requestParams = request.Params ?? new(); - if (requestParams.Cursor is not null) + if (request.Params.Arguments is null || + !request.Params.Arguments.TryGetValue("prompt", out var prompt) || + !request.Params.Arguments.TryGetValue("maxTokens", out var maxTokens)) { - try - { - var startIndexAsString = Encoding.UTF8.GetString(Convert.FromBase64String(requestParams.Cursor)); - startIndex = Convert.ToInt32(startIndexAsString); - } - catch (Exception e) - { - throw new McpException($"Invalid cursor: '{requestParams.Cursor}'", e, McpErrorCode.InvalidParams); - } + throw new McpException("Missing required arguments 'prompt' and 'maxTokens'", McpErrorCode.InvalidParams); } + var sampleResult = await request.Server.SampleAsync(CreateRequestSamplingParams(prompt.ToString(), "sampleLLM", Convert.ToInt32(maxTokens.ToString())), + cancellationToken); - int endIndex = Math.Min(startIndex + pageSize, resources.Count); - string? nextCursor = null; - - if (endIndex < resources.Count) + return new CallToolResult { - nextCursor = Convert.ToBase64String(Encoding.UTF8.GetBytes(endIndex.ToString())); - } + Content = [new TextContentBlock { Text = $"LLM sampling result: {(sampleResult.Content as TextContentBlock)?.Text}" }] + }; + } + else + { + throw new McpException($"Unknown tool: '{request.Params.Name}'", McpErrorCode.InvalidParams); + } + }, + ListResourceTemplatesHandler = async (request, cancellationToken) => + { - return new ListResourcesResult + return new ListResourceTemplatesResult + { + ResourceTemplates = [ + new ResourceTemplate { - NextCursor = nextCursor, - Resources = resources.GetRange(startIndex, endIndex - startIndex) - }; - }, - ReadResourceHandler = async (request, cancellationToken) => + UriTemplate = "test://dynamic/resource/{id}", + Name = "Dynamic Resource", + } + ] + }; + }, + ListResourcesHandler = async (request, cancellationToken) => + { + int startIndex = 0; + var requestParams = request.Params ?? new(); + if (requestParams.Cursor is not null) { - if (request.Params?.Uri is null) + try { - throw new McpException("Missing required argument 'uri'", McpErrorCode.InvalidParams); + var startIndexAsString = Encoding.UTF8.GetString(Convert.FromBase64String(requestParams.Cursor)); + startIndex = Convert.ToInt32(startIndexAsString); } - - if (request.Params.Uri.StartsWith("test://dynamic/resource/")) + catch (Exception e) { - var id = request.Params.Uri.Split('/').LastOrDefault(); - if (string.IsNullOrEmpty(id)) - { - throw new McpException($"Invalid resource URI: '{request.Params.Uri}'", McpErrorCode.InvalidParams); - } - - return new ReadResourceResult - { - Contents = [ - new TextResourceContents - { - Uri = request.Params.Uri, - MimeType = "text/plain", - Text = $"Dynamic resource {id}: This is a plaintext resource" - } - ] - }; + throw new McpException($"Invalid cursor: '{requestParams.Cursor}'", e, McpErrorCode.InvalidParams); } + } - ResourceContents? contents = resourceContents.FirstOrDefault(r => r.Uri == request.Params.Uri) ?? - throw new McpException($"Resource not found: '{request.Params.Uri}'", McpErrorCode.InvalidParams); + int endIndex = Math.Min(startIndex + pageSize, resources.Count); + string? nextCursor = null; - return new ReadResourceResult - { - Contents = [contents] - }; + if (endIndex < resources.Count) + { + nextCursor = Convert.ToBase64String(Encoding.UTF8.GetBytes(endIndex.ToString())); } + + return new ListResourcesResult + { + NextCursor = nextCursor, + Resources = resources.GetRange(startIndex, endIndex - startIndex) + }; }, - Prompts = new() + ReadResourceHandler = async (request, cancellationToken) => { - ListPromptsHandler = async (request, cancellationToken) => + if (request.Params?.Uri is null) { - return new ListPromptsResult + throw new McpException("Missing required argument 'uri'", McpErrorCode.InvalidParams); + } + + if (request.Params.Uri.StartsWith("test://dynamic/resource/")) + { + var id = request.Params.Uri.Split('/').LastOrDefault(); + if (string.IsNullOrEmpty(id)) { - Prompts = [ - new Prompt - { - Name = "simple_prompt", - Description = "A prompt without arguments" - }, - new Prompt - { - Name = "complex_prompt", - Description = "A prompt with arguments", - Arguments = - [ - new PromptArgument - { - Name = "temperature", - Description = "Temperature setting", - Required = true - }, - new PromptArgument + throw new McpException($"Invalid resource URI: '{request.Params.Uri}'", McpErrorCode.InvalidParams); + } + + return new ReadResourceResult + { + Contents = [ + new TextResourceContents { - Name = "style", - Description = "Output style", - Required = false + Uri = request.Params.Uri, + MimeType = "text/plain", + Text = $"Dynamic resource {id}: This is a plaintext resource" } - ], - } ] }; - }, - GetPromptHandler = async (request, cancellationToken) => + } + + ResourceContents? contents = resourceContents.FirstOrDefault(r => r.Uri == request.Params.Uri) ?? + throw new McpException($"Resource not found: '{request.Params.Uri}'", McpErrorCode.InvalidParams); + + return new ReadResourceResult { - if (request.Params is null) - { - throw new McpException("Missing required parameter 'name'", McpErrorCode.InvalidParams); - } - List messages = new(); - if (request.Params.Name == "simple_prompt") - { - messages.Add(new PromptMessage - { - Role = Role.User, - Content = new TextContentBlock { Text = "This is a simple prompt without arguments." }, - }); - } - else if (request.Params.Name == "complex_prompt") - { - string temperature = request.Params.Arguments?["temperature"].ToString() ?? "unknown"; - string style = request.Params.Arguments?["style"].ToString() ?? "unknown"; - messages.Add(new PromptMessage - { - Role = Role.User, - Content = new TextContentBlock { Text = $"This is a complex prompt with arguments: temperature={temperature}, style={style}" }, - }); - messages.Add(new PromptMessage + Contents = [contents] + }; + }, + ListPromptsHandler = async (request, cancellationToken) => + { + return new ListPromptsResult + { + Prompts = [ + new Prompt { - Role = Role.Assistant, - Content = new TextContentBlock { Text = "I understand. You've provided a complex prompt with temperature and style arguments. How would you like me to proceed?" }, - }); - messages.Add(new PromptMessage + Name = "simple_prompt", + Description = "A prompt without arguments" + }, + new Prompt { - Role = Role.User, - Content = new ImageContentBlock - { - Data = MCP_TINY_IMAGE, - MimeType = "image/png" - } - }); - } - else + Name = "complex_prompt", + Description = "A prompt with arguments", + Arguments = + [ + new PromptArgument + { + Name = "temperature", + Description = "Temperature setting", + Required = true + }, + new PromptArgument + { + Name = "style", + Description = "Output style", + Required = false + } + ], + } + ] + }; + }, + GetPromptHandler = async (request, cancellationToken) => + { + if (request.Params is null) + { + throw new McpException("Missing required parameter 'name'", McpErrorCode.InvalidParams); + } + List messages = new(); + if (request.Params.Name == "simple_prompt") + { + messages.Add(new PromptMessage { - throw new McpException($"Unknown prompt: {request.Params.Name}", McpErrorCode.InvalidParams); - } - - return new GetPromptResult + Role = Role.User, + Content = new TextContentBlock { Text = "This is a simple prompt without arguments." }, + }); + } + else if (request.Params.Name == "complex_prompt") + { + string temperature = request.Params.Arguments?["temperature"].ToString() ?? "unknown"; + string style = request.Params.Arguments?["style"].ToString() ?? "unknown"; + messages.Add(new PromptMessage { - Messages = messages - }; + Role = Role.User, + Content = new TextContentBlock { Text = $"This is a complex prompt with arguments: temperature={temperature}, style={style}" }, + }); + messages.Add(new PromptMessage + { + Role = Role.Assistant, + Content = new TextContentBlock { Text = "I understand. You've provided a complex prompt with temperature and style arguments. How would you like me to proceed?" }, + }); + messages.Add(new PromptMessage + { + Role = Role.User, + Content = new ImageContentBlock + { + Data = MCP_TINY_IMAGE, + MimeType = "image/png" + } + }); } - }, + else + { + throw new McpException($"Unknown prompt: {request.Params.Name}", McpErrorCode.InvalidParams); + } + + return new GetPromptResult + { + Messages = messages + }; + } }; } diff --git a/tests/ModelContextProtocol.Tests/Client/McpClientCreationTests.cs b/tests/ModelContextProtocol.Tests/Client/McpClientCreationTests.cs index 15127502e..0eb84262b 100644 --- a/tests/ModelContextProtocol.Tests/Client/McpClientCreationTests.cs +++ b/tests/ModelContextProtocol.Tests/Client/McpClientCreationTests.cs @@ -63,21 +63,20 @@ public async Task CreateAsync_WithCapabilitiesOptions(Type transportType) { Capabilities = new ClientCapabilities { - Sampling = new SamplingCapability - { - SamplingHandler = async (c, p, t) => - new CreateMessageResult - { - Content = new TextContentBlock { Text = "result" }, - Model = "test-model", - Role = Role.User, - StopReason = "endTurn" - }, - }, Roots = new RootsCapability { ListChanged = true, - RootsHandler = async (t, r) => new ListRootsResult { Roots = [] }, + } + }, + Handlers = new() + { + RootsHandler = async (t, r) => new ListRootsResult { Roots = [] }, + SamplingHandler = async (c, p, t) => new CreateMessageResult + { + Content = new TextContentBlock { Text = "result" }, + Model = "test-model", + Role = Role.User, + StopReason = "endTurn" } } }; diff --git a/tests/ModelContextProtocol.Tests/Client/McpClientTests.cs b/tests/ModelContextProtocol.Tests/Client/McpClientTests.cs index d7034660b..3f5b80ae7 100644 --- a/tests/ModelContextProtocol.Tests/Client/McpClientTests.cs +++ b/tests/ModelContextProtocol.Tests/Client/McpClientTests.cs @@ -331,7 +331,7 @@ public async Task WithProgress_ProgressReported() int remainingProgress = TotalNotifications; TaskCompletionSource allProgressReceived = new(TaskCreationOptions.RunContinuationsAsynchronously); - Server.ServerOptions.Capabilities?.Tools?.ToolCollection?.Add(McpServerTool.Create(async (IProgress progress) => + Server.ServerOptions.ToolCollection?.Add(McpServerTool.Create(async (IProgress progress) => { for (int i = 0; i < TotalNotifications; i++) { diff --git a/tests/ModelContextProtocol.Tests/ClientIntegrationTests.cs b/tests/ModelContextProtocol.Tests/ClientIntegrationTests.cs index e2f03805f..20c6f374b 100644 --- a/tests/ModelContextProtocol.Tests/ClientIntegrationTests.cs +++ b/tests/ModelContextProtocol.Tests/ClientIntegrationTests.cs @@ -276,7 +276,7 @@ public async Task SubscribeResource_Stdio() TaskCompletionSource tcs = new(); await using var client = await _fixture.CreateClientAsync(clientId, new() { - Capabilities = new() + Handlers = new() { NotificationHandlers = [ @@ -306,7 +306,7 @@ public async Task UnsubscribeResource_Stdio() TaskCompletionSource receivedNotification = new(); await using var client = await _fixture.CreateClientAsync(clientId, new() { - Capabilities = new() + Handlers = new() { NotificationHandlers = [ @@ -374,22 +374,19 @@ public async Task Sampling_Stdio(string clientId) int samplingHandlerCalls = 0; await using var client = await _fixture.CreateClientAsync(clientId, new() { - Capabilities = new() + Handlers = new() { - Sampling = new() + SamplingHandler = async (_, _, _) => { - SamplingHandler = async (_, _, _) => + samplingHandlerCalls++; + return new CreateMessageResult { - samplingHandlerCalls++; - return new CreateMessageResult - { - Model = "test-model", - Role = Role.Assistant, - Content = new TextContentBlock { Text = "Test response" }, - }; - }, - }, - }, + Model = "test-model", + Role = Role.Assistant, + Content = new TextContentBlock { Text = "Test response" }, + }; + } + } }); // Call the server's sampleLLM tool which should trigger our sampling handler @@ -477,8 +474,8 @@ public async Task CallTool_Stdio_MemoryServer() await using var client = await McpClient.CreateAsync( new StdioClientTransport(stdioOptions), - clientOptions, - loggerFactory: LoggerFactory, + clientOptions, + loggerFactory: LoggerFactory, cancellationToken: TestContext.Current.CancellationToken); // act @@ -533,13 +530,10 @@ public async Task SamplingViaChatClient_RequestResponseProperlyPropagated() .CreateSamplingHandler(); await using var client = await McpClient.CreateAsync(new StdioClientTransport(_fixture.EverythingServerTransportOptions), new() { - Capabilities = new() + Handlers = new() { - Sampling = new() - { - SamplingHandler = samplingHandler, - }, - }, + SamplingHandler = samplingHandler + } }, cancellationToken: TestContext.Current.CancellationToken); var result = await client.CallToolAsync("sampleLLM", new Dictionary() @@ -561,7 +555,7 @@ public async Task SetLoggingLevel_ReceivesLoggingMessages(string clientId) TaskCompletionSource receivedNotification = new(); await using var client = await _fixture.CreateClientAsync(clientId, new() { - Capabilities = new() + Handlers = new() { NotificationHandlers = [ diff --git a/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsPromptsTests.cs b/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsPromptsTests.cs index 1aea56193..18db1f14b 100644 --- a/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsPromptsTests.cs +++ b/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsPromptsTests.cs @@ -90,7 +90,7 @@ protected override void ConfigureServices(ServiceCollection services, IMcpServer public void Adds_Prompts_To_Server() { var serverOptions = ServiceProvider.GetRequiredService>().Value; - var prompts = serverOptions?.Capabilities?.Prompts?.PromptCollection; + var prompts = serverOptions.PromptCollection; Assert.NotNull(prompts); Assert.NotEmpty(prompts); } @@ -137,7 +137,7 @@ public async Task Can_Be_Notified_Of_Prompt_Changes() Assert.False(notificationRead.IsCompleted); var serverOptions = ServiceProvider.GetRequiredService>().Value; - var serverPrompts = serverOptions.Capabilities?.Prompts?.PromptCollection; + var serverPrompts = serverOptions.PromptCollection; Assert.NotNull(serverPrompts); var newPrompt = McpServerPrompt.Create([McpServerPrompt(Name = "NewPrompt")] () => "42"); diff --git a/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsResourcesTests.cs b/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsResourcesTests.cs index 47f2b224f..939904cb7 100644 --- a/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsResourcesTests.cs +++ b/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsResourcesTests.cs @@ -118,7 +118,7 @@ protected override void ConfigureServices(ServiceCollection services, IMcpServer public void Adds_Resources_To_Server() { var serverOptions = ServiceProvider.GetRequiredService>().Value; - var resources = serverOptions?.Capabilities?.Resources?.ResourceCollection; + var resources = serverOptions.ResourceCollection; Assert.NotNull(resources); Assert.NotEmpty(resources); } @@ -172,7 +172,7 @@ public async Task Can_Be_Notified_Of_Resource_Changes() Assert.False(notificationRead.IsCompleted); var serverOptions = ServiceProvider.GetRequiredService>().Value; - var serverResources = serverOptions.Capabilities?.Resources?.ResourceCollection; + var serverResources = serverOptions.ResourceCollection; Assert.NotNull(serverResources); var newResource = McpServerResource.Create([McpServerResource(Name = "NewResource")] () => "42"); diff --git a/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsToolsTests.cs b/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsToolsTests.cs index a581a81df..cf2dfd0f7 100644 --- a/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsToolsTests.cs +++ b/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsToolsTests.cs @@ -119,7 +119,7 @@ protected override void ConfigureServices(ServiceCollection services, IMcpServer public void Adds_Tools_To_Server() { var serverOptions = ServiceProvider.GetRequiredService>().Value; - var tools = serverOptions.Capabilities?.Tools?.ToolCollection; + var tools = serverOptions.ToolCollection; Assert.NotNull(tools); Assert.NotEmpty(tools); } @@ -201,7 +201,7 @@ public async Task Can_Be_Notified_Of_Tool_Changes() Assert.False(notificationRead.IsCompleted); var serverOptions = ServiceProvider.GetRequiredService>().Value; - var serverTools = serverOptions.Capabilities?.Tools?.ToolCollection; + var serverTools = serverOptions.ToolCollection; Assert.NotNull(serverTools); var newTool = McpServerTool.Create([McpServerTool(Name = "NewTool")] () => "42"); diff --git a/tests/ModelContextProtocol.Tests/Configuration/McpServerOptionsSetupTests.cs b/tests/ModelContextProtocol.Tests/Configuration/McpServerOptionsSetupTests.cs new file mode 100644 index 000000000..151111db2 --- /dev/null +++ b/tests/ModelContextProtocol.Tests/Configuration/McpServerOptionsSetupTests.cs @@ -0,0 +1,194 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using ModelContextProtocol.Protocol; +using ModelContextProtocol.Server; + +namespace ModelContextProtocol.Tests.Configuration; + +public class McpServerOptionsSetupTests +{ + #region Prompt Handler Tests + [Fact] + public void Configure_WithListPromptsHandler_CreatesPromptsCapability() + { + var services = new ServiceCollection(); + services.AddMcpServer() + .WithListPromptsHandler(async (request, ct) => new ListPromptsResult()); + + var options = services.BuildServiceProvider().GetRequiredService>().Value; + + Assert.NotNull(options.Handlers.ListPromptsHandler); + Assert.NotNull(options.Capabilities?.Prompts); + } + + [Fact] + public void Configure_WithGetPromptHandler_CreatesPromptsCapability() + { + var services = new ServiceCollection(); + services.AddMcpServer() + .WithGetPromptHandler(async (request, ct) => new GetPromptResult()); + + var options = services.BuildServiceProvider().GetRequiredService>().Value; + + Assert.NotNull(options.Handlers.GetPromptHandler); + Assert.NotNull(options.Capabilities?.Prompts); + } + #endregion + + #region Resource Handler Tests + [Fact] + public void Configure_WithListResourceTemplatesHandler_CreatesResourcesCapability() + { + var services = new ServiceCollection(); + services.AddMcpServer() + .WithListResourceTemplatesHandler(async (request, ct) => new ListResourceTemplatesResult()); + + var options = services.BuildServiceProvider().GetRequiredService>().Value; + + Assert.NotNull(options.Handlers.ListResourceTemplatesHandler); + Assert.NotNull(options.Capabilities?.Resources); + } + + [Fact] + public void Configure_WithListResourcesHandler_CreatesResourcesCapability() + { + var services = new ServiceCollection(); + services.AddMcpServer() + .WithListResourcesHandler(async (request, ct) => new ListResourcesResult()); + + var options = services.BuildServiceProvider().GetRequiredService>().Value; + + Assert.NotNull(options.Handlers.ListResourcesHandler); + Assert.NotNull(options.Capabilities?.Resources); + } + + [Fact] + public void Configure_WithReadResourceHandler_CreatesResourcesCapability() + { + var services = new ServiceCollection(); + services.AddMcpServer() + .WithReadResourceHandler(async (request, ct) => new ReadResourceResult()); + + var options = services.BuildServiceProvider().GetRequiredService>().Value; + + Assert.NotNull(options.Handlers.ReadResourceHandler); + Assert.NotNull(options.Capabilities?.Resources); + } + + [Fact] + public void Configure_WithSubscribeToResourcesHandler_And_WithOtherResourcesHandler_EnablesSubscription() + { + var services = new ServiceCollection(); + services.AddMcpServer() + .WithListResourcesHandler(async (request, ct) => new ListResourcesResult()) + .WithSubscribeToResourcesHandler(async (request, ct) => new EmptyResult()); + + var options = services.BuildServiceProvider().GetRequiredService>().Value; + + Assert.NotNull(options.Handlers.ListResourcesHandler); + Assert.NotNull(options.Handlers.SubscribeToResourcesHandler); + Assert.NotNull(options.Capabilities?.Resources); + Assert.True(options.Capabilities.Resources.Subscribe); + } + + [Fact] + public void Configure_WithUnsubscribeFromResourcesHandler_And_WithOtherResourcesHandler_EnablesSubscription() + { + var services = new ServiceCollection(); + services.AddMcpServer() + .WithListResourcesHandler(async (request, ct) => new ListResourcesResult()) + .WithUnsubscribeFromResourcesHandler(async (request, ct) => new EmptyResult()); + + var options = services.BuildServiceProvider().GetRequiredService>().Value; + + Assert.NotNull(options.Handlers.ListResourcesHandler); + Assert.NotNull(options.Handlers.UnsubscribeFromResourcesHandler); + Assert.NotNull(options.Capabilities?.Resources); + Assert.True(options.Capabilities.Resources.Subscribe); + } + + [Fact] + public void Configure_WithSubscribeToResourcesHandler_WithoutOtherResourcesHandler_DoesNotCreateResourcesCapability() + { + var services = new ServiceCollection(); + services.AddMcpServer() + .WithSubscribeToResourcesHandler(async (request, ct) => new EmptyResult()); + + var options = services.BuildServiceProvider().GetRequiredService>().Value; + + Assert.Null(options.Handlers.SubscribeToResourcesHandler); + Assert.Null(options.Capabilities?.Resources); + } + + [Fact] + public void Configure_WithUnsubscribeFromResourcesHandler_WithoutOtherResourcesHandler_DoesNotCreateResourcesCapability() + { + var services = new ServiceCollection(); + services.AddMcpServer() + .WithUnsubscribeFromResourcesHandler(async (request, ct) => new EmptyResult()); + + var options = services.BuildServiceProvider().GetRequiredService>().Value; + + Assert.Null(options.Handlers.UnsubscribeFromResourcesHandler); + Assert.Null(options.Capabilities?.Resources); + } + #endregion + + #region Tool Handler Tests + [Fact] + public void Configure_WithListToolsHandler_CreatesToolsCapability() + { + var services = new ServiceCollection(); + services.AddMcpServer() + .WithListToolsHandler(async (request, ct) => new ListToolsResult()); + + var options = services.BuildServiceProvider().GetRequiredService>().Value; + + Assert.NotNull(options.Handlers.ListToolsHandler); + Assert.NotNull(options.Capabilities?.Tools); + } + + [Fact] + public void Configure_WithCallToolHandler_CreatesToolsCapability() + { + var services = new ServiceCollection(); + services.AddMcpServer() + .WithCallToolHandler(async (request, ct) => new CallToolResult()); + + var options = services.BuildServiceProvider().GetRequiredService>().Value; + + Assert.NotNull(options.Handlers.CallToolHandler); + Assert.NotNull(options.Capabilities?.Tools); + } + #endregion + + #region Logging Handler Tests + [Fact] + public void Configure_WithSetLoggingLevelHandler_CreatesLoggingCapability() + { + var services = new ServiceCollection(); + services.AddMcpServer() + .WithSetLoggingLevelHandler(async (request, ct) => new EmptyResult()); + + var options = services.BuildServiceProvider().GetRequiredService>().Value; + + Assert.NotNull(options.Handlers.SetLoggingLevelHandler); + Assert.NotNull(options.Capabilities?.Logging); + } + #endregion + + #region Completion Handler Tests + [Fact] + public void Configure_WithCompleteHandler_CreatesCompletionsCapability() + { + var services = new ServiceCollection(); + services.AddMcpServer() + .WithCompleteHandler(async (request, ct) => new CompleteResult()); + + var options = services.BuildServiceProvider().GetRequiredService>().Value; + + Assert.NotNull(options.Handlers.CompleteHandler); + Assert.NotNull(options.Capabilities?.Completions); + } + #endregion +} \ No newline at end of file diff --git a/tests/ModelContextProtocol.Tests/DiagnosticTests.cs b/tests/ModelContextProtocol.Tests/DiagnosticTests.cs index 5ad30d282..8996b9962 100644 --- a/tests/ModelContextProtocol.Tests/DiagnosticTests.cs +++ b/tests/ModelContextProtocol.Tests/DiagnosticTests.cs @@ -139,16 +139,10 @@ private static async Task RunConnected(Func action, await using (McpServer server = McpServer.Create(serverTransport, new() { - Capabilities = new() - { - Tools = new() - { - ToolCollection = [ - McpServerTool.Create((int amount) => amount * 2, new() { Name = "DoubleValue", Description = "Doubles the value." }), - McpServerTool.Create(() => { throw new Exception("boom"); }, new() { Name = "Throw", Description = "Throws error." }), - ], - } - } + ToolCollection = [ + McpServerTool.Create((int amount) => amount * 2, new() { Name = "DoubleValue", Description = "Doubles the value." }), + McpServerTool.Create(() => { throw new Exception("boom"); }, new() { Name = "Throw", Description = "Throws error." }), + ] })) { serverTask = server.RunAsync(TestContext.Current.CancellationToken); diff --git a/tests/ModelContextProtocol.Tests/DockerEverythingServerTests.cs b/tests/ModelContextProtocol.Tests/DockerEverythingServerTests.cs index 842371f88..2d5ef5f2d 100644 --- a/tests/ModelContextProtocol.Tests/DockerEverythingServerTests.cs +++ b/tests/ModelContextProtocol.Tests/DockerEverythingServerTests.cs @@ -70,24 +70,21 @@ public async Task Sampling_Sse_EverythingServer() }; int samplingHandlerCalls = 0; - var defaultOptions = new McpClientOptions + var defaultOptions = new McpClientOptions() { - Capabilities = new() + Handlers = new() { - Sampling = new() + SamplingHandler = async (_, _, _) => { - SamplingHandler = async (_, _, _) => + samplingHandlerCalls++; + return new CreateMessageResult { - samplingHandlerCalls++; - return new CreateMessageResult - { - Model = "test-model", - Role = Role.Assistant, - Content = new TextContentBlock { Text = "Test response" }, - }; - }, - }, - }, + Model = "test-model", + Role = Role.Assistant, + Content = new TextContentBlock { Text = "Test response" }, + }; + } + } }; await using var client = await McpClient.CreateAsync( diff --git a/tests/ModelContextProtocol.Tests/Protocol/ElicitationTests.cs b/tests/ModelContextProtocol.Tests/Protocol/ElicitationTests.cs index 22fd69c17..f3ae33ed5 100644 --- a/tests/ModelContextProtocol.Tests/Protocol/ElicitationTests.cs +++ b/tests/ModelContextProtocol.Tests/Protocol/ElicitationTests.cs @@ -69,75 +69,72 @@ public async Task Can_Elicit_Information() { await using McpClient client = await CreateMcpClientForServer(new McpClientOptions { - Capabilities = new() + Handlers = new McpClientHandlers() { - Elicitation = new() + ElicitationHandler = async (request, cancellationtoken) => { - ElicitationHandler = async (request, cancellationtoken) => - { - Assert.NotNull(request); - Assert.Equal("Please provide more information.", request.Message); - Assert.Equal(4, request.RequestedSchema.Properties.Count); + Assert.NotNull(request); + Assert.Equal("Please provide more information.", request.Message); + Assert.Equal(4, request.RequestedSchema.Properties.Count); - foreach (var entry in request.RequestedSchema.Properties) + foreach (var entry in request.RequestedSchema.Properties) + { + switch (entry.Key) { - switch (entry.Key) - { - case "prop1": - var primitiveString = Assert.IsType(entry.Value); - Assert.Equal("title1", primitiveString.Title); - Assert.Equal(1, primitiveString.MinLength); - Assert.Equal(100, primitiveString.MaxLength); - break; + case "prop1": + var primitiveString = Assert.IsType(entry.Value); + Assert.Equal("title1", primitiveString.Title); + Assert.Equal(1, primitiveString.MinLength); + Assert.Equal(100, primitiveString.MaxLength); + break; - case "prop2": - var primitiveNumber = Assert.IsType(entry.Value); - Assert.Equal("description2", primitiveNumber.Description); - Assert.Equal(0, primitiveNumber.Minimum); - Assert.Equal(1000, primitiveNumber.Maximum); - break; + case "prop2": + var primitiveNumber = Assert.IsType(entry.Value); + Assert.Equal("description2", primitiveNumber.Description); + Assert.Equal(0, primitiveNumber.Minimum); + Assert.Equal(1000, primitiveNumber.Maximum); + break; - case "prop3": - var primitiveBool = Assert.IsType(entry.Value); - Assert.Equal("title3", primitiveBool.Title); - Assert.Equal("description4", primitiveBool.Description); - Assert.True(primitiveBool.Default); - break; + case "prop3": + var primitiveBool = Assert.IsType(entry.Value); + Assert.Equal("title3", primitiveBool.Title); + Assert.Equal("description4", primitiveBool.Description); + Assert.True(primitiveBool.Default); + break; - case "prop4": - var primitiveEnum = Assert.IsType(entry.Value); - Assert.Equal(["option1", "option2", "option3"], primitiveEnum.Enum); - Assert.Equal(["Name1", "Name2", "Name3"], primitiveEnum.EnumNames); - break; + case "prop4": + var primitiveEnum = Assert.IsType(entry.Value); + Assert.Equal(["option1", "option2", "option3"], primitiveEnum.Enum); + Assert.Equal(["Name1", "Name2", "Name3"], primitiveEnum.EnumNames); + break; - default: - Assert.Fail($"Unknown property: {entry.Key}"); - break; - } + default: + Assert.Fail($"Unknown property: {entry.Key}"); + break; } + } - return new ElicitResult + return new ElicitResult + { + Action = "accept", + Content = new Dictionary { - Action = "accept", - Content = new Dictionary - { - ["prop1"] = (JsonElement)JsonSerializer.Deserialize(""" - "string result" - """, McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonElement)))!, - ["prop2"] = (JsonElement)JsonSerializer.Deserialize(""" - 42 - """, McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonElement)))!, - ["prop3"] = (JsonElement)JsonSerializer.Deserialize(""" - true - """, McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonElement)))!, - ["prop4"] = (JsonElement)JsonSerializer.Deserialize(""" - "option2" - """, McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonElement)))!, - }, - }; - }, - }, - }, + ["prop1"] = (JsonElement)JsonSerializer.Deserialize(""" + "string result" + """, McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonElement)))!, + ["prop2"] = (JsonElement)JsonSerializer.Deserialize(""" + 42 + """, McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonElement)))!, + ["prop3"] = (JsonElement)JsonSerializer.Deserialize(""" + true + """, McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonElement)))!, + ["prop4"] = (JsonElement)JsonSerializer.Deserialize(""" + "option2" + """, McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonElement)))!, + }, + }; + } + } }); var result = await client.CallToolAsync("TestElicitation", cancellationToken: TestContext.Current.CancellationToken); diff --git a/tests/ModelContextProtocol.Tests/Protocol/ElicitationTypedTests.cs b/tests/ModelContextProtocol.Tests/Protocol/ElicitationTypedTests.cs index 7ac39d591..47da166ca 100644 --- a/tests/ModelContextProtocol.Tests/Protocol/ElicitationTypedTests.cs +++ b/tests/ModelContextProtocol.Tests/Protocol/ElicitationTypedTests.cs @@ -103,90 +103,87 @@ public async Task Can_Elicit_Typed_Information() { await using McpClient client = await CreateMcpClientForServer(new McpClientOptions { - Capabilities = new() + Handlers = new() { - Elicitation = new() + ElicitationHandler = async (request, cancellationToken) => { - ElicitationHandler = async (request, cancellationToken) => - { - Assert.NotNull(request); - Assert.Equal("Please provide more information.", request.Message); + Assert.NotNull(request); + Assert.Equal("Please provide more information.", request.Message); - Assert.Equal(6, request.RequestedSchema.Properties.Count); + Assert.Equal(6, request.RequestedSchema.Properties.Count); - foreach (var entry in request.RequestedSchema.Properties) + foreach (var entry in request.RequestedSchema.Properties) + { + var key = entry.Key; + var value = entry.Value; + switch (key) { - var key = entry.Key; - var value = entry.Value; - switch (key) - { - case nameof(SampleForm.Name): - var stringSchema = Assert.IsType(value); - Assert.Equal("string", stringSchema.Type); - break; - - case nameof(SampleForm.Age): - var intSchema = Assert.IsType(value); - Assert.Equal("integer", intSchema.Type); - break; - - case nameof(SampleForm.Active): - var boolSchema = Assert.IsType(value); - Assert.Equal("boolean", boolSchema.Type); - break; - - case nameof(SampleForm.Role): - var enumSchema = Assert.IsType(value); - Assert.Equal("string", enumSchema.Type); - Assert.Equal([nameof(SampleRole.User), nameof(SampleRole.Admin)], enumSchema.Enum); - break; - - case nameof(SampleForm.Score): - var numSchema = Assert.IsType(value); - Assert.Equal("number", numSchema.Type); - break; - - case nameof(SampleForm.Created): - var dateTimeSchema = Assert.IsType(value); - Assert.Equal("string", dateTimeSchema.Type); - Assert.Equal("date-time", dateTimeSchema.Format); - - break; - - default: - Assert.Fail($"Unexpected property in schema: {key}"); - break; - } + case nameof(SampleForm.Name): + var stringSchema = Assert.IsType(value); + Assert.Equal("string", stringSchema.Type); + break; + + case nameof(SampleForm.Age): + var intSchema = Assert.IsType(value); + Assert.Equal("integer", intSchema.Type); + break; + + case nameof(SampleForm.Active): + var boolSchema = Assert.IsType(value); + Assert.Equal("boolean", boolSchema.Type); + break; + + case nameof(SampleForm.Role): + var enumSchema = Assert.IsType(value); + Assert.Equal("string", enumSchema.Type); + Assert.Equal([nameof(SampleRole.User), nameof(SampleRole.Admin)], enumSchema.Enum); + break; + + case nameof(SampleForm.Score): + var numSchema = Assert.IsType(value); + Assert.Equal("number", numSchema.Type); + break; + + case nameof(SampleForm.Created): + var dateTimeSchema = Assert.IsType(value); + Assert.Equal("string", dateTimeSchema.Type); + Assert.Equal("date-time", dateTimeSchema.Format); + + break; + + default: + Assert.Fail($"Unexpected property in schema: {key}"); + break; } + } - return new ElicitResult + return new ElicitResult + { + Action = "accept", + Content = new Dictionary { - Action = "accept", - Content = new Dictionary - { - [nameof(SampleForm.Name)] = (JsonElement)JsonSerializer.Deserialize(""" - "Alice" - """, McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonElement)))!, - [nameof(SampleForm.Age)] = (JsonElement)JsonSerializer.Deserialize(""" - 30 - """, McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonElement)))!, - [nameof(SampleForm.Active)] = (JsonElement)JsonSerializer.Deserialize(""" - true - """, McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonElement)))!, - [nameof(SampleForm.Role)] = (JsonElement)JsonSerializer.Deserialize(""" - "Admin" - """, McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonElement)))!, - [nameof(SampleForm.Score)] = (JsonElement)JsonSerializer.Deserialize(""" - 99.5 - """, McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonElement)))!, - [nameof(SampleForm.Created)] = (JsonElement)JsonSerializer.Deserialize(""" - "2023-08-27T03:05:00" - """, McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonElement)))!, - }, - }; - }, + [nameof(SampleForm.Name)] = (JsonElement)JsonSerializer.Deserialize(""" + "Alice" + """, McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonElement)))!, + [nameof(SampleForm.Age)] = (JsonElement)JsonSerializer.Deserialize(""" + 30 + """, McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonElement)))!, + [nameof(SampleForm.Active)] = (JsonElement)JsonSerializer.Deserialize(""" + true + """, McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonElement)))!, + [nameof(SampleForm.Role)] = (JsonElement)JsonSerializer.Deserialize(""" + "Admin" + """, McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonElement)))!, + [nameof(SampleForm.Score)] = (JsonElement)JsonSerializer.Deserialize(""" + 99.5 + """, McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonElement)))!, + [nameof(SampleForm.Created)] = (JsonElement)JsonSerializer.Deserialize(""" + "2023-08-27T03:05:00" + """, McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonElement)))!, + }, + }; }, - }, + } }); var result = await client.CallToolAsync("TestElicitationTyped", cancellationToken: TestContext.Current.CancellationToken); @@ -199,37 +196,34 @@ public async Task Elicit_Typed_Respects_NamingPolicy() { await using McpClient client = await CreateMcpClientForServer(new McpClientOptions { - Capabilities = new() + Handlers = new() { - Elicitation = new() + ElicitationHandler = async (request, cancellationToken) => { - ElicitationHandler = async (request, cancellationToken) => - { - Assert.NotNull(request); - Assert.Equal("Please provide more information.", request.Message); + Assert.NotNull(request); + Assert.Equal("Please provide more information.", request.Message); - // Expect camelCase names based on serializer options - Assert.Contains("firstName", request.RequestedSchema.Properties.Keys); - Assert.Contains("zipCode", request.RequestedSchema.Properties.Keys); - Assert.Contains("isAdmin", request.RequestedSchema.Properties.Keys); + // Expect camelCase names based on serializer options + Assert.Contains("firstName", request.RequestedSchema.Properties.Keys); + Assert.Contains("zipCode", request.RequestedSchema.Properties.Keys); + Assert.Contains("isAdmin", request.RequestedSchema.Properties.Keys); - return new ElicitResult + return new ElicitResult + { + Action = "accept", + Content = new Dictionary { - Action = "accept", - Content = new Dictionary - { - ["firstName"] = (JsonElement)JsonSerializer.Deserialize(""" - "Bob" - """, McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonElement)))!, - ["zipCode"] = (JsonElement)JsonSerializer.Deserialize(""" - 90210 - """, McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonElement)))!, - ["isAdmin"] = (JsonElement)JsonSerializer.Deserialize(""" - false - """, McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonElement)))!, - }, - }; - }, + ["firstName"] = (JsonElement)JsonSerializer.Deserialize(""" + "Bob" + """, McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonElement)))!, + ["zipCode"] = (JsonElement)JsonSerializer.Deserialize(""" + 90210 + """, McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonElement)))!, + ["isAdmin"] = (JsonElement)JsonSerializer.Deserialize(""" + false + """, McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonElement)))!, + }, + }; }, }, }); @@ -243,16 +237,13 @@ public async Task Elicit_Typed_With_Unsupported_Property_Type_Throws() { await using McpClient client = await CreateMcpClientForServer(new McpClientOptions { - Capabilities = new() + Handlers = new() { - Elicitation = new() + // Handler should never be invoked because the exception occurs before the request is sent. + ElicitationHandler = async (req, ct) => { - // Handler should never be invoked because the exception occurs before the request is sent. - ElicitationHandler = async (req, ct) => - { - Assert.Fail("Elicitation handler should not be called for unsupported schema test."); - return new ElicitResult { Action = "cancel" }; - }, + Assert.Fail("Elicitation handler should not be called for unsupported schema test."); + return new ElicitResult { Action = "cancel" }; }, }, }); @@ -268,18 +259,15 @@ public async Task Elicit_Typed_With_Nullable_Property_Type_Throws() { await using McpClient client = await CreateMcpClientForServer(new McpClientOptions { - Capabilities = new() + Handlers = new() { - Elicitation = new() + // Handler should never be invoked because the exception occurs before the request is sent. + ElicitationHandler = async (req, ct) => { - // Handler should never be invoked because the exception occurs before the request is sent. - ElicitationHandler = async (req, ct) => - { - Assert.Fail("Elicitation handler should not be called for unsupported schema test."); - return new ElicitResult { Action = "cancel" }; - }, + Assert.Fail("Elicitation handler should not be called for unsupported schema test."); + return new ElicitResult { Action = "cancel" }; }, - }, + } }); var ex = await Assert.ThrowsAsync(async () => @@ -291,18 +279,15 @@ public async Task Elicit_Typed_With_NonObject_Generic_Type_Throws() { await using McpClient client = await CreateMcpClientForServer(new McpClientOptions { - Capabilities = new() + Handlers = new() { - Elicitation = new() + // Should not be invoked + ElicitationHandler = async (req, ct) => { - // Should not be invoked - ElicitationHandler = async (req, ct) => - { - Assert.Fail("Elicitation handler should not be called for non-object generic type test."); - return new ElicitResult { Action = "cancel" }; - }, + Assert.Fail("Elicitation handler should not be called for non-object generic type test."); + return new ElicitResult { Action = "cancel" }; }, - }, + } }); var ex = await Assert.ThrowsAsync(async () => diff --git a/tests/ModelContextProtocol.Tests/Server/McpServerLoggingLevelTests.cs b/tests/ModelContextProtocol.Tests/Server/McpServerLoggingLevelTests.cs index be271a686..10116e70e 100644 --- a/tests/ModelContextProtocol.Tests/Server/McpServerLoggingLevelTests.cs +++ b/tests/ModelContextProtocol.Tests/Server/McpServerLoggingLevelTests.cs @@ -42,7 +42,7 @@ public void AddingLoggingLevelHandlerSetsLoggingCapability() var server = provider.GetRequiredService(); Assert.NotNull(server.ServerOptions.Capabilities?.Logging); - Assert.NotNull(server.ServerOptions.Capabilities.Logging.SetLoggingLevelHandler); + Assert.NotNull(server.ServerOptions.Handlers.SetLoggingLevelHandler); } [Fact] diff --git a/tests/ModelContextProtocol.Tests/Server/McpServerTests.cs b/tests/ModelContextProtocol.Tests/Server/McpServerTests.cs index 736d63ec4..40461d415 100644 --- a/tests/ModelContextProtocol.Tests/Server/McpServerTests.cs +++ b/tests/ModelContextProtocol.Tests/Server/McpServerTests.cs @@ -263,24 +263,24 @@ await Can_Handle_Requests( public async Task Can_Handle_Completion_Requests() { await Can_Handle_Requests( - new() + new ServerCapabilities { Completions = new() - { - CompleteHandler = async (request, ct) => - new CompleteResult + }, + method: RequestMethods.CompletionComplete, + configureOptions: options => + { + options.Handlers.CompleteHandler = async (request, ct) => + new CompleteResult + { + Completion = new() { - Completion = new() - { - Values = ["test"], - Total = 2, - HasMore = true - } + Values = ["test"], + Total = 2, + HasMore = true } - } + }; }, - method: RequestMethods.CompletionComplete, - configureOptions: null, assertResult: (_, response) => { var result = JsonSerializer.Deserialize(response, McpJsonUtilities.DefaultOptions); @@ -298,26 +298,26 @@ await Can_Handle_Requests( new ServerCapabilities { Resources = new() + }, + RequestMethods.ResourcesTemplatesList, + configureOptions: options => + { + options.Handlers.ListResourceTemplatesHandler = async (request, ct) => { - ListResourceTemplatesHandler = async (request, ct) => + return new ListResourceTemplatesResult { - return new ListResourceTemplatesResult - { - ResourceTemplates = [new() { UriTemplate = "test", Name = "Test Resource" }] - }; - }, - ListResourcesHandler = async (request, ct) => + ResourceTemplates = [new() { UriTemplate = "test", Name = "Test Resource" }] + }; + }; + options.Handlers.ListResourcesHandler = async (request, ct) => + { + return new ListResourcesResult { - return new ListResourcesResult - { - Resources = [new() { Uri = "test", Name = "Test Resource" }] - }; - }, - ReadResourceHandler = (request, ct) => throw new NotImplementedException(), - } + Resources = [new() { Uri = "test", Name = "Test Resource" }] + }; + }; + options.Handlers.ReadResourceHandler = (request, ct) => throw new NotImplementedException(); }, - RequestMethods.ResourcesTemplatesList, - configureOptions: null, assertResult: (_, response) => { var result = JsonSerializer.Deserialize(response, McpJsonUtilities.DefaultOptions); @@ -334,19 +334,19 @@ await Can_Handle_Requests( new ServerCapabilities { Resources = new() + }, + RequestMethods.ResourcesList, + configureOptions: options => + { + options.Handlers.ListResourcesHandler = async (request, ct) => { - ListResourcesHandler = async (request, ct) => + return new ListResourcesResult { - return new ListResourcesResult - { - Resources = [new() { Uri = "test", Name = "Test Resource" }] - }; - }, - ReadResourceHandler = (request, ct) => throw new NotImplementedException(), - } + Resources = [new() { Uri = "test", Name = "Test Resource" }] + }; + }; + options.Handlers.ReadResourceHandler = (request, ct) => throw new NotImplementedException(); }, - RequestMethods.ResourcesList, - configureOptions: null, assertResult: (_, response) => { var result = JsonSerializer.Deserialize(response, McpJsonUtilities.DefaultOptions); @@ -369,19 +369,19 @@ await Can_Handle_Requests( new ServerCapabilities { Resources = new() - { - ReadResourceHandler = async (request, ct) => - { - return new ReadResourceResult - { - Contents = [new TextResourceContents { Text = "test" }] - }; - }, - ListResourcesHandler = (request, ct) => throw new NotImplementedException(), - } }, method: RequestMethods.ResourcesRead, - configureOptions: null, + configureOptions: options => + { + options.Handlers.ReadResourceHandler = async (request, ct) => + { + return new ReadResourceResult + { + Contents = [new TextResourceContents { Text = "test" }] + }; + }; + options.Handlers.ListResourcesHandler = (request, ct) => throw new NotImplementedException(); + }, assertResult: (_, response) => { var result = JsonSerializer.Deserialize(response, McpJsonUtilities.DefaultOptions); @@ -406,19 +406,19 @@ await Can_Handle_Requests( new ServerCapabilities { Prompts = new() + }, + method: RequestMethods.PromptsList, + configureOptions: options => + { + options.Handlers.ListPromptsHandler = async (request, ct) => { - ListPromptsHandler = async (request, ct) => + return new ListPromptsResult { - return new ListPromptsResult - { - Prompts = [new() { Name = "test" }] - }; - }, - GetPromptHandler = (request, ct) => throw new NotImplementedException(), - }, + Prompts = [new() { Name = "test" }] + }; + }; + options.Handlers.GetPromptHandler = (request, ct) => throw new NotImplementedException(); }, - method: RequestMethods.PromptsList, - configureOptions: null, assertResult: (_, response) => { var result = JsonSerializer.Deserialize(response, McpJsonUtilities.DefaultOptions); @@ -441,13 +441,13 @@ await Can_Handle_Requests( new ServerCapabilities { Prompts = new() - { - GetPromptHandler = async (request, ct) => new GetPromptResult { Description = "test" }, - ListPromptsHandler = (request, ct) => throw new NotImplementedException(), - } }, method: RequestMethods.PromptsGet, - configureOptions: null, + configureOptions: options => + { + options.Handlers.GetPromptHandler = async (request, ct) => new GetPromptResult { Description = "test" }; + options.Handlers.ListPromptsHandler = (request, ct) => throw new NotImplementedException(); + }, assertResult: (_, response) => { var result = JsonSerializer.Deserialize(response, McpJsonUtilities.DefaultOptions); @@ -469,19 +469,19 @@ await Can_Handle_Requests( new ServerCapabilities { Tools = new() + }, + method: RequestMethods.ToolsList, + configureOptions: options => + { + options.Handlers.ListToolsHandler = async (request, ct) => { - ListToolsHandler = async (request, ct) => + return new ListToolsResult { - return new ListToolsResult - { - Tools = [new() { Name = "test" }] - }; - }, - CallToolHandler = (request, ct) => throw new NotImplementedException(), - } + Tools = [new() { Name = "test" }] + }; + }; + options.Handlers.CallToolHandler = (request, ct) => throw new NotImplementedException(); }, - method: RequestMethods.ToolsList, - configureOptions: null, assertResult: (_, response) => { var result = JsonSerializer.Deserialize(response, McpJsonUtilities.DefaultOptions); @@ -504,19 +504,19 @@ await Can_Handle_Requests( new ServerCapabilities { Tools = new() - { - CallToolHandler = async (request, ct) => - { - return new CallToolResult - { - Content = [new TextContentBlock { Text = "test" }] - }; - }, - ListToolsHandler = (request, ct) => throw new NotImplementedException(), - } }, method: RequestMethods.ToolsCall, - configureOptions: null, + configureOptions: options => + { + options.Handlers.CallToolHandler = async (request, ct) => + { + return new CallToolResult + { + Content = [new TextContentBlock { Text = "test" }] + }; + }; + options.Handlers.ListToolsHandler = (request, ct) => throw new NotImplementedException(); + }, assertResult: (_, response) => { var result = JsonSerializer.Deserialize(response, McpJsonUtilities.DefaultOptions); @@ -703,14 +703,12 @@ public async Task NotifyProgress_Should_Be_Handled() var options = CreateOptions(); var notificationReceived = new TaskCompletionSource(); - options.Capabilities = new() - { - NotificationHandlers = [new(NotificationMethods.ProgressNotification, (notification, cancellationToken) => + options.Handlers.NotificationHandlers = + [new(NotificationMethods.ProgressNotification, (notification, cancellationToken) => { notificationReceived.TrySetResult(notification); return default; - })], - }; + })]; var server = McpServer.Create(transport, options, LoggerFactory); From 1cb8facf8aea522d7ce126310b314491ff6a19cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Cant=C3=BA?= Date: Mon, 22 Sep 2025 21:55:12 -0500 Subject: [PATCH 2/6] Add remarks for empty protocol capability types --- .../Protocol/CompletionsCapability.cs | 6 +++++- .../Protocol/ElicitationCapability.cs | 6 +++++- .../Protocol/LoggingCapability.cs | 8 +++++++- .../Protocol/SamplingCapability.cs | 6 +++++- 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/ModelContextProtocol.Core/Protocol/CompletionsCapability.cs b/src/ModelContextProtocol.Core/Protocol/CompletionsCapability.cs index e8d5932c9..0788fd2c3 100644 --- a/src/ModelContextProtocol.Core/Protocol/CompletionsCapability.cs +++ b/src/ModelContextProtocol.Core/Protocol/CompletionsCapability.cs @@ -16,8 +16,12 @@ namespace ModelContextProtocol.Protocol; /// /// See the schema for details. /// +/// +/// This class is intentionally empty as the Model Context Protocol specification does not +/// currently define additional properties for sampling capabilities. Future versions of the +/// specification may extend this capability with additional configuration options. +/// /// public sealed class CompletionsCapability { - // Currently empty in the spec, but may be extended in the future. } \ No newline at end of file diff --git a/src/ModelContextProtocol.Core/Protocol/ElicitationCapability.cs b/src/ModelContextProtocol.Core/Protocol/ElicitationCapability.cs index 39d76ad05..44635bf66 100644 --- a/src/ModelContextProtocol.Core/Protocol/ElicitationCapability.cs +++ b/src/ModelContextProtocol.Core/Protocol/ElicitationCapability.cs @@ -11,8 +11,12 @@ namespace ModelContextProtocol.Protocol; /// When this capability is enabled, an MCP server can request the client to provide additional information /// during interactions. The client must set a to process these requests. /// +/// +/// This class is intentionally empty as the Model Context Protocol specification does not +/// currently define additional properties for sampling capabilities. Future versions of the +/// specification may extend this capability with additional configuration options. +/// /// public sealed class ElicitationCapability { - // Currently empty in the spec, but may be extended in the future. } \ No newline at end of file diff --git a/src/ModelContextProtocol.Core/Protocol/LoggingCapability.cs b/src/ModelContextProtocol.Core/Protocol/LoggingCapability.cs index 3cc771b0c..75a045cf0 100644 --- a/src/ModelContextProtocol.Core/Protocol/LoggingCapability.cs +++ b/src/ModelContextProtocol.Core/Protocol/LoggingCapability.cs @@ -4,10 +4,16 @@ namespace ModelContextProtocol.Protocol; /// Represents the logging capability configuration for a Model Context Protocol server. /// /// +/// /// This capability allows clients to set the logging level and receive log messages from the server. /// See the schema for details. +/// +/// +/// This class is intentionally empty as the Model Context Protocol specification does not +/// currently define additional properties for sampling capabilities. Future versions of the +/// specification may extend this capability with additional configuration options. +/// /// public sealed class LoggingCapability { - // Currently empty in the spec, but may be extended in the future } \ No newline at end of file diff --git a/src/ModelContextProtocol.Core/Protocol/SamplingCapability.cs b/src/ModelContextProtocol.Core/Protocol/SamplingCapability.cs index ad82c7957..1341ba911 100644 --- a/src/ModelContextProtocol.Core/Protocol/SamplingCapability.cs +++ b/src/ModelContextProtocol.Core/Protocol/SamplingCapability.cs @@ -11,8 +11,12 @@ namespace ModelContextProtocol.Protocol; /// When this capability is enabled, an MCP server can request the client to generate content /// using an AI model. The client must set a to process these requests. /// +/// +/// This class is intentionally empty as the Model Context Protocol specification does not +/// currently define additional properties for sampling capabilities. Future versions of the +/// specification may extend this capability with additional configuration options. +/// /// public sealed class SamplingCapability { - // Currently empty in the spec, but may be extended in the future } \ No newline at end of file From b6c87b91787a1f3e1280c296ae24cb93652d3632 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Cant=C3=BA?= Date: Mon, 22 Sep 2025 22:23:02 -0500 Subject: [PATCH 3/6] Keep JsonIgnore properties and mark them as Obsolete --- .../Protocol/ClientCapabilities.cs | 10 +- .../Protocol/CompletionsCapability.cs | 18 +++ .../Protocol/ElicitationCapability.cs | 23 ++++ .../Protocol/LoggingCapability.cs | 13 +++ .../Protocol/PromptsCapability.cs | 64 +++++++++++ .../Protocol/ResourcesCapability.cs | 106 ++++++++++++++++++ .../Protocol/RootsCapability.cs | 16 +++ .../Protocol/SamplingCapability.cs | 29 +++++ .../Protocol/ServerCapabilities.cs | 34 +++++- .../Protocol/ToolsCapability.cs | 55 +++++++++ 10 files changed, 362 insertions(+), 6 deletions(-) diff --git a/src/ModelContextProtocol.Core/Protocol/ClientCapabilities.cs b/src/ModelContextProtocol.Core/Protocol/ClientCapabilities.cs index 058ea3c34..0666bda32 100644 --- a/src/ModelContextProtocol.Core/Protocol/ClientCapabilities.cs +++ b/src/ModelContextProtocol.Core/Protocol/ClientCapabilities.cs @@ -1,5 +1,6 @@ -using ModelContextProtocol.Server; using System.Text.Json.Serialization; +using ModelContextProtocol.Client; +using ModelContextProtocol.Server; namespace ModelContextProtocol.Protocol; @@ -83,5 +84,10 @@ public sealed class ClientCapabilities /// /// [JsonIgnore] - public IEnumerable>>? NotificationHandlers { get; set; } + [Obsolete($"Use {nameof(McpClientHandlers.NotificationHandlers)} instead.")] + public IEnumerable>>? NotificationHandlers + { + get => throw new NotSupportedException($"Use {nameof(McpClientHandlers.NotificationHandlers)} instead."); + set => throw new NotSupportedException($"Use {nameof(McpClientHandlers.NotificationHandlers)} instead."); + } } \ No newline at end of file diff --git a/src/ModelContextProtocol.Core/Protocol/CompletionsCapability.cs b/src/ModelContextProtocol.Core/Protocol/CompletionsCapability.cs index 0788fd2c3..2c7a067f3 100644 --- a/src/ModelContextProtocol.Core/Protocol/CompletionsCapability.cs +++ b/src/ModelContextProtocol.Core/Protocol/CompletionsCapability.cs @@ -1,3 +1,6 @@ +using System.Text.Json.Serialization; +using ModelContextProtocol.Server; + namespace ModelContextProtocol.Protocol; /// @@ -24,4 +27,19 @@ namespace ModelContextProtocol.Protocol; /// public sealed class CompletionsCapability { + /// + /// Gets or sets the handler for completion requests. + /// + /// + /// This handler provides auto-completion suggestions for prompt arguments or resource references in the Model Context Protocol. + /// The handler receives a reference type (e.g., "ref/prompt" or "ref/resource") and the current argument value, + /// and should return appropriate completion suggestions. + /// + [JsonIgnore] + [Obsolete($"Use {nameof(McpServerHandlers.CompleteHandler)} instead.")] + public McpRequestHandler? CompleteHandler + { + get => throw new NotSupportedException($"Use {nameof(McpServerHandlers.CompleteHandler)} instead."); + set => throw new NotSupportedException($"Use {nameof(McpServerHandlers.CompleteHandler)} instead."); + } } \ No newline at end of file diff --git a/src/ModelContextProtocol.Core/Protocol/ElicitationCapability.cs b/src/ModelContextProtocol.Core/Protocol/ElicitationCapability.cs index 44635bf66..65eab70d3 100644 --- a/src/ModelContextProtocol.Core/Protocol/ElicitationCapability.cs +++ b/src/ModelContextProtocol.Core/Protocol/ElicitationCapability.cs @@ -1,3 +1,6 @@ +using System.Text.Json.Serialization; +using ModelContextProtocol.Client; + namespace ModelContextProtocol.Protocol; /// @@ -19,4 +22,24 @@ namespace ModelContextProtocol.Protocol; /// public sealed class ElicitationCapability { + /// + /// Gets or sets the handler for processing requests. + /// + /// + /// + /// This handler function is called when an MCP server requests the client to provide additional + /// information during interactions. The client must set this property for the elicitation capability to work. + /// + /// + /// The handler receives message parameters and a cancellation token. + /// It should return a containing the response to the elicitation request. + /// + /// + [JsonIgnore] + [Obsolete($"Use {nameof(McpClientHandlers.ElicitationHandler)} instead.")] + public Func>? ElicitationHandler + { + get => throw new NotSupportedException($"Use {nameof(McpClientHandlers.ElicitationHandler)} instead."); + set => throw new NotSupportedException($"Use {nameof(McpClientHandlers.ElicitationHandler)} instead."); + } } \ No newline at end of file diff --git a/src/ModelContextProtocol.Core/Protocol/LoggingCapability.cs b/src/ModelContextProtocol.Core/Protocol/LoggingCapability.cs index 75a045cf0..5109f0178 100644 --- a/src/ModelContextProtocol.Core/Protocol/LoggingCapability.cs +++ b/src/ModelContextProtocol.Core/Protocol/LoggingCapability.cs @@ -1,3 +1,6 @@ +using System.Text.Json.Serialization; +using ModelContextProtocol.Server; + namespace ModelContextProtocol.Protocol; /// @@ -16,4 +19,14 @@ namespace ModelContextProtocol.Protocol; /// public sealed class LoggingCapability { + /// + /// Gets or sets the handler for set logging level requests from clients. + /// + [JsonIgnore] + [Obsolete($"Use {nameof(McpServerHandlers.SetLoggingLevelHandler)} instead.")] + public McpRequestHandler? SetLoggingLevelHandler + { + get => throw new NotSupportedException($"Use {nameof(McpServerHandlers.SetLoggingLevelHandler)} instead."); + set => throw new NotSupportedException($"Use {nameof(McpServerHandlers.SetLoggingLevelHandler)} instead."); + } } \ No newline at end of file diff --git a/src/ModelContextProtocol.Core/Protocol/PromptsCapability.cs b/src/ModelContextProtocol.Core/Protocol/PromptsCapability.cs index 50e5a6f56..7a2c4d02c 100644 --- a/src/ModelContextProtocol.Core/Protocol/PromptsCapability.cs +++ b/src/ModelContextProtocol.Core/Protocol/PromptsCapability.cs @@ -30,4 +30,68 @@ public sealed class PromptsCapability /// [JsonPropertyName("listChanged")] public bool? ListChanged { get; set; } + + /// + /// Gets or sets the handler for requests. + /// + /// + /// This handler is invoked when a client requests a list of available prompts from the server + /// via a request. Results from this handler are returned + /// along with any prompts defined in . + /// + [JsonIgnore] + [Obsolete($"Use {nameof(McpServerHandlers.ListPromptsHandler)} instead.")] + public McpRequestHandler? ListPromptsHandler + { + get => throw new NotSupportedException($"Use {nameof(McpServerHandlers.ListPromptsHandler)} instead."); + set => throw new NotSupportedException($"Use {nameof(McpServerHandlers.ListPromptsHandler)} instead."); + } + + /// + /// Gets or sets the handler for requests. + /// + /// + /// + /// This handler is invoked when a client requests details for a specific prompt by name and provides arguments + /// for the prompt if needed. The handler receives the request context containing the prompt name and any arguments, + /// and should return a with the prompt messages and other details. + /// + /// + /// This handler will be invoked if the requested prompt name is not found in the , + /// allowing for dynamic prompt generation or retrieval from external sources. + /// + /// + [JsonIgnore] + [Obsolete($"Use {nameof(McpServerHandlers.GetPromptHandler)} instead.")] + public McpRequestHandler? GetPromptHandler + { + get => throw new NotSupportedException($"Use {nameof(McpServerHandlers.GetPromptHandler)} instead."); + set => throw new NotSupportedException($"Use {nameof(McpServerHandlers.GetPromptHandler)} instead."); + } + + /// + /// Gets or sets a collection of prompts that will be served by the server. + /// + /// + /// + /// The contains the predefined prompts that clients can request from the server. + /// This collection works in conjunction with and + /// when those are provided: + /// + /// + /// - For requests: The server returns all prompts from this collection + /// plus any additional prompts provided by the if it's set. + /// + /// + /// - For requests: The server first checks this collection for the requested prompt. + /// If not found, it will invoke the as a fallback if one is set. + /// + /// + [JsonIgnore] + [Obsolete($"Use {nameof(McpServerOptions.PromptCollection)} instead.")] + public McpServerPrimitiveCollection? PromptCollection + { + get => throw new NotSupportedException($"Use {nameof(McpServerOptions.PromptCollection)} instead."); + set => throw new NotSupportedException($"Use {nameof(McpServerOptions.PromptCollection)} instead."); + } } \ No newline at end of file diff --git a/src/ModelContextProtocol.Core/Protocol/ResourcesCapability.cs b/src/ModelContextProtocol.Core/Protocol/ResourcesCapability.cs index cf1648de2..5aa664bc3 100644 --- a/src/ModelContextProtocol.Core/Protocol/ResourcesCapability.cs +++ b/src/ModelContextProtocol.Core/Protocol/ResourcesCapability.cs @@ -28,4 +28,110 @@ public sealed class ResourcesCapability /// [JsonPropertyName("listChanged")] public bool? ListChanged { get; set; } + + /// + /// Gets or sets the handler for requests. + /// + /// + /// This handler is called when clients request available resource templates that can be used + /// to create resources within the Model Context Protocol server. + /// Resource templates define the structure and URI patterns for resources accessible in the system, + /// allowing clients to discover available resource types and their access patterns. + /// + [JsonIgnore] + [Obsolete($"Use {nameof(McpServerHandlers.ListResourceTemplatesHandler)} instead.")] + public McpRequestHandler? ListResourceTemplatesHandler + { + get => throw new NotSupportedException($"Use {nameof(McpServerHandlers.ListResourceTemplatesHandler)} instead."); + set => throw new NotSupportedException($"Use {nameof(McpServerHandlers.ListResourceTemplatesHandler)} instead."); + } + + /// + /// Gets or sets the handler for requests. + /// + /// + /// This handler responds to client requests for available resources and returns information about resources accessible through the server. + /// The implementation should return a with the matching resources. + /// + [JsonIgnore] + [Obsolete($"Use {nameof(McpServerHandlers.ListResourcesHandler)} instead.")] + public McpRequestHandler? ListResourcesHandler + { + get => throw new NotSupportedException($"Use {nameof(McpServerHandlers.ListResourcesHandler)} instead."); + set => throw new NotSupportedException($"Use {nameof(McpServerHandlers.ListResourcesHandler)} instead."); + } + + /// + /// Gets or sets the handler for requests. + /// + /// + /// This handler is responsible for retrieving the content of a specific resource identified by its URI in the Model Context Protocol. + /// When a client sends a resources/read request, this handler is invoked with the resource URI. + /// The handler should implement logic to locate and retrieve the requested resource, then return + /// its contents in a ReadResourceResult object. + /// + [JsonIgnore] + [Obsolete($"Use {nameof(McpServerHandlers.ReadResourceHandler)} instead.")] + public McpRequestHandler? ReadResourceHandler + { + get => throw new NotSupportedException($"Use {nameof(McpServerHandlers.ReadResourceHandler)} instead."); + set => throw new NotSupportedException($"Use {nameof(McpServerHandlers.ReadResourceHandler)} instead."); + } + + /// + /// Gets or sets the handler for requests. + /// + /// + /// When a client sends a request, this handler is invoked with the resource URI + /// to be subscribed to. The implementation should register the client's interest in receiving updates + /// for the specified resource. + /// Subscriptions allow clients to receive real-time notifications when resources change, without + /// requiring polling. + /// + [JsonIgnore] + [Obsolete($"Use {nameof(McpServerHandlers.SubscribeToResourcesHandler)} instead.")] + public McpRequestHandler? SubscribeToResourcesHandler + { + get => throw new NotSupportedException($"Use {nameof(McpServerHandlers.SubscribeToResourcesHandler)} instead."); + set => throw new NotSupportedException($"Use {nameof(McpServerHandlers.SubscribeToResourcesHandler)} instead."); + } + + /// + /// Gets or sets the handler for requests. + /// + /// + /// When a client sends a request, this handler is invoked with the resource URI + /// to be unsubscribed from. The implementation should remove the client's registration for receiving updates + /// about the specified resource. + /// + [JsonIgnore] + [Obsolete($"Use {nameof(McpServerHandlers.UnsubscribeFromResourcesHandler)} instead.")] + public McpRequestHandler? UnsubscribeFromResourcesHandler + { + get => throw new NotSupportedException($"Use {nameof(McpServerHandlers.UnsubscribeFromResourcesHandler)} instead."); + set => throw new NotSupportedException($"Use {nameof(McpServerHandlers.UnsubscribeFromResourcesHandler)} instead."); + } + + /// + /// Gets or sets a collection of resources served by the server. + /// + /// + /// + /// Resources specified via augment the , + /// and handlers, if provided. Resources with template expressions in their URI templates are considered resource templates + /// and are listed via ListResourceTemplate, whereas resources without template parameters are considered static resources and are listed with ListResources. + /// + /// + /// ReadResource requests will first check the for the exact resource being requested. If no match is found, they'll proceed to + /// try to match the resource against each resource template in . If no match is still found, the request will fall back to + /// any handler registered for . + /// + /// + [JsonIgnore] + [Obsolete($"Use {nameof(McpServerOptions.ResourceCollection)} instead.")] + public McpServerResourceCollection? ResourceCollection + { + get => throw new NotSupportedException($"Use {nameof(McpServerOptions.ResourceCollection)} instead."); + set => throw new NotSupportedException($"Use {nameof(McpServerOptions.ResourceCollection)} instead."); + } } \ No newline at end of file diff --git a/src/ModelContextProtocol.Core/Protocol/RootsCapability.cs b/src/ModelContextProtocol.Core/Protocol/RootsCapability.cs index 9f0b0b812..19365c1cc 100644 --- a/src/ModelContextProtocol.Core/Protocol/RootsCapability.cs +++ b/src/ModelContextProtocol.Core/Protocol/RootsCapability.cs @@ -1,4 +1,5 @@ using System.Text.Json.Serialization; +using ModelContextProtocol.Client; namespace ModelContextProtocol.Protocol; @@ -31,4 +32,19 @@ public sealed class RootsCapability /// [JsonPropertyName("listChanged")] public bool? ListChanged { get; set; } + + /// + /// Gets or sets the handler for requests. + /// + /// + /// This handler is invoked when a client sends a request to retrieve available roots. + /// The handler receives request parameters and should return a containing the collection of available roots. + /// + [JsonIgnore] + [Obsolete($"Use {nameof(McpClientHandlers.RootsHandler)} instead.")] + public Func>? RootsHandler + { + get => throw new NotSupportedException($"Use {nameof(McpClientHandlers.RootsHandler)} instead."); + set => throw new NotSupportedException($"Use {nameof(McpClientHandlers.RootsHandler)} instead."); + } } \ No newline at end of file diff --git a/src/ModelContextProtocol.Core/Protocol/SamplingCapability.cs b/src/ModelContextProtocol.Core/Protocol/SamplingCapability.cs index 1341ba911..943587ba5 100644 --- a/src/ModelContextProtocol.Core/Protocol/SamplingCapability.cs +++ b/src/ModelContextProtocol.Core/Protocol/SamplingCapability.cs @@ -1,3 +1,7 @@ +using System.Text.Json.Serialization; +using Microsoft.Extensions.AI; +using ModelContextProtocol.Client; + namespace ModelContextProtocol.Protocol; /// @@ -19,4 +23,29 @@ namespace ModelContextProtocol.Protocol; /// public sealed class SamplingCapability { + /// + /// Gets or sets the handler for processing requests. + /// + /// + /// + /// This handler function is called when an MCP server requests the client to generate content + /// using an AI model. The client must set this property for the sampling capability to work. + /// + /// + /// The handler receives message parameters, a progress reporter for updates, and a + /// cancellation token. It should return a containing the + /// generated content. + /// + /// + /// You can create a handler using the extension + /// method with any implementation of . + /// + /// + [JsonIgnore] + [Obsolete($"Use {nameof(McpClientHandlers.SamplingHandler)} instead.")] + public Func, CancellationToken, ValueTask>? SamplingHandler + { + get => throw new NotSupportedException($"Use {nameof(McpClientHandlers.SamplingHandler)} instead."); + set => throw new NotSupportedException($"Use {nameof(McpClientHandlers.SamplingHandler)} instead."); + } } \ No newline at end of file diff --git a/src/ModelContextProtocol.Core/Protocol/ServerCapabilities.cs b/src/ModelContextProtocol.Core/Protocol/ServerCapabilities.cs index ed2f1f2af..a8e08c101 100644 --- a/src/ModelContextProtocol.Core/Protocol/ServerCapabilities.cs +++ b/src/ModelContextProtocol.Core/Protocol/ServerCapabilities.cs @@ -1,4 +1,5 @@ using System.Text.Json.Serialization; +using ModelContextProtocol.Server; namespace ModelContextProtocol.Protocol; @@ -21,13 +22,13 @@ public sealed class ServerCapabilities /// /// /// - /// The dictionary allows servers to advertise support for features that are not yet - /// standardized in the Model Context Protocol specification. This extension mechanism enables + /// The dictionary allows servers to advertise support for features that are not yet + /// standardized in the Model Context Protocol specification. This extension mechanism enables /// future protocol enhancements while maintaining backward compatibility. /// /// - /// Values in this dictionary are implementation-specific and should be coordinated between client - /// and server implementations. Clients should not assume the presence of any experimental capability + /// Values in this dictionary are implementation-specific and should be coordinated between client + /// and server implementations. Clients should not assume the presence of any experimental capability /// without checking for it first. /// /// @@ -63,4 +64,29 @@ public sealed class ServerCapabilities /// [JsonPropertyName("completions")] public CompletionsCapability? Completions { get; set; } + + /// Gets or sets notification handlers to register with the server. + /// + /// + /// When constructed, the server will enumerate these handlers once, which may contain multiple handlers per notification method key. + /// The server will not re-enumerate the sequence after initialization. + /// + /// + /// Notification handlers allow the server to respond to client-sent notifications for specific methods. + /// Each key in the collection is a notification method name, and each value is a callback that will be invoked + /// when a notification with that method is received. + /// + /// + /// Handlers provided via will be registered with the server for the lifetime of the server. + /// For transient handlers, may be used to register a handler that can + /// then be unregistered by disposing of the returned from the method. + /// + /// + [JsonIgnore] + [Obsolete($"Use {nameof(McpServerHandlers.NotificationHandlers)} instead.")] + public IEnumerable>>? NotificationHandlers + { + get => throw new NotSupportedException($"Use {nameof(McpServerHandlers.NotificationHandlers)} instead."); + set => throw new NotSupportedException($"Use {nameof(McpServerHandlers.NotificationHandlers)} instead."); + } } diff --git a/src/ModelContextProtocol.Core/Protocol/ToolsCapability.cs b/src/ModelContextProtocol.Core/Protocol/ToolsCapability.cs index dbbb9462f..3f5d69587 100644 --- a/src/ModelContextProtocol.Core/Protocol/ToolsCapability.cs +++ b/src/ModelContextProtocol.Core/Protocol/ToolsCapability.cs @@ -1,4 +1,5 @@ using System.Text.Json.Serialization; +using ModelContextProtocol.Server; namespace ModelContextProtocol.Protocol; @@ -20,4 +21,58 @@ public sealed class ToolsCapability /// [JsonPropertyName("listChanged")] public bool? ListChanged { get; set; } + + /// + /// Gets or sets the handler for requests. + /// + /// + /// The handler should return a list of available tools when requested by a client. + /// It supports pagination through the cursor mechanism, where the client can make + /// repeated calls with the cursor returned by the previous call to retrieve more tools. + /// When used in conjunction with , both the tools from this handler + /// and the tools from the collection will be combined to form the complete list of available tools. + /// + [JsonIgnore] + [Obsolete($"Use {nameof(McpServerHandlers.ListToolsHandler)} instead.")] + public McpRequestHandler? ListToolsHandler + { + get => throw new NotSupportedException($"Use {nameof(McpServerHandlers.ListToolsHandler)} instead."); + set => throw new NotSupportedException($"Use {nameof(McpServerHandlers.ListToolsHandler)} instead."); + } + + /// + /// Gets or sets the handler for requests. + /// + /// + /// This handler is invoked when a client makes a call to a tool that isn't found in the . + /// The handler should implement logic to execute the requested tool and return appropriate results. + /// It receives a containing information about the tool + /// being called and its arguments, and should return a with the execution results. + /// + [JsonIgnore] + [Obsolete($"Use {nameof(McpServerHandlers.CallToolHandler)} instead.")] + public McpRequestHandler? CallToolHandler + { + get => throw new NotSupportedException($"Use {nameof(McpServerHandlers.CallToolHandler)} instead."); + set => throw new NotSupportedException($"Use {nameof(McpServerHandlers.CallToolHandler)} instead."); + } + + /// + /// Gets or sets a collection of tools served by the server. + /// + /// + /// Tools will specified via augment the and + /// , if provided. ListTools requests will output information about every tool + /// in and then also any tools output by , if it's + /// non-. CallTool requests will first check for the tool + /// being requested, and if the tool is not found in the , any specified + /// will be invoked as a fallback. + /// + [JsonIgnore] + [Obsolete($"Use {nameof(McpServerOptions.ToolCollection)} instead.")] + public McpServerPrimitiveCollection? ToolCollection + { + get => throw new NotSupportedException($"Use {nameof(McpServerOptions.ToolCollection)} instead."); + set => throw new NotSupportedException($"Use {nameof(McpServerOptions.ToolCollection)} instead."); + } } \ No newline at end of file From 18abf1e47ff13ee8d36f3441434dbc3353d361d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Cant=C3=BA?= Date: Tue, 23 Sep 2025 11:39:49 -0500 Subject: [PATCH 4/6] Continue supporting Obsolete members --- README.md | 47 ++++++++-------- .../Client/McpClientHandlers.cs | 1 - .../Client/McpClientImpl.cs | 21 +++++-- .../Protocol/ClientCapabilities.cs | 10 ++-- .../Protocol/CompletionsCapability.cs | 10 ++-- .../Protocol/ElicitationCapability.cs | 10 ++-- .../Protocol/LoggingCapability.cs | 10 ++-- .../Protocol/PromptsCapability.cs | 28 ++++------ .../Protocol/ResourcesCapability.cs | 55 +++++++------------ .../Protocol/RootsCapability.cs | 10 ++-- .../Protocol/SamplingCapability.cs | 10 ++-- .../Protocol/ServerCapabilities.cs | 10 ++-- .../Protocol/ToolsCapability.cs | 28 ++++------ .../Server/McpServerImpl.cs | 29 +++++++++- 14 files changed, 134 insertions(+), 145 deletions(-) diff --git a/README.md b/README.md index 4aa1f5e94..4c87ba9bd 100644 --- a/README.md +++ b/README.md @@ -177,29 +177,30 @@ McpServerOptions options = new() Handlers = new McpServerHandlers() { ListToolsHandler = (request, cancellationToken) => - ValueTask.FromResult(new ListToolsResult - { - Tools = - [ - new Tool - { - Name = "echo", - Description = "Echoes the input back to the client.", - InputSchema = JsonSerializer.Deserialize(""" - { - "type": "object", - "properties": { - "message": { - "type": "string", - "description": "The input to echo back" - } - }, - "required": ["message"] - } - """), - } - ] - }), + ValueTask.FromResult(new ListToolsResult + { + Tools = + [ + new Tool + { + Name = "echo", + Description = "Echoes the input back to the client.", + InputSchema = JsonSerializer.Deserialize(""" + { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "The input to echo back" + } + }, + "required": ["message"] + } + """), + } + ] + }), + CallToolHandler = (request, cancellationToken) => { if (request.Params?.Name == "echo") diff --git a/src/ModelContextProtocol.Core/Client/McpClientHandlers.cs b/src/ModelContextProtocol.Core/Client/McpClientHandlers.cs index b03d20746..fecb83299 100644 --- a/src/ModelContextProtocol.Core/Client/McpClientHandlers.cs +++ b/src/ModelContextProtocol.Core/Client/McpClientHandlers.cs @@ -23,7 +23,6 @@ namespace ModelContextProtocol.Client; /// public class McpClientHandlers { - /// Gets or sets notification handlers to register with the client. /// /// diff --git a/src/ModelContextProtocol.Core/Client/McpClientImpl.cs b/src/ModelContextProtocol.Core/Client/McpClientImpl.cs index 7e664baeb..3a289d13e 100644 --- a/src/ModelContextProtocol.Core/Client/McpClientImpl.cs +++ b/src/ModelContextProtocol.Core/Client/McpClientImpl.cs @@ -49,19 +49,28 @@ internal McpClientImpl(ITransport transport, string endpointName, McpClientOptio var notificationHandlers = new NotificationHandlers(); var requestHandlers = new RequestHandlers(); - RegisterHandlers(options.Handlers, notificationHandlers, requestHandlers); + RegisterHandlers(options, notificationHandlers, requestHandlers); _sessionHandler = new McpSessionHandler(isServer: false, transport, endpointName, requestHandlers, notificationHandlers, _logger); } - private void RegisterHandlers(McpClientHandlers handlers, NotificationHandlers notificationHandlers, RequestHandlers requestHandlers) + private void RegisterHandlers(McpClientOptions options, NotificationHandlers notificationHandlers, RequestHandlers requestHandlers) { - if (handlers.NotificationHandlers is { } notificationHandlersFromOptions) + McpClientHandlers handlers = options.Handlers; + +#pragma warning disable CS0618 // Type or member is obsolete + var notificationHandlersFromOptions = handlers.NotificationHandlers ?? options.Capabilities?.NotificationHandlers; + var samplingHandler = handlers.SamplingHandler ?? options.Capabilities?.Sampling?.SamplingHandler; + var rootsHandler = handlers.RootsHandler ?? options.Capabilities?.Roots?.RootsHandler; + var elicitationHandler = handlers.ElicitationHandler ?? options.Capabilities?.Elicitation?.ElicitationHandler; +#pragma warning restore CS0618 // Type or member is obsolete + + if (notificationHandlersFromOptions is not null) { notificationHandlers.RegisterRange(notificationHandlersFromOptions); } - if (handlers.SamplingHandler is { } samplingHandler) + if (samplingHandler is not null) { requestHandlers.Set( RequestMethods.SamplingCreateMessage, @@ -76,7 +85,7 @@ private void RegisterHandlers(McpClientHandlers handlers, NotificationHandlers n _options.Capabilities.Sampling ??= new(); } - if (handlers.RootsHandler is { } rootsHandler) + if (rootsHandler is not null) { requestHandlers.Set( RequestMethods.RootsList, @@ -88,7 +97,7 @@ private void RegisterHandlers(McpClientHandlers handlers, NotificationHandlers n _options.Capabilities.Roots ??= new(); } - if (handlers.ElicitationHandler is { } elicitationHandler) + if (elicitationHandler is not null) { requestHandlers.Set( RequestMethods.ElicitationCreate, diff --git a/src/ModelContextProtocol.Core/Protocol/ClientCapabilities.cs b/src/ModelContextProtocol.Core/Protocol/ClientCapabilities.cs index 0666bda32..410fda41b 100644 --- a/src/ModelContextProtocol.Core/Protocol/ClientCapabilities.cs +++ b/src/ModelContextProtocol.Core/Protocol/ClientCapabilities.cs @@ -1,3 +1,4 @@ +using System.ComponentModel; using System.Text.Json.Serialization; using ModelContextProtocol.Client; using ModelContextProtocol.Server; @@ -84,10 +85,7 @@ public sealed class ClientCapabilities /// /// [JsonIgnore] - [Obsolete($"Use {nameof(McpClientHandlers.NotificationHandlers)} instead.")] - public IEnumerable>>? NotificationHandlers - { - get => throw new NotSupportedException($"Use {nameof(McpClientHandlers.NotificationHandlers)} instead."); - set => throw new NotSupportedException($"Use {nameof(McpClientHandlers.NotificationHandlers)} instead."); - } + [Obsolete($"Use {nameof(McpClientOptions.Handlers.NotificationHandlers)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [EditorBrowsable(EditorBrowsableState.Never)] + public IEnumerable>>? NotificationHandlers { get; set; } } \ No newline at end of file diff --git a/src/ModelContextProtocol.Core/Protocol/CompletionsCapability.cs b/src/ModelContextProtocol.Core/Protocol/CompletionsCapability.cs index 2c7a067f3..63841f95d 100644 --- a/src/ModelContextProtocol.Core/Protocol/CompletionsCapability.cs +++ b/src/ModelContextProtocol.Core/Protocol/CompletionsCapability.cs @@ -1,3 +1,4 @@ +using System.ComponentModel; using System.Text.Json.Serialization; using ModelContextProtocol.Server; @@ -36,10 +37,7 @@ public sealed class CompletionsCapability /// and should return appropriate completion suggestions. /// [JsonIgnore] - [Obsolete($"Use {nameof(McpServerHandlers.CompleteHandler)} instead.")] - public McpRequestHandler? CompleteHandler - { - get => throw new NotSupportedException($"Use {nameof(McpServerHandlers.CompleteHandler)} instead."); - set => throw new NotSupportedException($"Use {nameof(McpServerHandlers.CompleteHandler)} instead."); - } + [Obsolete($"Use {nameof(McpServerOptions.Handlers.CompleteHandler)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [EditorBrowsable(EditorBrowsableState.Never)] + public McpRequestHandler? CompleteHandler { get; set; } } \ No newline at end of file diff --git a/src/ModelContextProtocol.Core/Protocol/ElicitationCapability.cs b/src/ModelContextProtocol.Core/Protocol/ElicitationCapability.cs index 65eab70d3..9839eb317 100644 --- a/src/ModelContextProtocol.Core/Protocol/ElicitationCapability.cs +++ b/src/ModelContextProtocol.Core/Protocol/ElicitationCapability.cs @@ -1,3 +1,4 @@ +using System.ComponentModel; using System.Text.Json.Serialization; using ModelContextProtocol.Client; @@ -36,10 +37,7 @@ public sealed class ElicitationCapability /// /// [JsonIgnore] - [Obsolete($"Use {nameof(McpClientHandlers.ElicitationHandler)} instead.")] - public Func>? ElicitationHandler - { - get => throw new NotSupportedException($"Use {nameof(McpClientHandlers.ElicitationHandler)} instead."); - set => throw new NotSupportedException($"Use {nameof(McpClientHandlers.ElicitationHandler)} instead."); - } + [Obsolete($"Use {nameof(McpClientOptions.Handlers.ElicitationHandler)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [EditorBrowsable(EditorBrowsableState.Never)] + public Func>? ElicitationHandler { get; set; } } \ No newline at end of file diff --git a/src/ModelContextProtocol.Core/Protocol/LoggingCapability.cs b/src/ModelContextProtocol.Core/Protocol/LoggingCapability.cs index 5109f0178..af119b7f3 100644 --- a/src/ModelContextProtocol.Core/Protocol/LoggingCapability.cs +++ b/src/ModelContextProtocol.Core/Protocol/LoggingCapability.cs @@ -1,3 +1,4 @@ +using System.ComponentModel; using System.Text.Json.Serialization; using ModelContextProtocol.Server; @@ -23,10 +24,7 @@ public sealed class LoggingCapability /// Gets or sets the handler for set logging level requests from clients. /// [JsonIgnore] - [Obsolete($"Use {nameof(McpServerHandlers.SetLoggingLevelHandler)} instead.")] - public McpRequestHandler? SetLoggingLevelHandler - { - get => throw new NotSupportedException($"Use {nameof(McpServerHandlers.SetLoggingLevelHandler)} instead."); - set => throw new NotSupportedException($"Use {nameof(McpServerHandlers.SetLoggingLevelHandler)} instead."); - } + [Obsolete($"Use {nameof(McpServerOptions.Handlers.SetLoggingLevelHandler)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [EditorBrowsable(EditorBrowsableState.Never)] + public McpRequestHandler? SetLoggingLevelHandler { get; set; } } \ No newline at end of file diff --git a/src/ModelContextProtocol.Core/Protocol/PromptsCapability.cs b/src/ModelContextProtocol.Core/Protocol/PromptsCapability.cs index 7a2c4d02c..67bc3ff8d 100644 --- a/src/ModelContextProtocol.Core/Protocol/PromptsCapability.cs +++ b/src/ModelContextProtocol.Core/Protocol/PromptsCapability.cs @@ -1,4 +1,5 @@ using ModelContextProtocol.Server; +using System.ComponentModel; using System.Text.Json.Serialization; namespace ModelContextProtocol.Protocol; @@ -40,12 +41,9 @@ public sealed class PromptsCapability /// along with any prompts defined in . /// [JsonIgnore] - [Obsolete($"Use {nameof(McpServerHandlers.ListPromptsHandler)} instead.")] - public McpRequestHandler? ListPromptsHandler - { - get => throw new NotSupportedException($"Use {nameof(McpServerHandlers.ListPromptsHandler)} instead."); - set => throw new NotSupportedException($"Use {nameof(McpServerHandlers.ListPromptsHandler)} instead."); - } + [Obsolete($"Use {nameof(McpServerOptions.Handlers.ListPromptsHandler)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [EditorBrowsable(EditorBrowsableState.Never)] + public McpRequestHandler? ListPromptsHandler { get; set; } /// /// Gets or sets the handler for requests. @@ -62,12 +60,9 @@ public McpRequestHandler? ListPromp /// /// [JsonIgnore] - [Obsolete($"Use {nameof(McpServerHandlers.GetPromptHandler)} instead.")] - public McpRequestHandler? GetPromptHandler - { - get => throw new NotSupportedException($"Use {nameof(McpServerHandlers.GetPromptHandler)} instead."); - set => throw new NotSupportedException($"Use {nameof(McpServerHandlers.GetPromptHandler)} instead."); - } + [Obsolete($"Use {nameof(McpServerOptions.Handlers.GetPromptHandler)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [EditorBrowsable(EditorBrowsableState.Never)] + public McpRequestHandler? GetPromptHandler { get; set; } /// /// Gets or sets a collection of prompts that will be served by the server. @@ -88,10 +83,7 @@ public McpRequestHandler? GetPromptHand /// /// [JsonIgnore] - [Obsolete($"Use {nameof(McpServerOptions.PromptCollection)} instead.")] - public McpServerPrimitiveCollection? PromptCollection - { - get => throw new NotSupportedException($"Use {nameof(McpServerOptions.PromptCollection)} instead."); - set => throw new NotSupportedException($"Use {nameof(McpServerOptions.PromptCollection)} instead."); - } + [Obsolete($"Use {nameof(McpServerOptions.PromptCollection)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [EditorBrowsable(EditorBrowsableState.Never)] + public McpServerPrimitiveCollection? PromptCollection { get; set; } } \ No newline at end of file diff --git a/src/ModelContextProtocol.Core/Protocol/ResourcesCapability.cs b/src/ModelContextProtocol.Core/Protocol/ResourcesCapability.cs index 5aa664bc3..d59d838c6 100644 --- a/src/ModelContextProtocol.Core/Protocol/ResourcesCapability.cs +++ b/src/ModelContextProtocol.Core/Protocol/ResourcesCapability.cs @@ -1,4 +1,5 @@ using ModelContextProtocol.Server; +using System.ComponentModel; using System.Text.Json.Serialization; namespace ModelContextProtocol.Protocol; @@ -39,12 +40,9 @@ public sealed class ResourcesCapability /// allowing clients to discover available resource types and their access patterns. /// [JsonIgnore] - [Obsolete($"Use {nameof(McpServerHandlers.ListResourceTemplatesHandler)} instead.")] - public McpRequestHandler? ListResourceTemplatesHandler - { - get => throw new NotSupportedException($"Use {nameof(McpServerHandlers.ListResourceTemplatesHandler)} instead."); - set => throw new NotSupportedException($"Use {nameof(McpServerHandlers.ListResourceTemplatesHandler)} instead."); - } + [Obsolete($"Use {nameof(McpServerOptions.Handlers.ListResourceTemplatesHandler)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [EditorBrowsable(EditorBrowsableState.Never)] + public McpRequestHandler? ListResourceTemplatesHandler { get; set; } /// /// Gets or sets the handler for requests. @@ -54,12 +52,9 @@ public McpRequestHandler with the matching resources. /// [JsonIgnore] - [Obsolete($"Use {nameof(McpServerHandlers.ListResourcesHandler)} instead.")] - public McpRequestHandler? ListResourcesHandler - { - get => throw new NotSupportedException($"Use {nameof(McpServerHandlers.ListResourcesHandler)} instead."); - set => throw new NotSupportedException($"Use {nameof(McpServerHandlers.ListResourcesHandler)} instead."); - } + [Obsolete($"Use {nameof(McpServerOptions.Handlers.ListResourcesHandler)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [EditorBrowsable(EditorBrowsableState.Never)] + public McpRequestHandler? ListResourcesHandler { get; set; } /// /// Gets or sets the handler for requests. @@ -71,12 +66,9 @@ public McpRequestHandler? ListR /// its contents in a ReadResourceResult object. /// [JsonIgnore] - [Obsolete($"Use {nameof(McpServerHandlers.ReadResourceHandler)} instead.")] - public McpRequestHandler? ReadResourceHandler - { - get => throw new NotSupportedException($"Use {nameof(McpServerHandlers.ReadResourceHandler)} instead."); - set => throw new NotSupportedException($"Use {nameof(McpServerHandlers.ReadResourceHandler)} instead."); - } + [Obsolete($"Use {nameof(McpServerOptions.Handlers.ReadResourceHandler)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [EditorBrowsable(EditorBrowsableState.Never)] + public McpRequestHandler? ReadResourceHandler { get; set; } /// /// Gets or sets the handler for requests. @@ -89,12 +81,9 @@ public McpRequestHandler? ReadRes /// requiring polling. /// [JsonIgnore] - [Obsolete($"Use {nameof(McpServerHandlers.SubscribeToResourcesHandler)} instead.")] - public McpRequestHandler? SubscribeToResourcesHandler - { - get => throw new NotSupportedException($"Use {nameof(McpServerHandlers.SubscribeToResourcesHandler)} instead."); - set => throw new NotSupportedException($"Use {nameof(McpServerHandlers.SubscribeToResourcesHandler)} instead."); - } + [Obsolete($"Use {nameof(McpServerOptions.Handlers.SubscribeToResourcesHandler)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [EditorBrowsable(EditorBrowsableState.Never)] + public McpRequestHandler? SubscribeToResourcesHandler { get; set; } /// /// Gets or sets the handler for requests. @@ -105,12 +94,9 @@ public McpRequestHandler? SubscribeToResour /// about the specified resource. /// [JsonIgnore] - [Obsolete($"Use {nameof(McpServerHandlers.UnsubscribeFromResourcesHandler)} instead.")] - public McpRequestHandler? UnsubscribeFromResourcesHandler - { - get => throw new NotSupportedException($"Use {nameof(McpServerHandlers.UnsubscribeFromResourcesHandler)} instead."); - set => throw new NotSupportedException($"Use {nameof(McpServerHandlers.UnsubscribeFromResourcesHandler)} instead."); - } + [Obsolete($"Use {nameof(McpServerOptions.Handlers.UnsubscribeFromResourcesHandler)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [EditorBrowsable(EditorBrowsableState.Never)] + public McpRequestHandler? UnsubscribeFromResourcesHandler { get; set; } /// /// Gets or sets a collection of resources served by the server. @@ -128,10 +114,7 @@ public McpRequestHandler? UnsubscribeFrom /// /// [JsonIgnore] - [Obsolete($"Use {nameof(McpServerOptions.ResourceCollection)} instead.")] - public McpServerResourceCollection? ResourceCollection - { - get => throw new NotSupportedException($"Use {nameof(McpServerOptions.ResourceCollection)} instead."); - set => throw new NotSupportedException($"Use {nameof(McpServerOptions.ResourceCollection)} instead."); - } + [Obsolete($"Use {nameof(McpServerOptions.ResourceCollection)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [EditorBrowsable(EditorBrowsableState.Never)] + public McpServerResourceCollection? ResourceCollection { get; set; } } \ No newline at end of file diff --git a/src/ModelContextProtocol.Core/Protocol/RootsCapability.cs b/src/ModelContextProtocol.Core/Protocol/RootsCapability.cs index 19365c1cc..cfbbbec0e 100644 --- a/src/ModelContextProtocol.Core/Protocol/RootsCapability.cs +++ b/src/ModelContextProtocol.Core/Protocol/RootsCapability.cs @@ -1,3 +1,4 @@ +using System.ComponentModel; using System.Text.Json.Serialization; using ModelContextProtocol.Client; @@ -41,10 +42,7 @@ public sealed class RootsCapability /// The handler receives request parameters and should return a containing the collection of available roots. /// [JsonIgnore] - [Obsolete($"Use {nameof(McpClientHandlers.RootsHandler)} instead.")] - public Func>? RootsHandler - { - get => throw new NotSupportedException($"Use {nameof(McpClientHandlers.RootsHandler)} instead."); - set => throw new NotSupportedException($"Use {nameof(McpClientHandlers.RootsHandler)} instead."); - } + [Obsolete($"Use {nameof(McpClientOptions.Handlers.RootsHandler)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [EditorBrowsable(EditorBrowsableState.Never)] + public Func>? RootsHandler { get; set; } } \ No newline at end of file diff --git a/src/ModelContextProtocol.Core/Protocol/SamplingCapability.cs b/src/ModelContextProtocol.Core/Protocol/SamplingCapability.cs index 943587ba5..0c491dcf9 100644 --- a/src/ModelContextProtocol.Core/Protocol/SamplingCapability.cs +++ b/src/ModelContextProtocol.Core/Protocol/SamplingCapability.cs @@ -1,3 +1,4 @@ +using System.ComponentModel; using System.Text.Json.Serialization; using Microsoft.Extensions.AI; using ModelContextProtocol.Client; @@ -42,10 +43,7 @@ public sealed class SamplingCapability /// /// [JsonIgnore] - [Obsolete($"Use {nameof(McpClientHandlers.SamplingHandler)} instead.")] - public Func, CancellationToken, ValueTask>? SamplingHandler - { - get => throw new NotSupportedException($"Use {nameof(McpClientHandlers.SamplingHandler)} instead."); - set => throw new NotSupportedException($"Use {nameof(McpClientHandlers.SamplingHandler)} instead."); - } + [Obsolete($"Use {nameof(McpClientOptions.Handlers.SamplingHandler)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [EditorBrowsable(EditorBrowsableState.Never)] + public Func, CancellationToken, ValueTask>? SamplingHandler { get; set; } } \ No newline at end of file diff --git a/src/ModelContextProtocol.Core/Protocol/ServerCapabilities.cs b/src/ModelContextProtocol.Core/Protocol/ServerCapabilities.cs index a8e08c101..62405faf6 100644 --- a/src/ModelContextProtocol.Core/Protocol/ServerCapabilities.cs +++ b/src/ModelContextProtocol.Core/Protocol/ServerCapabilities.cs @@ -1,3 +1,4 @@ +using System.ComponentModel; using System.Text.Json.Serialization; using ModelContextProtocol.Server; @@ -83,10 +84,7 @@ public sealed class ServerCapabilities /// /// [JsonIgnore] - [Obsolete($"Use {nameof(McpServerHandlers.NotificationHandlers)} instead.")] - public IEnumerable>>? NotificationHandlers - { - get => throw new NotSupportedException($"Use {nameof(McpServerHandlers.NotificationHandlers)} instead."); - set => throw new NotSupportedException($"Use {nameof(McpServerHandlers.NotificationHandlers)} instead."); - } + [Obsolete($"Use {nameof(McpServerOptions.Handlers.NotificationHandlers)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [EditorBrowsable(EditorBrowsableState.Never)] + public IEnumerable>>? NotificationHandlers { get; set; } } diff --git a/src/ModelContextProtocol.Core/Protocol/ToolsCapability.cs b/src/ModelContextProtocol.Core/Protocol/ToolsCapability.cs index 3f5d69587..f19114121 100644 --- a/src/ModelContextProtocol.Core/Protocol/ToolsCapability.cs +++ b/src/ModelContextProtocol.Core/Protocol/ToolsCapability.cs @@ -1,3 +1,4 @@ +using System.ComponentModel; using System.Text.Json.Serialization; using ModelContextProtocol.Server; @@ -33,12 +34,9 @@ public sealed class ToolsCapability /// and the tools from the collection will be combined to form the complete list of available tools. /// [JsonIgnore] - [Obsolete($"Use {nameof(McpServerHandlers.ListToolsHandler)} instead.")] - public McpRequestHandler? ListToolsHandler - { - get => throw new NotSupportedException($"Use {nameof(McpServerHandlers.ListToolsHandler)} instead."); - set => throw new NotSupportedException($"Use {nameof(McpServerHandlers.ListToolsHandler)} instead."); - } + [Obsolete($"Use {nameof(McpServerOptions.Handlers.ListToolsHandler)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [EditorBrowsable(EditorBrowsableState.Never)] + public McpRequestHandler? ListToolsHandler { get; set; } /// /// Gets or sets the handler for requests. @@ -50,12 +48,9 @@ public McpRequestHandler? ListToolsHand /// being called and its arguments, and should return a with the execution results. /// [JsonIgnore] - [Obsolete($"Use {nameof(McpServerHandlers.CallToolHandler)} instead.")] - public McpRequestHandler? CallToolHandler - { - get => throw new NotSupportedException($"Use {nameof(McpServerHandlers.CallToolHandler)} instead."); - set => throw new NotSupportedException($"Use {nameof(McpServerHandlers.CallToolHandler)} instead."); - } + [Obsolete($"Use {nameof(McpServerOptions.Handlers.CallToolHandler)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [EditorBrowsable(EditorBrowsableState.Never)] + public McpRequestHandler? CallToolHandler { get; set; } /// /// Gets or sets a collection of tools served by the server. @@ -69,10 +64,7 @@ public McpRequestHandler? CallToolHandler /// will be invoked as a fallback. /// [JsonIgnore] - [Obsolete($"Use {nameof(McpServerOptions.ToolCollection)} instead.")] - public McpServerPrimitiveCollection? ToolCollection - { - get => throw new NotSupportedException($"Use {nameof(McpServerOptions.ToolCollection)} instead."); - set => throw new NotSupportedException($"Use {nameof(McpServerOptions.ToolCollection)} instead."); - } + [Obsolete($"Use {nameof(McpServerOptions.ToolCollection)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [EditorBrowsable(EditorBrowsableState.Never)] + public McpServerPrimitiveCollection? ToolCollection { get; set; } } \ No newline at end of file diff --git a/src/ModelContextProtocol.Core/Server/McpServerImpl.cs b/src/ModelContextProtocol.Core/Server/McpServerImpl.cs index 0771000cf..c152d3a0a 100644 --- a/src/ModelContextProtocol.Core/Server/McpServerImpl.cs +++ b/src/ModelContextProtocol.Core/Server/McpServerImpl.cs @@ -231,8 +231,13 @@ private void ConfigureInitialize(McpServerOptions options) private void ConfigureCompletion(McpServerOptions options) { var completeHandler = options.Handlers.CompleteHandler; + var completionsCapability = options.Capabilities?.Completions; - if (completeHandler is null && options.Capabilities?.Completions is null) +#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; } @@ -264,6 +269,14 @@ private void ConfigureResources(McpServerOptions options) 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) @@ -425,6 +438,11 @@ private void ConfigurePrompts(McpServerOptions options) 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) { @@ -508,6 +526,11 @@ private void ConfigureTools(McpServerOptions options) 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) { @@ -620,6 +643,10 @@ 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.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) { From 836c2c8e852169a0df49a73d16d65d52427bd956 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Cant=C3=BA?= Date: Tue, 23 Sep 2025 12:04:31 -0500 Subject: [PATCH 5/6] Update Obsolete messages to indicate the member will be removed --- .../Client/IMcpClient.cs | 2 +- .../Client/McpClientExtensions.cs | 38 +++++++++---------- .../Client/McpClientFactory.cs | 2 +- src/ModelContextProtocol.Core/IMcpEndpoint.cs | 2 +- .../McpEndpointExtensions.cs | 8 ++-- .../Protocol/ClientCapabilities.cs | 2 +- .../Protocol/CompletionsCapability.cs | 2 +- .../Protocol/ElicitationCapability.cs | 2 +- .../Protocol/LoggingCapability.cs | 2 +- .../Protocol/PromptsCapability.cs | 6 +-- .../Protocol/ResourcesCapability.cs | 12 +++--- .../Protocol/RootsCapability.cs | 2 +- .../Protocol/SamplingCapability.cs | 2 +- .../Protocol/ServerCapabilities.cs | 2 +- .../Protocol/ToolsCapability.cs | 6 +-- .../Server/IMcpServer.cs | 2 +- .../Server/McpServerExtensions.cs | 12 +++--- .../Server/McpServerFactory.cs | 2 +- 18 files changed, 53 insertions(+), 53 deletions(-) diff --git a/src/ModelContextProtocol.Core/Client/IMcpClient.cs b/src/ModelContextProtocol.Core/Client/IMcpClient.cs index 43930c030..47d664480 100644 --- a/src/ModelContextProtocol.Core/Client/IMcpClient.cs +++ b/src/ModelContextProtocol.Core/Client/IMcpClient.cs @@ -5,7 +5,7 @@ namespace ModelContextProtocol.Client; /// /// Represents an instance of a Model Context Protocol (MCP) client that connects to and communicates with an MCP server. /// -[Obsolete($"Use {nameof(McpClient)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 +[Obsolete($"Use {nameof(McpClient)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 public interface IMcpClient : IMcpEndpoint { /// diff --git a/src/ModelContextProtocol.Core/Client/McpClientExtensions.cs b/src/ModelContextProtocol.Core/Client/McpClientExtensions.cs index 817202a40..94b7d273f 100644 --- a/src/ModelContextProtocol.Core/Client/McpClientExtensions.cs +++ b/src/ModelContextProtocol.Core/Client/McpClientExtensions.cs @@ -84,7 +84,7 @@ public static class McpClientExtensions /// /// is . /// Thrown when the server cannot be reached or returns an error response. - [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.PingAsync)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.PingAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 public static Task PingAsync(this IMcpClient client, CancellationToken cancellationToken = default) => AsClientOrThrow(client).PingAsync(cancellationToken); @@ -127,7 +127,7 @@ public static Task PingAsync(this IMcpClient client, CancellationToken cancellat /// /// /// is . - [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.ListToolsAsync)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.ListToolsAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 public static ValueTask> ListToolsAsync( this IMcpClient client, JsonSerializerOptions? serializerOptions = null, @@ -166,7 +166,7 @@ public static ValueTask> ListToolsAsync( /// /// /// is . - [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.EnumerateToolsAsync)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.EnumerateToolsAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 public static IAsyncEnumerable EnumerateToolsAsync( this IMcpClient client, JsonSerializerOptions? serializerOptions = null, @@ -190,7 +190,7 @@ public static IAsyncEnumerable EnumerateToolsAsync( /// /// /// is . - [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.ListPromptsAsync)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.ListPromptsAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 public static ValueTask> ListPromptsAsync( this IMcpClient client, CancellationToken cancellationToken = default) => AsClientOrThrow(client).ListPromptsAsync(cancellationToken); @@ -222,7 +222,7 @@ public static ValueTask> ListPromptsAsync( /// /// /// is . - [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.EnumeratePromptsAsync)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.EnumeratePromptsAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 public static IAsyncEnumerable EnumeratePromptsAsync( this IMcpClient client, CancellationToken cancellationToken = default) => AsClientOrThrow(client).EnumeratePromptsAsync(cancellationToken); @@ -252,7 +252,7 @@ public static IAsyncEnumerable EnumeratePromptsAsync( /// /// Thrown when the prompt does not exist, when required arguments are missing, or when the server encounters an error processing the prompt. /// is . - [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.GetPromptAsync)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.GetPromptAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 public static ValueTask GetPromptAsync( this IMcpClient client, string name, @@ -278,7 +278,7 @@ public static ValueTask GetPromptAsync( /// /// /// is . - [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.ListResourceTemplatesAsync)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.ListResourceTemplatesAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 public static ValueTask> ListResourceTemplatesAsync( this IMcpClient client, CancellationToken cancellationToken = default) => AsClientOrThrow(client).ListResourceTemplatesAsync(cancellationToken); @@ -310,7 +310,7 @@ public static ValueTask> ListResourceTemplatesA /// /// /// is . - [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.EnumerateResourceTemplatesAsync)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.EnumerateResourceTemplatesAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 public static IAsyncEnumerable EnumerateResourceTemplatesAsync( this IMcpClient client, CancellationToken cancellationToken = default) => AsClientOrThrow(client).EnumerateResourceTemplatesAsync(cancellationToken); @@ -344,7 +344,7 @@ public static IAsyncEnumerable EnumerateResourceTempl /// /// /// is . - [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.ListResourcesAsync)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.ListResourcesAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 public static ValueTask> ListResourcesAsync( this IMcpClient client, CancellationToken cancellationToken = default) => AsClientOrThrow(client).ListResourcesAsync(cancellationToken); @@ -376,7 +376,7 @@ public static ValueTask> ListResourcesAsync( /// /// /// is . - [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.EnumerateResourcesAsync)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.EnumerateResourcesAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 public static IAsyncEnumerable EnumerateResourcesAsync( this IMcpClient client, CancellationToken cancellationToken = default) => AsClientOrThrow(client).EnumerateResourcesAsync(cancellationToken); @@ -390,7 +390,7 @@ public static IAsyncEnumerable EnumerateResourcesAsync( /// is . /// is . /// is empty or composed entirely of whitespace. - [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.ReadResourceAsync)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.ReadResourceAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 public static ValueTask ReadResourceAsync( this IMcpClient client, string uri, CancellationToken cancellationToken = default) => AsClientOrThrow(client).ReadResourceAsync(uri, cancellationToken); @@ -403,7 +403,7 @@ public static ValueTask ReadResourceAsync( /// The to monitor for cancellation requests. The default is . /// is . /// is . - [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.ReadResourceAsync)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.ReadResourceAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 public static ValueTask ReadResourceAsync( this IMcpClient client, Uri uri, CancellationToken cancellationToken = default) => AsClientOrThrow(client).ReadResourceAsync(uri, cancellationToken); @@ -418,7 +418,7 @@ public static ValueTask ReadResourceAsync( /// is . /// is . /// is empty or composed entirely of whitespace. - [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.ReadResourceAsync)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.ReadResourceAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 public static ValueTask ReadResourceAsync( this IMcpClient client, string uriTemplate, IReadOnlyDictionary arguments, CancellationToken cancellationToken = default) => AsClientOrThrow(client).ReadResourceAsync(uriTemplate, arguments, cancellationToken); @@ -452,7 +452,7 @@ public static ValueTask ReadResourceAsync( /// is . /// is empty or composed entirely of whitespace. /// The server returned an error response. - [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.CompleteAsync)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.CompleteAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 public static ValueTask CompleteAsync(this IMcpClient client, Reference reference, string argumentName, string argumentValue, CancellationToken cancellationToken = default) => AsClientOrThrow(client).CompleteAsync(reference, argumentName, argumentValue, cancellationToken); @@ -481,7 +481,7 @@ public static ValueTask CompleteAsync(this IMcpClient client, Re /// is . /// is . /// is empty or composed entirely of whitespace. - [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.SubscribeToResourceAsync)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.SubscribeToResourceAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 public static Task SubscribeToResourceAsync(this IMcpClient client, string uri, CancellationToken cancellationToken = default) => AsClientOrThrow(client).SubscribeToResourceAsync(uri, cancellationToken); @@ -509,7 +509,7 @@ public static Task SubscribeToResourceAsync(this IMcpClient client, string uri, /// /// is . /// is . - [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.SubscribeToResourceAsync)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.SubscribeToResourceAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 public static Task SubscribeToResourceAsync(this IMcpClient client, Uri uri, CancellationToken cancellationToken = default) => AsClientOrThrow(client).SubscribeToResourceAsync(uri, cancellationToken); @@ -537,7 +537,7 @@ public static Task SubscribeToResourceAsync(this IMcpClient client, Uri uri, Can /// is . /// is . /// is empty or composed entirely of whitespace. - [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.UnsubscribeFromResourceAsync)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.UnsubscribeFromResourceAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 public static Task UnsubscribeFromResourceAsync(this IMcpClient client, string uri, CancellationToken cancellationToken = default) => AsClientOrThrow(client).UnsubscribeFromResourceAsync(uri, cancellationToken); @@ -564,7 +564,7 @@ public static Task UnsubscribeFromResourceAsync(this IMcpClient client, string u /// /// is . /// is . - [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.UnsubscribeFromResourceAsync)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.UnsubscribeFromResourceAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 public static Task UnsubscribeFromResourceAsync(this IMcpClient client, Uri uri, CancellationToken cancellationToken = default) => AsClientOrThrow(client).UnsubscribeFromResourceAsync(uri, cancellationToken); @@ -603,7 +603,7 @@ public static Task UnsubscribeFromResourceAsync(this IMcpClient client, Uri uri, /// }); /// /// - [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.CallToolAsync)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.CallToolAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 public static ValueTask CallToolAsync( this IMcpClient client, string toolName, diff --git a/src/ModelContextProtocol.Core/Client/McpClientFactory.cs b/src/ModelContextProtocol.Core/Client/McpClientFactory.cs index 6934eb2b5..e5e19ebe7 100644 --- a/src/ModelContextProtocol.Core/Client/McpClientFactory.cs +++ b/src/ModelContextProtocol.Core/Client/McpClientFactory.cs @@ -10,7 +10,7 @@ namespace ModelContextProtocol.Client; /// that connect to MCP servers. It handles the creation and connection /// of appropriate implementations through the supplied transport. /// -[Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.CreateAsync)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 +[Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.CreateAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 public static partial class McpClientFactory { /// Creates an , connecting it to the specified server. diff --git a/src/ModelContextProtocol.Core/IMcpEndpoint.cs b/src/ModelContextProtocol.Core/IMcpEndpoint.cs index beb96521f..a9d0fa9f5 100644 --- a/src/ModelContextProtocol.Core/IMcpEndpoint.cs +++ b/src/ModelContextProtocol.Core/IMcpEndpoint.cs @@ -26,7 +26,7 @@ namespace ModelContextProtocol; /// All MCP endpoints should be properly disposed after use as they implement . /// /// -[Obsolete($"Use {nameof(McpSession)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 +[Obsolete($"Use {nameof(McpSession)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 public interface IMcpEndpoint : IAsyncDisposable { /// Gets an identifier associated with the current MCP session. diff --git a/src/ModelContextProtocol.Core/McpEndpointExtensions.cs b/src/ModelContextProtocol.Core/McpEndpointExtensions.cs index f51289ac2..d4494fa0f 100644 --- a/src/ModelContextProtocol.Core/McpEndpointExtensions.cs +++ b/src/ModelContextProtocol.Core/McpEndpointExtensions.cs @@ -34,7 +34,7 @@ public static class McpEndpointExtensions /// The options governing request serialization. /// The to monitor for cancellation requests. The default is . /// A task that represents the asynchronous operation. The task result contains the deserialized result. - [Obsolete($"Use {nameof(McpSession)}.{nameof(McpSession.SendRequestAsync)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [Obsolete($"Use {nameof(McpSession)}.{nameof(McpSession.SendRequestAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 public static ValueTask SendRequestAsync( this IMcpEndpoint endpoint, string method, @@ -59,7 +59,7 @@ public static ValueTask SendRequestAsync( /// changes in state. /// /// - [Obsolete($"Use {nameof(McpSession)}.{nameof(McpSession.SendNotificationAsync)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [Obsolete($"Use {nameof(McpSession)}.{nameof(McpSession.SendNotificationAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 public static Task SendNotificationAsync(this IMcpEndpoint client, string method, CancellationToken cancellationToken = default) => AsSessionOrThrow(client).SendNotificationAsync(method, cancellationToken); @@ -87,7 +87,7 @@ public static Task SendNotificationAsync(this IMcpEndpoint client, string method /// but custom methods can also be used for application-specific notifications. /// /// - [Obsolete($"Use {nameof(McpSession)}.{nameof(McpSession.SendNotificationAsync)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [Obsolete($"Use {nameof(McpSession)}.{nameof(McpSession.SendNotificationAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 public static Task SendNotificationAsync( this IMcpEndpoint endpoint, string method, @@ -115,7 +115,7 @@ public static Task SendNotificationAsync( /// Progress notifications are sent asynchronously and don't block the operation from continuing. /// /// - [Obsolete($"Use {nameof(McpSession)}.{nameof(McpSession.NotifyProgressAsync)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [Obsolete($"Use {nameof(McpSession)}.{nameof(McpSession.NotifyProgressAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 public static Task NotifyProgressAsync( this IMcpEndpoint endpoint, ProgressToken progressToken, diff --git a/src/ModelContextProtocol.Core/Protocol/ClientCapabilities.cs b/src/ModelContextProtocol.Core/Protocol/ClientCapabilities.cs index 410fda41b..f133e8dca 100644 --- a/src/ModelContextProtocol.Core/Protocol/ClientCapabilities.cs +++ b/src/ModelContextProtocol.Core/Protocol/ClientCapabilities.cs @@ -85,7 +85,7 @@ public sealed class ClientCapabilities /// /// [JsonIgnore] - [Obsolete($"Use {nameof(McpClientOptions.Handlers.NotificationHandlers)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [Obsolete($"Use {nameof(McpClientOptions.Handlers.NotificationHandlers)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 [EditorBrowsable(EditorBrowsableState.Never)] public IEnumerable>>? NotificationHandlers { get; set; } } \ No newline at end of file diff --git a/src/ModelContextProtocol.Core/Protocol/CompletionsCapability.cs b/src/ModelContextProtocol.Core/Protocol/CompletionsCapability.cs index 63841f95d..8e28e67d3 100644 --- a/src/ModelContextProtocol.Core/Protocol/CompletionsCapability.cs +++ b/src/ModelContextProtocol.Core/Protocol/CompletionsCapability.cs @@ -37,7 +37,7 @@ public sealed class CompletionsCapability /// and should return appropriate completion suggestions. /// [JsonIgnore] - [Obsolete($"Use {nameof(McpServerOptions.Handlers.CompleteHandler)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [Obsolete($"Use {nameof(McpServerOptions.Handlers.CompleteHandler)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 [EditorBrowsable(EditorBrowsableState.Never)] public McpRequestHandler? CompleteHandler { get; set; } } \ No newline at end of file diff --git a/src/ModelContextProtocol.Core/Protocol/ElicitationCapability.cs b/src/ModelContextProtocol.Core/Protocol/ElicitationCapability.cs index 9839eb317..e096e0e09 100644 --- a/src/ModelContextProtocol.Core/Protocol/ElicitationCapability.cs +++ b/src/ModelContextProtocol.Core/Protocol/ElicitationCapability.cs @@ -37,7 +37,7 @@ public sealed class ElicitationCapability /// /// [JsonIgnore] - [Obsolete($"Use {nameof(McpClientOptions.Handlers.ElicitationHandler)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [Obsolete($"Use {nameof(McpClientOptions.Handlers.ElicitationHandler)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 [EditorBrowsable(EditorBrowsableState.Never)] public Func>? ElicitationHandler { get; set; } } \ No newline at end of file diff --git a/src/ModelContextProtocol.Core/Protocol/LoggingCapability.cs b/src/ModelContextProtocol.Core/Protocol/LoggingCapability.cs index af119b7f3..c166a223a 100644 --- a/src/ModelContextProtocol.Core/Protocol/LoggingCapability.cs +++ b/src/ModelContextProtocol.Core/Protocol/LoggingCapability.cs @@ -24,7 +24,7 @@ public sealed class LoggingCapability /// Gets or sets the handler for set logging level requests from clients. /// [JsonIgnore] - [Obsolete($"Use {nameof(McpServerOptions.Handlers.SetLoggingLevelHandler)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [Obsolete($"Use {nameof(McpServerOptions.Handlers.SetLoggingLevelHandler)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 [EditorBrowsable(EditorBrowsableState.Never)] public McpRequestHandler? SetLoggingLevelHandler { get; set; } } \ No newline at end of file diff --git a/src/ModelContextProtocol.Core/Protocol/PromptsCapability.cs b/src/ModelContextProtocol.Core/Protocol/PromptsCapability.cs index 67bc3ff8d..223576254 100644 --- a/src/ModelContextProtocol.Core/Protocol/PromptsCapability.cs +++ b/src/ModelContextProtocol.Core/Protocol/PromptsCapability.cs @@ -41,7 +41,7 @@ public sealed class PromptsCapability /// along with any prompts defined in . /// [JsonIgnore] - [Obsolete($"Use {nameof(McpServerOptions.Handlers.ListPromptsHandler)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [Obsolete($"Use {nameof(McpServerOptions.Handlers.ListPromptsHandler)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 [EditorBrowsable(EditorBrowsableState.Never)] public McpRequestHandler? ListPromptsHandler { get; set; } @@ -60,7 +60,7 @@ public sealed class PromptsCapability /// /// [JsonIgnore] - [Obsolete($"Use {nameof(McpServerOptions.Handlers.GetPromptHandler)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [Obsolete($"Use {nameof(McpServerOptions.Handlers.GetPromptHandler)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 [EditorBrowsable(EditorBrowsableState.Never)] public McpRequestHandler? GetPromptHandler { get; set; } @@ -83,7 +83,7 @@ public sealed class PromptsCapability /// /// [JsonIgnore] - [Obsolete($"Use {nameof(McpServerOptions.PromptCollection)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [Obsolete($"Use {nameof(McpServerOptions.PromptCollection)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 [EditorBrowsable(EditorBrowsableState.Never)] public McpServerPrimitiveCollection? PromptCollection { get; set; } } \ No newline at end of file diff --git a/src/ModelContextProtocol.Core/Protocol/ResourcesCapability.cs b/src/ModelContextProtocol.Core/Protocol/ResourcesCapability.cs index d59d838c6..15ab02a06 100644 --- a/src/ModelContextProtocol.Core/Protocol/ResourcesCapability.cs +++ b/src/ModelContextProtocol.Core/Protocol/ResourcesCapability.cs @@ -40,7 +40,7 @@ public sealed class ResourcesCapability /// allowing clients to discover available resource types and their access patterns. /// [JsonIgnore] - [Obsolete($"Use {nameof(McpServerOptions.Handlers.ListResourceTemplatesHandler)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [Obsolete($"Use {nameof(McpServerOptions.Handlers.ListResourceTemplatesHandler)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 [EditorBrowsable(EditorBrowsableState.Never)] public McpRequestHandler? ListResourceTemplatesHandler { get; set; } @@ -52,7 +52,7 @@ public sealed class ResourcesCapability /// The implementation should return a with the matching resources. /// [JsonIgnore] - [Obsolete($"Use {nameof(McpServerOptions.Handlers.ListResourcesHandler)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [Obsolete($"Use {nameof(McpServerOptions.Handlers.ListResourcesHandler)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 [EditorBrowsable(EditorBrowsableState.Never)] public McpRequestHandler? ListResourcesHandler { get; set; } @@ -66,7 +66,7 @@ public sealed class ResourcesCapability /// its contents in a ReadResourceResult object. /// [JsonIgnore] - [Obsolete($"Use {nameof(McpServerOptions.Handlers.ReadResourceHandler)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [Obsolete($"Use {nameof(McpServerOptions.Handlers.ReadResourceHandler)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 [EditorBrowsable(EditorBrowsableState.Never)] public McpRequestHandler? ReadResourceHandler { get; set; } @@ -81,7 +81,7 @@ public sealed class ResourcesCapability /// requiring polling. /// [JsonIgnore] - [Obsolete($"Use {nameof(McpServerOptions.Handlers.SubscribeToResourcesHandler)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [Obsolete($"Use {nameof(McpServerOptions.Handlers.SubscribeToResourcesHandler)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 [EditorBrowsable(EditorBrowsableState.Never)] public McpRequestHandler? SubscribeToResourcesHandler { get; set; } @@ -94,7 +94,7 @@ public sealed class ResourcesCapability /// about the specified resource. /// [JsonIgnore] - [Obsolete($"Use {nameof(McpServerOptions.Handlers.UnsubscribeFromResourcesHandler)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [Obsolete($"Use {nameof(McpServerOptions.Handlers.UnsubscribeFromResourcesHandler)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 [EditorBrowsable(EditorBrowsableState.Never)] public McpRequestHandler? UnsubscribeFromResourcesHandler { get; set; } @@ -114,7 +114,7 @@ public sealed class ResourcesCapability /// /// [JsonIgnore] - [Obsolete($"Use {nameof(McpServerOptions.ResourceCollection)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [Obsolete($"Use {nameof(McpServerOptions.ResourceCollection)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 [EditorBrowsable(EditorBrowsableState.Never)] public McpServerResourceCollection? ResourceCollection { get; set; } } \ No newline at end of file diff --git a/src/ModelContextProtocol.Core/Protocol/RootsCapability.cs b/src/ModelContextProtocol.Core/Protocol/RootsCapability.cs index cfbbbec0e..8e2bcacfe 100644 --- a/src/ModelContextProtocol.Core/Protocol/RootsCapability.cs +++ b/src/ModelContextProtocol.Core/Protocol/RootsCapability.cs @@ -42,7 +42,7 @@ public sealed class RootsCapability /// The handler receives request parameters and should return a containing the collection of available roots. /// [JsonIgnore] - [Obsolete($"Use {nameof(McpClientOptions.Handlers.RootsHandler)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [Obsolete($"Use {nameof(McpClientOptions.Handlers.RootsHandler)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 [EditorBrowsable(EditorBrowsableState.Never)] public Func>? RootsHandler { get; set; } } \ No newline at end of file diff --git a/src/ModelContextProtocol.Core/Protocol/SamplingCapability.cs b/src/ModelContextProtocol.Core/Protocol/SamplingCapability.cs index 0c491dcf9..e917b2af9 100644 --- a/src/ModelContextProtocol.Core/Protocol/SamplingCapability.cs +++ b/src/ModelContextProtocol.Core/Protocol/SamplingCapability.cs @@ -43,7 +43,7 @@ public sealed class SamplingCapability /// /// [JsonIgnore] - [Obsolete($"Use {nameof(McpClientOptions.Handlers.SamplingHandler)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [Obsolete($"Use {nameof(McpClientOptions.Handlers.SamplingHandler)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 [EditorBrowsable(EditorBrowsableState.Never)] public Func, CancellationToken, ValueTask>? SamplingHandler { get; set; } } \ No newline at end of file diff --git a/src/ModelContextProtocol.Core/Protocol/ServerCapabilities.cs b/src/ModelContextProtocol.Core/Protocol/ServerCapabilities.cs index 62405faf6..ffe38d221 100644 --- a/src/ModelContextProtocol.Core/Protocol/ServerCapabilities.cs +++ b/src/ModelContextProtocol.Core/Protocol/ServerCapabilities.cs @@ -84,7 +84,7 @@ public sealed class ServerCapabilities /// /// [JsonIgnore] - [Obsolete($"Use {nameof(McpServerOptions.Handlers.NotificationHandlers)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [Obsolete($"Use {nameof(McpServerOptions.Handlers.NotificationHandlers)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 [EditorBrowsable(EditorBrowsableState.Never)] public IEnumerable>>? NotificationHandlers { get; set; } } diff --git a/src/ModelContextProtocol.Core/Protocol/ToolsCapability.cs b/src/ModelContextProtocol.Core/Protocol/ToolsCapability.cs index f19114121..b6903a7db 100644 --- a/src/ModelContextProtocol.Core/Protocol/ToolsCapability.cs +++ b/src/ModelContextProtocol.Core/Protocol/ToolsCapability.cs @@ -34,7 +34,7 @@ public sealed class ToolsCapability /// and the tools from the collection will be combined to form the complete list of available tools. /// [JsonIgnore] - [Obsolete($"Use {nameof(McpServerOptions.Handlers.ListToolsHandler)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [Obsolete($"Use {nameof(McpServerOptions.Handlers.ListToolsHandler)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 [EditorBrowsable(EditorBrowsableState.Never)] public McpRequestHandler? ListToolsHandler { get; set; } @@ -48,7 +48,7 @@ public sealed class ToolsCapability /// being called and its arguments, and should return a with the execution results. /// [JsonIgnore] - [Obsolete($"Use {nameof(McpServerOptions.Handlers.CallToolHandler)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [Obsolete($"Use {nameof(McpServerOptions.Handlers.CallToolHandler)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 [EditorBrowsable(EditorBrowsableState.Never)] public McpRequestHandler? CallToolHandler { get; set; } @@ -64,7 +64,7 @@ public sealed class ToolsCapability /// will be invoked as a fallback. /// [JsonIgnore] - [Obsolete($"Use {nameof(McpServerOptions.ToolCollection)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [Obsolete($"Use {nameof(McpServerOptions.ToolCollection)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 [EditorBrowsable(EditorBrowsableState.Never)] public McpServerPrimitiveCollection? ToolCollection { get; set; } } \ No newline at end of file diff --git a/src/ModelContextProtocol.Core/Server/IMcpServer.cs b/src/ModelContextProtocol.Core/Server/IMcpServer.cs index 016ad90b3..0d1e3d31a 100644 --- a/src/ModelContextProtocol.Core/Server/IMcpServer.cs +++ b/src/ModelContextProtocol.Core/Server/IMcpServer.cs @@ -5,7 +5,7 @@ namespace ModelContextProtocol.Server; /// /// Represents an instance of a Model Context Protocol (MCP) server that connects to and communicates with an MCP client. /// -[Obsolete($"Use {nameof(McpServer)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 +[Obsolete($"Use {nameof(McpServer)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 public interface IMcpServer : IMcpEndpoint { /// diff --git a/src/ModelContextProtocol.Core/Server/McpServerExtensions.cs b/src/ModelContextProtocol.Core/Server/McpServerExtensions.cs index 79d545285..6567b22c3 100644 --- a/src/ModelContextProtocol.Core/Server/McpServerExtensions.cs +++ b/src/ModelContextProtocol.Core/Server/McpServerExtensions.cs @@ -26,7 +26,7 @@ public static class McpServerExtensions /// It allows detailed control over sampling parameters including messages, system prompt, temperature, /// and token limits. /// - [Obsolete($"Use {nameof(McpServer)}.{nameof(McpServer.SampleAsync)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [Obsolete($"Use {nameof(McpServer)}.{nameof(McpServer.SampleAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 public static ValueTask SampleAsync( this IMcpServer server, CreateMessageRequestParams request, CancellationToken cancellationToken = default) => AsServerOrThrow(server).SampleAsync(request, cancellationToken); @@ -46,7 +46,7 @@ public static ValueTask SampleAsync( /// This method converts the provided chat messages into a format suitable for the sampling API, /// handling different content types such as text, images, and audio. /// - [Obsolete($"Use {nameof(McpServer)}.{nameof(McpServer.SampleAsync)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774] + [Obsolete($"Use {nameof(McpServer)}.{nameof(McpServer.SampleAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774] public static Task SampleAsync( this IMcpServer server, IEnumerable messages, ChatOptions? options = default, CancellationToken cancellationToken = default) @@ -59,14 +59,14 @@ public static Task SampleAsync( /// The that can be used to issue sampling requests to the client. /// is . /// The client does not support sampling. - [Obsolete($"Use {nameof(McpServer)}.{nameof(McpServer.AsSamplingChatClient)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [Obsolete($"Use {nameof(McpServer)}.{nameof(McpServer.AsSamplingChatClient)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 public static IChatClient AsSamplingChatClient(this IMcpServer server) => AsServerOrThrow(server).AsSamplingChatClient(); /// Gets an on which logged messages will be sent as notifications to the client. /// The server to wrap as an . /// An that can be used to log to the client.. - [Obsolete($"Use {nameof(McpServer)}.{nameof(McpServer.AsSamplingChatClient)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [Obsolete($"Use {nameof(McpServer)}.{nameof(McpServer.AsSamplingChatClient)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 public static ILoggerProvider AsClientLoggerProvider(this IMcpServer server) => AsServerOrThrow(server).AsClientLoggerProvider(); @@ -85,7 +85,7 @@ public static ILoggerProvider AsClientLoggerProvider(this IMcpServer server) /// navigated and accessed by the server. These resources might include file systems, databases, /// or other structured data sources that the client makes available through the protocol. /// - [Obsolete($"Use {nameof(McpServer)}.{nameof(McpServer.RequestRootsAsync)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [Obsolete($"Use {nameof(McpServer)}.{nameof(McpServer.RequestRootsAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 public static ValueTask RequestRootsAsync( this IMcpServer server, ListRootsRequestParams request, CancellationToken cancellationToken = default) => AsServerOrThrow(server).RequestRootsAsync(request, cancellationToken); @@ -102,7 +102,7 @@ public static ValueTask RequestRootsAsync( /// /// This method requires the client to support the elicitation capability. /// - [Obsolete($"Use {nameof(McpServer)}.{nameof(McpServer.ElicitAsync)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [Obsolete($"Use {nameof(McpServer)}.{nameof(McpServer.ElicitAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 public static ValueTask ElicitAsync( this IMcpServer server, ElicitRequestParams request, CancellationToken cancellationToken = default) => AsServerOrThrow(server).ElicitAsync(request, cancellationToken); diff --git a/src/ModelContextProtocol.Core/Server/McpServerFactory.cs b/src/ModelContextProtocol.Core/Server/McpServerFactory.cs index 00ecd8b13..269d2db88 100644 --- a/src/ModelContextProtocol.Core/Server/McpServerFactory.cs +++ b/src/ModelContextProtocol.Core/Server/McpServerFactory.cs @@ -10,7 +10,7 @@ namespace ModelContextProtocol.Server; /// This is the recommended way to create instances. /// The factory handles proper initialization of server instances with the required dependencies. /// -[Obsolete($"Use {nameof(McpServer)}.{nameof(McpServer.Create)} instead.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 +[Obsolete($"Use {nameof(McpServer)}.{nameof(McpServer.Create)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 public static class McpServerFactory { /// From cd916953400a8fd9dbf6eb906e214e86c8c8244d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Cant=C3=BA?= Date: Tue, 23 Sep 2025 12:23:44 -0500 Subject: [PATCH 6/6] Add EditorBrowsable Never to the remaining obsolete members --- .../Client/IMcpClient.cs | 2 ++ .../Client/McpClientExtensions.cs | 20 +++++++++++++++++++ .../Client/McpClientFactory.cs | 2 ++ src/ModelContextProtocol.Core/IMcpEndpoint.cs | 4 +++- .../McpEndpointExtensions.cs | 5 +++++ .../Server/IMcpServer.cs | 2 ++ .../Server/McpServerExtensions.cs | 9 ++++++++- .../Server/McpServerFactory.cs | 2 ++ 8 files changed, 44 insertions(+), 2 deletions(-) diff --git a/src/ModelContextProtocol.Core/Client/IMcpClient.cs b/src/ModelContextProtocol.Core/Client/IMcpClient.cs index 47d664480..141add86a 100644 --- a/src/ModelContextProtocol.Core/Client/IMcpClient.cs +++ b/src/ModelContextProtocol.Core/Client/IMcpClient.cs @@ -1,4 +1,5 @@ using ModelContextProtocol.Protocol; +using System.ComponentModel; namespace ModelContextProtocol.Client; @@ -6,6 +7,7 @@ namespace ModelContextProtocol.Client; /// Represents an instance of a Model Context Protocol (MCP) client that connects to and communicates with an MCP server. /// [Obsolete($"Use {nameof(McpClient)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 +[EditorBrowsable(EditorBrowsableState.Never)] public interface IMcpClient : IMcpEndpoint { /// diff --git a/src/ModelContextProtocol.Core/Client/McpClientExtensions.cs b/src/ModelContextProtocol.Core/Client/McpClientExtensions.cs index 94b7d273f..f0cd3c4f9 100644 --- a/src/ModelContextProtocol.Core/Client/McpClientExtensions.cs +++ b/src/ModelContextProtocol.Core/Client/McpClientExtensions.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.AI; using ModelContextProtocol.Protocol; +using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Text.Json; @@ -85,6 +86,7 @@ public static class McpClientExtensions /// is . /// Thrown when the server cannot be reached or returns an error response. [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.PingAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [EditorBrowsable(EditorBrowsableState.Never)] public static Task PingAsync(this IMcpClient client, CancellationToken cancellationToken = default) => AsClientOrThrow(client).PingAsync(cancellationToken); @@ -128,6 +130,7 @@ public static Task PingAsync(this IMcpClient client, CancellationToken cancellat /// /// is . [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.ListToolsAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [EditorBrowsable(EditorBrowsableState.Never)] public static ValueTask> ListToolsAsync( this IMcpClient client, JsonSerializerOptions? serializerOptions = null, @@ -167,6 +170,7 @@ public static ValueTask> ListToolsAsync( /// /// is . [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.EnumerateToolsAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [EditorBrowsable(EditorBrowsableState.Never)] public static IAsyncEnumerable EnumerateToolsAsync( this IMcpClient client, JsonSerializerOptions? serializerOptions = null, @@ -191,6 +195,7 @@ public static IAsyncEnumerable EnumerateToolsAsync( /// /// is . [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.ListPromptsAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [EditorBrowsable(EditorBrowsableState.Never)] public static ValueTask> ListPromptsAsync( this IMcpClient client, CancellationToken cancellationToken = default) => AsClientOrThrow(client).ListPromptsAsync(cancellationToken); @@ -223,6 +228,7 @@ public static ValueTask> ListPromptsAsync( /// /// is . [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.EnumeratePromptsAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [EditorBrowsable(EditorBrowsableState.Never)] public static IAsyncEnumerable EnumeratePromptsAsync( this IMcpClient client, CancellationToken cancellationToken = default) => AsClientOrThrow(client).EnumeratePromptsAsync(cancellationToken); @@ -253,6 +259,7 @@ public static IAsyncEnumerable EnumeratePromptsAsync( /// Thrown when the prompt does not exist, when required arguments are missing, or when the server encounters an error processing the prompt. /// is . [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.GetPromptAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [EditorBrowsable(EditorBrowsableState.Never)] public static ValueTask GetPromptAsync( this IMcpClient client, string name, @@ -279,6 +286,7 @@ public static ValueTask GetPromptAsync( /// /// is . [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.ListResourceTemplatesAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [EditorBrowsable(EditorBrowsableState.Never)] public static ValueTask> ListResourceTemplatesAsync( this IMcpClient client, CancellationToken cancellationToken = default) => AsClientOrThrow(client).ListResourceTemplatesAsync(cancellationToken); @@ -311,6 +319,7 @@ public static ValueTask> ListResourceTemplatesA /// /// is . [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.EnumerateResourceTemplatesAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [EditorBrowsable(EditorBrowsableState.Never)] public static IAsyncEnumerable EnumerateResourceTemplatesAsync( this IMcpClient client, CancellationToken cancellationToken = default) => AsClientOrThrow(client).EnumerateResourceTemplatesAsync(cancellationToken); @@ -345,6 +354,7 @@ public static IAsyncEnumerable EnumerateResourceTempl /// /// is . [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.ListResourcesAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [EditorBrowsable(EditorBrowsableState.Never)] public static ValueTask> ListResourcesAsync( this IMcpClient client, CancellationToken cancellationToken = default) => AsClientOrThrow(client).ListResourcesAsync(cancellationToken); @@ -377,6 +387,7 @@ public static ValueTask> ListResourcesAsync( /// /// is . [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.EnumerateResourcesAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [EditorBrowsable(EditorBrowsableState.Never)] public static IAsyncEnumerable EnumerateResourcesAsync( this IMcpClient client, CancellationToken cancellationToken = default) => AsClientOrThrow(client).EnumerateResourcesAsync(cancellationToken); @@ -391,6 +402,7 @@ public static IAsyncEnumerable EnumerateResourcesAsync( /// is . /// is empty or composed entirely of whitespace. [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.ReadResourceAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [EditorBrowsable(EditorBrowsableState.Never)] public static ValueTask ReadResourceAsync( this IMcpClient client, string uri, CancellationToken cancellationToken = default) => AsClientOrThrow(client).ReadResourceAsync(uri, cancellationToken); @@ -404,6 +416,7 @@ public static ValueTask ReadResourceAsync( /// is . /// is . [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.ReadResourceAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [EditorBrowsable(EditorBrowsableState.Never)] public static ValueTask ReadResourceAsync( this IMcpClient client, Uri uri, CancellationToken cancellationToken = default) => AsClientOrThrow(client).ReadResourceAsync(uri, cancellationToken); @@ -419,6 +432,7 @@ public static ValueTask ReadResourceAsync( /// is . /// is empty or composed entirely of whitespace. [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.ReadResourceAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [EditorBrowsable(EditorBrowsableState.Never)] public static ValueTask ReadResourceAsync( this IMcpClient client, string uriTemplate, IReadOnlyDictionary arguments, CancellationToken cancellationToken = default) => AsClientOrThrow(client).ReadResourceAsync(uriTemplate, arguments, cancellationToken); @@ -453,6 +467,7 @@ public static ValueTask ReadResourceAsync( /// is empty or composed entirely of whitespace. /// The server returned an error response. [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.CompleteAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [EditorBrowsable(EditorBrowsableState.Never)] public static ValueTask CompleteAsync(this IMcpClient client, Reference reference, string argumentName, string argumentValue, CancellationToken cancellationToken = default) => AsClientOrThrow(client).CompleteAsync(reference, argumentName, argumentValue, cancellationToken); @@ -482,6 +497,7 @@ public static ValueTask CompleteAsync(this IMcpClient client, Re /// is . /// is empty or composed entirely of whitespace. [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.SubscribeToResourceAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [EditorBrowsable(EditorBrowsableState.Never)] public static Task SubscribeToResourceAsync(this IMcpClient client, string uri, CancellationToken cancellationToken = default) => AsClientOrThrow(client).SubscribeToResourceAsync(uri, cancellationToken); @@ -510,6 +526,7 @@ public static Task SubscribeToResourceAsync(this IMcpClient client, string uri, /// is . /// is . [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.SubscribeToResourceAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [EditorBrowsable(EditorBrowsableState.Never)] public static Task SubscribeToResourceAsync(this IMcpClient client, Uri uri, CancellationToken cancellationToken = default) => AsClientOrThrow(client).SubscribeToResourceAsync(uri, cancellationToken); @@ -538,6 +555,7 @@ public static Task SubscribeToResourceAsync(this IMcpClient client, Uri uri, Can /// is . /// is empty or composed entirely of whitespace. [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.UnsubscribeFromResourceAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [EditorBrowsable(EditorBrowsableState.Never)] public static Task UnsubscribeFromResourceAsync(this IMcpClient client, string uri, CancellationToken cancellationToken = default) => AsClientOrThrow(client).UnsubscribeFromResourceAsync(uri, cancellationToken); @@ -565,6 +583,7 @@ public static Task UnsubscribeFromResourceAsync(this IMcpClient client, string u /// is . /// is . [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.UnsubscribeFromResourceAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [EditorBrowsable(EditorBrowsableState.Never)] public static Task UnsubscribeFromResourceAsync(this IMcpClient client, Uri uri, CancellationToken cancellationToken = default) => AsClientOrThrow(client).UnsubscribeFromResourceAsync(uri, cancellationToken); @@ -604,6 +623,7 @@ public static Task UnsubscribeFromResourceAsync(this IMcpClient client, Uri uri, /// /// [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.CallToolAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [EditorBrowsable(EditorBrowsableState.Never)] public static ValueTask CallToolAsync( this IMcpClient client, string toolName, diff --git a/src/ModelContextProtocol.Core/Client/McpClientFactory.cs b/src/ModelContextProtocol.Core/Client/McpClientFactory.cs index e5e19ebe7..805787256 100644 --- a/src/ModelContextProtocol.Core/Client/McpClientFactory.cs +++ b/src/ModelContextProtocol.Core/Client/McpClientFactory.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.Logging; +using System.ComponentModel; namespace ModelContextProtocol.Client; @@ -11,6 +12,7 @@ namespace ModelContextProtocol.Client; /// of appropriate implementations through the supplied transport. /// [Obsolete($"Use {nameof(McpClient)}.{nameof(McpClient.CreateAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 +[EditorBrowsable(EditorBrowsableState.Never)] public static partial class McpClientFactory { /// Creates an , connecting it to the specified server. diff --git a/src/ModelContextProtocol.Core/IMcpEndpoint.cs b/src/ModelContextProtocol.Core/IMcpEndpoint.cs index a9d0fa9f5..40106cb07 100644 --- a/src/ModelContextProtocol.Core/IMcpEndpoint.cs +++ b/src/ModelContextProtocol.Core/IMcpEndpoint.cs @@ -1,4 +1,5 @@ -using ModelContextProtocol.Client; +using System.ComponentModel; +using ModelContextProtocol.Client; using ModelContextProtocol.Protocol; using ModelContextProtocol.Server; @@ -27,6 +28,7 @@ namespace ModelContextProtocol; /// /// [Obsolete($"Use {nameof(McpSession)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 +[EditorBrowsable(EditorBrowsableState.Never)] public interface IMcpEndpoint : IAsyncDisposable { /// Gets an identifier associated with the current MCP session. diff --git a/src/ModelContextProtocol.Core/McpEndpointExtensions.cs b/src/ModelContextProtocol.Core/McpEndpointExtensions.cs index d4494fa0f..1a5b5c1e2 100644 --- a/src/ModelContextProtocol.Core/McpEndpointExtensions.cs +++ b/src/ModelContextProtocol.Core/McpEndpointExtensions.cs @@ -1,6 +1,7 @@ using ModelContextProtocol.Client; using ModelContextProtocol.Protocol; using ModelContextProtocol.Server; +using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Text.Json; @@ -35,6 +36,7 @@ public static class McpEndpointExtensions /// The to monitor for cancellation requests. The default is . /// A task that represents the asynchronous operation. The task result contains the deserialized result. [Obsolete($"Use {nameof(McpSession)}.{nameof(McpSession.SendRequestAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [EditorBrowsable(EditorBrowsableState.Never)] public static ValueTask SendRequestAsync( this IMcpEndpoint endpoint, string method, @@ -60,6 +62,7 @@ public static ValueTask SendRequestAsync( /// /// [Obsolete($"Use {nameof(McpSession)}.{nameof(McpSession.SendNotificationAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [EditorBrowsable(EditorBrowsableState.Never)] public static Task SendNotificationAsync(this IMcpEndpoint client, string method, CancellationToken cancellationToken = default) => AsSessionOrThrow(client).SendNotificationAsync(method, cancellationToken); @@ -88,6 +91,7 @@ public static Task SendNotificationAsync(this IMcpEndpoint client, string method /// /// [Obsolete($"Use {nameof(McpSession)}.{nameof(McpSession.SendNotificationAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [EditorBrowsable(EditorBrowsableState.Never)] public static Task SendNotificationAsync( this IMcpEndpoint endpoint, string method, @@ -116,6 +120,7 @@ public static Task SendNotificationAsync( /// /// [Obsolete($"Use {nameof(McpSession)}.{nameof(McpSession.NotifyProgressAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [EditorBrowsable(EditorBrowsableState.Never)] public static Task NotifyProgressAsync( this IMcpEndpoint endpoint, ProgressToken progressToken, diff --git a/src/ModelContextProtocol.Core/Server/IMcpServer.cs b/src/ModelContextProtocol.Core/Server/IMcpServer.cs index 0d1e3d31a..8b88aa7a2 100644 --- a/src/ModelContextProtocol.Core/Server/IMcpServer.cs +++ b/src/ModelContextProtocol.Core/Server/IMcpServer.cs @@ -1,4 +1,5 @@ using ModelContextProtocol.Protocol; +using System.ComponentModel; namespace ModelContextProtocol.Server; @@ -6,6 +7,7 @@ namespace ModelContextProtocol.Server; /// Represents an instance of a Model Context Protocol (MCP) server that connects to and communicates with an MCP client. /// [Obsolete($"Use {nameof(McpServer)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 +[EditorBrowsable(EditorBrowsableState.Never)] public interface IMcpServer : IMcpEndpoint { /// diff --git a/src/ModelContextProtocol.Core/Server/McpServerExtensions.cs b/src/ModelContextProtocol.Core/Server/McpServerExtensions.cs index 6567b22c3..cd8c368a1 100644 --- a/src/ModelContextProtocol.Core/Server/McpServerExtensions.cs +++ b/src/ModelContextProtocol.Core/Server/McpServerExtensions.cs @@ -1,6 +1,7 @@ using Microsoft.Extensions.AI; using Microsoft.Extensions.Logging; using ModelContextProtocol.Protocol; +using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Text.Json; @@ -27,6 +28,7 @@ public static class McpServerExtensions /// and token limits. /// [Obsolete($"Use {nameof(McpServer)}.{nameof(McpServer.SampleAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [EditorBrowsable(EditorBrowsableState.Never)] public static ValueTask SampleAsync( this IMcpServer server, CreateMessageRequestParams request, CancellationToken cancellationToken = default) => AsServerOrThrow(server).SampleAsync(request, cancellationToken); @@ -46,7 +48,8 @@ public static ValueTask SampleAsync( /// This method converts the provided chat messages into a format suitable for the sampling API, /// handling different content types such as text, images, and audio. /// - [Obsolete($"Use {nameof(McpServer)}.{nameof(McpServer.SampleAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774] + [Obsolete($"Use {nameof(McpServer)}.{nameof(McpServer.SampleAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [EditorBrowsable(EditorBrowsableState.Never)] public static Task SampleAsync( this IMcpServer server, IEnumerable messages, ChatOptions? options = default, CancellationToken cancellationToken = default) @@ -60,6 +63,7 @@ public static Task SampleAsync( /// is . /// The client does not support sampling. [Obsolete($"Use {nameof(McpServer)}.{nameof(McpServer.AsSamplingChatClient)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [EditorBrowsable(EditorBrowsableState.Never)] public static IChatClient AsSamplingChatClient(this IMcpServer server) => AsServerOrThrow(server).AsSamplingChatClient(); @@ -67,6 +71,7 @@ public static IChatClient AsSamplingChatClient(this IMcpServer server) /// The server to wrap as an . /// An that can be used to log to the client.. [Obsolete($"Use {nameof(McpServer)}.{nameof(McpServer.AsSamplingChatClient)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [EditorBrowsable(EditorBrowsableState.Never)] public static ILoggerProvider AsClientLoggerProvider(this IMcpServer server) => AsServerOrThrow(server).AsClientLoggerProvider(); @@ -86,6 +91,7 @@ public static ILoggerProvider AsClientLoggerProvider(this IMcpServer server) /// or other structured data sources that the client makes available through the protocol. /// [Obsolete($"Use {nameof(McpServer)}.{nameof(McpServer.RequestRootsAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [EditorBrowsable(EditorBrowsableState.Never)] public static ValueTask RequestRootsAsync( this IMcpServer server, ListRootsRequestParams request, CancellationToken cancellationToken = default) => AsServerOrThrow(server).RequestRootsAsync(request, cancellationToken); @@ -103,6 +109,7 @@ public static ValueTask RequestRootsAsync( /// This method requires the client to support the elicitation capability. /// [Obsolete($"Use {nameof(McpServer)}.{nameof(McpServer.ElicitAsync)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 + [EditorBrowsable(EditorBrowsableState.Never)] public static ValueTask ElicitAsync( this IMcpServer server, ElicitRequestParams request, CancellationToken cancellationToken = default) => AsServerOrThrow(server).ElicitAsync(request, cancellationToken); diff --git a/src/ModelContextProtocol.Core/Server/McpServerFactory.cs b/src/ModelContextProtocol.Core/Server/McpServerFactory.cs index 269d2db88..7a6609d0d 100644 --- a/src/ModelContextProtocol.Core/Server/McpServerFactory.cs +++ b/src/ModelContextProtocol.Core/Server/McpServerFactory.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.Logging; using ModelContextProtocol.Protocol; +using System.ComponentModel; namespace ModelContextProtocol.Server; @@ -11,6 +12,7 @@ namespace ModelContextProtocol.Server; /// The factory handles proper initialization of server instances with the required dependencies. /// [Obsolete($"Use {nameof(McpServer)}.{nameof(McpServer.Create)} instead. This member will be removed in a subsequent release.")] // See: https://github.com/modelcontextprotocol/csharp-sdk/issues/774 +[EditorBrowsable(EditorBrowsableState.Never)] public static class McpServerFactory { ///