From f636f4b08ad4a9ba44f7f2c05b9afadc12cba5f7 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 18 Nov 2025 22:23:11 +0000
Subject: [PATCH 01/12] Initial plan
From 701597c9bfdcca7ce1e02cd98d8dd96acab55f91 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 18 Nov 2025 22:34:39 +0000
Subject: [PATCH 02/12] Remove internal usages of obsolete capability
properties
Co-authored-by: MackinnonBuck <10456961+MackinnonBuck@users.noreply.github.com>
---
.../Client/McpClientImpl.cs | 10 +++----
.../Server/McpServerImpl.cs | 26 -------------------
2 files changed, 4 insertions(+), 32 deletions(-)
diff --git a/src/ModelContextProtocol.Core/Client/McpClientImpl.cs b/src/ModelContextProtocol.Core/Client/McpClientImpl.cs
index 3a289d13e..105d46590 100644
--- a/src/ModelContextProtocol.Core/Client/McpClientImpl.cs
+++ b/src/ModelContextProtocol.Core/Client/McpClientImpl.cs
@@ -58,12 +58,10 @@ private void RegisterHandlers(McpClientOptions options, NotificationHandlers not
{
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
+ var notificationHandlersFromOptions = handlers.NotificationHandlers;
+ var samplingHandler = handlers.SamplingHandler;
+ var rootsHandler = handlers.RootsHandler;
+ var elicitationHandler = handlers.ElicitationHandler;
if (notificationHandlersFromOptions is not null)
{
diff --git a/src/ModelContextProtocol.Core/Server/McpServerImpl.cs b/src/ModelContextProtocol.Core/Server/McpServerImpl.cs
index 41408c22b..706578d9b 100644
--- a/src/ModelContextProtocol.Core/Server/McpServerImpl.cs
+++ b/src/ModelContextProtocol.Core/Server/McpServerImpl.cs
@@ -230,10 +230,6 @@ private void ConfigureCompletion(McpServerOptions options)
var completeHandler = options.Handlers.CompleteHandler;
var completionsCapability = options.Capabilities?.Completions;
-#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;
@@ -266,14 +262,6 @@ 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)
@@ -427,11 +415,6 @@ 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)
{
@@ -515,11 +498,6 @@ 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)
{
@@ -618,10 +596,6 @@ 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 d826cd3dc1d9584e4e8e299f63310eb2168567ca Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 18 Nov 2025 22:45:40 +0000
Subject: [PATCH 03/12] Remove all obsolete API definitions and related tests
Co-authored-by: MackinnonBuck <10456961+MackinnonBuck@users.noreply.github.com>
---
.../Client/McpClientExtensions.cs | 589 ------------------
src/ModelContextProtocol.Core/IMcpEndpoint.cs | 5 +-
.../McpEndpointExtensions.cs | 126 ----
.../Protocol/ClientCapabilities.cs | 21 -
.../Protocol/CompletionsCapability.cs | 12 -
.../Protocol/ElicitationCapability.cs | 17 -
.../Protocol/LoggingCapability.cs | 7 -
.../Protocol/PromptsCapability.cs | 54 --
.../Protocol/ResourcesCapability.cs | 87 ---
.../Protocol/RootsCapability.cs | 11 -
.../Protocol/SamplingCapability.cs | 22 -
.../Protocol/ServerCapabilities.cs | 21 -
.../Protocol/ToolsCapability.cs | 44 --
.../Server/McpServerExtensions.cs | 122 ----
.../Server/McpServerFactory.cs | 34 -
.../Client/McpClientExtensionsTests.cs | 396 ------------
.../McpEndpointExtensionsTests.cs | 119 ----
.../Server/McpServerExtensionsTests.cs | 221 -------
18 files changed, 2 insertions(+), 1906 deletions(-)
delete mode 100644 src/ModelContextProtocol.Core/Server/McpServerFactory.cs
delete mode 100644 tests/ModelContextProtocol.Tests/Client/McpClientExtensionsTests.cs
delete mode 100644 tests/ModelContextProtocol.Tests/McpEndpointExtensionsTests.cs
delete mode 100644 tests/ModelContextProtocol.Tests/Server/McpServerExtensionsTests.cs
diff --git a/src/ModelContextProtocol.Core/Client/McpClientExtensions.cs b/src/ModelContextProtocol.Core/Client/McpClientExtensions.cs
index f0cd3c4f9..86f77a900 100644
--- a/src/ModelContextProtocol.Core/Client/McpClientExtensions.cs
+++ b/src/ModelContextProtocol.Core/Client/McpClientExtensions.cs
@@ -66,595 +66,6 @@ public static class McpClientExtensions
};
}
- ///
- /// Sends a ping request to verify server connectivity.
- ///
- /// The client instance used to communicate with the MCP server.
- /// The to monitor for cancellation requests. The default is .
- /// A task that completes when the ping is successful.
- ///
- ///
- /// This method is used to check if the MCP server is online and responding to requests.
- /// It can be useful for health checking, ensuring the connection is established, or verifying
- /// that the client has proper authorization to communicate with the server.
- ///
- ///
- /// The ping operation is lightweight and does not require any parameters. A successful completion
- /// of the task indicates that the server is operational and accessible.
- ///
- ///
- /// 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);
-
- ///
- /// Retrieves a list of available tools from the server.
- ///
- /// The client instance used to communicate with the MCP server.
- /// The serializer options governing tool parameter serialization. If null, the default options will be used.
- /// The to monitor for cancellation requests. The default is .
- /// A list of all available tools as instances.
- ///
- ///
- /// This method fetches all available tools from the MCP server and returns them as a complete list.
- /// It automatically handles pagination with cursors if the server responds with only a portion per request.
- ///
- ///
- /// For servers with a large number of tools and that responds with paginated responses, consider using
- /// instead, as it streams tools as they arrive rather than loading them all at once.
- ///
- ///
- /// The serializer options provided are flowed to each and will be used
- /// when invoking tools in order to serialize any parameters.
- ///
- ///
- ///
- ///
- /// // Get all tools available on the server
- /// var tools = await mcpClient.ListToolsAsync();
- ///
- /// // Use tools with an AI client
- /// ChatOptions chatOptions = new()
- /// {
- /// Tools = [.. tools]
- /// };
- ///
- /// await foreach (var update in chatClient.GetStreamingResponseAsync(userMessage, chatOptions))
- /// {
- /// Console.Write(update);
- /// }
- ///
- ///
- /// 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,
- CancellationToken cancellationToken = default)
- => AsClientOrThrow(client).ListToolsAsync(serializerOptions, cancellationToken);
-
- ///
- /// Creates an enumerable for asynchronously enumerating all available tools from the server.
- ///
- /// The client instance used to communicate with the MCP server.
- /// The serializer options governing tool parameter serialization. If null, the default options will be used.
- /// The to monitor for cancellation requests. The default is .
- /// An asynchronous sequence of all available tools as instances.
- ///
- ///
- /// This method uses asynchronous enumeration to retrieve tools from the server, which allows processing tools
- /// as they arrive rather than waiting for all tools to be retrieved. The method automatically handles pagination
- /// with cursors if the server responds with tools split across multiple responses.
- ///
- ///
- /// The serializer options provided are flowed to each and will be used
- /// when invoking tools in order to serialize any parameters.
- ///
- ///
- /// Every iteration through the returned
- /// will result in re-querying the server and yielding the sequence of available tools.
- ///
- ///
- ///
- ///
- /// // Enumerate all tools available on the server
- /// await foreach (var tool in client.EnumerateToolsAsync())
- /// {
- /// Console.WriteLine($"Tool: {tool.Name}");
- /// }
- ///
- ///
- /// 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,
- CancellationToken cancellationToken = default)
- => AsClientOrThrow(client).EnumerateToolsAsync(serializerOptions, cancellationToken);
-
- ///
- /// Retrieves a list of available prompts from the server.
- ///
- /// The client instance used to communicate with the MCP server.
- /// The to monitor for cancellation requests. The default is .
- /// A list of all available prompts as instances.
- ///
- ///
- /// This method fetches all available prompts from the MCP server and returns them as a complete list.
- /// It automatically handles pagination with cursors if the server responds with only a portion per request.
- ///
- ///
- /// For servers with a large number of prompts and that responds with paginated responses, consider using
- /// instead, as it streams prompts as they arrive rather than loading them all at once.
- ///
- ///
- /// 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);
-
- ///
- /// Creates an enumerable for asynchronously enumerating all available prompts from the server.
- ///
- /// The client instance used to communicate with the MCP server.
- /// The to monitor for cancellation requests. The default is .
- /// An asynchronous sequence of all available prompts as instances.
- ///
- ///
- /// This method uses asynchronous enumeration to retrieve prompts from the server, which allows processing prompts
- /// as they arrive rather than waiting for all prompts to be retrieved. The method automatically handles pagination
- /// with cursors if the server responds with prompts split across multiple responses.
- ///
- ///
- /// Every iteration through the returned
- /// will result in re-querying the server and yielding the sequence of available prompts.
- ///
- ///
- ///
- ///
- /// // Enumerate all prompts available on the server
- /// await foreach (var prompt in client.EnumeratePromptsAsync())
- /// {
- /// Console.WriteLine($"Prompt: {prompt.Name}");
- /// }
- ///
- ///
- /// 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);
-
- ///
- /// Retrieves a specific prompt from the MCP server.
- ///
- /// The client instance used to communicate with the MCP server.
- /// The name of the prompt to retrieve.
- /// Optional arguments for the prompt. Keys are parameter names, and values are the argument values.
- /// The serialization options governing argument serialization.
- /// The to monitor for cancellation requests. The default is .
- /// A task containing the prompt's result with content and messages.
- ///
- ///
- /// This method sends a request to the MCP server to create the specified prompt with the provided arguments.
- /// The server will process the arguments and return a prompt containing messages or other content.
- ///
- ///
- /// Arguments are serialized into JSON and passed to the server, where they may be used to customize the
- /// prompt's behavior or content. Each prompt may have different argument requirements.
- ///
- ///
- /// The returned contains a collection of objects,
- /// which can be converted to objects using the method.
- ///
- ///
- /// 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,
- IReadOnlyDictionary? arguments = null,
- JsonSerializerOptions? serializerOptions = null,
- CancellationToken cancellationToken = default)
- => AsClientOrThrow(client).GetPromptAsync(name, arguments, serializerOptions, cancellationToken);
-
- ///
- /// Retrieves a list of available resource templates from the server.
- ///
- /// The client instance used to communicate with the MCP server.
- /// The to monitor for cancellation requests. The default is .
- /// A list of all available resource templates as instances.
- ///
- ///
- /// This method fetches all available resource templates from the MCP server and returns them as a complete list.
- /// It automatically handles pagination with cursors if the server responds with only a portion per request.
- ///
- ///
- /// For servers with a large number of resource templates and that responds with paginated responses, consider using
- /// instead, as it streams templates as they arrive rather than loading them all at once.
- ///
- ///
- /// 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);
-
- ///
- /// Creates an enumerable for asynchronously enumerating all available resource templates from the server.
- ///
- /// The client instance used to communicate with the MCP server.
- /// The to monitor for cancellation requests. The default is .
- /// An asynchronous sequence of all available resource templates as instances.
- ///
- ///
- /// This method uses asynchronous enumeration to retrieve resource templates from the server, which allows processing templates
- /// as they arrive rather than waiting for all templates to be retrieved. The method automatically handles pagination
- /// with cursors if the server responds with templates split across multiple responses.
- ///
- ///
- /// Every iteration through the returned
- /// will result in re-querying the server and yielding the sequence of available resource templates.
- ///
- ///
- ///
- ///
- /// // Enumerate all resource templates available on the server
- /// await foreach (var template in client.EnumerateResourceTemplatesAsync())
- /// {
- /// Console.WriteLine($"Template: {template.Name}");
- /// }
- ///
- ///
- /// 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);
-
- ///
- /// Retrieves a list of available resources from the server.
- ///
- /// The client instance used to communicate with the MCP server.
- /// The to monitor for cancellation requests. The default is .
- /// A list of all available resources as instances.
- ///
- ///
- /// This method fetches all available resources from the MCP server and returns them as a complete list.
- /// It automatically handles pagination with cursors if the server responds with only a portion per request.
- ///
- ///
- /// For servers with a large number of resources and that responds with paginated responses, consider using
- /// instead, as it streams resources as they arrive rather than loading them all at once.
- ///
- ///
- ///
- ///
- /// // Get all resources available on the server
- /// var resources = await client.ListResourcesAsync();
- ///
- /// // Display information about each resource
- /// foreach (var resource in resources)
- /// {
- /// Console.WriteLine($"Resource URI: {resource.Uri}");
- /// }
- ///
- ///
- /// 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);
-
- ///
- /// Creates an enumerable for asynchronously enumerating all available resources from the server.
- ///
- /// The client instance used to communicate with the MCP server.
- /// The to monitor for cancellation requests. The default is .
- /// An asynchronous sequence of all available resources as instances.
- ///
- ///
- /// This method uses asynchronous enumeration to retrieve resources from the server, which allows processing resources
- /// as they arrive rather than waiting for all resources to be retrieved. The method automatically handles pagination
- /// with cursors if the server responds with resources split across multiple responses.
- ///
- ///
- /// Every iteration through the returned
- /// will result in re-querying the server and yielding the sequence of available resources.
- ///
- ///
- ///
- ///
- /// // Enumerate all resources available on the server
- /// await foreach (var resource in client.EnumerateResourcesAsync())
- /// {
- /// Console.WriteLine($"Resource URI: {resource.Uri}");
- /// }
- ///
- ///
- /// 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);
-
- ///
- /// Reads a resource from the server.
- ///
- /// The client instance used to communicate with the MCP server.
- /// The uri of the resource.
- /// The to monitor for cancellation requests. The default is .
- /// is .
- /// 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);
-
- ///
- /// Reads a resource from the server.
- ///
- /// The client instance used to communicate with the MCP server.
- /// The uri of the resource.
- /// The to monitor for cancellation requests. The default is .
- /// 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);
-
- ///
- /// Reads a resource from the server.
- ///
- /// The client instance used to communicate with the MCP server.
- /// The uri template of the resource.
- /// Arguments to use to format .
- /// The to monitor for cancellation requests. The default is .
- /// is .
- /// 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);
-
- ///
- /// Requests completion suggestions for a prompt argument or resource reference.
- ///
- /// The client instance used to communicate with the MCP server.
- /// The reference object specifying the type and optional URI or name.
- /// The name of the argument for which completions are requested.
- /// The current value of the argument, used to filter relevant completions.
- /// The to monitor for cancellation requests. The default is .
- /// A containing completion suggestions.
- ///
- ///
- /// This method allows clients to request auto-completion suggestions for arguments in a prompt template
- /// or for resource references.
- ///
- ///
- /// When working with prompt references, the server will return suggestions for the specified argument
- /// that match or begin with the current argument value. This is useful for implementing intelligent
- /// auto-completion in user interfaces.
- ///
- ///
- /// When working with resource references, the server will return suggestions relevant to the specified
- /// resource URI.
- ///
- ///
- /// is .
- /// is .
- /// is .
- /// 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);
-
- ///
- /// Subscribes to a resource on the server to receive notifications when it changes.
- ///
- /// The client instance used to communicate with the MCP server.
- /// The URI of the resource to which to subscribe.
- /// The to monitor for cancellation requests. The default is .
- /// A task that represents the asynchronous operation.
- ///
- ///
- /// This method allows the client to register interest in a specific resource identified by its URI.
- /// When the resource changes, the server will send notifications to the client, enabling real-time
- /// updates without polling.
- ///
- ///
- /// The subscription remains active until explicitly unsubscribed using
- /// or until the client disconnects from the server.
- ///
- ///
- /// To handle resource change notifications, register an event handler for the appropriate notification events,
- /// such as with .
- ///
- ///
- /// is .
- /// 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);
-
- ///
- /// Subscribes to a resource on the server to receive notifications when it changes.
- ///
- /// The client instance used to communicate with the MCP server.
- /// The URI of the resource to which to subscribe.
- /// The to monitor for cancellation requests. The default is .
- /// A task that represents the asynchronous operation.
- ///
- ///
- /// This method allows the client to register interest in a specific resource identified by its URI.
- /// When the resource changes, the server will send notifications to the client, enabling real-time
- /// updates without polling.
- ///
- ///
- /// The subscription remains active until explicitly unsubscribed using
- /// or until the client disconnects from the server.
- ///
- ///
- /// To handle resource change notifications, register an event handler for the appropriate notification events,
- /// such as with .
- ///
- ///
- /// 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);
-
- ///
- /// Unsubscribes from a resource on the server to stop receiving notifications about its changes.
- ///
- /// The client instance used to communicate with the MCP server.
- /// The URI of the resource to unsubscribe from.
- /// The to monitor for cancellation requests. The default is .
- /// A task that represents the asynchronous operation.
- ///
- ///
- /// This method cancels a previous subscription to a resource, stopping the client from receiving
- /// notifications when that resource changes.
- ///
- ///
- /// The unsubscribe operation is idempotent, meaning it can be called multiple times for the same
- /// resource without causing errors, even if there is no active subscription.
- ///
- ///
- /// Due to the nature of the MCP protocol, it is possible the client may receive notifications after
- /// unsubscribing if those notifications were issued by the server prior to the unsubscribe request being received.
- ///
- ///
- /// is .
- /// 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);
-
- ///
- /// Unsubscribes from a resource on the server to stop receiving notifications about its changes.
- ///
- /// The client instance used to communicate with the MCP server.
- /// The URI of the resource to unsubscribe from.
- /// The to monitor for cancellation requests. The default is .
- /// A task that represents the asynchronous operation.
- ///
- ///
- /// This method cancels a previous subscription to a resource, stopping the client from receiving
- /// notifications when that resource changes.
- ///
- ///
- /// The unsubscribe operation is idempotent, meaning it can be called multiple times for the same
- /// resource without causing errors, even if there is no active subscription.
- ///
- ///
- /// Due to the nature of the MCP protocol, it is possible the client may receive notifications after
- /// unsubscribing if those notifications were issued by the server prior to the unsubscribe request being received.
- ///
- ///
- /// 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);
-
- ///
- /// Invokes a tool on the server.
- ///
- /// The client instance used to communicate with the MCP server.
- /// The name of the tool to call on the server..
- /// An optional dictionary of arguments to pass to the tool. Each key represents a parameter name,
- /// and its associated value represents the argument value.
- ///
- ///
- /// An optional to have progress notifications reported to it. Setting this to a non-
- /// value will result in a progress token being included in the call, and any resulting progress notifications during the operation
- /// routed to this instance.
- ///
- ///
- /// The JSON serialization options governing argument serialization. If , the default serialization options will be used.
- ///
- /// The to monitor for cancellation requests. The default is .
- ///
- /// A task containing the from the tool execution. The response includes
- /// the tool's output content, which may be structured data, text, or an error message.
- ///
- /// is .
- /// is .
- /// The server could not find the requested tool, or the server encountered an error while processing the request.
- ///
- ///
- /// // Call a simple echo tool with a string argument
- /// var result = await client.CallToolAsync(
- /// "echo",
- /// new Dictionary<string, object?>
- /// {
- /// ["message"] = "Hello MCP!"
- /// });
- ///
- ///
- [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,
- IReadOnlyDictionary? arguments = null,
- IProgress? progress = null,
- JsonSerializerOptions? serializerOptions = null,
- CancellationToken cancellationToken = default)
- => AsClientOrThrow(client).CallToolAsync(toolName, arguments, progress, serializerOptions, cancellationToken);
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
-#pragma warning disable CS0618 // Type or member is obsolete
- private static McpClient AsClientOrThrow(IMcpClient client, [CallerMemberName] string memberName = "")
-#pragma warning restore CS0618 // Type or member is obsolete
- {
- if (client is not McpClient mcpClient)
- {
- ThrowInvalidEndpointType(memberName);
- }
-
- return mcpClient;
-
- [DoesNotReturn]
- [MethodImpl(MethodImplOptions.NoInlining)]
- static void ThrowInvalidEndpointType(string memberName)
- => throw new InvalidOperationException(
- $"Only arguments assignable to '{nameof(McpClient)}' are supported. " +
- $"Prefer using '{nameof(McpClient)}.{memberName}' instead, as " +
- $"'{nameof(McpClientExtensions)}.{memberName}' is obsolete and will be " +
- $"removed in the future.");
- }
-
///
/// Converts the contents of a into a pair of
/// and instances to use
diff --git a/src/ModelContextProtocol.Core/IMcpEndpoint.cs b/src/ModelContextProtocol.Core/IMcpEndpoint.cs
index 40106cb07..9fe5d6484 100644
--- a/src/ModelContextProtocol.Core/IMcpEndpoint.cs
+++ b/src/ModelContextProtocol.Core/IMcpEndpoint.cs
@@ -67,9 +67,8 @@ public interface IMcpEndpoint : IAsyncDisposable
///
///
/// This method provides low-level access to send any JSON-RPC message. For specific message types,
- /// consider using the higher-level methods such as or extension methods
- /// like ,
- /// which provide a simpler API.
+ /// consider using the higher-level methods such as or
+ /// , which provide a simpler API.
///
///
/// The method will serialize the message and transmit it using the underlying transport mechanism.
diff --git a/src/ModelContextProtocol.Core/McpEndpointExtensions.cs b/src/ModelContextProtocol.Core/McpEndpointExtensions.cs
index 1a5b5c1e2..a84cf5e73 100644
--- a/src/ModelContextProtocol.Core/McpEndpointExtensions.cs
+++ b/src/ModelContextProtocol.Core/McpEndpointExtensions.cs
@@ -23,130 +23,4 @@ namespace ModelContextProtocol;
///
public static class McpEndpointExtensions
{
- ///
- /// Sends a JSON-RPC request and attempts to deserialize the result to .
- ///
- /// The type of the request parameters to serialize from.
- /// The type of the result to deserialize to.
- /// The MCP client or server instance.
- /// The JSON-RPC method name to invoke.
- /// Object representing the request parameters.
- /// The request id for the request.
- /// 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. 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,
- TParameters parameters,
- JsonSerializerOptions? serializerOptions = null,
- RequestId requestId = default,
- CancellationToken cancellationToken = default)
- where TResult : notnull
- => AsSessionOrThrow(endpoint).SendRequestAsync(method, parameters, serializerOptions, requestId, cancellationToken);
-
- ///
- /// Sends a parameterless notification to the connected endpoint.
- ///
- /// The MCP client or server instance.
- /// The notification method name.
- /// The to monitor for cancellation requests. The default is .
- /// A task that represents the asynchronous send operation.
- ///
- ///
- /// This method sends a notification without any parameters. Notifications are one-way messages
- /// that don't expect a response. They are commonly used for events, status updates, or to signal
- /// changes in state.
- ///
- ///
- [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);
-
- ///
- /// Sends a notification with parameters to the connected endpoint.
- ///
- /// The type of the notification parameters to serialize.
- /// The MCP client or server instance.
- /// The JSON-RPC method name for the notification.
- /// Object representing the notification parameters.
- /// The options governing parameter serialization. If null, default options are used.
- /// The to monitor for cancellation requests. The default is .
- /// A task that represents the asynchronous send operation.
- ///
- ///
- /// This method sends a notification with parameters to the connected endpoint. Notifications are one-way
- /// messages that don't expect a response, commonly used for events, status updates, or signaling changes.
- ///
- ///
- /// The parameters object is serialized to JSON according to the provided serializer options or the default
- /// options if none are specified.
- ///
- ///
- /// The Model Context Protocol defines several standard notification methods in ,
- /// but custom methods can also be used for application-specific notifications.
- ///
- ///
- [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,
- TParameters parameters,
- JsonSerializerOptions? serializerOptions = null,
- CancellationToken cancellationToken = default)
- => AsSessionOrThrow(endpoint).SendNotificationAsync(method, parameters, serializerOptions, cancellationToken);
-
- ///
- /// Notifies the connected endpoint of progress for a long-running operation.
- ///
- /// The endpoint issuing the notification.
- /// The identifying the operation for which progress is being reported.
- /// The progress update to send, containing information such as percentage complete or status message.
- /// The to monitor for cancellation requests. The default is .
- /// A task representing the completion of the notification operation (not the operation being tracked).
- /// is .
- ///
- ///
- /// This method sends a progress notification to the connected endpoint using the Model Context Protocol's
- /// standardized progress notification format. Progress updates are identified by a
- /// that allows the recipient to correlate multiple updates with a specific long-running operation.
- ///
- ///
- /// Progress notifications are sent asynchronously and don't block the operation from continuing.
- ///
- ///
- [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,
- ProgressNotificationValue progress,
- CancellationToken cancellationToken = default)
- => AsSessionOrThrow(endpoint).NotifyProgressAsync(progressToken, progress, cancellationToken);
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
-#pragma warning disable CS0618 // Type or member is obsolete
- private static McpSession AsSessionOrThrow(IMcpEndpoint endpoint, [CallerMemberName] string memberName = "")
-#pragma warning restore CS0618 // Type or member is obsolete
- {
- if (endpoint is not McpSession session)
- {
- ThrowInvalidEndpointType(memberName);
- }
-
- return session;
-
- [DoesNotReturn]
- [MethodImpl(MethodImplOptions.NoInlining)]
- static void ThrowInvalidEndpointType(string memberName)
- => throw new InvalidOperationException(
- $"Only arguments assignable to '{nameof(McpSession)}' are supported. " +
- $"Prefer using '{nameof(McpServer)}.{memberName}' instead, as " +
- $"'{nameof(McpEndpointExtensions)}.{memberName}' is obsolete and will be " +
- $"removed in the future.");
- }
}
diff --git a/src/ModelContextProtocol.Core/Protocol/ClientCapabilities.cs b/src/ModelContextProtocol.Core/Protocol/ClientCapabilities.cs
index 102960e36..5b559ce95 100644
--- a/src/ModelContextProtocol.Core/Protocol/ClientCapabilities.cs
+++ b/src/ModelContextProtocol.Core/Protocol/ClientCapabilities.cs
@@ -67,25 +67,4 @@ public sealed class ClientCapabilities
[JsonPropertyName("elicitation")]
public ElicitationCapability? Elicitation { get; set; }
- /// 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.
- ///
- ///
- [JsonIgnore]
- [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 8e28e67d3..54260a52e 100644
--- a/src/ModelContextProtocol.Core/Protocol/CompletionsCapability.cs
+++ b/src/ModelContextProtocol.Core/Protocol/CompletionsCapability.cs
@@ -28,16 +28,4 @@ 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(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 9d46bcc43..3fdf5ddcb 100644
--- a/src/ModelContextProtocol.Core/Protocol/ElicitationCapability.cs
+++ b/src/ModelContextProtocol.Core/Protocol/ElicitationCapability.cs
@@ -23,21 +23,4 @@ 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(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 c166a223a..ac8f03f95 100644
--- a/src/ModelContextProtocol.Core/Protocol/LoggingCapability.cs
+++ b/src/ModelContextProtocol.Core/Protocol/LoggingCapability.cs
@@ -20,11 +20,4 @@ namespace ModelContextProtocol.Protocol;
///
public sealed class LoggingCapability
{
- ///
- /// Gets or sets the handler for set logging level requests from clients.
- ///
- [JsonIgnore]
- [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 223576254..e0e09fdb2 100644
--- a/src/ModelContextProtocol.Core/Protocol/PromptsCapability.cs
+++ b/src/ModelContextProtocol.Core/Protocol/PromptsCapability.cs
@@ -32,58 +32,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]
- [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; }
-
- ///
- /// 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(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; }
-
- ///
- /// 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. 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 15ab02a06..d30418384 100644
--- a/src/ModelContextProtocol.Core/Protocol/ResourcesCapability.cs
+++ b/src/ModelContextProtocol.Core/Protocol/ResourcesCapability.cs
@@ -30,91 +30,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]
- [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; }
-
- ///
- /// 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(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; }
-
- ///
- /// 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(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; }
-
- ///
- /// 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(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; }
-
- ///
- /// 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(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; }
-
- ///
- /// 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. 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 8e2bcacfe..a6b2bbb8a 100644
--- a/src/ModelContextProtocol.Core/Protocol/RootsCapability.cs
+++ b/src/ModelContextProtocol.Core/Protocol/RootsCapability.cs
@@ -34,15 +34,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]
- [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 8ddc7ecf8..fa1773e42 100644
--- a/src/ModelContextProtocol.Core/Protocol/SamplingCapability.cs
+++ b/src/ModelContextProtocol.Core/Protocol/SamplingCapability.cs
@@ -24,26 +24,4 @@ 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(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 ffe38d221..7cd3e705e 100644
--- a/src/ModelContextProtocol.Core/Protocol/ServerCapabilities.cs
+++ b/src/ModelContextProtocol.Core/Protocol/ServerCapabilities.cs
@@ -66,25 +66,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]
- [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 b6903a7db..70b998dea 100644
--- a/src/ModelContextProtocol.Core/Protocol/ToolsCapability.cs
+++ b/src/ModelContextProtocol.Core/Protocol/ToolsCapability.cs
@@ -23,48 +23,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]
- [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; }
-
- ///
- /// 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(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; }
-
- ///
- /// 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. 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/McpServerExtensions.cs b/src/ModelContextProtocol.Core/Server/McpServerExtensions.cs
index b20865576..640e6f606 100644
--- a/src/ModelContextProtocol.Core/Server/McpServerExtensions.cs
+++ b/src/ModelContextProtocol.Core/Server/McpServerExtensions.cs
@@ -13,126 +13,4 @@ namespace ModelContextProtocol.Server;
///
public static class McpServerExtensions
{
- ///
- /// Requests to sample an LLM via the client using the specified request parameters.
- ///
- /// The server instance initiating the request.
- /// The parameters for the sampling request.
- /// The to monitor for cancellation requests.
- /// A task containing the sampling result from the client.
- /// is .
- /// The client does not support sampling.
- ///
- /// This method requires the client to support sampling capabilities.
- /// It allows detailed control over sampling parameters including messages, system prompt, temperature,
- /// 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);
-
- ///
- /// Requests to sample an LLM via the client using the provided chat messages and options.
- ///
- /// The server initiating the request.
- /// The messages to send as part of the request.
- /// The options to use for the request, including model parameters and constraints.
- /// The to monitor for cancellation requests. The default is .
- /// A task containing the chat response from the model.
- /// is .
- /// is .
- /// The client does not support sampling.
- ///
- /// 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
- [EditorBrowsable(EditorBrowsableState.Never)]
- public static Task SampleAsync(
- this IMcpServer server,
- IEnumerable messages, ChatOptions? options = default, CancellationToken cancellationToken = default)
- => AsServerOrThrow(server).SampleAsync(messages, options, cancellationToken);
-
- ///
- /// Creates an wrapper that can be used to send sampling requests to the client.
- ///
- /// The server to be wrapped as an .
- /// 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. 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();
-
- /// 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. 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();
-
- ///
- /// Requests the client to list the roots it exposes.
- ///
- /// The server initiating the request.
- /// The parameters for the list roots request.
- /// The to monitor for cancellation requests.
- /// A task containing the list of roots exposed by the client.
- /// is .
- /// The client does not support roots.
- ///
- /// This method requires the client to support the roots capability.
- /// Root resources allow clients to expose a hierarchical structure of resources that can be
- /// 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. 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);
-
- ///
- /// Requests additional information from the user via the client, allowing the server to elicit structured data.
- ///
- /// The server initiating the request.
- /// The parameters for the elicitation request.
- /// The to monitor for cancellation requests.
- /// A task containing the elicitation result.
- /// is .
- /// The client does not support elicitation.
- ///
- /// 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);
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
-#pragma warning disable CS0618 // Type or member is obsolete
- private static McpServer AsServerOrThrow(IMcpServer server, [CallerMemberName] string memberName = "")
-#pragma warning restore CS0618 // Type or member is obsolete
- {
- if (server is not McpServer mcpServer)
- {
- ThrowInvalidSessionType(memberName);
- }
-
- return mcpServer;
-
- [DoesNotReturn]
- [MethodImpl(MethodImplOptions.NoInlining)]
- static void ThrowInvalidSessionType(string memberName)
- => throw new InvalidOperationException(
- $"Only arguments assignable to '{nameof(McpServer)}' are supported. " +
- $"Prefer using '{nameof(McpServer)}.{memberName}' instead, as " +
- $"'{nameof(McpServerExtensions)}.{memberName}' is obsolete and will be " +
- $"removed in the future.");
- }
}
diff --git a/src/ModelContextProtocol.Core/Server/McpServerFactory.cs b/src/ModelContextProtocol.Core/Server/McpServerFactory.cs
deleted file mode 100644
index 7a6609d0d..000000000
--- a/src/ModelContextProtocol.Core/Server/McpServerFactory.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-using Microsoft.Extensions.Logging;
-using ModelContextProtocol.Protocol;
-using System.ComponentModel;
-
-namespace ModelContextProtocol.Server;
-
-///
-/// Provides a factory for creating instances.
-///
-///
-/// 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. 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
-{
- ///
- /// Creates a new instance of an .
- ///
- /// Transport to use for the server representing an already-established MCP session.
- /// Configuration options for this server, including capabilities.
- /// Logger factory to use for logging. If null, logging will be disabled.
- /// Optional service provider to create new instances of tools and other dependencies.
- /// An instance that should be disposed when no longer needed.
- /// is .
- /// is .
- public static IMcpServer Create(
- ITransport transport,
- McpServerOptions serverOptions,
- ILoggerFactory? loggerFactory = null,
- IServiceProvider? serviceProvider = null)
- => McpServer.Create(transport, serverOptions, loggerFactory, serviceProvider);
-}
diff --git a/tests/ModelContextProtocol.Tests/Client/McpClientExtensionsTests.cs b/tests/ModelContextProtocol.Tests/Client/McpClientExtensionsTests.cs
deleted file mode 100644
index 8fb7d2203..000000000
--- a/tests/ModelContextProtocol.Tests/Client/McpClientExtensionsTests.cs
+++ /dev/null
@@ -1,396 +0,0 @@
-using ModelContextProtocol.Client;
-using ModelContextProtocol.Protocol;
-using Moq;
-using System.Text.Json;
-
-namespace ModelContextProtocol.Tests;
-
-#pragma warning disable CS0618 // Type or member is obsolete
-
-public class McpClientExtensionsTests
-{
- [Fact]
- public async Task PingAsync_Throws_When_Not_McpClient()
- {
- var client = new Mock(MockBehavior.Strict).Object;
-
- var ex = await Assert.ThrowsAsync(async () => await client.PingAsync(TestContext.Current.CancellationToken));
- Assert.Contains("Prefer using 'McpClient.PingAsync' instead", ex.Message);
- }
-
- [Fact]
- public async Task GetPromptAsync_Throws_When_Not_McpClient()
- {
- var client = new Mock(MockBehavior.Strict).Object;
-
- var ex = await Assert.ThrowsAsync(async () => await client.GetPromptAsync(
- "name", cancellationToken: TestContext.Current.CancellationToken));
- Assert.Contains("Prefer using 'McpClient.GetPromptAsync' instead", ex.Message);
- }
-
- [Fact]
- public async Task CallToolAsync_Throws_When_Not_McpClient()
- {
- var client = new Mock(MockBehavior.Strict).Object;
-
- var ex = await Assert.ThrowsAsync(async () => await client.CallToolAsync(
- "tool", cancellationToken: TestContext.Current.CancellationToken));
- Assert.Contains("Prefer using 'McpClient.CallToolAsync' instead", ex.Message);
- }
-
- [Fact]
- public async Task ListResourcesAsync_Throws_When_Not_McpClient()
- {
- var client = new Mock(MockBehavior.Strict).Object;
-
- var ex = await Assert.ThrowsAsync(async () => await client.ListResourcesAsync(
- cancellationToken: TestContext.Current.CancellationToken));
- Assert.Contains("Prefer using 'McpClient.ListResourcesAsync' instead", ex.Message);
- }
-
- [Fact]
- public void EnumerateResourcesAsync_Throws_When_Not_McpClient()
- {
- var client = new Mock(MockBehavior.Strict).Object;
-
- var ex = Assert.Throws(() => client.EnumerateResourcesAsync(cancellationToken: TestContext.Current.CancellationToken));
- Assert.Contains("Prefer using 'McpClient.EnumerateResourcesAsync' instead", ex.Message);
- }
-
- [Fact]
- public async Task SubscribeToResourceAsync_String_Throws_When_Not_McpClient()
- {
- var client = new Mock(MockBehavior.Strict).Object;
-
- var ex = await Assert.ThrowsAsync(async () => await client.SubscribeToResourceAsync(
- "mcp://resource/1", TestContext.Current.CancellationToken));
- Assert.Contains("Prefer using 'McpClient.SubscribeToResourceAsync' instead", ex.Message);
- }
-
- [Fact]
- public async Task SubscribeToResourceAsync_Uri_Throws_When_Not_McpClient()
- {
- var client = new Mock(MockBehavior.Strict).Object;
-
- var ex = await Assert.ThrowsAsync(async () => await client.SubscribeToResourceAsync(
- new Uri("mcp://resource/1"), TestContext.Current.CancellationToken));
- Assert.Contains("Prefer using 'McpClient.SubscribeToResourceAsync' instead", ex.Message);
- }
-
- [Fact]
- public async Task UnsubscribeFromResourceAsync_String_Throws_When_Not_McpClient()
- {
- var client = new Mock(MockBehavior.Strict).Object;
-
- var ex = await Assert.ThrowsAsync(async () => await client.UnsubscribeFromResourceAsync(
- "mcp://resource/1", TestContext.Current.CancellationToken));
- Assert.Contains("Prefer using 'McpClient.UnsubscribeFromResourceAsync' instead", ex.Message);
- }
-
- [Fact]
- public async Task UnsubscribeFromResourceAsync_Uri_Throws_When_Not_McpClient()
- {
- var client = new Mock(MockBehavior.Strict).Object;
-
- var ex = await Assert.ThrowsAsync(async () => await client.UnsubscribeFromResourceAsync(
- new Uri("mcp://resource/1"), TestContext.Current.CancellationToken));
- Assert.Contains("Prefer using 'McpClient.UnsubscribeFromResourceAsync' instead", ex.Message);
- }
-
- [Fact]
- public async Task ReadResourceAsync_String_Throws_When_Not_McpClient()
- {
- var client = new Mock(MockBehavior.Strict).Object;
-
- var ex = await Assert.ThrowsAsync(async () => await client.ReadResourceAsync(
- "mcp://resource/1", TestContext.Current.CancellationToken));
- Assert.Contains("Prefer using 'McpClient.ReadResourceAsync' instead", ex.Message);
- }
-
- [Fact]
- public async Task ReadResourceAsync_Uri_Throws_When_Not_McpClient()
- {
- var client = new Mock(MockBehavior.Strict).Object;
-
- var ex = await Assert.ThrowsAsync(async () => await client.ReadResourceAsync(
- new Uri("mcp://resource/1"), TestContext.Current.CancellationToken));
- Assert.Contains("Prefer using 'McpClient.ReadResourceAsync' instead", ex.Message);
- }
-
- [Fact]
- public async Task ReadResourceAsync_Template_Throws_When_Not_McpClient()
- {
- var client = new Mock(MockBehavior.Strict).Object;
-
- var ex = await Assert.ThrowsAsync(async () => await client.ReadResourceAsync(
- "mcp://resource/{id}", new Dictionary { ["id"] = 1 }, TestContext.Current.CancellationToken));
- Assert.Contains("Prefer using 'McpClient.ReadResourceAsync' instead", ex.Message);
- }
-
- [Fact]
- public async Task CompleteAsync_Throws_When_Not_McpClient()
- {
- var client = new Mock(MockBehavior.Strict).Object;
- var reference = new PromptReference { Name = "prompt" };
-
- var ex = await Assert.ThrowsAsync(async () => await client.CompleteAsync(
- reference, "arg", "val", TestContext.Current.CancellationToken));
- Assert.Contains("Prefer using 'McpClient.CompleteAsync' instead", ex.Message);
- }
-
- [Fact]
- public async Task ListToolsAsync_Throws_When_Not_McpClient()
- {
- var client = new Mock(MockBehavior.Strict).Object;
-
- var ex = await Assert.ThrowsAsync(async () => await client.ListToolsAsync(
- cancellationToken: TestContext.Current.CancellationToken));
- Assert.Contains("Prefer using 'McpClient.ListToolsAsync' instead", ex.Message);
- }
-
- [Fact]
- public void EnumerateToolsAsync_Throws_When_Not_McpClient()
- {
- var client = new Mock(MockBehavior.Strict).Object;
-
- var ex = Assert.Throws(() => client.EnumerateToolsAsync(cancellationToken: TestContext.Current.CancellationToken));
- Assert.Contains("Prefer using 'McpClient.EnumerateToolsAsync' instead", ex.Message);
- }
-
- [Fact]
- public async Task ListPromptsAsync_Throws_When_Not_McpClient()
- {
- var client = new Mock(MockBehavior.Strict).Object;
-
- var ex = await Assert.ThrowsAsync(async () => await client.ListPromptsAsync(
- cancellationToken: TestContext.Current.CancellationToken));
- Assert.Contains("Prefer using 'McpClient.ListPromptsAsync' instead", ex.Message);
- }
-
- [Fact]
- public void EnumeratePromptsAsync_Throws_When_Not_McpClient()
- {
- var client = new Mock(MockBehavior.Strict).Object;
-
- var ex = Assert.Throws(() => client.EnumeratePromptsAsync(cancellationToken: TestContext.Current.CancellationToken));
- Assert.Contains("Prefer using 'McpClient.EnumeratePromptsAsync' instead", ex.Message);
- }
-
- [Fact]
- public async Task ListResourceTemplatesAsync_Throws_When_Not_McpClient()
- {
- var client = new Mock(MockBehavior.Strict).Object;
-
- var ex = await Assert.ThrowsAsync(async () => await client.ListResourceTemplatesAsync(
- cancellationToken: TestContext.Current.CancellationToken));
- Assert.Contains("Prefer using 'McpClient.ListResourceTemplatesAsync' instead", ex.Message);
- }
-
- [Fact]
- public void EnumerateResourceTemplatesAsync_Throws_When_Not_McpClient()
- {
- var client = new Mock(MockBehavior.Strict).Object;
-
- var ex = Assert.Throws(() => client.EnumerateResourceTemplatesAsync(cancellationToken: TestContext.Current.CancellationToken));
- Assert.Contains("Prefer using 'McpClient.EnumerateResourceTemplatesAsync' instead", ex.Message);
- }
-
- [Fact]
- public async Task PingAsync_Forwards_To_McpClient_SendRequestAsync()
- {
- var mockClient = new Mock { CallBase = true };
-
- mockClient
- .Setup(c => c.SendRequestAsync(It.IsAny(), It.IsAny()))
- .ReturnsAsync(new JsonRpcResponse
- {
- Id = default,
- Result = JsonSerializer.SerializeToNode(new object(), McpJsonUtilities.DefaultOptions),
- });
-
- IMcpClient client = mockClient.Object;
-
- await client.PingAsync(TestContext.Current.CancellationToken);
-
- mockClient.Verify(c => c.SendRequestAsync(It.IsAny(), It.IsAny()), Times.Once);
- }
-
- [Fact]
- public async Task GetPromptAsync_Forwards_To_McpClient_SendRequestAsync()
- {
- var mockClient = new Mock { CallBase = true };
-
- var resultPayload = new GetPromptResult { Messages = [new PromptMessage { Role = Role.User, Content = new TextContentBlock { Text = "hi" } }] };
-
- mockClient
- .Setup(c => c.SendRequestAsync(It.IsAny(), It.IsAny()))
- .ReturnsAsync(new JsonRpcResponse
- {
- Id = default,
- Result = JsonSerializer.SerializeToNode(resultPayload, McpJsonUtilities.DefaultOptions),
- });
-
- IMcpClient client = mockClient.Object;
-
- var result = await client.GetPromptAsync("name", cancellationToken: TestContext.Current.CancellationToken);
-
- Assert.Equal("hi", Assert.IsType(result.Messages[0].Content).Text);
- mockClient.Verify(c => c.SendRequestAsync(It.IsAny(), It.IsAny()), Times.Once);
- }
-
- [Fact]
- public async Task CallToolAsync_Forwards_To_McpClient_SendRequestAsync()
- {
- var mockClient = new Mock { CallBase = true };
-
- var callResult = new CallToolResult { Content = [new TextContentBlock { Text = "ok" }] };
-
- mockClient
- .Setup(c => c.SendRequestAsync(It.IsAny(), It.IsAny()))
- .ReturnsAsync(new JsonRpcResponse
- {
- Id = default,
- Result = JsonSerializer.SerializeToNode(callResult, McpJsonUtilities.DefaultOptions),
- });
-
- IMcpClient client = mockClient.Object;
-
- var result = await client.CallToolAsync("tool", cancellationToken: TestContext.Current.CancellationToken);
-
- Assert.Equal("ok", Assert.IsType(result.Content[0]).Text);
- mockClient.Verify(c => c.SendRequestAsync(It.IsAny(), It.IsAny()), Times.Once);
- }
-
- [Fact]
- public async Task SubscribeToResourceAsync_Forwards_To_McpClient_SendRequestAsync()
- {
- var mockClient = new Mock { CallBase = true };
-
- mockClient
- .Setup(c => c.SendRequestAsync(It.IsAny(), It.IsAny()))
- .ReturnsAsync(new JsonRpcResponse
- {
- Id = default,
- Result = JsonSerializer.SerializeToNode(new EmptyResult(), McpJsonUtilities.DefaultOptions),
- });
-
- IMcpClient client = mockClient.Object;
-
- await client.SubscribeToResourceAsync("mcp://resource/1", TestContext.Current.CancellationToken);
-
- mockClient.Verify(c => c.SendRequestAsync(It.IsAny(), It.IsAny()), Times.Once);
- }
-
- [Fact]
- public async Task UnsubscribeFromResourceAsync_Forwards_To_McpClient_SendRequestAsync()
- {
- var mockClient = new Mock { CallBase = true };
-
- mockClient
- .Setup(c => c.SendRequestAsync(It.IsAny(), It.IsAny()))
- .ReturnsAsync(new JsonRpcResponse
- {
- Id = default,
- Result = JsonSerializer.SerializeToNode(new EmptyResult(), McpJsonUtilities.DefaultOptions),
- });
-
- IMcpClient client = mockClient.Object;
-
- await client.UnsubscribeFromResourceAsync("mcp://resource/1", TestContext.Current.CancellationToken);
-
- mockClient.Verify(c => c.SendRequestAsync(It.IsAny(), It.IsAny()), Times.Once);
- }
-
- [Fact]
- public async Task CompleteAsync_Forwards_To_McpClient_SendRequestAsync()
- {
- var mockClient = new Mock { CallBase = true };
-
- var completion = new Completion { Values = ["one", "two"] };
- var resultPayload = new CompleteResult { Completion = completion };
-
- mockClient
- .Setup(c => c.SendRequestAsync(It.IsAny(), It.IsAny()))
- .ReturnsAsync(new JsonRpcResponse
- {
- Id = default,
- Result = JsonSerializer.SerializeToNode(resultPayload, McpJsonUtilities.DefaultOptions),
- });
-
- IMcpClient client = mockClient.Object;
-
- var result = await client.CompleteAsync(new PromptReference { Name = "p" }, "arg", "val", TestContext.Current.CancellationToken);
-
- Assert.Contains("one", result.Completion.Values);
- mockClient.Verify(c => c.SendRequestAsync(It.IsAny(), It.IsAny()), Times.Once);
- }
-
- [Fact]
- public async Task ReadResourceAsync_String_Forwards_To_McpClient_SendRequestAsync()
- {
- var mockClient = new Mock { CallBase = true };
-
- var resultPayload = new ReadResourceResult { Contents = [] };
-
- mockClient
- .Setup(c => c.SendRequestAsync(It.IsAny(), It.IsAny()))
- .ReturnsAsync(new JsonRpcResponse
- {
- Id = default,
- Result = JsonSerializer.SerializeToNode(resultPayload, McpJsonUtilities.DefaultOptions),
- });
-
- IMcpClient client = mockClient.Object;
-
- var result = await client.ReadResourceAsync("mcp://resource/1", TestContext.Current.CancellationToken);
-
- Assert.NotNull(result);
- mockClient.Verify(c => c.SendRequestAsync(It.IsAny(), It.IsAny()), Times.Once);
- }
-
- [Fact]
- public async Task ReadResourceAsync_Uri_Forwards_To_McpClient_SendRequestAsync()
- {
- var mockClient = new Mock { CallBase = true };
-
- var resultPayload = new ReadResourceResult { Contents = [] };
-
- mockClient
- .Setup(c => c.SendRequestAsync(It.IsAny(), It.IsAny()))
- .ReturnsAsync(new JsonRpcResponse
- {
- Id = default,
- Result = JsonSerializer.SerializeToNode(resultPayload, McpJsonUtilities.DefaultOptions),
- });
-
- IMcpClient client = mockClient.Object;
-
- var result = await client.ReadResourceAsync(new Uri("mcp://resource/1"), TestContext.Current.CancellationToken);
-
- Assert.NotNull(result);
- mockClient.Verify(c => c.SendRequestAsync(It.IsAny(), It.IsAny()), Times.Once);
- }
-
- [Fact]
- public async Task ReadResourceAsync_Template_Forwards_To_McpClient_SendRequestAsync()
- {
- var mockClient = new Mock { CallBase = true };
-
- var resultPayload = new ReadResourceResult { Contents = [] };
-
- mockClient
- .Setup(c => c.SendRequestAsync(It.IsAny(), It.IsAny()))
- .ReturnsAsync(new JsonRpcResponse
- {
- Id = default,
- Result = JsonSerializer.SerializeToNode(resultPayload, McpJsonUtilities.DefaultOptions),
- });
-
- IMcpClient client = mockClient.Object;
-
- var result = await client.ReadResourceAsync("mcp://resource/{id}", new Dictionary { ["id"] = 1 }, TestContext.Current.CancellationToken);
-
- Assert.NotNull(result);
- mockClient.Verify(c => c.SendRequestAsync(It.IsAny(), It.IsAny()), Times.Once);
- }
-}
diff --git a/tests/ModelContextProtocol.Tests/McpEndpointExtensionsTests.cs b/tests/ModelContextProtocol.Tests/McpEndpointExtensionsTests.cs
deleted file mode 100644
index 402b1d09d..000000000
--- a/tests/ModelContextProtocol.Tests/McpEndpointExtensionsTests.cs
+++ /dev/null
@@ -1,119 +0,0 @@
-using ModelContextProtocol.Protocol;
-using Moq;
-using System.Text.Json;
-
-namespace ModelContextProtocol.Tests;
-
-#pragma warning disable CS0618 // Type or member is obsolete
-
-public class McpEndpointExtensionsTests
-{
- [Fact]
- public async Task SendRequestAsync_Generic_Throws_When_Not_McpSession()
- {
- var endpoint = new Mock(MockBehavior.Strict).Object;
-
- var ex = await Assert.ThrowsAsync(async () => await McpEndpointExtensions.SendRequestAsync(
- endpoint, "method", "param", cancellationToken: TestContext.Current.CancellationToken));
- Assert.Contains("Prefer using 'McpServer.SendRequestAsync' instead", ex.Message);
- }
-
- [Fact]
- public async Task SendNotificationAsync_Parameterless_Throws_When_Not_McpSession()
- {
- var endpoint = new Mock(MockBehavior.Strict).Object;
-
- var ex = await Assert.ThrowsAsync(async () => await McpEndpointExtensions.SendNotificationAsync(
- endpoint, "notify", cancellationToken: TestContext.Current.CancellationToken));
- Assert.Contains("Prefer using 'McpServer.SendNotificationAsync' instead", ex.Message);
- }
-
- [Fact]
- public async Task SendNotificationAsync_Generic_Throws_When_Not_McpSession()
- {
- var endpoint = new Mock(MockBehavior.Strict).Object;
-
- var ex = await Assert.ThrowsAsync(async () => await McpEndpointExtensions.SendNotificationAsync(
- endpoint, "notify", "payload", cancellationToken: TestContext.Current.CancellationToken));
- Assert.Contains("Prefer using 'McpServer.SendNotificationAsync' instead", ex.Message);
- }
-
- [Fact]
- public async Task NotifyProgressAsync_Throws_When_Not_McpSession()
- {
- var endpoint = new Mock(MockBehavior.Strict).Object;
-
- var ex = await Assert.ThrowsAsync(async () => await McpEndpointExtensions.NotifyProgressAsync(
- endpoint, new ProgressToken("t1"), new ProgressNotificationValue { Progress = 0.5f }, cancellationToken: TestContext.Current.CancellationToken));
- Assert.Contains("Prefer using 'McpServer.NotifyProgressAsync' instead", ex.Message);
- }
-
- [Fact]
- public async Task SendRequestAsync_Generic_Forwards_To_McpSession_SendRequestAsync()
- {
- var mockSession = new Mock { CallBase = true };
-
- mockSession
- .Setup(s => s.SendRequestAsync(It.IsAny(), It.IsAny()))
- .ReturnsAsync(new JsonRpcResponse
- {
- Id = default,
- Result = JsonSerializer.SerializeToNode(42, McpJsonUtilities.DefaultOptions),
- });
-
- IMcpEndpoint endpoint = mockSession.Object;
-
- var result = await endpoint.SendRequestAsync("method", "param", cancellationToken: TestContext.Current.CancellationToken);
-
- Assert.Equal(42, result);
- mockSession.Verify(s => s.SendRequestAsync(It.IsAny(), It.IsAny()), Times.Once);
- }
-
- [Fact]
- public async Task SendNotificationAsync_Parameterless_Forwards_To_McpSession_SendMessageAsync()
- {
- var mockSession = new Mock { CallBase = true };
-
- mockSession
- .Setup(s => s.SendMessageAsync(It.IsAny(), It.IsAny()))
- .Returns(Task.CompletedTask);
-
- IMcpEndpoint endpoint = mockSession.Object;
-
- await endpoint.SendNotificationAsync("notify", cancellationToken: TestContext.Current.CancellationToken);
-
- mockSession.Verify(s => s.SendMessageAsync(It.IsAny(), It.IsAny()), Times.Once);
- }
-
- [Fact]
- public async Task SendNotificationAsync_Generic_Forwards_To_McpSession_SendMessageAsync()
- {
- var mockSession = new Mock { CallBase = true };
-
- mockSession
- .Setup(s => s.SendMessageAsync(It.IsAny(), It.IsAny()))
- .Returns(Task.CompletedTask);
-
- IMcpEndpoint endpoint = mockSession.Object;
-
- await endpoint.SendNotificationAsync("notify", "payload", cancellationToken: TestContext.Current.CancellationToken);
-
- mockSession.Verify(s => s.SendMessageAsync(It.IsAny(), It.IsAny()), Times.Once);
- }
-
- [Fact]
- public async Task NotifyProgressAsync_Forwards_To_McpSession_SendMessageAsync()
- {
- var mockSession = new Mock { CallBase = true };
-
- mockSession
- .Setup(s => s.SendMessageAsync(It.IsAny(), It.IsAny()))
- .Returns(Task.CompletedTask);
-
- IMcpEndpoint endpoint = mockSession.Object;
-
- await endpoint.NotifyProgressAsync(new ProgressToken("progress-token"), new ProgressNotificationValue { Progress = 1 }, cancellationToken: TestContext.Current.CancellationToken);
-
- mockSession.Verify(s => s.SendMessageAsync(It.IsAny(), It.IsAny()), Times.Once);
- }
-}
\ No newline at end of file
diff --git a/tests/ModelContextProtocol.Tests/Server/McpServerExtensionsTests.cs b/tests/ModelContextProtocol.Tests/Server/McpServerExtensionsTests.cs
deleted file mode 100644
index bf90b218a..000000000
--- a/tests/ModelContextProtocol.Tests/Server/McpServerExtensionsTests.cs
+++ /dev/null
@@ -1,221 +0,0 @@
-using Microsoft.Extensions.AI;
-using ModelContextProtocol.Protocol;
-using ModelContextProtocol.Server;
-using Moq;
-using System.Text.Json;
-
-namespace ModelContextProtocol.Tests.Server;
-
-#pragma warning disable CS0618 // Type or member is obsolete
-
-public class McpServerExtensionsTests
-{
- [Fact]
- public async Task SampleAsync_Request_Throws_When_Not_McpServer()
- {
- var server = new Mock(MockBehavior.Strict).Object;
-
- var ex = await Assert.ThrowsAsync(async () => await server.SampleAsync(
- new CreateMessageRequestParams
- {
- Messages = [new SamplingMessage { Role = Role.User, Content = new TextContentBlock { Text = "hi" } }],
- MaxTokens = 1000
- },
- TestContext.Current.CancellationToken));
- Assert.Contains("Prefer using 'McpServer.SampleAsync' instead", ex.Message);
- }
-
- [Fact]
- public async Task SampleAsync_Messages_Throws_When_Not_McpServer()
- {
- var server = new Mock(MockBehavior.Strict).Object;
-
- var ex = await Assert.ThrowsAsync(async () => await server.SampleAsync(
- [new ChatMessage(ChatRole.User, "hi")], cancellationToken: TestContext.Current.CancellationToken));
- Assert.Contains("Prefer using 'McpServer.SampleAsync' instead", ex.Message);
- }
-
- [Fact]
- public void AsSamplingChatClient_Throws_When_Not_McpServer()
- {
- var server = new Mock(MockBehavior.Strict).Object;
-
- var ex = Assert.Throws(server.AsSamplingChatClient);
- Assert.Contains("Prefer using 'McpServer.AsSamplingChatClient' instead", ex.Message);
- }
-
- [Fact]
- public void AsClientLoggerProvider_Throws_When_Not_McpServer()
- {
- var server = new Mock(MockBehavior.Strict).Object;
-
- var ex = Assert.Throws(server.AsClientLoggerProvider);
- Assert.Contains("Prefer using 'McpServer.AsClientLoggerProvider' instead", ex.Message);
- }
-
- [Fact]
- public async Task RequestRootsAsync_Throws_When_Not_McpServer()
- {
- var server = new Mock(MockBehavior.Strict).Object;
-
- var ex = await Assert.ThrowsAsync(async () => await server.RequestRootsAsync(
- new ListRootsRequestParams(), TestContext.Current.CancellationToken));
- Assert.Contains("Prefer using 'McpServer.RequestRootsAsync' instead", ex.Message);
- }
-
- [Fact]
- public async Task ElicitAsync_Throws_When_Not_McpServer()
- {
- var server = new Mock(MockBehavior.Strict).Object;
-
- var ex = await Assert.ThrowsAsync(async () => await server.ElicitAsync(
- new ElicitRequestParams { Message = "hello" }, TestContext.Current.CancellationToken));
- Assert.Contains("Prefer using 'McpServer.ElicitAsync' instead", ex.Message);
- }
-
- [Fact]
- public async Task SampleAsync_Request_Forwards_To_McpServer_SendRequestAsync()
- {
- var mockServer = new Mock { CallBase = true };
-
- var resultPayload = new CreateMessageResult
- {
- Content = new TextContentBlock { Text = "resp" },
- Model = "test-model",
- Role = Role.Assistant,
- StopReason = "endTurn",
- };
-
- mockServer
- .Setup(s => s.ClientCapabilities)
- .Returns(new ClientCapabilities() { Sampling = new() });
-
- mockServer
- .Setup(s => s.SendRequestAsync(It.IsAny(), It.IsAny()))
- .ReturnsAsync(new JsonRpcResponse
- {
- Id = default,
- Result = JsonSerializer.SerializeToNode(resultPayload, McpJsonUtilities.DefaultOptions),
- });
-
- IMcpServer server = mockServer.Object;
-
- var result = await server.SampleAsync(new CreateMessageRequestParams
- {
- Messages = [new SamplingMessage { Role = Role.User, Content = new TextContentBlock { Text = "hi" } }],
- MaxTokens = 1000
- }, TestContext.Current.CancellationToken);
-
- Assert.Equal("test-model", result.Model);
- Assert.Equal(Role.Assistant, result.Role);
- Assert.Equal("resp", Assert.IsType(result.Content).Text);
- mockServer.Verify(s => s.SendRequestAsync(It.IsAny(), It.IsAny()), Times.Once);
- }
-
- [Fact]
- public async Task SampleAsync_Messages_Forwards_To_McpServer_SendRequestAsync()
- {
- var mockServer = new Mock { CallBase = true };
-
- var resultPayload = new CreateMessageResult
- {
- Content = new TextContentBlock { Text = "resp" },
- Model = "test-model",
- Role = Role.Assistant,
- StopReason = "endTurn",
- };
-
- const int CustomMaxSamplingOutputTokens = 500;
-
- mockServer
- .Setup(s => s.ClientCapabilities)
- .Returns(new ClientCapabilities() { Sampling = new() });
-
- mockServer
- .Setup(s => s.ServerOptions)
- .Returns(new McpServerOptions { MaxSamplingOutputTokens = CustomMaxSamplingOutputTokens });
-
- CreateMessageRequestParams? capturedRequest = null;
- mockServer
- .Setup(s => s.SendRequestAsync(It.IsAny(), It.IsAny()))
- .Callback((request, _) =>
- {
- capturedRequest = JsonSerializer.Deserialize(
- request.Params ?? throw new InvalidOperationException(),
- McpJsonUtilities.DefaultOptions);
- })
- .ReturnsAsync(new JsonRpcResponse
- {
- Id = default,
- Result = JsonSerializer.SerializeToNode(resultPayload, McpJsonUtilities.DefaultOptions),
- });
-
- IMcpServer server = mockServer.Object;
-
- var chatResponse = await server.SampleAsync([new ChatMessage(ChatRole.User, "hi")], cancellationToken: TestContext.Current.CancellationToken);
-
- Assert.Equal("test-model", chatResponse.ModelId);
- var last = chatResponse.Messages.Last();
- Assert.Equal(ChatRole.Assistant, last.Role);
- Assert.Equal("resp", last.Text);
- mockServer.Verify(s => s.SendRequestAsync(It.IsAny(), It.IsAny()), Times.Once);
-
- // Verify that the default value was used
- Assert.NotNull(capturedRequest);
- Assert.Equal(CustomMaxSamplingOutputTokens, capturedRequest.MaxTokens);
- }
-
- [Fact]
- public async Task RequestRootsAsync_Forwards_To_McpServer_SendRequestAsync()
- {
- var mockServer = new Mock { CallBase = true };
-
- var resultPayload = new ListRootsResult { Roots = [new Root { Uri = "root://a" }] };
-
- mockServer
- .Setup(s => s.ClientCapabilities)
- .Returns(new ClientCapabilities() { Roots = new() });
-
- mockServer
- .Setup(s => s.SendRequestAsync(It.IsAny(), It.IsAny()))
- .ReturnsAsync(new JsonRpcResponse
- {
- Id = default,
- Result = JsonSerializer.SerializeToNode(resultPayload, McpJsonUtilities.DefaultOptions),
- });
-
- IMcpServer server = mockServer.Object;
-
- var result = await server.RequestRootsAsync(new ListRootsRequestParams(), TestContext.Current.CancellationToken);
-
- Assert.Equal("root://a", result.Roots[0].Uri);
- mockServer.Verify(s => s.SendRequestAsync(It.IsAny(), It.IsAny()), Times.Once);
- }
-
- [Fact]
- public async Task ElicitAsync_Forwards_To_McpServer_SendRequestAsync()
- {
- var mockServer = new Mock { CallBase = true };
-
- var resultPayload = new ElicitResult { Action = "accept" };
-
- mockServer
- .Setup(s => s.ClientCapabilities)
- .Returns(new ClientCapabilities() { Elicitation = new() });
-
- mockServer
- .Setup(s => s.SendRequestAsync(It.IsAny(), It.IsAny()))
- .ReturnsAsync(new JsonRpcResponse
- {
- Id = default,
- Result = JsonSerializer.SerializeToNode(resultPayload, McpJsonUtilities.DefaultOptions),
- });
-
- IMcpServer server = mockServer.Object;
-
- var result = await server.ElicitAsync(new ElicitRequestParams { Message = "hi" }, TestContext.Current.CancellationToken);
-
- Assert.Equal("accept", result.Action);
- mockServer.Verify(s => s.SendRequestAsync(It.IsAny(), It.IsAny()), Times.Once);
- }
-}
From cd8f7ae328a8f862027a0cfeaed2036b02b8c4ce Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 19 Nov 2025 15:11:32 +0000
Subject: [PATCH 04/12] Merge main branch and resolve conflicts
Co-authored-by: eiriktsarpalis <2813363+eiriktsarpalis@users.noreply.github.com>
---
src/ModelContextProtocol.AspNetCore/StreamableHttpHandler.cs | 1 -
src/ModelContextProtocol.Core/Protocol/JsonRpcMessageWithId.cs | 2 +-
.../StatelessServerTests.cs | 3 ---
3 files changed, 1 insertion(+), 5 deletions(-)
diff --git a/src/ModelContextProtocol.AspNetCore/StreamableHttpHandler.cs b/src/ModelContextProtocol.AspNetCore/StreamableHttpHandler.cs
index 9528ddba7..9f4af7ea5 100644
--- a/src/ModelContextProtocol.AspNetCore/StreamableHttpHandler.cs
+++ b/src/ModelContextProtocol.AspNetCore/StreamableHttpHandler.cs
@@ -250,7 +250,6 @@ private static Task WriteJsonRpcErrorAsync(HttpContext context, string errorMess
{
var jsonRpcError = new JsonRpcError
{
- Id = default,
Error = new()
{
Code = errorCode,
diff --git a/src/ModelContextProtocol.Core/Protocol/JsonRpcMessageWithId.cs b/src/ModelContextProtocol.Core/Protocol/JsonRpcMessageWithId.cs
index 79f4b8687..495a432ee 100644
--- a/src/ModelContextProtocol.Core/Protocol/JsonRpcMessageWithId.cs
+++ b/src/ModelContextProtocol.Core/Protocol/JsonRpcMessageWithId.cs
@@ -26,5 +26,5 @@ private protected JsonRpcMessageWithId()
/// Each ID is expected to be unique within the context of a given session.
///
[JsonPropertyName("id")]
- public required RequestId Id { get; set; }
+ public RequestId Id { get; set; }
}
diff --git a/tests/ModelContextProtocol.AspNetCore.Tests/StatelessServerTests.cs b/tests/ModelContextProtocol.AspNetCore.Tests/StatelessServerTests.cs
index 252af4b88..a843e2975 100644
--- a/tests/ModelContextProtocol.AspNetCore.Tests/StatelessServerTests.cs
+++ b/tests/ModelContextProtocol.AspNetCore.Tests/StatelessServerTests.cs
@@ -203,7 +203,6 @@ public static async Task TestSamplingErrors(McpServer server)
var ex = await Assert.ThrowsAsync(() => server.SendRequestAsync(new JsonRpcRequest
{
- Id = default,
Method = RequestMethods.SamplingCreateMessage
}));
return ex.Message;
@@ -222,7 +221,6 @@ public static async Task TestRootsErrors(McpServer server)
var ex = await Assert.ThrowsAsync(() => server.SendRequestAsync(new JsonRpcRequest
{
- Id = default,
Method = RequestMethods.RootsList
}));
return ex.Message;
@@ -241,7 +239,6 @@ public static async Task TestElicitationErrors(McpServer server)
var ex = await Assert.ThrowsAsync(() => server.SendRequestAsync(new JsonRpcRequest
{
- Id = default,
Method = RequestMethods.ElicitationCreate
}));
return ex.Message;
From eb31afb02db5720976a31a589d57e56def01ce57 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 19 Nov 2025 16:13:33 +0000
Subject: [PATCH 05/12] Remove unused duplicate CreateSamplingHandler and
helper methods from McpClient.Methods.cs
Co-authored-by: eiriktsarpalis <2813363+eiriktsarpalis@users.noreply.github.com>
---
.../Client/McpClient.Methods.cs | 112 ------------------
1 file changed, 112 deletions(-)
diff --git a/src/ModelContextProtocol.Core/Client/McpClient.Methods.cs b/src/ModelContextProtocol.Core/Client/McpClient.Methods.cs
index 5550e786e..6397d8e78 100644
--- a/src/ModelContextProtocol.Core/Client/McpClient.Methods.cs
+++ b/src/ModelContextProtocol.Core/Client/McpClient.Methods.cs
@@ -555,118 +555,6 @@ async ValueTask SendRequestWithProgressAsync(
}
}
- ///
- /// Converts the contents of a into a pair of
- /// and instances to use
- /// as inputs into a operation.
- ///
- ///
- /// The created pair of messages and options.
- /// is .
- internal static (IList Messages, ChatOptions? Options) ToChatClientArguments(
- CreateMessageRequestParams requestParams)
- {
- Throw.IfNull(requestParams);
-
- ChatOptions? options = null;
-
- if (requestParams.MaxTokens is int maxTokens)
- {
- (options ??= new()).MaxOutputTokens = maxTokens;
- }
-
- if (requestParams.Temperature is float temperature)
- {
- (options ??= new()).Temperature = temperature;
- }
-
- if (requestParams.StopSequences is { } stopSequences)
- {
- (options ??= new()).StopSequences = stopSequences.ToArray();
- }
-
- List messages =
- (from sm in requestParams.Messages
- let aiContent = sm.Content.ToAIContent()
- where aiContent is not null
- select new ChatMessage(sm.Role == Role.Assistant ? ChatRole.Assistant : ChatRole.User, [aiContent]))
- .ToList();
-
- return (messages, options);
- }
-
- /// Converts the contents of a into a .
- /// The whose contents should be extracted.
- /// The created .
- /// is .
- internal static CreateMessageResult ToCreateMessageResult(ChatResponse chatResponse)
- {
- Throw.IfNull(chatResponse);
-
- // The ChatResponse can include multiple messages, of varying modalities, but CreateMessageResult supports
- // only either a single blob of text or a single image. Heuristically, we'll use an image if there is one
- // in any of the response messages, or we'll use all the text from them concatenated, otherwise.
-
- ChatMessage? lastMessage = chatResponse.Messages.LastOrDefault();
-
- ContentBlock? content = null;
- if (lastMessage is not null)
- {
- foreach (var lmc in lastMessage.Contents)
- {
- if (lmc is DataContent dc && (dc.HasTopLevelMediaType("image") || dc.HasTopLevelMediaType("audio")))
- {
- content = dc.ToContent();
- }
- }
- }
-
- return new()
- {
- Content = content ?? new TextContentBlock { Text = lastMessage?.Text ?? string.Empty },
- Model = chatResponse.ModelId ?? "unknown",
- Role = lastMessage?.Role == ChatRole.User ? Role.User : Role.Assistant,
- StopReason = chatResponse.FinishReason == ChatFinishReason.Length ? "maxTokens" : "endTurn",
- };
- }
-
- ///
- /// 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 .
- /// is .
- public static Func, CancellationToken, ValueTask> CreateSamplingHandler(
- IChatClient chatClient)
- {
- Throw.IfNull(chatClient);
-
- return async (requestParams, progress, cancellationToken) =>
- {
- Throw.IfNull(requestParams);
-
- var (messages, options) = ToChatClientArguments(requestParams);
- var progressToken = requestParams.ProgressToken;
-
- List updates = [];
- await foreach (var update in chatClient.GetStreamingResponseAsync(messages, options, cancellationToken).ConfigureAwait(false))
- {
- updates.Add(update);
-
- if (progressToken is not null)
- {
- progress.Report(new()
- {
- Progress = updates.Count,
- });
- }
- }
-
- return ToCreateMessageResult(updates.ToChatResponse());
- };
- }
-
///
/// Sets the logging level for the server to control which log messages are sent to the client.
///
From c7deb7f037c4194c5f029c6e2e533d4ae8dcbf37 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 19 Nov 2025 16:19:08 +0000
Subject: [PATCH 06/12] Delete empty McpEndpointExtensions and
McpServerExtensions classes
Co-authored-by: eiriktsarpalis <2813363+eiriktsarpalis@users.noreply.github.com>
---
.../McpEndpointExtensions.cs | 26 -------------------
.../Server/McpServerExtensions.cs | 16 ------------
2 files changed, 42 deletions(-)
delete mode 100644 src/ModelContextProtocol.Core/McpEndpointExtensions.cs
delete mode 100644 src/ModelContextProtocol.Core/Server/McpServerExtensions.cs
diff --git a/src/ModelContextProtocol.Core/McpEndpointExtensions.cs b/src/ModelContextProtocol.Core/McpEndpointExtensions.cs
deleted file mode 100644
index a84cf5e73..000000000
--- a/src/ModelContextProtocol.Core/McpEndpointExtensions.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-using ModelContextProtocol.Client;
-using ModelContextProtocol.Protocol;
-using ModelContextProtocol.Server;
-using System.ComponentModel;
-using System.Diagnostics.CodeAnalysis;
-using System.Runtime.CompilerServices;
-using System.Text.Json;
-
-namespace ModelContextProtocol;
-
-///
-/// Provides extension methods for interacting with an .
-///
-///
-///
-/// This class provides strongly-typed methods for working with the Model Context Protocol (MCP) endpoints,
-/// simplifying JSON-RPC communication by handling serialization and deserialization of parameters and results.
-///
-///
-/// These extension methods are designed to be used with both client () and
-/// server () implementations of the interface.
-///
-///
-public static class McpEndpointExtensions
-{
-}
diff --git a/src/ModelContextProtocol.Core/Server/McpServerExtensions.cs b/src/ModelContextProtocol.Core/Server/McpServerExtensions.cs
deleted file mode 100644
index 640e6f606..000000000
--- a/src/ModelContextProtocol.Core/Server/McpServerExtensions.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-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;
-
-namespace ModelContextProtocol.Server;
-
-///
-/// Provides extension methods for interacting with an instance.
-///
-public static class McpServerExtensions
-{
-}
From 4d6fe0a55af1ca37bc2fb74f6d66af39800e20f3 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 19 Nov 2025 19:44:15 +0000
Subject: [PATCH 07/12] Remove obsolete IMcpEndpoint, IMcpClient, IMcpServer
interfaces and McpClientFactory
Co-authored-by: MackinnonBuck <10456961+MackinnonBuck@users.noreply.github.com>
---
.../Client/IMcpClient.cs | 50 -----------
.../Client/McpClient.Methods.cs | 4 +-
.../Client/McpClient.cs | 4 +-
.../Client/McpClientFactory.cs | 35 --------
.../Client/McpClientHandlers.cs | 2 +-
src/ModelContextProtocol.Core/IMcpEndpoint.cs | 84 -------------------
.../McpSession.Methods.cs | 4 +-
src/ModelContextProtocol.Core/McpSession.cs | 4 +-
.../Server/IMcpServer.cs | 65 --------------
.../Server/McpServer.Methods.cs | 4 +-
.../Server/McpServer.cs | 4 +-
.../Server/McpServerHandlers.cs | 2 +-
.../Server/RequestServiceProvider.cs | 7 +-
13 files changed, 9 insertions(+), 260 deletions(-)
delete mode 100644 src/ModelContextProtocol.Core/Client/IMcpClient.cs
delete mode 100644 src/ModelContextProtocol.Core/Client/McpClientFactory.cs
delete mode 100644 src/ModelContextProtocol.Core/IMcpEndpoint.cs
delete mode 100644 src/ModelContextProtocol.Core/Server/IMcpServer.cs
diff --git a/src/ModelContextProtocol.Core/Client/IMcpClient.cs b/src/ModelContextProtocol.Core/Client/IMcpClient.cs
deleted file mode 100644
index 141add86a..000000000
--- a/src/ModelContextProtocol.Core/Client/IMcpClient.cs
+++ /dev/null
@@ -1,50 +0,0 @@
-using ModelContextProtocol.Protocol;
-using System.ComponentModel;
-
-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
-{
- ///
- /// Gets the capabilities supported by the connected server.
- ///
- /// The client is not connected.
- ServerCapabilities ServerCapabilities { get; }
-
- ///
- /// Gets the implementation information of the connected server.
- ///
- ///
- ///
- /// This property provides identification details about the connected server, including its name and version.
- /// It is populated during the initialization handshake and is available after a successful connection.
- ///
- ///
- /// This information can be useful for logging, debugging, compatibility checks, and displaying server
- /// information to users.
- ///
- ///
- /// The client is not connected.
- Implementation ServerInfo { get; }
-
- ///
- /// Gets any instructions describing how to use the connected server and its features.
- ///
- ///
- ///
- /// This property contains instructions provided by the server during initialization that explain
- /// how to effectively use its capabilities. These instructions can include details about available
- /// tools, expected input formats, limitations, or any other helpful information.
- ///
- ///
- /// This can be used by clients to improve an LLM's understanding of available tools, prompts, and resources.
- /// It can be thought of like a "hint" to the model and may be added to a system prompt.
- ///
- ///
- string? ServerInstructions { get; }
-}
diff --git a/src/ModelContextProtocol.Core/Client/McpClient.Methods.cs b/src/ModelContextProtocol.Core/Client/McpClient.Methods.cs
index 6397d8e78..387cc615a 100644
--- a/src/ModelContextProtocol.Core/Client/McpClient.Methods.cs
+++ b/src/ModelContextProtocol.Core/Client/McpClient.Methods.cs
@@ -10,9 +10,7 @@ namespace ModelContextProtocol.Client;
///
/// Represents an instance of a Model Context Protocol (MCP) client session that connects to and communicates with an MCP server.
///
-#pragma warning disable CS0618 // Type or member is obsolete
-public abstract partial class McpClient : McpSession, IMcpClient
-#pragma warning restore CS0618 // Type or member is obsolete
+public abstract partial class McpClient : McpSession
{
/// Creates an , connecting it to the specified server.
/// The transport instance used to communicate with the server.
diff --git a/src/ModelContextProtocol.Core/Client/McpClient.cs b/src/ModelContextProtocol.Core/Client/McpClient.cs
index c4abe33b7..2af310163 100644
--- a/src/ModelContextProtocol.Core/Client/McpClient.cs
+++ b/src/ModelContextProtocol.Core/Client/McpClient.cs
@@ -5,9 +5,7 @@ namespace ModelContextProtocol.Client;
///
/// Represents an instance of a Model Context Protocol (MCP) client session that connects to and communicates with an MCP server.
///
-#pragma warning disable CS0618 // Type or member is obsolete
-public abstract partial class McpClient : McpSession, IMcpClient
-#pragma warning restore CS0618 // Type or member is obsolete
+public abstract partial class McpClient : McpSession
{
///
/// Gets the capabilities supported by the connected server.
diff --git a/src/ModelContextProtocol.Core/Client/McpClientFactory.cs b/src/ModelContextProtocol.Core/Client/McpClientFactory.cs
deleted file mode 100644
index 805787256..000000000
--- a/src/ModelContextProtocol.Core/Client/McpClientFactory.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using Microsoft.Extensions.Logging;
-using System.ComponentModel;
-
-namespace ModelContextProtocol.Client;
-
-///
-/// Provides factory methods for creating Model Context Protocol (MCP) clients.
-///
-///
-/// This factory class is the primary way to instantiate instances
-/// 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. 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.
- /// The transport instance used to communicate with the server.
- ///
- /// A client configuration object which specifies client capabilities and protocol version.
- /// If , details based on the current process will be employed.
- ///
- /// A logger factory for creating loggers for clients.
- /// The to monitor for cancellation requests. The default is .
- /// An that's connected to the specified server.
- /// is .
- /// is .
- public static async Task CreateAsync(
- IClientTransport clientTransport,
- McpClientOptions? clientOptions = null,
- ILoggerFactory? loggerFactory = null,
- CancellationToken cancellationToken = default)
- => await McpClient.CreateAsync(clientTransport, clientOptions, loggerFactory, cancellationToken);
-}
\ No newline at end of file
diff --git a/src/ModelContextProtocol.Core/Client/McpClientHandlers.cs b/src/ModelContextProtocol.Core/Client/McpClientHandlers.cs
index fecb83299..6200cede3 100644
--- a/src/ModelContextProtocol.Core/Client/McpClientHandlers.cs
+++ b/src/ModelContextProtocol.Core/Client/McpClientHandlers.cs
@@ -36,7 +36,7 @@ public class McpClientHandlers
///
///
/// 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
+ /// For transient handlers, may be used to register a handler that can
/// then be unregistered by disposing of the returned from the method.
///
///
diff --git a/src/ModelContextProtocol.Core/IMcpEndpoint.cs b/src/ModelContextProtocol.Core/IMcpEndpoint.cs
deleted file mode 100644
index 9fe5d6484..000000000
--- a/src/ModelContextProtocol.Core/IMcpEndpoint.cs
+++ /dev/null
@@ -1,84 +0,0 @@
-using System.ComponentModel;
-using ModelContextProtocol.Client;
-using ModelContextProtocol.Protocol;
-using ModelContextProtocol.Server;
-
-namespace ModelContextProtocol;
-
-///
-/// Represents a client or server Model Context Protocol (MCP) endpoint.
-///
-///
-///
-/// The MCP endpoint provides the core communication functionality used by both clients and servers:
-///
-/// Sending JSON-RPC requests and receiving responses.
-/// Sending notifications to the connected endpoint.
-/// Registering handlers for receiving notifications.
-///
-///
-///
-/// serves as the base interface for both and
-/// interfaces, providing the common functionality needed for MCP protocol
-/// communication. Most applications will use these more specific interfaces rather than working with
-/// directly.
-///
-///
-/// All MCP endpoints should be properly disposed after use as they implement .
-///
-///
-[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.
- ///
- /// Typically populated in transports supporting multiple sessions such as Streamable HTTP or SSE.
- /// Can return if the session hasn't initialized or if the transport doesn't
- /// support multiple sessions (as is the case with STDIO).
- ///
- string? SessionId { get; }
-
- ///
- /// Sends a JSON-RPC request to the connected endpoint and waits for a response.
- ///
- /// The JSON-RPC request to send.
- /// The to monitor for cancellation requests. The default is .
- /// A task containing the endpoint's response.
- /// The transport is not connected, or another error occurs during request processing.
- /// An error occured during request processing.
- ///
- /// This method provides low-level access to send raw JSON-RPC requests. For most use cases,
- /// consider using the strongly-typed extension methods that provide a more convenient API.
- ///
- Task SendRequestAsync(JsonRpcRequest request, CancellationToken cancellationToken = default);
-
- ///
- /// Sends a JSON-RPC message to the connected endpoint.
- ///
- ///
- /// The JSON-RPC message to send. This can be any type that implements JsonRpcMessage, such as
- /// JsonRpcRequest, JsonRpcResponse, JsonRpcNotification, or JsonRpcError.
- ///
- /// The to monitor for cancellation requests. The default is .
- /// A task that represents the asynchronous send operation.
- /// The transport is not connected.
- /// is .
- ///
- ///
- /// This method provides low-level access to send any JSON-RPC message. For specific message types,
- /// consider using the higher-level methods such as or
- /// , which provide a simpler API.
- ///
- ///
- /// The method will serialize the message and transmit it using the underlying transport mechanism.
- ///
- ///
- Task SendMessageAsync(JsonRpcMessage message, CancellationToken cancellationToken = default);
-
- /// Registers a handler to be invoked when a notification for the specified method is received.
- /// The notification method.
- /// The handler to be invoked.
- /// An that will remove the registered handler when disposed.
- IAsyncDisposable RegisterNotificationHandler(string method, Func handler);
-}
diff --git a/src/ModelContextProtocol.Core/McpSession.Methods.cs b/src/ModelContextProtocol.Core/McpSession.Methods.cs
index c537732f1..1ad7d7107 100644
--- a/src/ModelContextProtocol.Core/McpSession.Methods.cs
+++ b/src/ModelContextProtocol.Core/McpSession.Methods.cs
@@ -5,9 +5,7 @@
namespace ModelContextProtocol;
-#pragma warning disable CS0618 // Type or member is obsolete
-public abstract partial class McpSession : IMcpEndpoint, IAsyncDisposable
-#pragma warning restore CS0618 // Type or member is obsolete
+public abstract partial class McpSession : IAsyncDisposable
{
///
/// Sends a JSON-RPC request and attempts to deserialize the result to .
diff --git a/src/ModelContextProtocol.Core/McpSession.cs b/src/ModelContextProtocol.Core/McpSession.cs
index 429fdbfd4..7ceb1ad90 100644
--- a/src/ModelContextProtocol.Core/McpSession.cs
+++ b/src/ModelContextProtocol.Core/McpSession.cs
@@ -26,9 +26,7 @@ namespace ModelContextProtocol;
/// All MCP sessions should be properly disposed after use as they implement .
///
///
-#pragma warning disable CS0618 // Type or member is obsolete
-public abstract partial class McpSession : IMcpEndpoint, IAsyncDisposable
-#pragma warning restore CS0618 // Type or member is obsolete
+public abstract partial class McpSession : IAsyncDisposable
{
/// Gets an identifier associated with the current MCP session.
///
diff --git a/src/ModelContextProtocol.Core/Server/IMcpServer.cs b/src/ModelContextProtocol.Core/Server/IMcpServer.cs
deleted file mode 100644
index 8b88aa7a2..000000000
--- a/src/ModelContextProtocol.Core/Server/IMcpServer.cs
+++ /dev/null
@@ -1,65 +0,0 @@
-using ModelContextProtocol.Protocol;
-using System.ComponentModel;
-
-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
-{
- ///
- /// Gets the capabilities supported by the client.
- ///
- ///
- ///
- /// These capabilities are established during the initialization handshake and indicate
- /// which features the client supports, such as sampling, roots, and other
- /// protocol-specific functionality.
- ///
- ///
- /// Server implementations can check these capabilities to determine which features
- /// are available when interacting with the client.
- ///
- ///
- ClientCapabilities? ClientCapabilities { get; }
-
- ///
- /// Gets the version and implementation information of the connected client.
- ///
- ///
- ///
- /// This property contains identification information about the client that has connected to this server,
- /// including its name and version. This information is provided by the client during initialization.
- ///
- ///
- /// Server implementations can use this information for logging, tracking client versions,
- /// or implementing client-specific behaviors.
- ///
- ///
- Implementation? ClientInfo { get; }
-
- ///
- /// Gets the options used to construct this server.
- ///
- ///
- /// These options define the server's capabilities, protocol version, and other configuration
- /// settings that were used to initialize the server.
- ///
- McpServerOptions ServerOptions { get; }
-
- ///
- /// Gets the service provider for the server.
- ///
- IServiceProvider? Services { get; }
-
- /// Gets the last logging level set by the client, or if it's never been set.
- LoggingLevel? LoggingLevel { get; }
-
- ///
- /// Runs the server, listening for and handling client requests.
- ///
- Task RunAsync(CancellationToken cancellationToken = default);
-}
diff --git a/src/ModelContextProtocol.Core/Server/McpServer.Methods.cs b/src/ModelContextProtocol.Core/Server/McpServer.Methods.cs
index 609da53c1..877b9cda7 100644
--- a/src/ModelContextProtocol.Core/Server/McpServer.Methods.cs
+++ b/src/ModelContextProtocol.Core/Server/McpServer.Methods.cs
@@ -14,9 +14,7 @@ namespace ModelContextProtocol.Server;
///
/// Represents an instance of a Model Context Protocol (MCP) server that connects to and communicates with an MCP client.
///
-#pragma warning disable CS0618 // Type or member is obsolete
-public abstract partial class McpServer : McpSession, IMcpServer
-#pragma warning restore CS0618 // Type or member is obsolete
+public abstract partial class McpServer : McpSession
{
///
/// Caches request schemas for elicitation requests based on the type and serializer options.
diff --git a/src/ModelContextProtocol.Core/Server/McpServer.cs b/src/ModelContextProtocol.Core/Server/McpServer.cs
index 02c17de1a..2d8ea6826 100644
--- a/src/ModelContextProtocol.Core/Server/McpServer.cs
+++ b/src/ModelContextProtocol.Core/Server/McpServer.cs
@@ -5,9 +5,7 @@ namespace ModelContextProtocol.Server;
///
/// Represents an instance of a Model Context Protocol (MCP) server that connects to and communicates with an MCP client.
///
-#pragma warning disable CS0618 // Type or member is obsolete
-public abstract partial class McpServer : McpSession, IMcpServer
-#pragma warning restore CS0618 // Type or member is obsolete
+public abstract partial class McpServer : McpSession
{
///
/// Gets the capabilities supported by the client.
diff --git a/src/ModelContextProtocol.Core/Server/McpServerHandlers.cs b/src/ModelContextProtocol.Core/Server/McpServerHandlers.cs
index 0d8deba13..4251f487e 100644
--- a/src/ModelContextProtocol.Core/Server/McpServerHandlers.cs
+++ b/src/ModelContextProtocol.Core/Server/McpServerHandlers.cs
@@ -169,7 +169,7 @@ public sealed class McpServerHandlers
///
///
/// 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
+ /// For transient handlers, may be used to register a handler that can
/// then be unregistered by disposing of the returned from the method.
///
///
diff --git a/src/ModelContextProtocol.Core/Server/RequestServiceProvider.cs b/src/ModelContextProtocol.Core/Server/RequestServiceProvider.cs
index 9359ea157..86f0a6884 100644
--- a/src/ModelContextProtocol.Core/Server/RequestServiceProvider.cs
+++ b/src/ModelContextProtocol.Core/Server/RequestServiceProvider.cs
@@ -19,18 +19,13 @@ internal sealed class RequestServiceProvider(RequestContext
serviceType == typeof(RequestContext) ||
serviceType == typeof(McpServer) ||
-#pragma warning disable CS0618 // Type or member is obsolete
- serviceType == typeof(IMcpServer) ||
-#pragma warning restore CS0618 // Type or member is obsolete
serviceType == typeof(IProgress) ||
serviceType == typeof(ClaimsPrincipal);
///
public object? GetService(Type serviceType) =>
serviceType == typeof(RequestContext) ? request :
-#pragma warning disable CS0618 // Type or member is obsolete
- serviceType == typeof(McpServer) || serviceType == typeof(IMcpServer) ? request.Server :
-#pragma warning restore CS0618 // Type or member is obsolete
+ serviceType == typeof(McpServer) ? request.Server :
serviceType == typeof(IProgress) ?
(request.Params?.ProgressToken is { } progressToken ? new TokenProgress(request.Server, progressToken) : NullProgress.Instance) :
serviceType == typeof(ClaimsPrincipal) ? request.User :
From 2decb2d0d9d6606d4bc1fae55db9a82741b2cec6 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 19 Nov 2025 21:40:35 +0000
Subject: [PATCH 08/12] Remove extra newlines before closing braces in
capability classes
Co-authored-by: MackinnonBuck <10456961+MackinnonBuck@users.noreply.github.com>
---
src/ModelContextProtocol.Core/Protocol/ClientCapabilities.cs | 1 -
src/ModelContextProtocol.Core/Protocol/PromptsCapability.cs | 1 -
src/ModelContextProtocol.Core/Protocol/ResourcesCapability.cs | 1 -
src/ModelContextProtocol.Core/Protocol/RootsCapability.cs | 1 -
src/ModelContextProtocol.Core/Protocol/ServerCapabilities.cs | 1 -
src/ModelContextProtocol.Core/Protocol/ToolsCapability.cs | 1 -
6 files changed, 6 deletions(-)
diff --git a/src/ModelContextProtocol.Core/Protocol/ClientCapabilities.cs b/src/ModelContextProtocol.Core/Protocol/ClientCapabilities.cs
index 5b559ce95..942d90aec 100644
--- a/src/ModelContextProtocol.Core/Protocol/ClientCapabilities.cs
+++ b/src/ModelContextProtocol.Core/Protocol/ClientCapabilities.cs
@@ -66,5 +66,4 @@ public sealed class ClientCapabilities
///
[JsonPropertyName("elicitation")]
public ElicitationCapability? Elicitation { 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 e0e09fdb2..650ebe8c0 100644
--- a/src/ModelContextProtocol.Core/Protocol/PromptsCapability.cs
+++ b/src/ModelContextProtocol.Core/Protocol/PromptsCapability.cs
@@ -31,5 +31,4 @@ public sealed class PromptsCapability
///
[JsonPropertyName("listChanged")]
public bool? ListChanged { 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 d30418384..47cfb3d50 100644
--- a/src/ModelContextProtocol.Core/Protocol/ResourcesCapability.cs
+++ b/src/ModelContextProtocol.Core/Protocol/ResourcesCapability.cs
@@ -29,5 +29,4 @@ public sealed class ResourcesCapability
///
[JsonPropertyName("listChanged")]
public bool? ListChanged { 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 a6b2bbb8a..966c23e2b 100644
--- a/src/ModelContextProtocol.Core/Protocol/RootsCapability.cs
+++ b/src/ModelContextProtocol.Core/Protocol/RootsCapability.cs
@@ -33,5 +33,4 @@ public sealed class RootsCapability
///
[JsonPropertyName("listChanged")]
public bool? ListChanged { 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 7cd3e705e..1b5d0c882 100644
--- a/src/ModelContextProtocol.Core/Protocol/ServerCapabilities.cs
+++ b/src/ModelContextProtocol.Core/Protocol/ServerCapabilities.cs
@@ -65,5 +65,4 @@ public sealed class ServerCapabilities
///
[JsonPropertyName("completions")]
public CompletionsCapability? Completions { get; set; }
-
}
diff --git a/src/ModelContextProtocol.Core/Protocol/ToolsCapability.cs b/src/ModelContextProtocol.Core/Protocol/ToolsCapability.cs
index 70b998dea..2779e03aa 100644
--- a/src/ModelContextProtocol.Core/Protocol/ToolsCapability.cs
+++ b/src/ModelContextProtocol.Core/Protocol/ToolsCapability.cs
@@ -22,5 +22,4 @@ public sealed class ToolsCapability
///
[JsonPropertyName("listChanged")]
public bool? ListChanged { get; set; }
-
}
\ No newline at end of file
From 4443cfcb6ec07179fa801d3eebd9cef04e32c137 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 20 Nov 2025 17:18:31 +0000
Subject: [PATCH 09/12] Merge main branch and resolve conflicts (PR #976
integration)
Co-authored-by: MackinnonBuck <10456961+MackinnonBuck@users.noreply.github.com>
---
.github/dependabot.yml | 1 -
Directory.Build.props | 5 +-
Directory.Packages.props | 30 +-
Makefile | 19 +-
ModelContextProtocol.slnx | 2 +
README.md | 10 +-
docs/concepts/elicitation/elicitation.md | 9 +-
.../samples/server/Tools/InteractiveTools.cs | 148 ++
docs/index.md | 6 +
docs/toc.yml | 6 +-
docs/versioning.md | 61 +
global.json | 3 -
samples/ChatWithTools/Program.cs | 1 +
.../EverythingServer/Tools/SampleLlmTool.cs | 4 +-
.../Tools/SampleLlmTool.cs | 4 +-
.../Diagnostics.cs | 31 +
.../ModelContextProtocol.Analyzers.csproj | 20 +
.../XmlToDescriptionGenerator.cs | 414 +++++
.../AIContentExtensions.cs | 212 ++-
.../Client/McpClientExtensions.cs | 143 --
.../Client/McpClientHandlers.cs | 2 +-
src/ModelContextProtocol.Core/Diagnostics.cs | 2 +-
.../ModelContextProtocol.Core.csproj | 17 +
.../Protocol/ContentBlock.cs | 248 ++-
.../Protocol/ContextInclusion.cs | 15 +
.../Protocol/CreateMessageRequestParams.cs | 20 +
.../Protocol/CreateMessageResult.cs | 26 +-
.../Protocol/ElicitRequestParams.cs | 499 +++++-
.../Protocol/ElicitResult.cs | 2 +-
.../Protocol/SamplingCapability.cs | 24 +-
.../Protocol/SamplingContextCapability.cs | 6 +
.../Protocol/SamplingMessage.cs | 25 +-
.../Protocol/SamplingToolsCapability.cs | 6 +
.../Protocol/SingleItemOrListConverter.cs | 67 +
.../Protocol/ToolChoice.cs | 32 +
.../Server/AIFunctionMcpServerTool.cs | 4 +-
.../Server/McpServer.Methods.cs | 98 +-
.../Server/McpServerPrompt.cs | 2 +-
.../Server/McpServerPromptAttribute.cs | 2 +-
.../Server/McpServerPromptCreateOptions.cs | 4 +-
.../Server/McpServerResourceCreateOptions.cs | 6 +-
.../Server/McpServerTool.cs | 4 +-
.../Server/McpServerToolAttribute.cs | 4 +-
.../Server/McpServerToolCreateOptions.cs | 2 +-
tests/Common/Utils/TestServerTransport.cs | 4 +-
...odelContextProtocol.Analyzers.Tests.csproj | 36 +
.../XmlToDescriptionGeneratorTests.cs | 1545 +++++++++++++++++
.../HttpServerIntegrationTests.cs | 5 +-
.../MapMcpTests.cs | 12 +-
...delContextProtocol.AspNetCore.Tests.csproj | 29 +-
.../Program.cs | 6 +-
.../Program.cs | 8 +-
.../AIContentExtensionsTests.cs | 123 ++
.../Client/McpClientCreationTests.cs | 4 +-
.../Client/McpClientTests.cs | 139 +-
.../Client/McpClientToolTests.cs | 9 +-
.../ClientIntegrationTests.cs | 4 +-
.../DockerEverythingServerTests.cs | 4 +-
.../ModelContextProtocol.Tests.csproj | 28 +-
.../Protocol/ContentBlockTests.cs | 102 +-
.../CreateMessageRequestParamsTests.cs | 174 ++
.../Protocol/CreateMessageResultTests.cs | 247 +++
.../Protocol/ElicitationDefaultValuesTests.cs | 8 +-
.../Protocol/ElicitationTests.cs | 2 +
.../Protocol/ElicitationTypedTests.cs | 4 +-
.../Protocol/EnumSchemaTests.cs | 309 ++++
.../PrimitiveSchemaDefinitionTests.cs | 2 +
.../Protocol/SamplingMessageTests.cs | 111 ++
.../Protocol/ToolChoiceTests.cs | 30 +
.../Server/McpServerTests.cs | 8 +-
.../Transport/StdioClientTransportTests.cs | 3 +-
71 files changed, 4803 insertions(+), 399 deletions(-)
create mode 100644 docs/versioning.md
create mode 100644 src/ModelContextProtocol.Analyzers/Diagnostics.cs
create mode 100644 src/ModelContextProtocol.Analyzers/ModelContextProtocol.Analyzers.csproj
create mode 100644 src/ModelContextProtocol.Analyzers/XmlToDescriptionGenerator.cs
delete mode 100644 src/ModelContextProtocol.Core/Client/McpClientExtensions.cs
create mode 100644 src/ModelContextProtocol.Core/Protocol/SamplingContextCapability.cs
create mode 100644 src/ModelContextProtocol.Core/Protocol/SamplingToolsCapability.cs
create mode 100644 src/ModelContextProtocol.Core/Protocol/SingleItemOrListConverter.cs
create mode 100644 src/ModelContextProtocol.Core/Protocol/ToolChoice.cs
create mode 100644 tests/ModelContextProtocol.Analyzers.Tests/ModelContextProtocol.Analyzers.Tests.csproj
create mode 100644 tests/ModelContextProtocol.Analyzers.Tests/XmlToDescriptionGeneratorTests.cs
create mode 100644 tests/ModelContextProtocol.Tests/Protocol/CreateMessageRequestParamsTests.cs
create mode 100644 tests/ModelContextProtocol.Tests/Protocol/CreateMessageResultTests.cs
create mode 100644 tests/ModelContextProtocol.Tests/Protocol/EnumSchemaTests.cs
create mode 100644 tests/ModelContextProtocol.Tests/Protocol/SamplingMessageTests.cs
create mode 100644 tests/ModelContextProtocol.Tests/Protocol/ToolChoiceTests.cs
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 57236c8cf..7ffd6a269 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -17,7 +17,6 @@ updates:
patterns:
- "xunit.*"
- "Microsoft.NET.Test.Sdk"
- - "Microsoft.Testing.*"
- "coverlet.*"
- "GitHubActionsTestLogger"
- "Moq"
diff --git a/Directory.Build.props b/Directory.Build.props
index 1fad98569..bd2aed325 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -29,9 +29,8 @@
- true
- <_MTPResultsDirectory>$(ArtifactsTestResultsDir)
- $(TestingPlatformCommandLineArguments) --results-directory $(_MTPResultsDirectory) --report-trx --report-trx-filename $(MSBuildProjectName).$(TargetFramework).$(OS).trx
+ trx%3bLogFileName=$(MSBuildProjectName).$(TargetFramework).$(OS).trx
+ $(ArtifactsTestResultsDir)
diff --git a/Directory.Packages.props b/Directory.Packages.props
index f21988562..65f62f16e 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -4,7 +4,6 @@
8.0.229.0.1110.0.0
- 2.0.2
@@ -47,16 +46,25 @@
+
+
+
+
+
+
+
-
+
-
+
+
-
-
-
-
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
@@ -64,6 +72,7 @@
+
@@ -72,13 +81,14 @@
-
-
+
+
-
+
+
diff --git a/Makefile b/Makefile
index 8fd72b1e1..f300ca158 100644
--- a/Makefile
+++ b/Makefile
@@ -18,15 +18,18 @@ build: restore
test: build
dotnet test \
--no-build \
- --no-progress \
--configuration $(CONFIGURATION) \
- --filter-not-trait 'Execution=Manual' \
- --crashdump \
- --hangdump \
- --hangdump-timeout 7m \
- --coverage \
- --coverage-output-format cobertura \
- -p:_MTPResultsDirectory=$(ARTIFACT_PATH)/testresults \
+ --filter '(Execution!=Manual)' \
+ --blame \
+ --blame-crash \
+ --blame-hang-timeout 7m \
+ --diag "$(ARTIFACT_PATH)/diag.txt" \
+ --logger "trx" \
+ --logger "GitHubActions;summary.includePassedTests=true;summary.includeSkippedTests=true" \
+ --collect "XPlat Code Coverage" \
+ --results-directory $(ARTIFACT_PATH)/testresults \
+ -- \
+ RunConfiguration.CollectSourceInformation=true
pack: restore
dotnet pack --no-restore --configuration $(CONFIGURATION)
diff --git a/ModelContextProtocol.slnx b/ModelContextProtocol.slnx
index a70e3e310..1f6dce1ed 100644
--- a/ModelContextProtocol.slnx
+++ b/ModelContextProtocol.slnx
@@ -62,11 +62,13 @@
+
+
diff --git a/README.md b/README.md
index 3099dfcd3..73c71bd2b 100644
--- a/README.md
+++ b/README.md
@@ -41,6 +41,9 @@ To get started writing a client, the `McpClient.CreateAsync` method is used to i
to a server. Once you have an `McpClient`, you can interact with it, such as to enumerate all available tools and invoke tools.
```csharp
+using ModelContextProtocol.Client;
+using ModelContextProtocol.Protocol;
+
var clientTransport = new StdioClientTransport(new StdioClientTransportOptions
{
Name = "Everything",
@@ -63,7 +66,7 @@ var result = await client.CallToolAsync(
cancellationToken:CancellationToken.None);
// echo always returns one and only one text content object
-Console.WriteLine(result.Content.First(c => c.Type == "text").Text);
+Console.WriteLine(result.Content.OfType().First().Text);
```
You can find samples demonstrating how to use ModelContextProtocol with an LLM SDK in the [samples](samples) directory, and also refer to the [tests](tests/ModelContextProtocol.Tests) project for more examples. Additional examples and documentation will be added as in the near future.
@@ -225,6 +228,11 @@ await using McpServer server = McpServer.Create(new StdioServerTransport("MyServ
await server.RunAsync();
```
+Descriptions can be added to tools, prompts, and resources in a variety of ways, including via the `[Description]` attribute from `System.ComponentModel`.
+This attribute may be placed on a method to provide for the tool, prompt, or resource, or on individual parameters to describe each's purpose.
+XML comments may also be used; if an `[McpServerTool]`, `[McpServerPrompt]`, or `[McpServerResource]`-attributed method is marked as `partial`,
+XML comments placed on the method will be used automatically to generate `[Description]` attributes for the method and its parameters.
+
## Acknowledgements
The starting point for this library was a project called [mcpdotnet](https://github.com/PederHP/mcpdotnet), initiated by [Peder Holdgaard Pedersen](https://github.com/PederHP). We are grateful for the work done by Peder and other contributors to that repository, which created a solid foundation for this library.
diff --git a/docs/concepts/elicitation/elicitation.md b/docs/concepts/elicitation/elicitation.md
index 2d9d43c9f..bc1fc7ee2 100644
--- a/docs/concepts/elicitation/elicitation.md
+++ b/docs/concepts/elicitation/elicitation.md
@@ -16,9 +16,16 @@ The C# SDK registers an instance of
so tools can simply add a parameter of type to their method signature to access it.
The MCP Server must specify the schema of each input value it is requesting from the user.
-Only primitive types (string, number, boolean) are supported for elicitation requests.
+Primitive types (string, number, boolean) and enum types are supported for elicitation requests.
The schema may include a description to help the user understand what is being requested.
+For enum types, the SDK supports several schema formats:
+- **UntitledSingleSelectEnumSchema**: A single-select enum where the enum values serve as both the value and display text
+- **TitledSingleSelectEnumSchema**: A single-select enum with separate display titles for each option (using JSON Schema `oneOf` with `const` and `title`)
+- **UntitledMultiSelectEnumSchema**: A multi-select enum allowing multiple values to be selected
+- **TitledMultiSelectEnumSchema**: A multi-select enum with display titles for each option
+- **LegacyTitledEnumSchema** (deprecated): The legacy enum schema using `enumNames` for backward compatibility
+
The server can request a single input or multiple inputs at once.
To help distinguish multiple inputs, each input has a unique name.
diff --git a/docs/concepts/elicitation/samples/server/Tools/InteractiveTools.cs b/docs/concepts/elicitation/samples/server/Tools/InteractiveTools.cs
index 1528fa5a6..1dfcf9ac4 100644
--- a/docs/concepts/elicitation/samples/server/Tools/InteractiveTools.cs
+++ b/docs/concepts/elicitation/samples/server/Tools/InteractiveTools.cs
@@ -123,4 +123,152 @@ CancellationToken token
}
}
}
+
+ //
+ [McpServerTool, Description("Example tool demonstrating various enum schema types")]
+ public async Task EnumExamples(
+ McpServer server,
+ CancellationToken token
+ )
+ {
+ // Example 1: UntitledSingleSelectEnumSchema - Simple enum without display titles
+ var prioritySchema = new RequestSchema
+ {
+ Properties =
+ {
+ ["Priority"] = new UntitledSingleSelectEnumSchema
+ {
+ Title = "Priority Level",
+ Description = "Select the priority level",
+ Enum = ["low", "medium", "high", "critical"],
+ Default = "medium"
+ }
+ }
+ };
+
+ var priorityResponse = await server.ElicitAsync(new ElicitRequestParams
+ {
+ Message = "Select a priority level:",
+ RequestedSchema = prioritySchema
+ }, token);
+
+ if (priorityResponse.Action != "accept")
+ {
+ return "Operation cancelled";
+ }
+
+ string? priority = priorityResponse.Content?["Priority"].GetString();
+
+ // Example 2: TitledSingleSelectEnumSchema - Enum with custom display titles
+ var severitySchema = new RequestSchema
+ {
+ Properties =
+ {
+ ["Severity"] = new TitledSingleSelectEnumSchema
+ {
+ Title = "Issue Severity",
+ Description = "Select the issue severity level",
+ OneOf =
+ [
+ new EnumSchemaOption { Const = "p0", Title = "P0 - Critical (Immediate attention required)" },
+ new EnumSchemaOption { Const = "p1", Title = "P1 - High (Urgent, within 24 hours)" },
+ new EnumSchemaOption { Const = "p2", Title = "P2 - Medium (Within a week)" },
+ new EnumSchemaOption { Const = "p3", Title = "P3 - Low (As time permits)" }
+ ],
+ Default = "p2"
+ }
+ }
+ };
+
+ var severityResponse = await server.ElicitAsync(new ElicitRequestParams
+ {
+ Message = "Select the issue severity:",
+ RequestedSchema = severitySchema
+ }, token);
+
+ if (severityResponse.Action != "accept")
+ {
+ return "Operation cancelled";
+ }
+
+ string? severity = severityResponse.Content?["Severity"].GetString();
+
+ // Example 3: UntitledMultiSelectEnumSchema - Select multiple values
+ var tagsSchema = new RequestSchema
+ {
+ Properties =
+ {
+ ["Tags"] = new UntitledMultiSelectEnumSchema
+ {
+ Title = "Tags",
+ Description = "Select one or more tags",
+ MinItems = 1,
+ MaxItems = 3,
+ Items = new UntitledEnumItemsSchema
+ {
+ Type = "string",
+ Enum = ["bug", "feature", "documentation", "enhancement", "question"]
+ },
+ Default = ["bug"]
+ }
+ }
+ };
+
+ var tagsResponse = await server.ElicitAsync(new ElicitRequestParams
+ {
+ Message = "Select up to 3 tags:",
+ RequestedSchema = tagsSchema
+ }, token);
+
+ if (tagsResponse.Action != "accept")
+ {
+ return "Operation cancelled";
+ }
+
+ // For multi-select, the value is an array
+ var tags = tagsResponse.Content?["Tags"].EnumerateArray()
+ .Select(e => e.GetString())
+ .ToArray();
+
+ // Example 4: TitledMultiSelectEnumSchema - Multi-select with custom titles
+ var featuresSchema = new RequestSchema
+ {
+ Properties =
+ {
+ ["Features"] = new TitledMultiSelectEnumSchema
+ {
+ Title = "Features",
+ Description = "Select desired features",
+ Items = new TitledEnumItemsSchema
+ {
+ AnyOf =
+ [
+ new EnumSchemaOption { Const = "auth", Title = "Authentication & Authorization" },
+ new EnumSchemaOption { Const = "api", Title = "RESTful API" },
+ new EnumSchemaOption { Const = "ui", Title = "Modern UI Components" },
+ new EnumSchemaOption { Const = "db", Title = "Database Integration" }
+ ]
+ }
+ }
+ }
+ };
+
+ var featuresResponse = await server.ElicitAsync(new ElicitRequestParams
+ {
+ Message = "Select desired features:",
+ RequestedSchema = featuresSchema
+ }, token);
+
+ if (featuresResponse.Action != "accept")
+ {
+ return "Operation cancelled";
+ }
+
+ var features = featuresResponse.Content?["Features"].EnumerateArray()
+ .Select(e => e.GetString())
+ .ToArray();
+
+ return $"Selected: Priority={priority}, Severity={severity}, Tags=[{string.Join(", ", tags ?? [])}], Features=[{string.Join(", ", features ?? [])}]";
+ }
+ //
}
\ No newline at end of file
diff --git a/docs/index.md b/docs/index.md
index facf2093b..f2163f094 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -18,6 +18,12 @@ For more information about MCP:
For how-to guides, tutorials, and additional guidance, refer to the [official MCP documentation](https://modelcontextprotocol.io/).
+## Official SDK packages
+
+The official C# SDK packages for stable and pre-release versions are published to the [NuGet Gallery](https://www.nuget.org) under the [ModelContextProtocolOfficial](https://www.nuget.org/profiles/ModelContextProtocolOfficial) profile.
+
+Continuous integration builds are published to the modelcontextprotocol organization's [GitHub NuGet package registry](https://github.com/orgs/modelcontextprotocol/packages?ecosystem=nuget).
+
## License
This project is licensed under the [MIT License](https://github.com/modelcontextprotocol/csharp-sdk/blob/main/LICENSE).
diff --git a/docs/toc.yml b/docs/toc.yml
index 350a2ae3b..84cf4de03 100644
--- a/docs/toc.yml
+++ b/docs/toc.yml
@@ -3,5 +3,7 @@ items:
href: concepts/index.md
- name: API Reference
href: api/ModelContextProtocol.yml
-- name: Github
- href: https://github.com/ModelContextProtocol/csharp-sdk
\ No newline at end of file
+- name: Versioning
+ href: versioning.md
+- name: GitHub
+ href: https://github.com/ModelContextProtocol/csharp-sdk
diff --git a/docs/versioning.md b/docs/versioning.md
new file mode 100644
index 000000000..c9174a74e
--- /dev/null
+++ b/docs/versioning.md
@@ -0,0 +1,61 @@
+---
+title: C# SDK Versioning
+author: jeffhandley
+description: ModelContextProtocol C# SDK approach to versioning, breaking changes, and support
+uid: versioning
+---
+The ModelContextProtocol specification continues to evolve rapidly, and it's important for the C# SDK to remain current with specification additions and updates. To enable this, all NuGet packages that compose the SDK will follow [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html) with MAJOR.MINOR.PATCH version numbers, and optional pre-release versions.
+
+Given a version number MAJOR.MINOR.PATCH, the package versions will increment the:
+
+* MAJOR version when incompatible API changes are included
+* MINOR version when functionality is added in a backward-compatible manner
+* PATCH version when backward-compatible bug fixes are included
+
+*A pre-release version indicates that the version is unstable and might not satisfy the intended compatibility requirements.*
+
+## Supported versions
+
+Beginning with the 1.0.0 release, the following support policy will be applied for the official C# ModelContextProtocol SDK packages:
+
+1. New functionality and additive APIs will be introduced in MINOR releases within the current MAJOR version only
+ * New functionality will not be added to an earlier MAJOR version
+2. Bugs will be fixed within either:
+ 1. A new PATCH release against the latest MAJOR.MINOR version
+ 2. A new MINOR release against the latest MAJOR version
+3. Critical, blocking issues will be fixed against:
+ 1. The latest MINOR version within _the current_ MAJOR version
+ 2. The latest MINOR version within _one previous_ MAJOR version, until the latest MAJOR version has been published for 3 months
+
+## Experimental APIs
+
+MAJOR or MINOR version updates might introduce or alter APIs annotated as [`[Experimental]`](https://learn.microsoft.com/dotnet/api/system.diagnostics.codeanalysis.experimentalattribute). This attribute indicates that an API is experimental and it may change at any time--including within PATCH or MINOR version updates.
+
+Experimental APIs require suppression of diagnostic codes specific to the MCP SDK APIs, using an `MCPEXP` prefix.
+
+## Breaking changes
+
+Prior to the release of a stable 1.0.0 set of NuGet packages, the SDK remains in preview and breaking changes can be introduced without prior notice. All versions beginning with the stable 1.0.0 release will follow semantic versioning, and breaking changes will require increments to the MAJOR version.
+
+If feasible, the SDK will support all versions of the MCP spec. However, if breaking changes to the spec make this infeasible, preference will be given to the most recent version of the MCP spec, and this would be considered a breaking change necessitating a new MAJOR version.
+
+All releases are posted to https://github.com/modelcontextprotocol/csharp-sdk/releases with release notes. Issues and pull requests labeled with `breaking-change` are highlighted in the corresponding release notes.
+
+### Specification schema changes
+
+If the MCP specification changes the schema for JSON payloads, the C# SDK may use the [`McpSession.NegotiatedProtocolVersion`](https://modelcontextprotocol.github.io/csharp-sdk/api/ModelContextProtocol.McpSession.html#ModelContextProtocol_McpSession_NegotiatedProtocolVersion) to dynamically change the payload schema, potentially using internal data transfer objects (DTOs) to achieve the needed deserialization behavior. These techniques will be applied where feasible to maintain backward- and forward-compatibility between MCP specification versions.
+
+Refer to the following prototypes for illustrations of how this could be achieved:
+
+* [Support multiple contents in sampling results](https://github.com/eiriktsarpalis/csharp-sdk/pull/2)
+* [Support multiple contents in sampling results (using DTOs)](https://github.com/eiriktsarpalis/csharp-sdk/pull/3)
+
+### Obsolete APIs
+
+If APIs within the SDK become obsolete due to changes in the MCP spec or other evolution of the SDK's APIs, the [`[Obsolete]`](https://learn.microsoft.com/dotnet/api/system.obsoleteattribute) attribute will be applied to the affected APIs.
+
+1. Within a MINOR version update, APIs may be marked as `[Obsolete]` to produce _build warnings_ while the API remains functional. The build warnings will provide guidance specific to the affected APIs.
+2. Within a MAJOR version update, APIs may be marked as `[Obsolete]` to produce _build errors_ indicating the API is no longer functional and always throws exceptions. The build errors will provide guidance specific to the affected APIs.
+3. Within a MAJOR version update, obsolete APIs may be removed. API removals are expected to be rare and avoided wherever possible, and `[Obsolete]` attributes will be applied ahead of the API removal.
+
+Beginning with the 1.0.0 release, all obsoletions will use diagnostic codes specific to the MCP SDK APIs, using an `MCPOBS` prefix.
diff --git a/global.json b/global.json
index 4ed7c32bc..fcb4599c2 100644
--- a/global.json
+++ b/global.json
@@ -2,8 +2,5 @@
"sdk": {
"version": "10.0.100",
"rollForward": "minor"
- },
- "test": {
- "runner": "Microsoft.Testing.Platform"
}
}
diff --git a/samples/ChatWithTools/Program.cs b/samples/ChatWithTools/Program.cs
index c6fca0493..c5870cdc3 100644
--- a/samples/ChatWithTools/Program.cs
+++ b/samples/ChatWithTools/Program.cs
@@ -1,5 +1,6 @@
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Logging;
+using ModelContextProtocol;
using ModelContextProtocol.Client;
using OpenAI;
using OpenTelemetry;
diff --git a/samples/EverythingServer/Tools/SampleLlmTool.cs b/samples/EverythingServer/Tools/SampleLlmTool.cs
index 6bbe6e51d..48c5184b3 100644
--- a/samples/EverythingServer/Tools/SampleLlmTool.cs
+++ b/samples/EverythingServer/Tools/SampleLlmTool.cs
@@ -17,7 +17,7 @@ public static async Task SampleLLM(
var samplingParams = CreateRequestSamplingParams(prompt ?? string.Empty, "sampleLLM", maxTokens);
var sampleResult = await server.SampleAsync(samplingParams, cancellationToken);
- return $"LLM sampling result: {(sampleResult.Content as TextContentBlock)?.Text}";
+ return $"LLM sampling result: {sampleResult.Content.OfType().FirstOrDefault()?.Text}";
}
private static CreateMessageRequestParams CreateRequestSamplingParams(string context, string uri, int maxTokens = 100)
@@ -27,7 +27,7 @@ private static CreateMessageRequestParams CreateRequestSamplingParams(string con
Messages = [new SamplingMessage
{
Role = Role.User,
- Content = new TextContentBlock { Text = $"Resource {uri} context: {context}" },
+ Content = [new TextContentBlock { Text = $"Resource {uri} context: {context}" }],
}],
SystemPrompt = "You are a helpful test server.",
MaxTokens = maxTokens,
diff --git a/samples/TestServerWithHosting/Tools/SampleLlmTool.cs b/samples/TestServerWithHosting/Tools/SampleLlmTool.cs
index 2c96b8c35..7d4c61784 100644
--- a/samples/TestServerWithHosting/Tools/SampleLlmTool.cs
+++ b/samples/TestServerWithHosting/Tools/SampleLlmTool.cs
@@ -20,7 +20,7 @@ public static async Task SampleLLM(
var samplingParams = CreateRequestSamplingParams(prompt ?? string.Empty, "sampleLLM", maxTokens);
var sampleResult = await thisServer.SampleAsync(samplingParams, cancellationToken);
- return $"LLM sampling result: {(sampleResult.Content as TextContentBlock)?.Text}";
+ return $"LLM sampling result: {sampleResult.Content.OfType().FirstOrDefault()?.Text}";
}
private static CreateMessageRequestParams CreateRequestSamplingParams(string context, string uri, int maxTokens = 100)
@@ -30,7 +30,7 @@ private static CreateMessageRequestParams CreateRequestSamplingParams(string con
Messages = [new SamplingMessage
{
Role = Role.User,
- Content = new TextContentBlock { Text = $"Resource {uri} context: {context}" },
+ Content = [new TextContentBlock { Text = $"Resource {uri} context: {context}" }],
}],
SystemPrompt = "You are a helpful test server.",
MaxTokens = maxTokens,
diff --git a/src/ModelContextProtocol.Analyzers/Diagnostics.cs b/src/ModelContextProtocol.Analyzers/Diagnostics.cs
new file mode 100644
index 000000000..e2c70412b
--- /dev/null
+++ b/src/ModelContextProtocol.Analyzers/Diagnostics.cs
@@ -0,0 +1,31 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Text;
+using System.Collections.Immutable;
+using System.Text;
+using System.Xml.Linq;
+
+namespace ModelContextProtocol.Analyzers;
+
+/// Provides the diagnostic descriptors used by the assembly.
+internal static class Diagnostics
+{
+ public static DiagnosticDescriptor InvalidXmlDocumentation { get; } = new(
+ id: "MCP001",
+ title: "Invalid XML documentation for MCP method",
+ messageFormat: "XML comment for method '{0}' is invalid and cannot be processed to generate [Description] attributes.",
+ category: "mcp",
+ defaultSeverity: DiagnosticSeverity.Warning,
+ isEnabledByDefault: true,
+ description: "The XML documentation comment contains invalid XML and cannot be processed to generate Description attributes.");
+
+ public static DiagnosticDescriptor McpMethodMustBePartial { get; } = new(
+ id: "MCP002",
+ title: "MCP method must be partial to generate [Description] attributes",
+ messageFormat: "Method '{0}' has XML documentation that could be used to generate [Description] attributes, but the method is not declared as partial.",
+ category: "mcp",
+ defaultSeverity: DiagnosticSeverity.Warning,
+ isEnabledByDefault: true,
+ description: "Methods with MCP attributes should be declared as partial to allow the source generator to emit Description attributes from XML documentation comments.");
+}
diff --git a/src/ModelContextProtocol.Analyzers/ModelContextProtocol.Analyzers.csproj b/src/ModelContextProtocol.Analyzers/ModelContextProtocol.Analyzers.csproj
new file mode 100644
index 000000000..5338bbb84
--- /dev/null
+++ b/src/ModelContextProtocol.Analyzers/ModelContextProtocol.Analyzers.csproj
@@ -0,0 +1,20 @@
+
+
+
+ netstandard2.0
+ true
+ false
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/ModelContextProtocol.Analyzers/XmlToDescriptionGenerator.cs b/src/ModelContextProtocol.Analyzers/XmlToDescriptionGenerator.cs
new file mode 100644
index 000000000..a5dff0c70
--- /dev/null
+++ b/src/ModelContextProtocol.Analyzers/XmlToDescriptionGenerator.cs
@@ -0,0 +1,414 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Text;
+using System.CodeDom.Compiler;
+using System.Collections.Immutable;
+using System.Text;
+using System.Xml.Linq;
+
+namespace ModelContextProtocol.Analyzers;
+
+///
+/// Source generator that creates [Description] attributes from XML comments
+/// for partial methods tagged with MCP attributes.
+///
+[Generator]
+public sealed class XmlToDescriptionGenerator : IIncrementalGenerator
+{
+ private const string GeneratedFileName = "ModelContextProtocol.Descriptions.g.cs";
+ private const string McpServerToolAttributeName = "ModelContextProtocol.Server.McpServerToolAttribute";
+ private const string McpServerPromptAttributeName = "ModelContextProtocol.Server.McpServerPromptAttribute";
+ private const string McpServerResourceAttributeName = "ModelContextProtocol.Server.McpServerResourceAttribute";
+ private const string DescriptionAttributeName = "System.ComponentModel.DescriptionAttribute";
+
+ public void Initialize(IncrementalGeneratorInitializationContext context)
+ {
+ // Use ForAttributeWithMetadataName for each MCP attribute type
+ var toolMethods = CreateProviderForAttribute(context, McpServerToolAttributeName);
+ var promptMethods = CreateProviderForAttribute(context, McpServerPromptAttributeName);
+ var resourceMethods = CreateProviderForAttribute(context, McpServerResourceAttributeName);
+
+ // Combine all three providers
+ var allMethods = toolMethods
+ .Collect()
+ .Combine(promptMethods.Collect())
+ .Combine(resourceMethods.Collect())
+ .Select(static (tuple, _) =>
+ {
+ var ((tool, prompt), resource) = tuple;
+ return tool.AddRange(prompt).AddRange(resource);
+ });
+
+ // Combine with compilation to get well-known type symbols.
+ var compilationAndMethods = context.CompilationProvider.Combine(allMethods);
+
+ // Write out the source for all methods.
+ context.RegisterSourceOutput(compilationAndMethods, static (spc, source) => Execute(source.Left, source.Right, spc));
+ }
+
+ private static IncrementalValuesProvider CreateProviderForAttribute(
+ IncrementalGeneratorInitializationContext context,
+ string attributeMetadataName) =>
+ context.SyntaxProvider.ForAttributeWithMetadataName(
+ attributeMetadataName,
+ static (node, _) => node is MethodDeclarationSyntax,
+ static (ctx, ct) =>
+ {
+ var methodDeclaration = (MethodDeclarationSyntax)ctx.TargetNode;
+ var methodSymbol = (IMethodSymbol)ctx.TargetSymbol;
+ return new MethodToGenerate(methodDeclaration, methodSymbol);
+ });
+
+ private static void Execute(Compilation compilation, ImmutableArray methods, SourceProductionContext context)
+ {
+ if (methods.IsDefaultOrEmpty ||
+ compilation.GetTypeByMetadataName(DescriptionAttributeName) is not { } descriptionAttribute)
+ {
+ return;
+ }
+
+ // Gather a list of all methods needing generation.
+ List<(IMethodSymbol MethodSymbol, MethodDeclarationSyntax MethodDeclaration, XmlDocumentation? XmlDocs)> methodsToGenerate = new(methods.Length);
+ foreach (var methodModel in methods)
+ {
+ var xmlDocs = ExtractXmlDocumentation(methodModel.MethodSymbol, context);
+
+ // Generate implementation for partial methods.
+ if (methodModel.MethodDeclaration.Modifiers.Any(SyntaxKind.PartialKeyword))
+ {
+ methodsToGenerate.Add((methodModel.MethodSymbol, methodModel.MethodDeclaration, xmlDocs));
+ }
+ else if (xmlDocs is not null && HasGeneratableContent(xmlDocs, methodModel.MethodSymbol, descriptionAttribute))
+ {
+ // The method is not partial but has XML docs that would generate attributes; issue a diagnostic.
+ context.ReportDiagnostic(Diagnostic.Create(
+ Diagnostics.McpMethodMustBePartial,
+ methodModel.MethodDeclaration.Identifier.GetLocation(),
+ methodModel.MethodSymbol.Name));
+ }
+ }
+
+ // Generate a single file with all partial declarations.
+ if (methodsToGenerate.Count > 0)
+ {
+ string source = GenerateSourceFile(compilation, methodsToGenerate, descriptionAttribute);
+ context.AddSource(GeneratedFileName, SourceText.From(source, Encoding.UTF8));
+ }
+ }
+
+ private static XmlDocumentation? ExtractXmlDocumentation(IMethodSymbol methodSymbol, SourceProductionContext context)
+ {
+ string? xmlDoc = methodSymbol.GetDocumentationCommentXml();
+ if (string.IsNullOrWhiteSpace(xmlDoc))
+ {
+ return null;
+ }
+
+ try
+ {
+ if (XDocument.Parse(xmlDoc).Element("member") is not { } memberElement)
+ {
+ return null;
+ }
+
+ var summary = CleanXmlDocText(memberElement.Element("summary")?.Value);
+ var remarks = CleanXmlDocText(memberElement.Element("remarks")?.Value);
+ var returns = CleanXmlDocText(memberElement.Element("returns")?.Value);
+
+ // Combine summary and remarks for method description.
+ var methodDescription =
+ string.IsNullOrWhiteSpace(remarks) ? summary :
+ string.IsNullOrWhiteSpace(summary) ? remarks :
+ $"{summary}\n{remarks}";
+
+ Dictionary paramDocs = new(StringComparer.Ordinal);
+ foreach (var paramElement in memberElement.Elements("param"))
+ {
+ var name = paramElement.Attribute("name")?.Value;
+ var value = CleanXmlDocText(paramElement.Value);
+ if (!string.IsNullOrWhiteSpace(name) && !string.IsNullOrWhiteSpace(value))
+ {
+ paramDocs[name!] = value;
+ }
+ }
+
+ // Return documentation even if empty - we'll still generate the partial implementation
+ return new(methodDescription ?? string.Empty, returns ?? string.Empty, paramDocs);
+ }
+ catch (System.Xml.XmlException)
+ {
+ // Emit warning for invalid XML
+ context.ReportDiagnostic(Diagnostic.Create(
+ Diagnostics.InvalidXmlDocumentation,
+ methodSymbol.Locations.FirstOrDefault(),
+ methodSymbol.Name));
+ return null;
+ }
+ }
+
+ private static string CleanXmlDocText(string? text)
+ {
+ if (string.IsNullOrWhiteSpace(text))
+ {
+ return string.Empty;
+ }
+
+ // Remove leading/trailing whitespace and normalize line breaks
+ var lines = text!.Split('\n')
+ .Select(line => line.Trim())
+ .Where(line => !string.IsNullOrEmpty(line));
+
+ return string.Join(" ", lines).Trim();
+ }
+
+ private static string GenerateSourceFile(
+ Compilation compilation,
+ List<(IMethodSymbol MethodSymbol, MethodDeclarationSyntax MethodDeclaration, XmlDocumentation? XmlDocs)> methods,
+ INamedTypeSymbol descriptionAttribute)
+ {
+ StringWriter sw = new();
+ IndentedTextWriter writer = new(sw);
+
+ writer.WriteLine("// ");
+ writer.WriteLine($"// ModelContextProtocol.Analyzers {typeof(XmlToDescriptionGenerator).Assembly.GetName().Version}");
+ writer.WriteLine();
+ writer.WriteLine("#pragma warning disable");
+ writer.WriteLine();
+ writer.WriteLine("using System.ComponentModel;");
+ writer.WriteLine("using ModelContextProtocol.Server;");
+ writer.WriteLine();
+
+ // Group methods by namespace and containing type
+ var groupedMethods = methods.GroupBy(m =>
+ m.MethodSymbol.ContainingNamespace.Name == compilation.GlobalNamespace.Name ? "" :
+ m.MethodSymbol.ContainingNamespace?.ToDisplayString() ??
+ "");
+
+ bool firstNamespace = true;
+ foreach (var namespaceGroup in groupedMethods)
+ {
+ if (!firstNamespace)
+ {
+ writer.WriteLine();
+ }
+ firstNamespace = false;
+
+ // Check if this is the global namespace (methods with null ContainingNamespace)
+ bool isGlobalNamespace = string.IsNullOrEmpty(namespaceGroup.Key);
+ if (!isGlobalNamespace)
+ {
+ writer.WriteLine($"namespace {namespaceGroup.Key}");
+ writer.WriteLine("{");
+ writer.Indent++;
+ }
+
+ // Group by containing type within namespace
+ bool isFirstTypeInNamespace = true;
+ foreach (var typeGroup in namespaceGroup.GroupBy(m => m.MethodSymbol.ContainingType, SymbolEqualityComparer.Default))
+ {
+ if (typeGroup.Key is not INamedTypeSymbol containingType)
+ {
+ continue;
+ }
+
+ if (!isFirstTypeInNamespace)
+ {
+ writer.WriteLine();
+ }
+ isFirstTypeInNamespace = false;
+
+ // Write out the type, which could include parent types.
+ AppendNestedTypeDeclarations(writer, containingType, typeGroup, descriptionAttribute);
+ }
+
+ if (!isGlobalNamespace)
+ {
+ writer.Indent--;
+ writer.WriteLine("}");
+ }
+ }
+
+ return sw.ToString();
+ }
+
+ private static void AppendNestedTypeDeclarations(
+ IndentedTextWriter writer,
+ INamedTypeSymbol typeSymbol,
+ IGrouping typeGroup,
+ INamedTypeSymbol descriptionAttribute)
+ {
+ // Build stack of nested types from innermost to outermost
+ Stack types = [];
+ for (var current = typeSymbol; current is not null; current = current.ContainingType)
+ {
+ types.Push(current);
+ }
+
+ // Generate type declarations from outermost to innermost
+ int nestingCount = types.Count;
+ while (types.Count > 0)
+ {
+ // Get the type keyword and handle records
+ var type = types.Pop();
+ var typeDecl = type.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax() as TypeDeclarationSyntax;
+ string typeKeyword;
+ if (typeDecl is RecordDeclarationSyntax rds)
+ {
+ string classOrStruct = rds.ClassOrStructKeyword.ValueText;
+ if (string.IsNullOrEmpty(classOrStruct))
+ {
+ classOrStruct = "class";
+ }
+
+ typeKeyword = $"{typeDecl.Keyword.ValueText} {classOrStruct}";
+ }
+ else
+ {
+ typeKeyword = typeDecl?.Keyword.ValueText ?? "class";
+ }
+
+ writer.WriteLine($"partial {typeKeyword} {type.Name}");
+ writer.WriteLine("{");
+ writer.Indent++;
+ }
+
+ // Generate methods for this type.
+ bool firstMethodInType = true;
+ foreach (var (methodSymbol, methodDeclaration, xmlDocs) in typeGroup)
+ {
+ AppendMethodDeclaration(writer, methodSymbol, methodDeclaration, xmlDocs, descriptionAttribute, firstMethodInType);
+ firstMethodInType = false;
+ }
+
+ // Close all type declarations.
+ for (int i = 0; i < nestingCount; i++)
+ {
+ writer.Indent--;
+ writer.WriteLine("}");
+ }
+ }
+
+ private static void AppendMethodDeclaration(
+ IndentedTextWriter writer,
+ IMethodSymbol methodSymbol,
+ MethodDeclarationSyntax methodDeclaration,
+ XmlDocumentation? xmlDocs,
+ INamedTypeSymbol descriptionAttribute,
+ bool firstMethodInType)
+ {
+ if (!firstMethodInType)
+ {
+ writer.WriteLine();
+ }
+
+ // Add the Description attribute for method if needed and documentation exists
+ if (xmlDocs is not null &&
+ !string.IsNullOrWhiteSpace(xmlDocs.MethodDescription) &&
+ !HasAttribute(methodSymbol, descriptionAttribute))
+ {
+ writer.WriteLine($"[Description(\"{EscapeString(xmlDocs.MethodDescription)}\")]");
+ }
+
+ // Add return: Description attribute if needed and documentation exists
+ if (xmlDocs is not null &&
+ !string.IsNullOrWhiteSpace(xmlDocs.Returns) &&
+ methodSymbol.GetReturnTypeAttributes().All(attr => !SymbolEqualityComparer.Default.Equals(attr.AttributeClass, descriptionAttribute)))
+ {
+ writer.WriteLine($"[return: Description(\"{EscapeString(xmlDocs.Returns)}\")]");
+ }
+
+ // Copy modifiers from original method syntax.
+ // Add return type (without nullable annotations).
+ // Add method name.
+ writer.Write(string.Join(" ", methodDeclaration.Modifiers.Select(m => m.Text)));
+ writer.Write(' ');
+ writer.Write(methodSymbol.ReturnType.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat));
+ writer.Write(' ');
+ writer.Write(methodSymbol.Name);
+
+ // Add parameters with their Description attributes.
+ writer.Write("(");
+ for (int i = 0; i < methodSymbol.Parameters.Length; i++)
+ {
+ IParameterSymbol param = methodSymbol.Parameters[i];
+
+ if (i > 0)
+ {
+ writer.Write(", ");
+ }
+
+ if (xmlDocs is not null &&
+ !HasAttribute(param, descriptionAttribute) &&
+ xmlDocs.Parameters.TryGetValue(param.Name, out var paramDoc) &&
+ !string.IsNullOrWhiteSpace(paramDoc))
+ {
+ writer.Write($"[Description(\"{EscapeString(paramDoc)}\")] ");
+ }
+
+ writer.Write(param.Type.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat));
+ writer.Write(' ');
+ writer.Write(param.Name);
+ }
+ writer.WriteLine(");");
+ }
+
+ /// Checks if a symbol has a specific attribute applied.
+ private static bool HasAttribute(ISymbol symbol, INamedTypeSymbol attributeType)
+ {
+ foreach (var attr in symbol.GetAttributes())
+ {
+ if (SymbolEqualityComparer.Default.Equals(attr.AttributeClass, attributeType))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /// Escape special characters for C# string literals.
+ private static string EscapeString(string text) =>
+ string.IsNullOrEmpty(text) ? text :
+ text.Replace("\\", "\\\\")
+ .Replace("\"", "\\\"")
+ .Replace("\r", "\\r")
+ .Replace("\n", "\\n")
+ .Replace("\t", "\\t");
+
+ /// Checks if XML documentation would generate any Description attributes for a method.
+ private static bool HasGeneratableContent(XmlDocumentation xmlDocs, IMethodSymbol methodSymbol, INamedTypeSymbol descriptionAttribute)
+ {
+ // Check if method description would be generated
+ if (!string.IsNullOrWhiteSpace(xmlDocs.MethodDescription) && !HasAttribute(methodSymbol, descriptionAttribute))
+ {
+ return true;
+ }
+
+ // Check if return description would be generated
+ if (!string.IsNullOrWhiteSpace(xmlDocs.Returns) &&
+ methodSymbol.GetReturnTypeAttributes().All(attr => !SymbolEqualityComparer.Default.Equals(attr.AttributeClass, descriptionAttribute)))
+ {
+ return true;
+ }
+
+ // Check if any parameter descriptions would be generated
+ foreach (var param in methodSymbol.Parameters)
+ {
+ if (!HasAttribute(param, descriptionAttribute) &&
+ xmlDocs.Parameters.TryGetValue(param.Name, out var paramDoc) &&
+ !string.IsNullOrWhiteSpace(paramDoc))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /// Represents a method that may need Description attributes generated.
+ private readonly record struct MethodToGenerate(MethodDeclarationSyntax MethodDeclaration, IMethodSymbol MethodSymbol);
+
+ /// Holds extracted XML documentation for a method.
+ private sealed record XmlDocumentation(string MethodDescription, string Returns, Dictionary Parameters);
+}
diff --git a/src/ModelContextProtocol.Core/AIContentExtensions.cs b/src/ModelContextProtocol.Core/AIContentExtensions.cs
index 8686b7b6a..374f00555 100644
--- a/src/ModelContextProtocol.Core/AIContentExtensions.cs
+++ b/src/ModelContextProtocol.Core/AIContentExtensions.cs
@@ -1,9 +1,11 @@
using Microsoft.Extensions.AI;
+using ModelContextProtocol.Client;
using ModelContextProtocol.Protocol;
#if !NET
using System.Runtime.InteropServices;
#endif
using System.Text.Json;
+using System.Text.Json.Nodes;
namespace ModelContextProtocol;
@@ -16,6 +18,140 @@ namespace ModelContextProtocol;
///
public static class AIContentExtensions
{
+ ///
+ /// 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 .
+ ///
+ ///
+ /// This method creates a function that converts MCP message requests into chat client calls, enabling
+ /// an MCP client to generate text or other content using an actual AI model via the provided chat client.
+ ///
+ ///
+ /// The handler can process text messages, image messages, resource messages, and tool use/results as defined in the
+ /// Model Context Protocol.
+ ///
+ ///
+ /// is .
+ public static Func, CancellationToken, ValueTask> CreateSamplingHandler(
+ this IChatClient chatClient)
+ {
+ Throw.IfNull(chatClient);
+
+ return async (requestParams, progress, cancellationToken) =>
+ {
+ Throw.IfNull(requestParams);
+
+ var (messages, options) = ToChatClientArguments(requestParams);
+ var progressToken = requestParams.ProgressToken;
+
+ List updates = [];
+ await foreach (var update in chatClient.GetStreamingResponseAsync(messages, options, cancellationToken).ConfigureAwait(false))
+ {
+ updates.Add(update);
+
+ if (progressToken is not null)
+ {
+ progress.Report(new() { Progress = updates.Count });
+ }
+ }
+
+ ChatResponse? chatResponse = updates.ToChatResponse();
+ ChatMessage? lastMessage = chatResponse.Messages.LastOrDefault();
+
+ IList? contents = lastMessage?.Contents.Select(c => c.ToContentBlock()).ToList();
+ if (contents is not { Count: > 0 })
+ {
+ (contents ??= []).Add(new TextContentBlock() { Text = "" });
+ }
+
+ return new()
+ {
+ Model = chatResponse.ModelId ?? "",
+ StopReason =
+ chatResponse.FinishReason == ChatFinishReason.Stop ? CreateMessageResult.StopReasonEndTurn :
+ chatResponse.FinishReason == ChatFinishReason.Length ? CreateMessageResult.StopReasonMaxTokens :
+ chatResponse.FinishReason == ChatFinishReason.ToolCalls ? CreateMessageResult.StopReasonToolUse :
+ chatResponse.FinishReason.ToString(),
+ Meta = chatResponse.AdditionalProperties?.ToJsonObject(),
+ Role = lastMessage?.Role == ChatRole.User ? Role.User : Role.Assistant,
+ Content = contents,
+ };
+
+ static (IList Messages, ChatOptions? Options) ToChatClientArguments(CreateMessageRequestParams requestParams)
+ {
+ ChatOptions? options = null;
+
+ if (requestParams.MaxTokens is int maxTokens)
+ {
+ (options ??= new()).MaxOutputTokens = maxTokens;
+ }
+
+ if (requestParams.Temperature is float temperature)
+ {
+ (options ??= new()).Temperature = temperature;
+ }
+
+ if (requestParams.StopSequences is { } stopSequences)
+ {
+ (options ??= new()).StopSequences = stopSequences.ToArray();
+ }
+
+ if (requestParams.SystemPrompt is { } systemPrompt)
+ {
+ (options ??= new()).Instructions = systemPrompt;
+ }
+
+ if (requestParams.Tools is { } tools)
+ {
+ foreach (var tool in tools)
+ {
+ ((options ??= new()).Tools ??= []).Add(new ToolAIFunctionDeclaration(tool));
+ }
+
+ if (options.Tools is { Count: > 0 } && requestParams.ToolChoice is { } toolChoice)
+ {
+ options.ToolMode = toolChoice.Mode switch
+ {
+ ToolChoice.ModeAuto => ChatToolMode.Auto,
+ ToolChoice.ModeRequired => ChatToolMode.RequireAny,
+ ToolChoice.ModeNone => ChatToolMode.None,
+ _ => null,
+ };
+ }
+ }
+
+ List messages = [];
+ foreach (var sm in requestParams.Messages)
+ {
+ if (sm.Content?.Select(b => b.ToAIContent()).OfType().ToList() is { Count: > 0 } aiContents)
+ {
+ messages.Add(new ChatMessage(sm.Role is Role.Assistant ? ChatRole.Assistant : ChatRole.User, aiContents));
+ }
+ }
+
+ return (messages, options);
+ }
+ };
+ }
+
+ /// Converts the specified dictionary to a .
+ internal static JsonObject? ToJsonObject(this IReadOnlyDictionary properties) =>
+ JsonSerializer.SerializeToNode(properties, McpJsonUtilities.JsonContext.Default.IReadOnlyDictionaryStringObject) as JsonObject;
+
+ internal static AdditionalPropertiesDictionary ToAdditionalProperties(this JsonObject obj)
+ {
+ AdditionalPropertiesDictionary d = [];
+ foreach (var kvp in obj)
+ {
+ d.Add(kvp.Key, kvp.Value);
+ }
+
+ return d;
+ }
+
///
/// Converts a to a object.
///
@@ -99,7 +235,7 @@ public static IList ToPromptMessages(this ChatMessage chatMessage
{
if (content is TextContent or DataContent)
{
- messages.Add(new PromptMessage { Role = r, Content = content.ToContent() });
+ messages.Add(new PromptMessage { Role = r, Content = content.ToContentBlock() });
}
}
@@ -122,13 +258,31 @@ public static IList ToPromptMessages(this ChatMessage chatMessage
AIContent? ac = content switch
{
TextContentBlock textContent => new TextContent(textContent.Text),
+
ImageContentBlock imageContent => new DataContent(Convert.FromBase64String(imageContent.Data), imageContent.MimeType),
+
AudioContentBlock audioContent => new DataContent(Convert.FromBase64String(audioContent.Data), audioContent.MimeType),
+
EmbeddedResourceBlock resourceContent => resourceContent.Resource.ToAIContent(),
+
+ ToolUseContentBlock toolUse => FunctionCallContent.CreateFromParsedArguments(toolUse.Input, toolUse.Id, toolUse.Name,
+ static json => JsonSerializer.Deserialize(json, McpJsonUtilities.JsonContext.Default.IDictionaryStringObject)),
+
+ ToolResultContentBlock toolResult => new FunctionResultContent(
+ toolResult.ToolUseId,
+ toolResult.Content.Count == 1 ? toolResult.Content[0].ToAIContent() : toolResult.Content.Select(c => c.ToAIContent()).OfType().ToList())
+ {
+ Exception = toolResult.IsError is true ? new() : null,
+ },
+
_ => null,
};
- ac?.RawRepresentation = content;
+ if (ac is not null)
+ {
+ ac.RawRepresentation = content;
+ ac.AdditionalProperties = content.Meta?.ToAdditionalProperties();
+ }
return ac;
}
@@ -200,8 +354,12 @@ public static IList ToAIContents(this IEnumerable c
return [.. contents.Select(ToAIContent)];
}
- internal static ContentBlock ToContent(this AIContent content) =>
- content switch
+ /// Creates a new from the content of an .
+ /// The to convert.
+ /// The created .
+ public static ContentBlock ToContentBlock(this AIContent content)
+ {
+ ContentBlock contentBlock = content switch
{
TextContent textContent => new TextContentBlock
{
@@ -230,9 +388,55 @@ internal static ContentBlock ToContent(this AIContent content) =>
}
},
+ FunctionCallContent callContent => new ToolUseContentBlock()
+ {
+ Id = callContent.CallId,
+ Name = callContent.Name,
+ Input = JsonSerializer.SerializeToElement(callContent.Arguments, McpJsonUtilities.DefaultOptions.GetTypeInfo>()!),
+ },
+
+ FunctionResultContent resultContent => new ToolResultContentBlock()
+ {
+ ToolUseId = resultContent.CallId,
+ IsError = resultContent.Exception is not null,
+ Content =
+ resultContent.Result is AIContent c ? [c.ToContentBlock()] :
+ resultContent.Result is IEnumerable ec ? [.. ec.Select(c => c.ToContentBlock())] :
+ [new TextContentBlock { Text = JsonSerializer.Serialize(content, McpJsonUtilities.DefaultOptions.GetTypeInfo