From 35f48e0041487b1abd125a5c7e174a43a7ca68f8 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Thu, 6 Nov 2025 17:18:58 -0800 Subject: [PATCH 1/8] Remove trailing whitespace to make the real changes easier to review --- src/ModelContextProtocol.Core/Client/McpClientPrompt.cs | 4 ++-- src/ModelContextProtocol.Core/Client/McpClientResource.cs | 2 +- .../Client/McpClientResourceTemplate.cs | 2 +- src/ModelContextProtocol.Core/Client/McpClientTool.cs | 8 ++++---- src/ModelContextProtocol.Core/McpJsonUtilities.cs | 4 ++-- src/ModelContextProtocol.Core/McpSession.Methods.cs | 8 ++++---- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/ModelContextProtocol.Core/Client/McpClientPrompt.cs b/src/ModelContextProtocol.Core/Client/McpClientPrompt.cs index c84e0787b..5d0acca31 100644 --- a/src/ModelContextProtocol.Core/Client/McpClientPrompt.cs +++ b/src/ModelContextProtocol.Core/Client/McpClientPrompt.cs @@ -57,7 +57,7 @@ public McpClientPrompt(McpClient client, Prompt prompt) /// which can be useful for advanced scenarios or when implementing custom MCP client extensions. /// /// - /// For most common use cases, you can use the more convenient and + /// For most common use cases, you can use the more convenient and /// properties instead of accessing the directly. /// /// @@ -85,7 +85,7 @@ public McpClientPrompt(McpClient client, Prompt prompt) /// The server will process the request and return a result containing messages or other content. /// /// - /// This is a convenience method that internally calls + /// This is a convenience method that internally calls /// with this prompt's name and arguments. /// /// diff --git a/src/ModelContextProtocol.Core/Client/McpClientResource.cs b/src/ModelContextProtocol.Core/Client/McpClientResource.cs index 504eca088..3c08ab84e 100644 --- a/src/ModelContextProtocol.Core/Client/McpClientResource.cs +++ b/src/ModelContextProtocol.Core/Client/McpClientResource.cs @@ -52,7 +52,7 @@ public McpClientResource(McpClient client, Resource resource) /// which can be useful for advanced scenarios or when implementing custom MCP client extensions. /// /// - /// For most common use cases, you can use the more convenient and + /// For most common use cases, you can use the more convenient and /// properties instead of accessing the directly. /// /// diff --git a/src/ModelContextProtocol.Core/Client/McpClientResourceTemplate.cs b/src/ModelContextProtocol.Core/Client/McpClientResourceTemplate.cs index 134cef570..b08bf9d03 100644 --- a/src/ModelContextProtocol.Core/Client/McpClientResourceTemplate.cs +++ b/src/ModelContextProtocol.Core/Client/McpClientResourceTemplate.cs @@ -52,7 +52,7 @@ public McpClientResourceTemplate(McpClient client, ResourceTemplate resourceTemp /// which can be useful for advanced scenarios or when implementing custom MCP client extensions. /// /// - /// For most common use cases, you can use the more convenient and + /// For most common use cases, you can use the more convenient and /// properties instead of accessing the directly. /// /// diff --git a/src/ModelContextProtocol.Core/Client/McpClientTool.cs b/src/ModelContextProtocol.Core/Client/McpClientTool.cs index 012bc813a..4a2bccdd6 100644 --- a/src/ModelContextProtocol.Core/Client/McpClientTool.cs +++ b/src/ModelContextProtocol.Core/Client/McpClientTool.cs @@ -10,7 +10,7 @@ namespace ModelContextProtocol.Client; /// /// /// -/// The class encapsulates an along with a description of +/// The class encapsulates an along with a description of /// a tool available via that client, allowing it to be invoked as an . This enables integration /// with AI models that support function calling capabilities. /// @@ -203,7 +203,7 @@ public ValueTask CallAsync( /// A new instance of with the provided name. /// /// - /// This is useful for optimizing the tool name for specific models or for prefixing the tool name + /// This is useful for optimizing the tool name for specific models or for prefixing the tool name /// with a namespace to avoid conflicts. /// /// @@ -215,7 +215,7 @@ public ValueTask CallAsync( /// Creating specialized versions of a general tool for specific contexts /// /// - /// When invoking , the MCP server will still be called with + /// When invoking , the MCP server will still be called with /// the original tool name, so no mapping is required on the server side. This new name only affects /// the value returned from this instance's . /// @@ -238,7 +238,7 @@ public McpClientTool WithName(string name) => /// You need to tailor the tool's description for specific model requirements /// /// - /// When invoking , the MCP server will still be called with + /// When invoking , the MCP server will still be called with /// the original tool description, so no mapping is required on the server side. This new description only affects /// the value returned from this instance's . /// diff --git a/src/ModelContextProtocol.Core/McpJsonUtilities.cs b/src/ModelContextProtocol.Core/McpJsonUtilities.cs index 3d08bd82e..23006cf88 100644 --- a/src/ModelContextProtocol.Core/McpJsonUtilities.cs +++ b/src/ModelContextProtocol.Core/McpJsonUtilities.cs @@ -16,7 +16,7 @@ public static partial class McpJsonUtilities /// /// /// - /// For Native AOT or applications disabling , this instance + /// For Native AOT or applications disabling , this instance /// includes source generated contracts for all common exchange types contained in the ModelContextProtocol library. /// /// @@ -88,7 +88,7 @@ internal static bool IsValidMcpToolSchema(JsonElement element) [JsonSourceGenerationOptions(JsonSerializerDefaults.Web, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, NumberHandling = JsonNumberHandling.AllowReadingFromString)] - + // JSON-RPC [JsonSerializable(typeof(JsonRpcMessage))] [JsonSerializable(typeof(JsonRpcMessage[]))] diff --git a/src/ModelContextProtocol.Core/McpSession.Methods.cs b/src/ModelContextProtocol.Core/McpSession.Methods.cs index c537732f1..c929f6039 100644 --- a/src/ModelContextProtocol.Core/McpSession.Methods.cs +++ b/src/ModelContextProtocol.Core/McpSession.Methods.cs @@ -80,8 +80,8 @@ internal async ValueTask SendRequestAsync( /// 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 + /// 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. /// /// @@ -102,11 +102,11 @@ public Task SendNotificationAsync(string method, CancellationToken cancellationT /// A task that represents the asynchronous send operation. /// /// - /// This method sends a notification with parameters to the connected session. Notifications are one-way + /// This method sends a notification with parameters to the connected session. 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 + /// The parameters object is serialized to JSON according to the provided serializer options or the default /// options if none are specified. /// /// From 826a615237f591ad664446542b7c95ff731ddae9 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Thu, 6 Nov 2025 17:21:02 -0800 Subject: [PATCH 2/8] Copliot changes to add meta to high-level methods --- .../Client/McpClient.Methods.cs | 103 ++++++++++++------ .../Client/McpClientPrompt.cs | 2 +- .../Client/McpClientResource.cs | 4 +- .../Client/McpClientResourceTemplate.cs | 2 +- .../Client/McpClientTool.cs | 2 +- .../McpJsonUtilities.cs | 1 + .../McpSession.Methods.cs | 3 + .../Protocol/PingRequestParams.cs | 11 ++ .../Server/McpServer.Methods.cs | 21 +++- 9 files changed, 105 insertions(+), 44 deletions(-) create mode 100644 src/ModelContextProtocol.Core/Protocol/PingRequestParams.cs diff --git a/src/ModelContextProtocol.Core/Client/McpClient.Methods.cs b/src/ModelContextProtocol.Core/Client/McpClient.Methods.cs index 5550e786e..f0bac4661 100644 --- a/src/ModelContextProtocol.Core/Client/McpClient.Methods.cs +++ b/src/ModelContextProtocol.Core/Client/McpClient.Methods.cs @@ -4,6 +4,7 @@ using ModelContextProtocol.Server; using System.Runtime.CompilerServices; using System.Text.Json; +using System.Text.Json.Nodes; namespace ModelContextProtocol.Client; @@ -53,27 +54,29 @@ public static async Task CreateAsync( /// /// Sends a ping request to verify server connectivity. /// + /// Optional metadata to include in the request. /// The to monitor for cancellation requests. The default is . /// A task that completes when the ping is successful. /// Thrown when the server cannot be reached or returns an error response. - public Task PingAsync(CancellationToken cancellationToken = default) + public ValueTask PingAsync(JsonObject? meta = null, CancellationToken cancellationToken = default) { - var opts = McpJsonUtilities.DefaultOptions; - opts.MakeReadOnly(); - return SendRequestAsync( + return SendRequestAsync( RequestMethods.Ping, - parameters: null, - serializerOptions: opts, - cancellationToken: cancellationToken).AsTask(); + new PingRequestParams { Meta = meta }, + McpJsonUtilities.JsonContext.Default.PingRequestParams, + McpJsonUtilities.JsonContext.Default.PingResult, + cancellationToken: cancellationToken); } /// /// Retrieves a list of available tools from the server. /// + /// Optional metadata to include in the request. /// 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. public async ValueTask> ListToolsAsync( + JsonObject? meta = null, JsonSerializerOptions? serializerOptions = null, CancellationToken cancellationToken = default) { @@ -86,7 +89,7 @@ public async ValueTask> ListToolsAsync( { var toolResults = await SendRequestAsync( RequestMethods.ToolsList, - new() { Cursor = cursor }, + new() { Cursor = cursor, Meta = meta }, McpJsonUtilities.JsonContext.Default.ListToolsRequestParams, McpJsonUtilities.JsonContext.Default.ListToolsResult, cancellationToken: cancellationToken).ConfigureAwait(false); @@ -107,10 +110,12 @@ public async ValueTask> ListToolsAsync( /// /// Creates an enumerable for asynchronously enumerating all available tools from the server. /// + /// Optional metadata to include in the requests. /// 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. public async IAsyncEnumerable EnumerateToolsAsync( + JsonObject? meta = null, JsonSerializerOptions? serializerOptions = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) { @@ -122,7 +127,7 @@ public async IAsyncEnumerable EnumerateToolsAsync( { var toolResults = await SendRequestAsync( RequestMethods.ToolsList, - new() { Cursor = cursor }, + new() { Cursor = cursor, Meta = meta }, McpJsonUtilities.JsonContext.Default.ListToolsRequestParams, McpJsonUtilities.JsonContext.Default.ListToolsResult, cancellationToken: cancellationToken).ConfigureAwait(false); @@ -140,9 +145,11 @@ public async IAsyncEnumerable EnumerateToolsAsync( /// /// Retrieves a list of available prompts from the server. /// + /// Optional metadata to include in the request. /// The to monitor for cancellation requests. The default is . /// A list of all available prompts as instances. public async ValueTask> ListPromptsAsync( + JsonObject? meta = null, CancellationToken cancellationToken = default) { List? prompts = null; @@ -151,7 +158,7 @@ public async ValueTask> ListPromptsAsync( { var promptResults = await SendRequestAsync( RequestMethods.PromptsList, - new() { Cursor = cursor }, + new() { Cursor = cursor, Meta = meta }, McpJsonUtilities.JsonContext.Default.ListPromptsRequestParams, McpJsonUtilities.JsonContext.Default.ListPromptsResult, cancellationToken: cancellationToken).ConfigureAwait(false); @@ -172,9 +179,11 @@ public async ValueTask> ListPromptsAsync( /// /// Creates an enumerable for asynchronously enumerating all available prompts from the server. /// + /// Optional metadata to include in the requests. /// The to monitor for cancellation requests. The default is . /// An asynchronous sequence of all available prompts as instances. public async IAsyncEnumerable EnumeratePromptsAsync( + JsonObject? meta = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) { string? cursor = null; @@ -182,7 +191,7 @@ public async IAsyncEnumerable EnumeratePromptsAsync( { var promptResults = await SendRequestAsync( RequestMethods.PromptsList, - new() { Cursor = cursor }, + new() { Cursor = cursor, Meta = meta }, McpJsonUtilities.JsonContext.Default.ListPromptsRequestParams, McpJsonUtilities.JsonContext.Default.ListPromptsResult, cancellationToken: cancellationToken).ConfigureAwait(false); @@ -202,12 +211,14 @@ public async IAsyncEnumerable EnumeratePromptsAsync( /// /// The name of the prompt to retrieve. /// Optional arguments for the prompt. Keys are parameter names, and values are the argument values. + /// Optional metadata to include in the request. /// 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. public ValueTask GetPromptAsync( string name, IReadOnlyDictionary? arguments = null, + JsonObject? meta = null, JsonSerializerOptions? serializerOptions = null, CancellationToken cancellationToken = default) { @@ -218,7 +229,7 @@ public ValueTask GetPromptAsync( return SendRequestAsync( RequestMethods.PromptsGet, - new() { Name = name, Arguments = ToArgumentsDictionary(arguments, serializerOptions) }, + new() { Name = name, Arguments = ToArgumentsDictionary(arguments, serializerOptions), Meta = meta }, McpJsonUtilities.JsonContext.Default.GetPromptRequestParams, McpJsonUtilities.JsonContext.Default.GetPromptResult, cancellationToken: cancellationToken); @@ -227,9 +238,11 @@ public ValueTask GetPromptAsync( /// /// Retrieves a list of available resource templates from the server. /// + /// Optional metadata to include in the request. /// The to monitor for cancellation requests. The default is . /// A list of all available resource templates as instances. public async ValueTask> ListResourceTemplatesAsync( + JsonObject? meta = null, CancellationToken cancellationToken = default) { List? resourceTemplates = null; @@ -239,7 +252,7 @@ public async ValueTask> ListResourceTemplatesAs { var templateResults = await SendRequestAsync( RequestMethods.ResourcesTemplatesList, - new() { Cursor = cursor }, + new() { Cursor = cursor, Meta = meta }, McpJsonUtilities.JsonContext.Default.ListResourceTemplatesRequestParams, McpJsonUtilities.JsonContext.Default.ListResourceTemplatesResult, cancellationToken: cancellationToken).ConfigureAwait(false); @@ -260,9 +273,11 @@ public async ValueTask> ListResourceTemplatesAs /// /// Creates an enumerable for asynchronously enumerating all available resource templates from the server. /// + /// Optional metadata to include in the requests. /// The to monitor for cancellation requests. The default is . /// An asynchronous sequence of all available resource templates as instances. public async IAsyncEnumerable EnumerateResourceTemplatesAsync( + JsonObject? meta = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) { string? cursor = null; @@ -270,7 +285,7 @@ public async IAsyncEnumerable EnumerateResourceTempla { var templateResults = await SendRequestAsync( RequestMethods.ResourcesTemplatesList, - new() { Cursor = cursor }, + new() { Cursor = cursor, Meta = meta }, McpJsonUtilities.JsonContext.Default.ListResourceTemplatesRequestParams, McpJsonUtilities.JsonContext.Default.ListResourceTemplatesResult, cancellationToken: cancellationToken).ConfigureAwait(false); @@ -288,9 +303,11 @@ public async IAsyncEnumerable EnumerateResourceTempla /// /// Retrieves a list of available resources from the server. /// + /// Optional metadata to include in the request. /// The to monitor for cancellation requests. The default is . /// A list of all available resources as instances. public async ValueTask> ListResourcesAsync( + JsonObject? meta = null, CancellationToken cancellationToken = default) { List? resources = null; @@ -300,7 +317,7 @@ public async ValueTask> ListResourcesAsync( { var resourceResults = await SendRequestAsync( RequestMethods.ResourcesList, - new() { Cursor = cursor }, + new() { Cursor = cursor, Meta = meta }, McpJsonUtilities.JsonContext.Default.ListResourcesRequestParams, McpJsonUtilities.JsonContext.Default.ListResourcesResult, cancellationToken: cancellationToken).ConfigureAwait(false); @@ -321,9 +338,11 @@ public async ValueTask> ListResourcesAsync( /// /// Creates an enumerable for asynchronously enumerating all available resources from the server. /// + /// Optional metadata to include in the requests. /// The to monitor for cancellation requests. The default is . /// An asynchronous sequence of all available resources as instances. public async IAsyncEnumerable EnumerateResourcesAsync( + JsonObject? meta = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) { string? cursor = null; @@ -331,7 +350,7 @@ public async IAsyncEnumerable EnumerateResourcesAsync( { var resourceResults = await SendRequestAsync( RequestMethods.ResourcesList, - new() { Cursor = cursor }, + new() { Cursor = cursor, Meta = meta }, McpJsonUtilities.JsonContext.Default.ListResourcesRequestParams, McpJsonUtilities.JsonContext.Default.ListResourcesResult, cancellationToken: cancellationToken).ConfigureAwait(false); @@ -350,15 +369,16 @@ public async IAsyncEnumerable EnumerateResourcesAsync( /// Reads a resource from the server. /// /// The uri of the resource. + /// Optional metadata to include in the request. /// The to monitor for cancellation requests. The default is . public ValueTask ReadResourceAsync( - string uri, CancellationToken cancellationToken = default) + string uri, JsonObject? meta = null, CancellationToken cancellationToken = default) { Throw.IfNullOrWhiteSpace(uri); return SendRequestAsync( RequestMethods.ResourcesRead, - new() { Uri = uri }, + new() { Uri = uri, Meta = meta }, McpJsonUtilities.JsonContext.Default.ReadResourceRequestParams, McpJsonUtilities.JsonContext.Default.ReadResourceResult, cancellationToken: cancellationToken); @@ -368,13 +388,14 @@ public ValueTask ReadResourceAsync( /// Reads a resource from the server. /// /// The uri of the resource. + /// Optional metadata to include in the request. /// The to monitor for cancellation requests. The default is . public ValueTask ReadResourceAsync( - Uri uri, CancellationToken cancellationToken = default) + Uri uri, JsonObject? meta = null, CancellationToken cancellationToken = default) { Throw.IfNull(uri); - return ReadResourceAsync(uri.ToString(), cancellationToken); + return ReadResourceAsync(uri.ToString(), meta, cancellationToken); } /// @@ -382,16 +403,17 @@ public ValueTask ReadResourceAsync( /// /// The uri template of the resource. /// Arguments to use to format . + /// Optional metadata to include in the request. /// The to monitor for cancellation requests. The default is . public ValueTask ReadResourceAsync( - string uriTemplate, IReadOnlyDictionary arguments, CancellationToken cancellationToken = default) + string uriTemplate, IReadOnlyDictionary arguments, JsonObject? meta = null, CancellationToken cancellationToken = default) { Throw.IfNullOrWhiteSpace(uriTemplate); Throw.IfNull(arguments); return SendRequestAsync( RequestMethods.ResourcesRead, - new() { Uri = UriTemplate.FormatUri(uriTemplate, arguments) }, + new() { Uri = UriTemplate.FormatUri(uriTemplate, arguments), Meta = meta }, McpJsonUtilities.JsonContext.Default.ReadResourceRequestParams, McpJsonUtilities.JsonContext.Default.ReadResourceResult, cancellationToken: cancellationToken); @@ -426,15 +448,16 @@ public ValueTask CompleteAsync(Reference reference, string argum /// Subscribes to a resource on the server to receive notifications when it changes. /// /// The URI of the resource to which to subscribe. + /// Optional metadata to include in the request. /// The to monitor for cancellation requests. The default is . /// A task that represents the asynchronous operation. - public Task SubscribeToResourceAsync(string uri, CancellationToken cancellationToken = default) + public Task SubscribeToResourceAsync(string uri, JsonObject? meta = null, CancellationToken cancellationToken = default) { Throw.IfNullOrWhiteSpace(uri); return SendRequestAsync( RequestMethods.ResourcesSubscribe, - new() { Uri = uri }, + new() { Uri = uri, Meta = meta }, McpJsonUtilities.JsonContext.Default.SubscribeRequestParams, McpJsonUtilities.JsonContext.Default.EmptyResult, cancellationToken: cancellationToken).AsTask(); @@ -444,28 +467,30 @@ public Task SubscribeToResourceAsync(string uri, CancellationToken cancellationT /// Subscribes to a resource on the server to receive notifications when it changes. /// /// The URI of the resource to which to subscribe. + /// Optional metadata to include in the request. /// The to monitor for cancellation requests. The default is . /// A task that represents the asynchronous operation. - public Task SubscribeToResourceAsync(Uri uri, CancellationToken cancellationToken = default) + public Task SubscribeToResourceAsync(Uri uri, JsonObject? meta = null, CancellationToken cancellationToken = default) { Throw.IfNull(uri); - return SubscribeToResourceAsync(uri.ToString(), cancellationToken); + return SubscribeToResourceAsync(uri.ToString(), meta, cancellationToken); } /// /// Unsubscribes from a resource on the server to stop receiving notifications about its changes. /// /// The URI of the resource to unsubscribe from. + /// Optional metadata to include in the request. /// The to monitor for cancellation requests. The default is . /// A task that represents the asynchronous operation. - public Task UnsubscribeFromResourceAsync(string uri, CancellationToken cancellationToken = default) + public Task UnsubscribeFromResourceAsync(string uri, JsonObject? meta = null, CancellationToken cancellationToken = default) { Throw.IfNullOrWhiteSpace(uri); return SendRequestAsync( RequestMethods.ResourcesUnsubscribe, - new() { Uri = uri }, + new() { Uri = uri, Meta = meta }, McpJsonUtilities.JsonContext.Default.UnsubscribeRequestParams, McpJsonUtilities.JsonContext.Default.EmptyResult, cancellationToken: cancellationToken).AsTask(); @@ -475,13 +500,14 @@ public Task UnsubscribeFromResourceAsync(string uri, CancellationToken cancellat /// Unsubscribes from a resource on the server to stop receiving notifications about its changes. /// /// The URI of the resource to unsubscribe from. + /// Optional metadata to include in the request. /// The to monitor for cancellation requests. The default is . /// A task that represents the asynchronous operation. - public Task UnsubscribeFromResourceAsync(Uri uri, CancellationToken cancellationToken = default) + public Task UnsubscribeFromResourceAsync(Uri uri, JsonObject? meta = null, CancellationToken cancellationToken = default) { Throw.IfNull(uri); - return UnsubscribeFromResourceAsync(uri.ToString(), cancellationToken); + return UnsubscribeFromResourceAsync(uri.ToString(), meta, cancellationToken); } /// @@ -490,6 +516,7 @@ public Task UnsubscribeFromResourceAsync(Uri uri, CancellationToken cancellation /// The name of the tool to call on the server.. /// An optional dictionary of arguments to pass to the tool. /// Optional progress reporter for server notifications. + /// Optional metadata to include in the request. /// JSON serializer options. /// A cancellation token. /// The from the tool execution. @@ -497,6 +524,7 @@ public ValueTask CallToolAsync( string toolName, IReadOnlyDictionary? arguments = null, IProgress? progress = null, + JsonObject? meta = null, JsonSerializerOptions? serializerOptions = null, CancellationToken cancellationToken = default) { @@ -506,7 +534,7 @@ public ValueTask CallToolAsync( if (progress is not null) { - return SendRequestWithProgressAsync(toolName, arguments, progress, serializerOptions, cancellationToken); + return SendRequestWithProgressAsync(toolName, arguments, progress, meta, serializerOptions, cancellationToken); } return SendRequestAsync( @@ -515,6 +543,7 @@ public ValueTask CallToolAsync( { Name = toolName, Arguments = ToArgumentsDictionary(arguments, serializerOptions), + Meta = meta, }, McpJsonUtilities.JsonContext.Default.CallToolRequestParams, McpJsonUtilities.JsonContext.Default.CallToolResult, @@ -524,6 +553,7 @@ async ValueTask SendRequestWithProgressAsync( string toolName, IReadOnlyDictionary? arguments, IProgress progress, + JsonObject? meta, JsonSerializerOptions serializerOptions, CancellationToken cancellationToken) { @@ -548,6 +578,7 @@ async ValueTask SendRequestWithProgressAsync( Name = toolName, Arguments = ToArgumentsDictionary(arguments, serializerOptions), ProgressToken = progressToken, + Meta = meta, }, McpJsonUtilities.JsonContext.Default.CallToolRequestParams, McpJsonUtilities.JsonContext.Default.CallToolResult, @@ -671,13 +702,14 @@ internal static CreateMessageResult ToCreateMessageResult(ChatResponse chatRespo /// Sets the logging level for the server to control which log messages are sent to the client. /// /// The minimum severity level of log messages to receive from the server. + /// Optional metadata to include in the request. /// The to monitor for cancellation requests. The default is . /// A task representing the asynchronous operation. - public Task SetLoggingLevel(LoggingLevel level, CancellationToken cancellationToken = default) + public Task SetLoggingLevel(LoggingLevel level, JsonObject? meta = null, CancellationToken cancellationToken = default) { return SendRequestAsync( RequestMethods.LoggingSetLevel, - new() { Level = level }, + new() { Level = level, Meta = meta }, McpJsonUtilities.JsonContext.Default.SetLevelRequestParams, McpJsonUtilities.JsonContext.Default.EmptyResult, cancellationToken: cancellationToken).AsTask(); @@ -687,10 +719,11 @@ public Task SetLoggingLevel(LoggingLevel level, CancellationToken cancellationTo /// Sets the logging level for the server to control which log messages are sent to the client. /// /// The minimum severity level of log messages to receive from the server. + /// Optional metadata to include in the request. /// The to monitor for cancellation requests. The default is . /// A task representing the asynchronous operation. - public Task SetLoggingLevel(LogLevel level, CancellationToken cancellationToken = default) => - SetLoggingLevel(McpServerImpl.ToLoggingLevel(level), cancellationToken); + public Task SetLoggingLevel(LogLevel level, JsonObject? meta = null, CancellationToken cancellationToken = default) => + SetLoggingLevel(McpServerImpl.ToLoggingLevel(level), meta, cancellationToken); /// Convers a dictionary with values to a dictionary with values. private static Dictionary? ToArgumentsDictionary( diff --git a/src/ModelContextProtocol.Core/Client/McpClientPrompt.cs b/src/ModelContextProtocol.Core/Client/McpClientPrompt.cs index 5d0acca31..472e1ef7c 100644 --- a/src/ModelContextProtocol.Core/Client/McpClientPrompt.cs +++ b/src/ModelContextProtocol.Core/Client/McpClientPrompt.cs @@ -98,6 +98,6 @@ public async ValueTask GetAsync( arguments as IReadOnlyDictionary ?? arguments?.ToDictionary(); - return await _client.GetPromptAsync(ProtocolPrompt.Name, argDict, serializerOptions, cancellationToken: cancellationToken).ConfigureAwait(false); + return await _client.GetPromptAsync(ProtocolPrompt.Name, argDict, null, serializerOptions, cancellationToken: cancellationToken).ConfigureAwait(false); } } \ No newline at end of file diff --git a/src/ModelContextProtocol.Core/Client/McpClientResource.cs b/src/ModelContextProtocol.Core/Client/McpClientResource.cs index 3c08ab84e..39dd752ef 100644 --- a/src/ModelContextProtocol.Core/Client/McpClientResource.cs +++ b/src/ModelContextProtocol.Core/Client/McpClientResource.cs @@ -80,10 +80,10 @@ public McpClientResource(McpClient client, Resource resource) /// A containing the resource's result with content and messages. /// /// - /// This is a convenience method that internally calls . + /// This is a convenience method that internally calls . /// /// public ValueTask ReadAsync( CancellationToken cancellationToken = default) => - _client.ReadResourceAsync(Uri, cancellationToken); + _client.ReadResourceAsync(Uri, null, cancellationToken); } \ No newline at end of file diff --git a/src/ModelContextProtocol.Core/Client/McpClientResourceTemplate.cs b/src/ModelContextProtocol.Core/Client/McpClientResourceTemplate.cs index b08bf9d03..8a8b0039a 100644 --- a/src/ModelContextProtocol.Core/Client/McpClientResourceTemplate.cs +++ b/src/ModelContextProtocol.Core/Client/McpClientResourceTemplate.cs @@ -85,5 +85,5 @@ public McpClientResourceTemplate(McpClient client, ResourceTemplate resourceTemp public ValueTask ReadAsync( IReadOnlyDictionary arguments, CancellationToken cancellationToken = default) => - _client.ReadResourceAsync(UriTemplate, arguments, cancellationToken); + _client.ReadResourceAsync(UriTemplate, arguments, null, cancellationToken); } \ No newline at end of file diff --git a/src/ModelContextProtocol.Core/Client/McpClientTool.cs b/src/ModelContextProtocol.Core/Client/McpClientTool.cs index 4a2bccdd6..8eab1156a 100644 --- a/src/ModelContextProtocol.Core/Client/McpClientTool.cs +++ b/src/ModelContextProtocol.Core/Client/McpClientTool.cs @@ -194,7 +194,7 @@ public ValueTask CallAsync( IProgress? progress = null, JsonSerializerOptions? serializerOptions = null, CancellationToken cancellationToken = default) => - _client.CallToolAsync(ProtocolTool.Name, arguments, progress, serializerOptions, cancellationToken); + _client.CallToolAsync(ProtocolTool.Name, arguments, progress, null, serializerOptions, cancellationToken); /// /// Creates a new instance of the tool but modified to return the specified name from its property. diff --git a/src/ModelContextProtocol.Core/McpJsonUtilities.cs b/src/ModelContextProtocol.Core/McpJsonUtilities.cs index 23006cf88..0d5a44ba3 100644 --- a/src/ModelContextProtocol.Core/McpJsonUtilities.cs +++ b/src/ModelContextProtocol.Core/McpJsonUtilities.cs @@ -132,6 +132,7 @@ internal static bool IsValidMcpToolSchema(JsonElement element) [JsonSerializable(typeof(ListRootsResult))] [JsonSerializable(typeof(ListToolsRequestParams))] [JsonSerializable(typeof(ListToolsResult))] + [JsonSerializable(typeof(PingRequestParams))] [JsonSerializable(typeof(PingResult))] [JsonSerializable(typeof(ReadResourceRequestParams))] [JsonSerializable(typeof(ReadResourceResult))] diff --git a/src/ModelContextProtocol.Core/McpSession.Methods.cs b/src/ModelContextProtocol.Core/McpSession.Methods.cs index c929f6039..a3bac861f 100644 --- a/src/ModelContextProtocol.Core/McpSession.Methods.cs +++ b/src/ModelContextProtocol.Core/McpSession.Methods.cs @@ -152,6 +152,7 @@ internal Task SendNotificationAsync( /// /// The identifying the operation for which progress is being reported. /// The progress update to send, containing information such as percentage complete or status message. + /// Optional metadata to include in the notification. /// The to monitor for cancellation requests. The default is . /// A task representing the completion of the notification operation (not the operation being tracked). /// The current session instance is . @@ -168,6 +169,7 @@ internal Task SendNotificationAsync( public Task NotifyProgressAsync( ProgressToken progressToken, ProgressNotificationValue progress, + JsonObject? meta = null, CancellationToken cancellationToken = default) { return SendNotificationAsync( @@ -176,6 +178,7 @@ public Task NotifyProgressAsync( { ProgressToken = progressToken, Progress = progress, + Meta = meta, }, McpJsonUtilities.JsonContext.Default.ProgressNotificationParams, cancellationToken); diff --git a/src/ModelContextProtocol.Core/Protocol/PingRequestParams.cs b/src/ModelContextProtocol.Core/Protocol/PingRequestParams.cs new file mode 100644 index 000000000..e8557f55f --- /dev/null +++ b/src/ModelContextProtocol.Core/Protocol/PingRequestParams.cs @@ -0,0 +1,11 @@ +namespace ModelContextProtocol.Protocol; + +/// +/// Represents the parameters used with a request to verify +/// server connectivity. +/// +/// +/// The server responds with a . +/// See the schema for details. +/// +public sealed class PingRequestParams : RequestParams; \ No newline at end of file diff --git a/src/ModelContextProtocol.Core/Server/McpServer.Methods.cs b/src/ModelContextProtocol.Core/Server/McpServer.Methods.cs index 609da53c1..2d88475d5 100644 --- a/src/ModelContextProtocol.Core/Server/McpServer.Methods.cs +++ b/src/ModelContextProtocol.Core/Server/McpServer.Methods.cs @@ -51,14 +51,20 @@ public static McpServer Create( /// Requests to sample an LLM via the client using the specified request parameters. /// /// The parameters for the sampling request. + /// Optional metadata to include in the request. /// The to monitor for cancellation requests. /// A task containing the sampling result from the client. /// The client does not support sampling. public ValueTask SampleAsync( - CreateMessageRequestParams request, CancellationToken cancellationToken = default) + CreateMessageRequestParams request, JsonObject? meta = null, CancellationToken cancellationToken = default) { ThrowIfSamplingUnsupported(); + if (meta is not null) + { + request.Meta = meta; + } + return SendRequestAsync( RequestMethods.SamplingCreateMessage, request, @@ -72,12 +78,13 @@ public ValueTask SampleAsync( /// /// The messages to send as part of the request. /// The options to use for the request, including model parameters and constraints. + /// Optional metadata to include in the request. /// The to monitor for cancellation requests. The default is . /// A task containing the chat response from the model. /// is . /// The client does not support sampling. public async Task SampleAsync( - IEnumerable messages, ChatOptions? options = default, CancellationToken cancellationToken = default) + IEnumerable messages, ChatOptions? options = default, JsonObject? meta = null, CancellationToken cancellationToken = default) { Throw.IfNull(messages); @@ -158,7 +165,7 @@ public async Task SampleAsync( SystemPrompt = systemPrompt?.ToString(), Temperature = options?.Temperature, ModelPreferences = modelPreferences, - }, cancellationToken).ConfigureAwait(false); + }, meta, cancellationToken).ConfigureAwait(false); AIContent? responseContent = result.Content.ToAIContent(); @@ -195,14 +202,20 @@ public ILoggerProvider AsClientLoggerProvider() /// Requests the client to list the roots it exposes. /// /// The parameters for the list roots request. + /// Optional metadata to include in the request. /// The to monitor for cancellation requests. /// A task containing the list of roots exposed by the client. /// The client does not support roots. public ValueTask RequestRootsAsync( - ListRootsRequestParams request, CancellationToken cancellationToken = default) + ListRootsRequestParams request, JsonObject? meta = null, CancellationToken cancellationToken = default) { ThrowIfRootsUnsupported(); + if (meta is not null) + { + request.Meta = meta; + } + return SendRequestAsync( RequestMethods.RootsList, request, From e19ad6ad273940cf58bd6c82710b2654f64b1f10 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Thu, 6 Nov 2025 17:55:46 -0800 Subject: [PATCH 3/8] Fix core build breaks --- samples/EverythingServer/Tools/SampleLlmTool.cs | 2 +- samples/TestServerWithHosting/Tools/SampleLlmTool.cs | 2 +- src/ModelContextProtocol.Core/Server/McpServer.Methods.cs | 2 +- src/ModelContextProtocol.Core/Server/McpServerOptions.cs | 2 +- src/ModelContextProtocol.Core/TokenProgress.cs | 2 +- tests/ModelContextProtocol.TestServer/Program.cs | 2 +- tests/ModelContextProtocol.TestSseServer/Program.cs | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/samples/EverythingServer/Tools/SampleLlmTool.cs b/samples/EverythingServer/Tools/SampleLlmTool.cs index 6bbe6e51d..ceb24153f 100644 --- a/samples/EverythingServer/Tools/SampleLlmTool.cs +++ b/samples/EverythingServer/Tools/SampleLlmTool.cs @@ -15,7 +15,7 @@ public static async Task SampleLLM( CancellationToken cancellationToken) { var samplingParams = CreateRequestSamplingParams(prompt ?? string.Empty, "sampleLLM", maxTokens); - var sampleResult = await server.SampleAsync(samplingParams, cancellationToken); + var sampleResult = await server.SampleAsync(samplingParams, meta: null, cancellationToken); return $"LLM sampling result: {(sampleResult.Content as TextContentBlock)?.Text}"; } diff --git a/samples/TestServerWithHosting/Tools/SampleLlmTool.cs b/samples/TestServerWithHosting/Tools/SampleLlmTool.cs index 2c96b8c35..5cc9650fc 100644 --- a/samples/TestServerWithHosting/Tools/SampleLlmTool.cs +++ b/samples/TestServerWithHosting/Tools/SampleLlmTool.cs @@ -18,7 +18,7 @@ public static async Task SampleLLM( CancellationToken cancellationToken) { var samplingParams = CreateRequestSamplingParams(prompt ?? string.Empty, "sampleLLM", maxTokens); - var sampleResult = await thisServer.SampleAsync(samplingParams, cancellationToken); + var sampleResult = await thisServer.SampleAsync(samplingParams, meta: null, cancellationToken); return $"LLM sampling result: {(sampleResult.Content as TextContentBlock)?.Text}"; } diff --git a/src/ModelContextProtocol.Core/Server/McpServer.Methods.cs b/src/ModelContextProtocol.Core/Server/McpServer.Methods.cs index 2d88475d5..8162aacfc 100644 --- a/src/ModelContextProtocol.Core/Server/McpServer.Methods.cs +++ b/src/ModelContextProtocol.Core/Server/McpServer.Methods.cs @@ -467,7 +467,7 @@ private sealed class SamplingChatClient(McpServer server) : IChatClient /// public Task GetResponseAsync(IEnumerable messages, ChatOptions? options = null, CancellationToken cancellationToken = default) => - _server.SampleAsync(messages, options, cancellationToken); + _server.SampleAsync(messages, options, meta: null, cancellationToken); /// async IAsyncEnumerable IChatClient.GetStreamingResponseAsync( diff --git a/src/ModelContextProtocol.Core/Server/McpServerOptions.cs b/src/ModelContextProtocol.Core/Server/McpServerOptions.cs index 7b915b943..37cf58dc3 100644 --- a/src/ModelContextProtocol.Core/Server/McpServerOptions.cs +++ b/src/ModelContextProtocol.Core/Server/McpServerOptions.cs @@ -158,7 +158,7 @@ public McpServerHandlers Handlers /// /// /// - /// This value is used in + /// This value is used in /// when is not set in the request options. /// /// diff --git a/src/ModelContextProtocol.Core/TokenProgress.cs b/src/ModelContextProtocol.Core/TokenProgress.cs index 6b7a91e00..73bb464ec 100644 --- a/src/ModelContextProtocol.Core/TokenProgress.cs +++ b/src/ModelContextProtocol.Core/TokenProgress.cs @@ -11,6 +11,6 @@ internal sealed class TokenProgress(McpSession session, ProgressToken progressTo /// public void Report(ProgressNotificationValue value) { - _ = session.NotifyProgressAsync(progressToken, value, CancellationToken.None); + _ = session.NotifyProgressAsync(progressToken, value, meta: null, CancellationToken.None); } } diff --git a/tests/ModelContextProtocol.TestServer/Program.cs b/tests/ModelContextProtocol.TestServer/Program.cs index 9a54ed71d..92ce21984 100644 --- a/tests/ModelContextProtocol.TestServer/Program.cs +++ b/tests/ModelContextProtocol.TestServer/Program.cs @@ -193,7 +193,7 @@ private static void ConfigureTools(McpServerOptions options, string? cliArg) throw new McpProtocolException("Missing required arguments 'prompt' and 'maxTokens'", McpErrorCode.InvalidParams); } var sampleResult = await request.Server.SampleAsync(CreateRequestSamplingParams(prompt.ToString(), "sampleLLM", Convert.ToInt32(maxTokens.GetRawText())), - cancellationToken); + meta: null, cancellationToken); return new CallToolResult { diff --git a/tests/ModelContextProtocol.TestSseServer/Program.cs b/tests/ModelContextProtocol.TestSseServer/Program.cs index 183a64e7e..166f8508e 100644 --- a/tests/ModelContextProtocol.TestSseServer/Program.cs +++ b/tests/ModelContextProtocol.TestSseServer/Program.cs @@ -187,7 +187,7 @@ static CreateMessageRequestParams CreateRequestSamplingParams(string context, st throw new McpProtocolException("Missing required arguments 'prompt' and 'maxTokens'", McpErrorCode.InvalidParams); } var sampleResult = await request.Server.SampleAsync(CreateRequestSamplingParams(prompt.ToString(), "sampleLLM", Convert.ToInt32(maxTokens.ToString())), - cancellationToken); + meta: null, cancellationToken); return new CallToolResult { From f6887eaccfc0ebf6778c1d860c3ba294f06605d8 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Thu, 6 Nov 2025 20:23:09 -0800 Subject: [PATCH 4/8] Clean build --- .../EverythingServer/Tools/SampleLlmTool.cs | 2 +- .../Tools/SampleLlmTool.cs | 2 +- .../Client/McpClient.Methods.cs | 138 +++++++++--------- .../Client/McpClientPrompt.cs | 2 +- .../Client/McpClientResource.cs | 2 +- .../Client/McpClientTool.cs | 10 +- .../McpSession.Methods.cs | 6 +- .../RequestOptions.cs | 60 ++++++++ .../Server/McpServer.Methods.cs | 24 +-- .../Server/McpServerOptions.cs | 18 +-- .../TokenProgress.cs | 2 +- .../HttpServerIntegrationTests.cs | 10 +- .../MapMcpTests.cs | 4 +- .../Program.cs | 2 +- .../Program.cs | 2 +- .../Client/McpClientExtensionsTests.cs | 2 +- .../Client/McpClientResourceTemplateTests.cs | 38 ++--- .../Client/McpClientTests.cs | 14 +- .../ClientIntegrationTests.cs | 24 +-- .../McpServerBuilderExtensionsPromptsTests.cs | 8 +- ...cpServerBuilderExtensionsResourcesTests.cs | 10 +- ...erverResourceCapabilityIntegrationTests.cs | 4 +- .../McpServerResourceRoutingTests.cs | 10 +- .../Configuration/McpServerScopedTests.cs | 2 +- .../Server/EmptyCollectionTests.cs | 16 +- .../Server/McpServerTests.cs | 8 +- 26 files changed, 240 insertions(+), 180 deletions(-) create mode 100644 src/ModelContextProtocol.Core/RequestOptions.cs diff --git a/samples/EverythingServer/Tools/SampleLlmTool.cs b/samples/EverythingServer/Tools/SampleLlmTool.cs index ceb24153f..1a01f2cd0 100644 --- a/samples/EverythingServer/Tools/SampleLlmTool.cs +++ b/samples/EverythingServer/Tools/SampleLlmTool.cs @@ -15,7 +15,7 @@ public static async Task SampleLLM( CancellationToken cancellationToken) { var samplingParams = CreateRequestSamplingParams(prompt ?? string.Empty, "sampleLLM", maxTokens); - var sampleResult = await server.SampleAsync(samplingParams, meta: null, cancellationToken); + var sampleResult = await server.SampleAsync(samplingParams, options: null, cancellationToken); return $"LLM sampling result: {(sampleResult.Content as TextContentBlock)?.Text}"; } diff --git a/samples/TestServerWithHosting/Tools/SampleLlmTool.cs b/samples/TestServerWithHosting/Tools/SampleLlmTool.cs index 5cc9650fc..5e6c610c7 100644 --- a/samples/TestServerWithHosting/Tools/SampleLlmTool.cs +++ b/samples/TestServerWithHosting/Tools/SampleLlmTool.cs @@ -18,7 +18,7 @@ public static async Task SampleLLM( CancellationToken cancellationToken) { var samplingParams = CreateRequestSamplingParams(prompt ?? string.Empty, "sampleLLM", maxTokens); - var sampleResult = await thisServer.SampleAsync(samplingParams, meta: null, cancellationToken); + var sampleResult = await thisServer.SampleAsync(samplingParams, options: null, cancellationToken); return $"LLM sampling result: {(sampleResult.Content as TextContentBlock)?.Text}"; } diff --git a/src/ModelContextProtocol.Core/Client/McpClient.Methods.cs b/src/ModelContextProtocol.Core/Client/McpClient.Methods.cs index f0bac4661..8eb7983cb 100644 --- a/src/ModelContextProtocol.Core/Client/McpClient.Methods.cs +++ b/src/ModelContextProtocol.Core/Client/McpClient.Methods.cs @@ -54,15 +54,15 @@ public static async Task CreateAsync( /// /// Sends a ping request to verify server connectivity. /// - /// Optional metadata to include in the request. + /// Optional request options including metadata, serialization settings, and progress tracking. /// The to monitor for cancellation requests. The default is . /// A task that completes when the ping is successful. /// Thrown when the server cannot be reached or returns an error response. - public ValueTask PingAsync(JsonObject? meta = null, CancellationToken cancellationToken = default) + public ValueTask PingAsync(RequestOptions? options = null, CancellationToken cancellationToken = default) { return SendRequestAsync( RequestMethods.Ping, - new PingRequestParams { Meta = meta }, + new PingRequestParams { Meta = options?.Meta }, McpJsonUtilities.JsonContext.Default.PingRequestParams, McpJsonUtilities.JsonContext.Default.PingResult, cancellationToken: cancellationToken); @@ -71,16 +71,14 @@ public ValueTask PingAsync(JsonObject? meta = null, CancellationToke /// /// Retrieves a list of available tools from the server. /// - /// Optional metadata to include in the request. - /// The serializer options governing tool parameter serialization. If null, the default options will be used. + /// Optional request options including metadata, serialization settings, and progress tracking. /// The to monitor for cancellation requests. The default is . /// A list of all available tools as instances. public async ValueTask> ListToolsAsync( - JsonObject? meta = null, - JsonSerializerOptions? serializerOptions = null, + RequestOptions? options = null, CancellationToken cancellationToken = default) { - serializerOptions ??= McpJsonUtilities.DefaultOptions; + var serializerOptions = options?.JsonSerializerOptions ?? McpJsonUtilities.DefaultOptions; serializerOptions.MakeReadOnly(); List? tools = null; @@ -89,7 +87,7 @@ public async ValueTask> ListToolsAsync( { var toolResults = await SendRequestAsync( RequestMethods.ToolsList, - new() { Cursor = cursor, Meta = meta }, + new() { Cursor = cursor, Meta = options?.Meta }, McpJsonUtilities.JsonContext.Default.ListToolsRequestParams, McpJsonUtilities.JsonContext.Default.ListToolsResult, cancellationToken: cancellationToken).ConfigureAwait(false); @@ -110,16 +108,14 @@ public async ValueTask> ListToolsAsync( /// /// Creates an enumerable for asynchronously enumerating all available tools from the server. /// - /// Optional metadata to include in the requests. - /// The serializer options governing tool parameter serialization. If null, the default options will be used. + /// Optional request options including metadata, serialization settings, and progress tracking. /// The to monitor for cancellation requests. The default is . /// An asynchronous sequence of all available tools as instances. public async IAsyncEnumerable EnumerateToolsAsync( - JsonObject? meta = null, - JsonSerializerOptions? serializerOptions = null, + RequestOptions? options = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) { - serializerOptions ??= McpJsonUtilities.DefaultOptions; + var serializerOptions = options?.JsonSerializerOptions ?? McpJsonUtilities.DefaultOptions; serializerOptions.MakeReadOnly(); string? cursor = null; @@ -127,7 +123,7 @@ public async IAsyncEnumerable EnumerateToolsAsync( { var toolResults = await SendRequestAsync( RequestMethods.ToolsList, - new() { Cursor = cursor, Meta = meta }, + new() { Cursor = cursor, Meta = options?.Meta }, McpJsonUtilities.JsonContext.Default.ListToolsRequestParams, McpJsonUtilities.JsonContext.Default.ListToolsResult, cancellationToken: cancellationToken).ConfigureAwait(false); @@ -145,11 +141,11 @@ public async IAsyncEnumerable EnumerateToolsAsync( /// /// Retrieves a list of available prompts from the server. /// - /// Optional metadata to include in the request. + /// Optional request options including metadata, serialization settings, and progress tracking. /// The to monitor for cancellation requests. The default is . /// A list of all available prompts as instances. public async ValueTask> ListPromptsAsync( - JsonObject? meta = null, + RequestOptions? options = null, CancellationToken cancellationToken = default) { List? prompts = null; @@ -158,7 +154,7 @@ public async ValueTask> ListPromptsAsync( { var promptResults = await SendRequestAsync( RequestMethods.PromptsList, - new() { Cursor = cursor, Meta = meta }, + new() { Cursor = cursor, Meta = options?.Meta }, McpJsonUtilities.JsonContext.Default.ListPromptsRequestParams, McpJsonUtilities.JsonContext.Default.ListPromptsResult, cancellationToken: cancellationToken).ConfigureAwait(false); @@ -179,11 +175,11 @@ public async ValueTask> ListPromptsAsync( /// /// Creates an enumerable for asynchronously enumerating all available prompts from the server. /// - /// Optional metadata to include in the requests. + /// Optional request options including metadata, serialization settings, and progress tracking. /// The to monitor for cancellation requests. The default is . /// An asynchronous sequence of all available prompts as instances. public async IAsyncEnumerable EnumeratePromptsAsync( - JsonObject? meta = null, + RequestOptions? options = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) { string? cursor = null; @@ -191,7 +187,7 @@ public async IAsyncEnumerable EnumeratePromptsAsync( { var promptResults = await SendRequestAsync( RequestMethods.PromptsList, - new() { Cursor = cursor, Meta = meta }, + new() { Cursor = cursor, Meta = options?.Meta }, McpJsonUtilities.JsonContext.Default.ListPromptsRequestParams, McpJsonUtilities.JsonContext.Default.ListPromptsResult, cancellationToken: cancellationToken).ConfigureAwait(false); @@ -211,25 +207,23 @@ public async IAsyncEnumerable EnumeratePromptsAsync( /// /// The name of the prompt to retrieve. /// Optional arguments for the prompt. Keys are parameter names, and values are the argument values. - /// Optional metadata to include in the request. - /// The serialization options governing argument serialization. + /// Optional request options including metadata, serialization settings, and progress tracking. /// The to monitor for cancellation requests. The default is . /// A task containing the prompt's result with content and messages. public ValueTask GetPromptAsync( string name, IReadOnlyDictionary? arguments = null, - JsonObject? meta = null, - JsonSerializerOptions? serializerOptions = null, + RequestOptions? options = null, CancellationToken cancellationToken = default) { Throw.IfNullOrWhiteSpace(name); - serializerOptions ??= McpJsonUtilities.DefaultOptions; + var serializerOptions = options?.JsonSerializerOptions ?? McpJsonUtilities.DefaultOptions; serializerOptions.MakeReadOnly(); return SendRequestAsync( RequestMethods.PromptsGet, - new() { Name = name, Arguments = ToArgumentsDictionary(arguments, serializerOptions), Meta = meta }, + new() { Name = name, Arguments = ToArgumentsDictionary(arguments, serializerOptions), Meta = options?.Meta }, McpJsonUtilities.JsonContext.Default.GetPromptRequestParams, McpJsonUtilities.JsonContext.Default.GetPromptResult, cancellationToken: cancellationToken); @@ -238,11 +232,11 @@ public ValueTask GetPromptAsync( /// /// Retrieves a list of available resource templates from the server. /// - /// Optional metadata to include in the request. + /// Optional request options including metadata, serialization settings, and progress tracking. /// The to monitor for cancellation requests. The default is . /// A list of all available resource templates as instances. public async ValueTask> ListResourceTemplatesAsync( - JsonObject? meta = null, + RequestOptions? options = null, CancellationToken cancellationToken = default) { List? resourceTemplates = null; @@ -252,7 +246,7 @@ public async ValueTask> ListResourceTemplatesAs { var templateResults = await SendRequestAsync( RequestMethods.ResourcesTemplatesList, - new() { Cursor = cursor, Meta = meta }, + new() { Cursor = cursor, Meta = options?.Meta }, McpJsonUtilities.JsonContext.Default.ListResourceTemplatesRequestParams, McpJsonUtilities.JsonContext.Default.ListResourceTemplatesResult, cancellationToken: cancellationToken).ConfigureAwait(false); @@ -273,11 +267,11 @@ public async ValueTask> ListResourceTemplatesAs /// /// Creates an enumerable for asynchronously enumerating all available resource templates from the server. /// - /// Optional metadata to include in the requests. + /// Optional request options including metadata, serialization settings, and progress tracking. /// The to monitor for cancellation requests. The default is . /// An asynchronous sequence of all available resource templates as instances. public async IAsyncEnumerable EnumerateResourceTemplatesAsync( - JsonObject? meta = null, + RequestOptions? options = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) { string? cursor = null; @@ -285,7 +279,7 @@ public async IAsyncEnumerable EnumerateResourceTempla { var templateResults = await SendRequestAsync( RequestMethods.ResourcesTemplatesList, - new() { Cursor = cursor, Meta = meta }, + new() { Cursor = cursor, Meta = options?.Meta }, McpJsonUtilities.JsonContext.Default.ListResourceTemplatesRequestParams, McpJsonUtilities.JsonContext.Default.ListResourceTemplatesResult, cancellationToken: cancellationToken).ConfigureAwait(false); @@ -303,11 +297,11 @@ public async IAsyncEnumerable EnumerateResourceTempla /// /// Retrieves a list of available resources from the server. /// - /// Optional metadata to include in the request. + /// Optional request options including metadata, serialization settings, and progress tracking. /// The to monitor for cancellation requests. The default is . /// A list of all available resources as instances. public async ValueTask> ListResourcesAsync( - JsonObject? meta = null, + RequestOptions? options = null, CancellationToken cancellationToken = default) { List? resources = null; @@ -317,7 +311,7 @@ public async ValueTask> ListResourcesAsync( { var resourceResults = await SendRequestAsync( RequestMethods.ResourcesList, - new() { Cursor = cursor, Meta = meta }, + new() { Cursor = cursor, Meta = options?.Meta }, McpJsonUtilities.JsonContext.Default.ListResourcesRequestParams, McpJsonUtilities.JsonContext.Default.ListResourcesResult, cancellationToken: cancellationToken).ConfigureAwait(false); @@ -338,11 +332,11 @@ public async ValueTask> ListResourcesAsync( /// /// Creates an enumerable for asynchronously enumerating all available resources from the server. /// - /// Optional metadata to include in the requests. + /// Optional request options including metadata, serialization settings, and progress tracking. /// The to monitor for cancellation requests. The default is . /// An asynchronous sequence of all available resources as instances. public async IAsyncEnumerable EnumerateResourcesAsync( - JsonObject? meta = null, + RequestOptions? options = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) { string? cursor = null; @@ -350,7 +344,7 @@ public async IAsyncEnumerable EnumerateResourcesAsync( { var resourceResults = await SendRequestAsync( RequestMethods.ResourcesList, - new() { Cursor = cursor, Meta = meta }, + new() { Cursor = cursor, Meta = options?.Meta }, McpJsonUtilities.JsonContext.Default.ListResourcesRequestParams, McpJsonUtilities.JsonContext.Default.ListResourcesResult, cancellationToken: cancellationToken).ConfigureAwait(false); @@ -369,16 +363,16 @@ public async IAsyncEnumerable EnumerateResourcesAsync( /// Reads a resource from the server. /// /// The uri of the resource. - /// Optional metadata to include in the request. + /// Optional request options including metadata, serialization settings, and progress tracking. /// The to monitor for cancellation requests. The default is . public ValueTask ReadResourceAsync( - string uri, JsonObject? meta = null, CancellationToken cancellationToken = default) + string uri, RequestOptions? options = null, CancellationToken cancellationToken = default) { Throw.IfNullOrWhiteSpace(uri); return SendRequestAsync( RequestMethods.ResourcesRead, - new() { Uri = uri, Meta = meta }, + new() { Uri = uri, Meta = options?.Meta }, McpJsonUtilities.JsonContext.Default.ReadResourceRequestParams, McpJsonUtilities.JsonContext.Default.ReadResourceResult, cancellationToken: cancellationToken); @@ -388,14 +382,14 @@ public ValueTask ReadResourceAsync( /// Reads a resource from the server. /// /// The uri of the resource. - /// Optional metadata to include in the request. + /// Optional request options including metadata, serialization settings, and progress tracking. /// The to monitor for cancellation requests. The default is . public ValueTask ReadResourceAsync( - Uri uri, JsonObject? meta = null, CancellationToken cancellationToken = default) + Uri uri, RequestOptions? options = null, CancellationToken cancellationToken = default) { Throw.IfNull(uri); - return ReadResourceAsync(uri.ToString(), meta, cancellationToken); + return ReadResourceAsync(uri.ToString(), options, cancellationToken); } /// @@ -403,17 +397,17 @@ public ValueTask ReadResourceAsync( /// /// The uri template of the resource. /// Arguments to use to format . - /// Optional metadata to include in the request. + /// Optional request options including metadata, serialization settings, and progress tracking. /// The to monitor for cancellation requests. The default is . public ValueTask ReadResourceAsync( - string uriTemplate, IReadOnlyDictionary arguments, JsonObject? meta = null, CancellationToken cancellationToken = default) + string uriTemplate, IReadOnlyDictionary arguments, RequestOptions? options = null, CancellationToken cancellationToken = default) { Throw.IfNullOrWhiteSpace(uriTemplate); Throw.IfNull(arguments); return SendRequestAsync( RequestMethods.ResourcesRead, - new() { Uri = UriTemplate.FormatUri(uriTemplate, arguments), Meta = meta }, + new() { Uri = UriTemplate.FormatUri(uriTemplate, arguments), Meta = options?.Meta }, McpJsonUtilities.JsonContext.Default.ReadResourceRequestParams, McpJsonUtilities.JsonContext.Default.ReadResourceResult, cancellationToken: cancellationToken); @@ -448,16 +442,16 @@ public ValueTask CompleteAsync(Reference reference, string argum /// Subscribes to a resource on the server to receive notifications when it changes. /// /// The URI of the resource to which to subscribe. - /// Optional metadata to include in the request. + /// Optional request options including metadata, serialization settings, and progress tracking. /// The to monitor for cancellation requests. The default is . /// A task that represents the asynchronous operation. - public Task SubscribeToResourceAsync(string uri, JsonObject? meta = null, CancellationToken cancellationToken = default) + public Task SubscribeToResourceAsync(string uri, RequestOptions? options = null, CancellationToken cancellationToken = default) { Throw.IfNullOrWhiteSpace(uri); return SendRequestAsync( RequestMethods.ResourcesSubscribe, - new() { Uri = uri, Meta = meta }, + new() { Uri = uri, Meta = options?.Meta }, McpJsonUtilities.JsonContext.Default.SubscribeRequestParams, McpJsonUtilities.JsonContext.Default.EmptyResult, cancellationToken: cancellationToken).AsTask(); @@ -467,30 +461,30 @@ public Task SubscribeToResourceAsync(string uri, JsonObject? meta = null, Cancel /// Subscribes to a resource on the server to receive notifications when it changes. /// /// The URI of the resource to which to subscribe. - /// Optional metadata to include in the request. + /// Optional request options including metadata, serialization settings, and progress tracking. /// The to monitor for cancellation requests. The default is . /// A task that represents the asynchronous operation. - public Task SubscribeToResourceAsync(Uri uri, JsonObject? meta = null, CancellationToken cancellationToken = default) + public Task SubscribeToResourceAsync(Uri uri, RequestOptions? options = null, CancellationToken cancellationToken = default) { Throw.IfNull(uri); - return SubscribeToResourceAsync(uri.ToString(), meta, cancellationToken); + return SubscribeToResourceAsync(uri.ToString(), options, cancellationToken); } /// /// Unsubscribes from a resource on the server to stop receiving notifications about its changes. /// /// The URI of the resource to unsubscribe from. - /// Optional metadata to include in the request. + /// Optional request options including metadata, serialization settings, and progress tracking. /// The to monitor for cancellation requests. The default is . /// A task that represents the asynchronous operation. - public Task UnsubscribeFromResourceAsync(string uri, JsonObject? meta = null, CancellationToken cancellationToken = default) + public Task UnsubscribeFromResourceAsync(string uri, RequestOptions? options = null, CancellationToken cancellationToken = default) { Throw.IfNullOrWhiteSpace(uri); return SendRequestAsync( RequestMethods.ResourcesUnsubscribe, - new() { Uri = uri, Meta = meta }, + new() { Uri = uri, Meta = options?.Meta }, McpJsonUtilities.JsonContext.Default.UnsubscribeRequestParams, McpJsonUtilities.JsonContext.Default.EmptyResult, cancellationToken: cancellationToken).AsTask(); @@ -500,14 +494,14 @@ public Task UnsubscribeFromResourceAsync(string uri, JsonObject? meta = null, Ca /// Unsubscribes from a resource on the server to stop receiving notifications about its changes. /// /// The URI of the resource to unsubscribe from. - /// Optional metadata to include in the request. + /// Optional request options including metadata, serialization settings, and progress tracking. /// The to monitor for cancellation requests. The default is . /// A task that represents the asynchronous operation. - public Task UnsubscribeFromResourceAsync(Uri uri, JsonObject? meta = null, CancellationToken cancellationToken = default) + public Task UnsubscribeFromResourceAsync(Uri uri, RequestOptions? options = null, CancellationToken cancellationToken = default) { Throw.IfNull(uri); - return UnsubscribeFromResourceAsync(uri.ToString(), meta, cancellationToken); + return UnsubscribeFromResourceAsync(uri.ToString(), options, cancellationToken); } /// @@ -516,25 +510,23 @@ public Task UnsubscribeFromResourceAsync(Uri uri, JsonObject? meta = null, Cance /// The name of the tool to call on the server.. /// An optional dictionary of arguments to pass to the tool. /// Optional progress reporter for server notifications. - /// Optional metadata to include in the request. - /// JSON serializer options. + /// Optional request options including metadata, serialization settings, and progress tracking. /// A cancellation token. /// The from the tool execution. public ValueTask CallToolAsync( string toolName, IReadOnlyDictionary? arguments = null, IProgress? progress = null, - JsonObject? meta = null, - JsonSerializerOptions? serializerOptions = null, + RequestOptions? options = null, CancellationToken cancellationToken = default) { Throw.IfNull(toolName); - serializerOptions ??= McpJsonUtilities.DefaultOptions; + var serializerOptions = options?.JsonSerializerOptions ?? McpJsonUtilities.DefaultOptions; serializerOptions.MakeReadOnly(); if (progress is not null) { - return SendRequestWithProgressAsync(toolName, arguments, progress, meta, serializerOptions, cancellationToken); + return SendRequestWithProgressAsync(toolName, arguments, progress, options?.Meta, serializerOptions, cancellationToken); } return SendRequestAsync( @@ -543,7 +535,7 @@ public ValueTask CallToolAsync( { Name = toolName, Arguments = ToArgumentsDictionary(arguments, serializerOptions), - Meta = meta, + Meta = options?.Meta, }, McpJsonUtilities.JsonContext.Default.CallToolRequestParams, McpJsonUtilities.JsonContext.Default.CallToolResult, @@ -702,14 +694,14 @@ internal static CreateMessageResult ToCreateMessageResult(ChatResponse chatRespo /// Sets the logging level for the server to control which log messages are sent to the client. /// /// The minimum severity level of log messages to receive from the server. - /// Optional metadata to include in the request. + /// Optional request options including metadata, serialization settings, and progress tracking. /// The to monitor for cancellation requests. The default is . /// A task representing the asynchronous operation. - public Task SetLoggingLevel(LoggingLevel level, JsonObject? meta = null, CancellationToken cancellationToken = default) + public Task SetLoggingLevel(LoggingLevel level, RequestOptions? options = null, CancellationToken cancellationToken = default) { return SendRequestAsync( RequestMethods.LoggingSetLevel, - new() { Level = level, Meta = meta }, + new() { Level = level, Meta = options?.Meta }, McpJsonUtilities.JsonContext.Default.SetLevelRequestParams, McpJsonUtilities.JsonContext.Default.EmptyResult, cancellationToken: cancellationToken).AsTask(); @@ -719,11 +711,11 @@ public Task SetLoggingLevel(LoggingLevel level, JsonObject? meta = null, Cancell /// Sets the logging level for the server to control which log messages are sent to the client. /// /// The minimum severity level of log messages to receive from the server. - /// Optional metadata to include in the request. + /// Optional request options including metadata, serialization settings, and progress tracking. /// The to monitor for cancellation requests. The default is . /// A task representing the asynchronous operation. - public Task SetLoggingLevel(LogLevel level, JsonObject? meta = null, CancellationToken cancellationToken = default) => - SetLoggingLevel(McpServerImpl.ToLoggingLevel(level), meta, cancellationToken); + public Task SetLoggingLevel(LogLevel level, RequestOptions? options = null, CancellationToken cancellationToken = default) => + SetLoggingLevel(McpServerImpl.ToLoggingLevel(level), options, cancellationToken); /// Convers a dictionary with values to a dictionary with values. private static Dictionary? ToArgumentsDictionary( diff --git a/src/ModelContextProtocol.Core/Client/McpClientPrompt.cs b/src/ModelContextProtocol.Core/Client/McpClientPrompt.cs index 472e1ef7c..1d61cdba3 100644 --- a/src/ModelContextProtocol.Core/Client/McpClientPrompt.cs +++ b/src/ModelContextProtocol.Core/Client/McpClientPrompt.cs @@ -98,6 +98,6 @@ public async ValueTask GetAsync( arguments as IReadOnlyDictionary ?? arguments?.ToDictionary(); - return await _client.GetPromptAsync(ProtocolPrompt.Name, argDict, null, serializerOptions, cancellationToken: cancellationToken).ConfigureAwait(false); + return await _client.GetPromptAsync(ProtocolPrompt.Name, argDict, new RequestOptions(null, serializerOptions), cancellationToken).ConfigureAwait(false); } } \ No newline at end of file diff --git a/src/ModelContextProtocol.Core/Client/McpClientResource.cs b/src/ModelContextProtocol.Core/Client/McpClientResource.cs index 39dd752ef..243460afc 100644 --- a/src/ModelContextProtocol.Core/Client/McpClientResource.cs +++ b/src/ModelContextProtocol.Core/Client/McpClientResource.cs @@ -80,7 +80,7 @@ public McpClientResource(McpClient client, Resource resource) /// A containing the resource's result with content and messages. /// /// - /// This is a convenience method that internally calls . + /// This is a convenience method that internally calls . /// /// public ValueTask ReadAsync( diff --git a/src/ModelContextProtocol.Core/Client/McpClientTool.cs b/src/ModelContextProtocol.Core/Client/McpClientTool.cs index 8eab1156a..915730daf 100644 --- a/src/ModelContextProtocol.Core/Client/McpClientTool.cs +++ b/src/ModelContextProtocol.Core/Client/McpClientTool.cs @@ -194,7 +194,15 @@ public ValueTask CallAsync( IProgress? progress = null, JsonSerializerOptions? serializerOptions = null, CancellationToken cancellationToken = default) => - _client.CallToolAsync(ProtocolTool.Name, arguments, progress, null, serializerOptions, cancellationToken); + _client.CallToolAsync( + ProtocolTool.Name, + arguments, + progress, + new RequestOptions + { + JsonSerializerOptions = serializerOptions + }, + cancellationToken); /// /// Creates a new instance of the tool but modified to return the specified name from its property. diff --git a/src/ModelContextProtocol.Core/McpSession.Methods.cs b/src/ModelContextProtocol.Core/McpSession.Methods.cs index a3bac861f..d1847631d 100644 --- a/src/ModelContextProtocol.Core/McpSession.Methods.cs +++ b/src/ModelContextProtocol.Core/McpSession.Methods.cs @@ -152,7 +152,7 @@ internal Task SendNotificationAsync( /// /// The identifying the operation for which progress is being reported. /// The progress update to send, containing information such as percentage complete or status message. - /// Optional metadata to include in the notification. + /// Optional request options including metadata, serialization settings, and progress tracking. /// The to monitor for cancellation requests. The default is . /// A task representing the completion of the notification operation (not the operation being tracked). /// The current session instance is . @@ -169,7 +169,7 @@ internal Task SendNotificationAsync( public Task NotifyProgressAsync( ProgressToken progressToken, ProgressNotificationValue progress, - JsonObject? meta = null, + RequestOptions? options = null, CancellationToken cancellationToken = default) { return SendNotificationAsync( @@ -178,7 +178,7 @@ public Task NotifyProgressAsync( { ProgressToken = progressToken, Progress = progress, - Meta = meta, + Meta = options?.Meta, }, McpJsonUtilities.JsonContext.Default.ProgressNotificationParams, cancellationToken); diff --git a/src/ModelContextProtocol.Core/RequestOptions.cs b/src/ModelContextProtocol.Core/RequestOptions.cs new file mode 100644 index 000000000..0e0231c77 --- /dev/null +++ b/src/ModelContextProtocol.Core/RequestOptions.cs @@ -0,0 +1,60 @@ +using System.Text.Json; +using System.Text.Json.Nodes; +using ModelContextProtocol.Protocol; + +namespace ModelContextProtocol; + +/// +/// Contains optional parameters for MCP requests. +/// +public sealed class RequestOptions +{ + /// + /// Gets or sets optional metadata to include in the request. + /// + public JsonObject? Meta { get; set; } + + /// + /// Gets or sets the JSON serializer options to use for serialization and deserialization. + /// + public JsonSerializerOptions? JsonSerializerOptions { get; set; } + + /// + /// Gets or sets the progress token for tracking long-running operations. + /// + public ProgressToken? ProgressToken { get; set; } + + /// + /// Gets a default instance with all properties set to null. + /// + public static RequestOptions Default { get; } = new(); + + /// + /// Initializes a new instance of the class. + /// + public RequestOptions() + { + } + + /// + /// Initializes a new instance of the class with the specified metadata. + /// + /// Optional metadata to include in the request. + public RequestOptions(JsonObject? meta) + { + Meta = meta; + } + + /// + /// Initializes a new instance of the class with the specified options. + /// + /// Optional metadata to include in the request. + /// The JSON serializer options to use. + /// The progress token for tracking operations. + public RequestOptions(JsonObject? meta = null, JsonSerializerOptions? jsonSerializerOptions = null, ProgressToken? progressToken = null) + { + Meta = meta; + JsonSerializerOptions = jsonSerializerOptions; + ProgressToken = progressToken; + } +} \ No newline at end of file diff --git a/src/ModelContextProtocol.Core/Server/McpServer.Methods.cs b/src/ModelContextProtocol.Core/Server/McpServer.Methods.cs index 8162aacfc..8380cac35 100644 --- a/src/ModelContextProtocol.Core/Server/McpServer.Methods.cs +++ b/src/ModelContextProtocol.Core/Server/McpServer.Methods.cs @@ -51,18 +51,18 @@ public static McpServer Create( /// Requests to sample an LLM via the client using the specified request parameters. /// /// The parameters for the sampling request. - /// Optional metadata to include in the request. + /// Optional request options including metadata, serialization settings, and progress tracking. /// The to monitor for cancellation requests. /// A task containing the sampling result from the client. /// The client does not support sampling. public ValueTask SampleAsync( - CreateMessageRequestParams request, JsonObject? meta = null, CancellationToken cancellationToken = default) + CreateMessageRequestParams request, RequestOptions? options = null, CancellationToken cancellationToken = default) { ThrowIfSamplingUnsupported(); - if (meta is not null) + if (options?.Meta is not null) { - request.Meta = meta; + request.Meta = options.Meta; } return SendRequestAsync( @@ -78,13 +78,13 @@ public ValueTask SampleAsync( /// /// The messages to send as part of the request. /// The options to use for the request, including model parameters and constraints. - /// Optional metadata to include in the request. + /// Optional request options including metadata, serialization settings, and progress tracking. /// The to monitor for cancellation requests. The default is . /// A task containing the chat response from the model. /// is . /// The client does not support sampling. public async Task SampleAsync( - IEnumerable messages, ChatOptions? options = default, JsonObject? meta = null, CancellationToken cancellationToken = default) + IEnumerable messages, ChatOptions? options = default, RequestOptions? requestOptions = null, CancellationToken cancellationToken = default) { Throw.IfNull(messages); @@ -165,7 +165,7 @@ public async Task SampleAsync( SystemPrompt = systemPrompt?.ToString(), Temperature = options?.Temperature, ModelPreferences = modelPreferences, - }, meta, cancellationToken).ConfigureAwait(false); + }, requestOptions, cancellationToken).ConfigureAwait(false); AIContent? responseContent = result.Content.ToAIContent(); @@ -202,18 +202,18 @@ public ILoggerProvider AsClientLoggerProvider() /// Requests the client to list the roots it exposes. /// /// The parameters for the list roots request. - /// Optional metadata to include in the request. + /// Optional request options including metadata, serialization settings, and progress tracking. /// The to monitor for cancellation requests. /// A task containing the list of roots exposed by the client. /// The client does not support roots. public ValueTask RequestRootsAsync( - ListRootsRequestParams request, JsonObject? meta = null, CancellationToken cancellationToken = default) + ListRootsRequestParams request, RequestOptions? options = null, CancellationToken cancellationToken = default) { ThrowIfRootsUnsupported(); - if (meta is not null) + if (options?.Meta is not null) { - request.Meta = meta; + request.Meta = options.Meta; } return SendRequestAsync( @@ -467,7 +467,7 @@ private sealed class SamplingChatClient(McpServer server) : IChatClient /// public Task GetResponseAsync(IEnumerable messages, ChatOptions? options = null, CancellationToken cancellationToken = default) => - _server.SampleAsync(messages, options, meta: null, cancellationToken); + _server.SampleAsync(messages, options, requestOptions: null, cancellationToken); /// async IAsyncEnumerable IChatClient.GetStreamingResponseAsync( diff --git a/src/ModelContextProtocol.Core/Server/McpServerOptions.cs b/src/ModelContextProtocol.Core/Server/McpServerOptions.cs index 37cf58dc3..f32974d9b 100644 --- a/src/ModelContextProtocol.Core/Server/McpServerOptions.cs +++ b/src/ModelContextProtocol.Core/Server/McpServerOptions.cs @@ -21,7 +21,7 @@ public sealed class McpServerOptions /// /// /// These determine which features will be available when a client connects. - /// Capabilities can include "tools", "prompts", "resources", "logging", and other + /// Capabilities can include "tools", "prompts", "resources", "logging", and other /// protocol-specific functionality. /// public ServerCapabilities? Capabilities { get; set; } @@ -84,8 +84,8 @@ public sealed class McpServerOptions /// Gets the filter collections for MCP server handlers. /// /// - /// This property provides access to filter collections that can be used to modify the behavior - /// of various MCP server handlers. Filters are applied in reverse order, so the last filter + /// This property provides access to filter collections that can be used to modify the behavior + /// of various MCP server handlers. Filters are applied in reverse order, so the last filter /// added will be the outermost (first to execute). /// public McpServerFilters Filters { get; } = new(); @@ -93,12 +93,12 @@ public sealed class McpServerOptions /// /// Gets or sets the container of handlers used by the server for processing protocol messages. /// - public McpServerHandlers Handlers - { + public McpServerHandlers Handlers + { get => field ??= new(); set - { - Throw.IfNull(value); + { + Throw.IfNull(value); field = value; } } @@ -143,7 +143,7 @@ public McpServerHandlers Handlers /// when those are provided: /// /// - /// - For requests: The server returns all prompts from this collection + /// - For requests: The server returns all prompts from this collection /// plus any additional prompts provided by the if it's set. /// /// @@ -158,7 +158,7 @@ public McpServerHandlers Handlers /// /// /// - /// This value is used in + /// This value is used in /// when is not set in the request options. /// /// diff --git a/src/ModelContextProtocol.Core/TokenProgress.cs b/src/ModelContextProtocol.Core/TokenProgress.cs index 73bb464ec..5bdd06bc5 100644 --- a/src/ModelContextProtocol.Core/TokenProgress.cs +++ b/src/ModelContextProtocol.Core/TokenProgress.cs @@ -11,6 +11,6 @@ internal sealed class TokenProgress(McpSession session, ProgressToken progressTo /// public void Report(ProgressNotificationValue value) { - _ = session.NotifyProgressAsync(progressToken, value, meta: null, CancellationToken.None); + _ = session.NotifyProgressAsync(progressToken, value, options: null, CancellationToken.None); } } diff --git a/tests/ModelContextProtocol.AspNetCore.Tests/HttpServerIntegrationTests.cs b/tests/ModelContextProtocol.AspNetCore.Tests/HttpServerIntegrationTests.cs index 78acaeb5e..57911ea65 100644 --- a/tests/ModelContextProtocol.AspNetCore.Tests/HttpServerIntegrationTests.cs +++ b/tests/ModelContextProtocol.AspNetCore.Tests/HttpServerIntegrationTests.cs @@ -35,7 +35,7 @@ public async Task ConnectAndPing_Sse_TestServer() // Act await using var client = await GetClientAsync(); - await client.PingAsync(TestContext.Current.CancellationToken); + await client.PingAsync(null, TestContext.Current.CancellationToken); // Assert Assert.NotNull(client); @@ -139,7 +139,7 @@ public async Task ListResources_Sse_TestServer() // act await using var client = await GetClientAsync(); - IList allResources = await client.ListResourcesAsync(TestContext.Current.CancellationToken); + IList allResources = await client.ListResourcesAsync(null, TestContext.Current.CancellationToken); // The everything server provides 100 test resources Assert.Equal(100, allResources.Count); @@ -155,7 +155,7 @@ public async Task ReadResource_Sse_TextResource() // Odd numbered resources are text in the everything server (despite the docs saying otherwise) // 1 is index 0, which is "even" in the 0-based index // We copied this oddity to the test server - var result = await client.ReadResourceAsync("test://static/resource/1", TestContext.Current.CancellationToken); + var result = await client.ReadResourceAsync("test://static/resource/1", null, TestContext.Current.CancellationToken); Assert.NotNull(result); Assert.Single(result.Contents); @@ -174,7 +174,7 @@ public async Task ReadResource_Sse_BinaryResource() // Even numbered resources are binary in the everything server (despite the docs saying otherwise) // 2 is index 1, which is "odd" in the 0-based index // We copied this oddity to the test server - var result = await client.ReadResourceAsync("test://static/resource/2", TestContext.Current.CancellationToken); + var result = await client.ReadResourceAsync("test://static/resource/2", null, TestContext.Current.CancellationToken); Assert.NotNull(result); Assert.Single(result.Contents); @@ -190,7 +190,7 @@ public async Task ListPrompts_Sse_TestServer() // act await using var client = await GetClientAsync(); - var prompts = await client.ListPromptsAsync(TestContext.Current.CancellationToken); + var prompts = await client.ListPromptsAsync(null, TestContext.Current.CancellationToken); // assert Assert.NotNull(prompts); diff --git a/tests/ModelContextProtocol.AspNetCore.Tests/MapMcpTests.cs b/tests/ModelContextProtocol.AspNetCore.Tests/MapMcpTests.cs index 0c71e56e3..dd57934ca 100644 --- a/tests/ModelContextProtocol.AspNetCore.Tests/MapMcpTests.cs +++ b/tests/ModelContextProtocol.AspNetCore.Tests/MapMcpTests.cs @@ -291,8 +291,8 @@ public static async Task SamplingToolAsync(McpServer server, string prom MaxTokens = 1000 }; - await server.SampleAsync(samplingRequest, cancellationToken); - var samplingResult = await server.SampleAsync(samplingRequest, cancellationToken); + await server.SampleAsync(samplingRequest, null, cancellationToken); + var samplingResult = await server.SampleAsync(samplingRequest, null, cancellationToken); return $"Sampling completed successfully. Client responded: {Assert.IsType(samplingResult.Content).Text}"; } diff --git a/tests/ModelContextProtocol.TestServer/Program.cs b/tests/ModelContextProtocol.TestServer/Program.cs index 92ce21984..a85205c3c 100644 --- a/tests/ModelContextProtocol.TestServer/Program.cs +++ b/tests/ModelContextProtocol.TestServer/Program.cs @@ -193,7 +193,7 @@ private static void ConfigureTools(McpServerOptions options, string? cliArg) throw new McpProtocolException("Missing required arguments 'prompt' and 'maxTokens'", McpErrorCode.InvalidParams); } var sampleResult = await request.Server.SampleAsync(CreateRequestSamplingParams(prompt.ToString(), "sampleLLM", Convert.ToInt32(maxTokens.GetRawText())), - meta: null, cancellationToken); + options: null, cancellationToken); return new CallToolResult { diff --git a/tests/ModelContextProtocol.TestSseServer/Program.cs b/tests/ModelContextProtocol.TestSseServer/Program.cs index 166f8508e..384b1a7df 100644 --- a/tests/ModelContextProtocol.TestSseServer/Program.cs +++ b/tests/ModelContextProtocol.TestSseServer/Program.cs @@ -187,7 +187,7 @@ static CreateMessageRequestParams CreateRequestSamplingParams(string context, st throw new McpProtocolException("Missing required arguments 'prompt' and 'maxTokens'", McpErrorCode.InvalidParams); } var sampleResult = await request.Server.SampleAsync(CreateRequestSamplingParams(prompt.ToString(), "sampleLLM", Convert.ToInt32(maxTokens.ToString())), - meta: null, cancellationToken); + options: null, cancellationToken); return new CallToolResult { diff --git a/tests/ModelContextProtocol.Tests/Client/McpClientExtensionsTests.cs b/tests/ModelContextProtocol.Tests/Client/McpClientExtensionsTests.cs index 8fb7d2203..e2c47022f 100644 --- a/tests/ModelContextProtocol.Tests/Client/McpClientExtensionsTests.cs +++ b/tests/ModelContextProtocol.Tests/Client/McpClientExtensionsTests.cs @@ -342,7 +342,7 @@ public async Task ReadResourceAsync_String_Forwards_To_McpClient_SendRequestAsyn IMcpClient client = mockClient.Object; - var result = await client.ReadResourceAsync("mcp://resource/1", TestContext.Current.CancellationToken); + var result = await client.ReadResourceAsync("mcp://resource/1", cancellationToken: TestContext.Current.CancellationToken); Assert.NotNull(result); mockClient.Verify(c => c.SendRequestAsync(It.IsAny(), It.IsAny()), Times.Once); diff --git a/tests/ModelContextProtocol.Tests/Client/McpClientResourceTemplateTests.cs b/tests/ModelContextProtocol.Tests/Client/McpClientResourceTemplateTests.cs index 5014022c7..693dab282 100644 --- a/tests/ModelContextProtocol.Tests/Client/McpClientResourceTemplateTests.cs +++ b/tests/ModelContextProtocol.Tests/Client/McpClientResourceTemplateTests.cs @@ -75,7 +75,7 @@ public async Task UriTemplate_InputsProduceExpectedOutputs( { await using McpClient client = await CreateMcpClientForServer(); - var result = await client.ReadResourceAsync(uriTemplate, variables, TestContext.Current.CancellationToken); + var result = await client.ReadResourceAsync(uriTemplate, variables, null, TestContext.Current.CancellationToken); Assert.NotNull(result); var actualUri = Assert.IsType(Assert.Single(result.Contents)).Text; @@ -110,13 +110,13 @@ internal partial class JsonContext7 : JsonSerializerContext; // The JSON from the test case files has been extracted below. // Copyright 2011- The Authors - // + // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at - // + // // http://www.apache.org/licenses/LICENSE-2.0 - // + // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -255,7 +255,7 @@ internal partial class JsonContext7 : JsonSerializerContext; ["X{.var:3}", "X.val"], ["X{.list}", "X.red,green,blue"], ["X{.list*}", "X.red.green.blue"], - ["X{.keys}", [ + ["X{.keys}", [ "X.comma,%2C,dot,.,semi,%3B", "X.comma,%2C,semi,%3B,dot,.", "X.dot,.,comma,%2C,semi,%3B", @@ -275,7 +275,7 @@ internal partial class JsonContext7 : JsonSerializerContext; "/semi,%3B,comma,%2C,dot,.", "/semi,%3B,dot,.,comma,%2C" ]], - ["{/keys*}", [ + ["{/keys*}", [ "/comma=%2C/dot=./semi=%3B", "/comma=%2C/semi=%3B/dot=.", "/dot=./comma=%2C/semi=%3B", @@ -286,7 +286,7 @@ internal partial class JsonContext7 : JsonSerializerContext; ["{;hello:5}", ";hello=Hello"], ["{;list}", ";list=red,green,blue"], ["{;list*}", ";list=red;list=green;list=blue"], - ["{;keys}", [ + ["{;keys}", [ ";keys=comma,%2C,dot,.,semi,%3B", ";keys=comma,%2C,semi,%3B,dot,.", ";keys=dot,.,comma,%2C,semi,%3B", @@ -294,7 +294,7 @@ internal partial class JsonContext7 : JsonSerializerContext; ";keys=semi,%3B,comma,%2C,dot,.", ";keys=semi,%3B,dot,.,comma,%2C" ]], - ["{;keys*}", [ + ["{;keys*}", [ ";comma=%2C;dot=.;semi=%3B", ";comma=%2C;semi=%3B;dot=.", ";dot=.;comma=%2C;semi=%3B", @@ -305,7 +305,7 @@ internal partial class JsonContext7 : JsonSerializerContext; ["{?var:3}", "?var=val"], ["{?list}", "?list=red,green,blue"], ["{?list*}", "?list=red&list=green&list=blue"], - ["{?keys}", [ + ["{?keys}", [ "?keys=comma,%2C,dot,.,semi,%3B", "?keys=comma,%2C,semi,%3B,dot,.", "?keys=dot,.,comma,%2C,semi,%3B", @@ -313,7 +313,7 @@ internal partial class JsonContext7 : JsonSerializerContext; "?keys=semi,%3B,comma,%2C,dot,.", "?keys=semi,%3B,dot,.,comma,%2C" ]], - ["{?keys*}", [ + ["{?keys*}", [ "?comma=%2C&dot=.&semi=%3B", "?comma=%2C&semi=%3B&dot=.", "?dot=.&comma=%2C&semi=%3B", @@ -324,7 +324,7 @@ internal partial class JsonContext7 : JsonSerializerContext; ["{&var:3}", "&var=val"], ["{&list}", "&list=red,green,blue"], ["{&list*}", "&list=red&list=green&list=blue"], - ["{&keys}", [ + ["{&keys}", [ "&keys=comma,%2C,dot,.,semi,%3B", "&keys=comma,%2C,semi,%3B,dot,.", "&keys=dot,.,comma,%2C,semi,%3B", @@ -332,7 +332,7 @@ internal partial class JsonContext7 : JsonSerializerContext; "&keys=semi,%3B,comma,%2C,dot,.", "&keys=semi,%3B,dot,.,comma,%2C" ]], - ["{&keys*}", [ + ["{&keys*}", [ "&comma=%2C&dot=.&semi=%3B", "&comma=%2C&semi=%3B&dot=.", "&dot=.&comma=%2C&semi=%3B", @@ -636,7 +636,7 @@ internal partial class JsonContext7 : JsonSerializerContext; "/semi,%3B,comma,%2C,dot,.", "/semi,%3B,dot,.,comma,%2C" ]], - ["{/keys*}", [ + ["{/keys*}", [ "/comma=%2C/dot=./semi=%3B", "/comma=%2C/semi=%3B/dot=.", "/dot=./comma=%2C/semi=%3B", @@ -679,7 +679,7 @@ internal partial class JsonContext7 : JsonSerializerContext; ["{;x,y,undef}", ";x=1024;y=768"], ["{;list}", ";list=red,green,blue"], ["{;list*}", ";list=red;list=green;list=blue"], - ["{;keys}", [ + ["{;keys}", [ ";keys=comma,%2C,dot,.,semi,%3B", ";keys=comma,%2C,semi,%3B,dot,.", ";keys=dot,.,comma,%2C,semi,%3B", @@ -687,7 +687,7 @@ internal partial class JsonContext7 : JsonSerializerContext; ";keys=semi,%3B,comma,%2C,dot,.", ";keys=semi,%3B,dot,.,comma,%2C" ]], - ["{;keys*}", [ + ["{;keys*}", [ ";comma=%2C;dot=.;semi=%3B", ";comma=%2C;semi=%3B;dot=.", ";dot=.;comma=%2C;semi=%3B", @@ -727,7 +727,7 @@ internal partial class JsonContext7 : JsonSerializerContext; ["{?var:3}", "?var=val"], ["{?list}", "?list=red,green,blue"], ["{?list*}", "?list=red&list=green&list=blue"], - ["{?keys}", [ + ["{?keys}", [ "?keys=comma,%2C,dot,.,semi,%3B", "?keys=comma,%2C,semi,%3B,dot,.", "?keys=dot,.,comma,%2C,semi,%3B", @@ -735,7 +735,7 @@ internal partial class JsonContext7 : JsonSerializerContext; "?keys=semi,%3B,comma,%2C,dot,.", "?keys=semi,%3B,dot,.,comma,%2C" ]], - ["{?keys*}", [ + ["{?keys*}", [ "?comma=%2C&dot=.&semi=%3B", "?comma=%2C&semi=%3B&dot=.", "?dot=.&comma=%2C&semi=%3B", @@ -775,7 +775,7 @@ internal partial class JsonContext7 : JsonSerializerContext; ["{&x,y,undef}", "&x=1024&y=768"], ["{&list}", "&list=red,green,blue"], ["{&list*}", "&list=red&list=green&list=blue"], - ["{&keys}", [ + ["{&keys}", [ "&keys=comma,%2C,dot,.,semi,%3B", "&keys=comma,%2C,semi,%3B,dot,.", "&keys=dot,.,comma,%2C,semi,%3B", @@ -783,7 +783,7 @@ internal partial class JsonContext7 : JsonSerializerContext; "&keys=semi,%3B,comma,%2C,dot,.", "&keys=semi,%3B,dot,.,comma,%2C" ]], - ["{&keys*}", [ + ["{&keys*}", [ "&comma=%2C&dot=.&semi=%3B", "&comma=%2C&semi=%3B&dot=.", "&dot=.&comma=%2C&semi=%3B", diff --git a/tests/ModelContextProtocol.Tests/Client/McpClientTests.cs b/tests/ModelContextProtocol.Tests/Client/McpClientTests.cs index 09be7385e..0e23cd435 100644 --- a/tests/ModelContextProtocol.Tests/Client/McpClientTests.cs +++ b/tests/ModelContextProtocol.Tests/Client/McpClientTests.cs @@ -310,13 +310,13 @@ public async Task EnumerateToolsAsync_FlowsJsonSerializerOptions() await using McpClient client = await CreateMcpClientForServer(); bool hasTools = false; - await foreach (var tool in client.EnumerateToolsAsync(options, TestContext.Current.CancellationToken)) + await foreach (var tool in client.EnumerateToolsAsync(new RequestOptions { JsonSerializerOptions = options }, TestContext.Current.CancellationToken)) { Assert.Same(options, tool.JsonSerializerOptions); hasTools = true; } - foreach (var tool in await client.ListToolsAsync(options, TestContext.Current.CancellationToken)) + foreach (var tool in await client.ListToolsAsync(new RequestOptions { JsonSerializerOptions = options }, TestContext.Current.CancellationToken)) { Assert.Same(options, tool.JsonSerializerOptions); } @@ -330,7 +330,7 @@ public async Task EnumerateToolsAsync_HonorsJsonSerializerOptions() JsonSerializerOptions emptyOptions = new() { TypeInfoResolver = JsonTypeInfoResolver.Combine() }; await using McpClient client = await CreateMcpClientForServer(); - var tool = (await client.ListToolsAsync(emptyOptions, TestContext.Current.CancellationToken)).First(); + var tool = (await client.ListToolsAsync(new RequestOptions { JsonSerializerOptions = emptyOptions }, TestContext.Current.CancellationToken)).First(); await Assert.ThrowsAsync(async () => await tool.InvokeAsync(new() { ["i"] = 42 }, TestContext.Current.CancellationToken)); } @@ -358,7 +358,7 @@ public async Task GetPromptsAsync_HonorsJsonSerializerOptions() JsonSerializerOptions emptyOptions = new() { TypeInfoResolver = JsonTypeInfoResolver.Combine() }; await using McpClient client = await CreateMcpClientForServer(); - await Assert.ThrowsAsync(async () => await client.GetPromptAsync("Prompt", new Dictionary { ["i"] = 42 }, emptyOptions, cancellationToken: TestContext.Current.CancellationToken)); + await Assert.ThrowsAsync(async () => await client.GetPromptAsync("Prompt", new Dictionary { ["i"] = 42 }, new RequestOptions { JsonSerializerOptions = emptyOptions }, cancellationToken: TestContext.Current.CancellationToken)); } [Fact] @@ -367,7 +367,7 @@ public async Task WithName_ChangesToolName() JsonSerializerOptions options = new(JsonSerializerOptions.Default); await using McpClient client = await CreateMcpClientForServer(); - var tool = (await client.ListToolsAsync(options, TestContext.Current.CancellationToken)).First(); + var tool = (await client.ListToolsAsync(new RequestOptions { JsonSerializerOptions = options }, TestContext.Current.CancellationToken)).First(); var originalName = tool.Name; var renamedTool = tool.WithName("RenamedTool"); @@ -381,7 +381,7 @@ public async Task WithDescription_ChangesToolDescription() { JsonSerializerOptions options = new(JsonSerializerOptions.Default); await using McpClient client = await CreateMcpClientForServer(); - var tool = (await client.ListToolsAsync(options, TestContext.Current.CancellationToken)).FirstOrDefault(); + var tool = (await client.ListToolsAsync(new RequestOptions { JsonSerializerOptions = options }, TestContext.Current.CancellationToken)).FirstOrDefault(); var originalDescription = tool?.Description; var redescribedTool = tool?.WithDescription("ToolWithNewDescription"); Assert.NotNull(redescribedTool); @@ -455,7 +455,7 @@ public async Task AsClientLoggerProvider_MessagesSentToClient() Assert.False(logger.IsEnabled(LogLevel.Error)); Assert.False(logger.IsEnabled(LogLevel.Critical)); - await client.SetLoggingLevel(LoggingLevel.Info, TestContext.Current.CancellationToken); + await client.SetLoggingLevel(LoggingLevel.Info, options: null, TestContext.Current.CancellationToken); DateTime start = DateTime.UtcNow; while (Server.LoggingLevel is null) diff --git a/tests/ModelContextProtocol.Tests/ClientIntegrationTests.cs b/tests/ModelContextProtocol.Tests/ClientIntegrationTests.cs index 16fad124a..b70deff09 100644 --- a/tests/ModelContextProtocol.Tests/ClientIntegrationTests.cs +++ b/tests/ModelContextProtocol.Tests/ClientIntegrationTests.cs @@ -34,7 +34,7 @@ public async Task ConnectAndPing_Stdio(string clientId) // Act await using var client = await _fixture.CreateClientAsync(clientId); - await client.PingAsync(TestContext.Current.CancellationToken); + await client.PingAsync(null, TestContext.Current.CancellationToken); // Assert Assert.NotNull(client); @@ -141,7 +141,7 @@ public async Task ListPrompts_Stdio(string clientId) // act await using var client = await _fixture.CreateClientAsync(clientId); - var prompts = await client.ListPromptsAsync(TestContext.Current.CancellationToken); + var prompts = await client.ListPromptsAsync(null, TestContext.Current.CancellationToken); // assert Assert.NotEmpty(prompts); @@ -193,7 +193,7 @@ public async Task GetPrompt_NonExistent_ThrowsException(string clientId) // act await using var client = await _fixture.CreateClientAsync(clientId); - await Assert.ThrowsAsync(async () => + await Assert.ThrowsAsync(async () => await client.GetPromptAsync("non_existent_prompt", null, cancellationToken: TestContext.Current.CancellationToken)); } @@ -206,7 +206,7 @@ public async Task ListResourceTemplates_Stdio(string clientId) // act await using var client = await _fixture.CreateClientAsync(clientId); - IList allResourceTemplates = await client.ListResourceTemplatesAsync(TestContext.Current.CancellationToken); + IList allResourceTemplates = await client.ListResourceTemplatesAsync(null, TestContext.Current.CancellationToken); // The server provides a single test resource template Assert.Single(allResourceTemplates); @@ -221,7 +221,7 @@ public async Task ListResources_Stdio(string clientId) // act await using var client = await _fixture.CreateClientAsync(clientId); - IList allResources = await client.ListResourcesAsync(TestContext.Current.CancellationToken); + IList allResources = await client.ListResourcesAsync(null, TestContext.Current.CancellationToken); // The server provides 100 test resources Assert.Equal(100, allResources.Count); @@ -237,7 +237,7 @@ public async Task ReadResource_Stdio_TextResource(string clientId) await using var client = await _fixture.CreateClientAsync(clientId); // Odd numbered resources are text in the everything server (despite the docs saying otherwise) // 1 is index 0, which is "even" in the 0-based index - var result = await client.ReadResourceAsync("test://static/resource/1", TestContext.Current.CancellationToken); + var result = await client.ReadResourceAsync("test://static/resource/1", null, TestContext.Current.CancellationToken); Assert.NotNull(result); Assert.Single(result.Contents); @@ -256,7 +256,7 @@ public async Task ReadResource_Stdio_BinaryResource(string clientId) await using var client = await _fixture.CreateClientAsync(clientId); // Even numbered resources are binary in the everything server (despite the docs saying otherwise) // 2 is index 1, which is "odd" in the 0-based index - var result = await client.ReadResourceAsync("test://static/resource/2", TestContext.Current.CancellationToken); + var result = await client.ReadResourceAsync("test://static/resource/2", null, TestContext.Current.CancellationToken); Assert.NotNull(result); Assert.Single(result.Contents); @@ -290,7 +290,7 @@ public async Task SubscribeResource_Stdio() } }); - await client.SubscribeToResourceAsync("test://static/resource/1", TestContext.Current.CancellationToken); + await client.SubscribeToResourceAsync("test://static/resource/1", null, TestContext.Current.CancellationToken); await tcs.Task; } @@ -319,13 +319,13 @@ public async Task UnsubscribeResource_Stdio() ] } }); - await client.SubscribeToResourceAsync("test://static/resource/1", TestContext.Current.CancellationToken); + await client.SubscribeToResourceAsync("test://static/resource/1", null, TestContext.Current.CancellationToken); // wait until we received a notification await receivedNotification.Task; // unsubscribe - await client.UnsubscribeFromResourceAsync("test://static/resource/1", TestContext.Current.CancellationToken); + await client.UnsubscribeFromResourceAsync("test://static/resource/1", null, TestContext.Current.CancellationToken); } [Theory] @@ -408,7 +408,7 @@ public async Task Sampling_Stdio(string clientId) //[Theory] //[MemberData(nameof(GetClients))] //public async Task Roots_Stdio_EverythingServer(string clientId) - //{ + //{ // var rootsHandlerCalls = 0; // var testRoots = new List // { @@ -573,7 +573,7 @@ public async Task SetLoggingLevel_ReceivesLoggingMessages(string clientId) }); // act - await client.SetLoggingLevel(LoggingLevel.Debug, TestContext.Current.CancellationToken); + await client.SetLoggingLevel(LoggingLevel.Debug, options: null, TestContext.Current.CancellationToken); // assert await receivedNotification.Task; diff --git a/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsPromptsTests.cs b/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsPromptsTests.cs index 8fdeacb9b..40aa343b8 100644 --- a/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsPromptsTests.cs +++ b/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsPromptsTests.cs @@ -100,7 +100,7 @@ public async Task Can_List_And_Call_Registered_Prompts() { await using McpClient client = await CreateMcpClientForServer(); - var prompts = await client.ListPromptsAsync(TestContext.Current.CancellationToken); + var prompts = await client.ListPromptsAsync(null, TestContext.Current.CancellationToken); Assert.Equal(6, prompts.Count); var prompt = prompts.First(t => t.Name == "returns_chat_messages"); @@ -129,7 +129,7 @@ public async Task Can_Be_Notified_Of_Prompt_Changes() { await using McpClient client = await CreateMcpClientForServer(); - var prompts = await client.ListPromptsAsync(TestContext.Current.CancellationToken); + var prompts = await client.ListPromptsAsync(null, TestContext.Current.CancellationToken); Assert.Equal(6, prompts.Count); Channel listChanged = Channel.CreateUnbounded(); @@ -150,7 +150,7 @@ public async Task Can_Be_Notified_Of_Prompt_Changes() serverPrompts.Add(newPrompt); await notificationRead; - prompts = await client.ListPromptsAsync(TestContext.Current.CancellationToken); + prompts = await client.ListPromptsAsync(null, TestContext.Current.CancellationToken); Assert.Equal(7, prompts.Count); Assert.Contains(prompts, t => t.Name == "NewPrompt"); @@ -160,7 +160,7 @@ public async Task Can_Be_Notified_Of_Prompt_Changes() await notificationRead; } - prompts = await client.ListPromptsAsync(TestContext.Current.CancellationToken); + prompts = await client.ListPromptsAsync(null, TestContext.Current.CancellationToken); Assert.Equal(6, prompts.Count); Assert.DoesNotContain(prompts, t => t.Name == "NewPrompt"); } diff --git a/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsResourcesTests.cs b/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsResourcesTests.cs index 4dd2c3d4b..da5361641 100644 --- a/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsResourcesTests.cs +++ b/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsResourcesTests.cs @@ -129,7 +129,7 @@ public async Task Can_List_And_Call_Registered_Resources() Assert.NotNull(client.ServerCapabilities.Resources); - var resources = await client.ListResourcesAsync(TestContext.Current.CancellationToken); + var resources = await client.ListResourcesAsync(null, TestContext.Current.CancellationToken); Assert.Equal(5, resources.Count); var resource = resources.First(t => t.Name == "some_neat_direct_resource"); @@ -146,7 +146,7 @@ public async Task Can_List_And_Call_Registered_ResourceTemplates() { await using McpClient client = await CreateMcpClientForServer(); - var resources = await client.ListResourceTemplatesAsync(TestContext.Current.CancellationToken); + var resources = await client.ListResourceTemplatesAsync(null, TestContext.Current.CancellationToken); Assert.Equal(3, resources.Count); var resource = resources.First(t => t.Name == "some_neat_templated_resource"); @@ -163,7 +163,7 @@ public async Task Can_Be_Notified_Of_Resource_Changes() { await using McpClient client = await CreateMcpClientForServer(); - var resources = await client.ListResourcesAsync(TestContext.Current.CancellationToken); + var resources = await client.ListResourcesAsync(null, TestContext.Current.CancellationToken); Assert.Equal(5, resources.Count); Channel listChanged = Channel.CreateUnbounded(); @@ -184,7 +184,7 @@ public async Task Can_Be_Notified_Of_Resource_Changes() serverResources.Add(newResource); await notificationRead; - resources = await client.ListResourcesAsync(TestContext.Current.CancellationToken); + resources = await client.ListResourcesAsync(null, TestContext.Current.CancellationToken); Assert.Equal(6, resources.Count); Assert.Contains(resources, t => t.Name == "NewResource"); @@ -194,7 +194,7 @@ public async Task Can_Be_Notified_Of_Resource_Changes() await notificationRead; } - resources = await client.ListResourcesAsync(TestContext.Current.CancellationToken); + resources = await client.ListResourcesAsync(null, TestContext.Current.CancellationToken); Assert.Equal(5, resources.Count); Assert.DoesNotContain(resources, t => t.Name == "NewResource"); } diff --git a/tests/ModelContextProtocol.Tests/Configuration/McpServerResourceCapabilityIntegrationTests.cs b/tests/ModelContextProtocol.Tests/Configuration/McpServerResourceCapabilityIntegrationTests.cs index c518b7030..0f457aae2 100644 --- a/tests/ModelContextProtocol.Tests/Configuration/McpServerResourceCapabilityIntegrationTests.cs +++ b/tests/ModelContextProtocol.Tests/Configuration/McpServerResourceCapabilityIntegrationTests.cs @@ -41,7 +41,7 @@ public async Task Client_CanListResources_WhenSubscribeCapabilityIsManuallySet() Assert.True(client.ServerCapabilities.Resources.Subscribe, "Server should advertise Subscribe capability when manually set"); // The resources should be exposed and listable - var resources = await client.ListResourcesAsync(TestContext.Current.CancellationToken); + var resources = await client.ListResourcesAsync(null, TestContext.Current.CancellationToken); Assert.NotEmpty(resources); Assert.Contains(resources, r => r.Name == "test_resource"); } @@ -56,7 +56,7 @@ public async Task Client_CanListResources_WhenCapabilitySetViaAddMcpServerCallba Assert.NotNull(client.ServerCapabilities.Resources); // The resources should be exposed and listable - var resources = await client.ListResourcesAsync(TestContext.Current.CancellationToken); + var resources = await client.ListResourcesAsync(null, TestContext.Current.CancellationToken); Assert.NotEmpty(resources); } diff --git a/tests/ModelContextProtocol.Tests/Configuration/McpServerResourceRoutingTests.cs b/tests/ModelContextProtocol.Tests/Configuration/McpServerResourceRoutingTests.cs index 63eb9fb52..ca82c648b 100644 --- a/tests/ModelContextProtocol.Tests/Configuration/McpServerResourceRoutingTests.cs +++ b/tests/ModelContextProtocol.Tests/Configuration/McpServerResourceRoutingTests.cs @@ -23,19 +23,19 @@ public async Task MultipleTemplatedResources_MatchesCorrectResource() // Regression test for https://github.com/modelcontextprotocol/csharp-sdk/issues/821. await using McpClient client = await CreateMcpClientForServer(); - var nonTemplatedResult = await client.ReadResourceAsync("test://resource/non-templated", TestContext.Current.CancellationToken); + var nonTemplatedResult = await client.ReadResourceAsync("test://resource/non-templated", null, TestContext.Current.CancellationToken); Assert.Equal("static", ((TextResourceContents)nonTemplatedResult.Contents[0]).Text); - var templatedResult = await client.ReadResourceAsync("test://resource/12345", TestContext.Current.CancellationToken); + var templatedResult = await client.ReadResourceAsync("test://resource/12345", null, TestContext.Current.CancellationToken); Assert.Equal("template: 12345", ((TextResourceContents)templatedResult.Contents[0]).Text); - var exactTemplatedResult = await client.ReadResourceAsync("test://resource/{id}", TestContext.Current.CancellationToken); + var exactTemplatedResult = await client.ReadResourceAsync("test://resource/{id}", null, TestContext.Current.CancellationToken); Assert.Equal("template: {id}", ((TextResourceContents)exactTemplatedResult.Contents[0]).Text); - var paramsResult = await client.ReadResourceAsync("test://params?a1=a&a2=b&a3=c", TestContext.Current.CancellationToken); + var paramsResult = await client.ReadResourceAsync("test://params?a1=a&a2=b&a3=c", null, TestContext.Current.CancellationToken); Assert.Equal("params: a, b, c", ((TextResourceContents)paramsResult.Contents[0]).Text); - var mcpEx = await Assert.ThrowsAsync(async () => await client.ReadResourceAsync("test://params{?a1,a2,a3}", TestContext.Current.CancellationToken)); + var mcpEx = await Assert.ThrowsAsync(async () => await client.ReadResourceAsync("test://params{?a1,a2,a3}", null, TestContext.Current.CancellationToken)); Assert.Equal(McpErrorCode.InvalidParams, mcpEx.ErrorCode); Assert.Equal("Request failed (remote): Unknown resource URI: 'test://params{?a1,a2,a3}'", mcpEx.Message); } diff --git a/tests/ModelContextProtocol.Tests/Configuration/McpServerScopedTests.cs b/tests/ModelContextProtocol.Tests/Configuration/McpServerScopedTests.cs index 5ddc3c54a..574eec3ee 100644 --- a/tests/ModelContextProtocol.Tests/Configuration/McpServerScopedTests.cs +++ b/tests/ModelContextProtocol.Tests/Configuration/McpServerScopedTests.cs @@ -24,7 +24,7 @@ public async Task InjectScopedServiceAsArgument() { await using McpClient client = await CreateMcpClientForServer(); - var tools = await client.ListToolsAsync(McpServerScopedTestsJsonContext.Default.Options, TestContext.Current.CancellationToken); + var tools = await client.ListToolsAsync(new RequestOptions { JsonSerializerOptions = McpServerScopedTestsJsonContext.Default.Options }, TestContext.Current.CancellationToken); var tool = tools.First(t => t.Name == "echo_complex"); Assert.DoesNotContain("\"complex\"", JsonSerializer.Serialize(tool.JsonSchema, McpJsonUtilities.DefaultOptions)); diff --git a/tests/ModelContextProtocol.Tests/Server/EmptyCollectionTests.cs b/tests/ModelContextProtocol.Tests/Server/EmptyCollectionTests.cs index 882fd5045..a5b68f8c1 100644 --- a/tests/ModelContextProtocol.Tests/Server/EmptyCollectionTests.cs +++ b/tests/ModelContextProtocol.Tests/Server/EmptyCollectionTests.cs @@ -31,7 +31,7 @@ public async Task EmptyResourceCollection_CanAddResourcesDynamically() var client = await CreateMcpClientForServer(); // Initially, the resource collection is empty - var initialResources = await client.ListResourcesAsync(TestContext.Current.CancellationToken); + var initialResources = await client.ListResourcesAsync(options: null, TestContext.Current.CancellationToken); Assert.Empty(initialResources); // Add a resource dynamically @@ -40,7 +40,7 @@ public async Task EmptyResourceCollection_CanAddResourcesDynamically() new() { UriTemplate = "test://resource/1" })); // The resource should now be listed - var updatedResources = await client.ListResourcesAsync(TestContext.Current.CancellationToken); + var updatedResources = await client.ListResourcesAsync(options: null, TestContext.Current.CancellationToken); Assert.Single(updatedResources); Assert.Equal("test://resource/1", updatedResources[0].Uri); } @@ -71,7 +71,7 @@ public async Task EmptyPromptCollection_CanAddPromptsDynamically() var client = await CreateMcpClientForServer(); // Initially, the prompt collection is empty - var initialPrompts = await client.ListPromptsAsync(TestContext.Current.CancellationToken); + var initialPrompts = await client.ListPromptsAsync(options: null, TestContext.Current.CancellationToken); Assert.Empty(initialPrompts); // Add a prompt dynamically @@ -80,7 +80,7 @@ public async Task EmptyPromptCollection_CanAddPromptsDynamically() new() { Name = "test_prompt", Description = "A test prompt" })); // The prompt should now be listed - var updatedPrompts = await client.ListPromptsAsync(TestContext.Current.CancellationToken); + var updatedPrompts = await client.ListPromptsAsync(options: null, TestContext.Current.CancellationToken); Assert.Single(updatedPrompts); Assert.Equal("test_prompt", updatedPrompts[0].Name); } @@ -96,7 +96,7 @@ public async Task EmptyResourceCollection_CanCallReadResourceAfterAddingDynamica new() { UriTemplate = "test://resource/dynamic" })); // Read the resource - var result = await client.ReadResourceAsync("test://resource/dynamic", TestContext.Current.CancellationToken); + var result = await client.ReadResourceAsync("test://resource/dynamic", options: null, TestContext.Current.CancellationToken); Assert.NotNull(result); Assert.Single(result.Contents); Assert.IsType(result.Contents[0]); @@ -158,8 +158,8 @@ public async Task ListFails() var client = await CreateMcpClientForServer(); - await Assert.ThrowsAsync(async () => await client.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken)); - await Assert.ThrowsAsync(async () => await client.ListPromptsAsync(TestContext.Current.CancellationToken)); - await Assert.ThrowsAsync(async () => await client.ListResourcesAsync(TestContext.Current.CancellationToken)); + await Assert.ThrowsAsync(async () => await client.ListToolsAsync(options: null, cancellationToken: TestContext.Current.CancellationToken)); + await Assert.ThrowsAsync(async () => await client.ListPromptsAsync(options: null, TestContext.Current.CancellationToken)); + await Assert.ThrowsAsync(async () => await client.ListResourcesAsync(options: null, TestContext.Current.CancellationToken)); } } diff --git a/tests/ModelContextProtocol.Tests/Server/McpServerTests.cs b/tests/ModelContextProtocol.Tests/Server/McpServerTests.cs index 810bcef48..7a1bba2ce 100644 --- a/tests/ModelContextProtocol.Tests/Server/McpServerTests.cs +++ b/tests/ModelContextProtocol.Tests/Server/McpServerTests.cs @@ -130,7 +130,7 @@ public async Task SampleAsync_Should_Throw_Exception_If_Client_Does_Not_Support_ await using var server = McpServer.Create(transport, _options, LoggerFactory); SetClientCapabilities(server, new ClientCapabilities()); - var action = async () => await server.SampleAsync(new CreateMessageRequestParams { Messages = [], MaxTokens = 1000 }, CancellationToken.None); + var action = async () => await server.SampleAsync(new CreateMessageRequestParams { Messages = [], MaxTokens = 1000 }, null, CancellationToken.None); // Act & Assert await Assert.ThrowsAsync(action); @@ -147,7 +147,7 @@ public async Task SampleAsync_Should_SendRequest() var runTask = server.RunAsync(TestContext.Current.CancellationToken); // Act - var result = await server.SampleAsync(new CreateMessageRequestParams { Messages = [], MaxTokens = 1000 }, CancellationToken.None); + var result = await server.SampleAsync(new CreateMessageRequestParams { Messages = [], MaxTokens = 1000 }, null, CancellationToken.None); Assert.NotNull(result); Assert.NotEmpty(transport.SentMessages); @@ -167,7 +167,7 @@ public async Task RequestRootsAsync_Should_Throw_Exception_If_Client_Does_Not_Su SetClientCapabilities(server, new ClientCapabilities()); // Act & Assert - await Assert.ThrowsAsync(async () => await server.RequestRootsAsync(new ListRootsRequestParams(), CancellationToken.None)); + await Assert.ThrowsAsync(async () => await server.RequestRootsAsync(new ListRootsRequestParams(), null, CancellationToken.None)); } [Fact] @@ -180,7 +180,7 @@ public async Task RequestRootsAsync_Should_SendRequest() var runTask = server.RunAsync(TestContext.Current.CancellationToken); // Act - var result = await server.RequestRootsAsync(new ListRootsRequestParams(), CancellationToken.None); + var result = await server.RequestRootsAsync(new ListRootsRequestParams(), null, CancellationToken.None); // Assert Assert.NotNull(result); From 5ac3f93055232ee43fef860d5819bd7d88f26a6e Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Sat, 8 Nov 2025 09:47:16 -0800 Subject: [PATCH 5/8] Fix tests --- .../Client/McpClientExtensions.cs | 56 +++++++++---------- .../McpEndpointExtensions.cs | 10 ++-- .../Server/McpServerExtensions.cs | 6 +- 3 files changed, 36 insertions(+), 36 deletions(-) diff --git a/src/ModelContextProtocol.Core/Client/McpClientExtensions.cs b/src/ModelContextProtocol.Core/Client/McpClientExtensions.cs index f0cd3c4f9..e4c79835a 100644 --- a/src/ModelContextProtocol.Core/Client/McpClientExtensions.cs +++ b/src/ModelContextProtocol.Core/Client/McpClientExtensions.cs @@ -75,7 +75,7 @@ public static class McpClientExtensions /// /// /// 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 + /// 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. /// /// @@ -88,7 +88,7 @@ public static class McpClientExtensions [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); + => AsClientOrThrow(client).PingAsync(null, cancellationToken).AsTask(); /// /// Retrieves a list of available tools from the server. @@ -103,7 +103,7 @@ public static Task PingAsync(this IMcpClient client, CancellationToken cancellat /// 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 + /// 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. /// /// @@ -115,13 +115,13 @@ public static Task PingAsync(this IMcpClient client, CancellationToken cancellat /// /// // 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); @@ -135,7 +135,7 @@ public static ValueTask> ListToolsAsync( this IMcpClient client, JsonSerializerOptions? serializerOptions = null, CancellationToken cancellationToken = default) - => AsClientOrThrow(client).ListToolsAsync(serializerOptions, cancellationToken); + => AsClientOrThrow(client).ListToolsAsync(new RequestOptions { JsonSerializerOptions = serializerOptions }, cancellationToken); /// /// Creates an enumerable for asynchronously enumerating all available tools from the server. @@ -175,7 +175,7 @@ public static IAsyncEnumerable EnumerateToolsAsync( this IMcpClient client, JsonSerializerOptions? serializerOptions = null, CancellationToken cancellationToken = default) - => AsClientOrThrow(client).EnumerateToolsAsync(serializerOptions, cancellationToken); + => AsClientOrThrow(client).EnumerateToolsAsync(new RequestOptions { JsonSerializerOptions = serializerOptions }, cancellationToken); /// /// Retrieves a list of available prompts from the server. @@ -189,7 +189,7 @@ public static IAsyncEnumerable EnumerateToolsAsync( /// 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 + /// 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. /// /// @@ -198,7 +198,7 @@ public static IAsyncEnumerable EnumerateToolsAsync( [EditorBrowsable(EditorBrowsableState.Never)] public static ValueTask> ListPromptsAsync( this IMcpClient client, CancellationToken cancellationToken = default) - => AsClientOrThrow(client).ListPromptsAsync(cancellationToken); + => AsClientOrThrow(client).ListPromptsAsync(null, cancellationToken); /// /// Creates an enumerable for asynchronously enumerating all available prompts from the server. @@ -231,7 +231,7 @@ public static ValueTask> ListPromptsAsync( [EditorBrowsable(EditorBrowsableState.Never)] public static IAsyncEnumerable EnumeratePromptsAsync( this IMcpClient client, CancellationToken cancellationToken = default) - => AsClientOrThrow(client).EnumeratePromptsAsync(cancellationToken); + => AsClientOrThrow(client).EnumeratePromptsAsync(null, cancellationToken); /// /// Retrieves a specific prompt from the MCP server. @@ -248,7 +248,7 @@ public static IAsyncEnumerable EnumeratePromptsAsync( /// 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 + /// 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. /// /// @@ -266,7 +266,7 @@ public static ValueTask GetPromptAsync( IReadOnlyDictionary? arguments = null, JsonSerializerOptions? serializerOptions = null, CancellationToken cancellationToken = default) - => AsClientOrThrow(client).GetPromptAsync(name, arguments, serializerOptions, cancellationToken); + => AsClientOrThrow(client).GetPromptAsync(name, arguments, new RequestOptions { JsonSerializerOptions = serializerOptions }, cancellationToken); /// /// Retrieves a list of available resource templates from the server. @@ -280,7 +280,7 @@ public static ValueTask GetPromptAsync( /// 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 + /// 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. /// /// @@ -289,7 +289,7 @@ public static ValueTask GetPromptAsync( [EditorBrowsable(EditorBrowsableState.Never)] public static ValueTask> ListResourceTemplatesAsync( this IMcpClient client, CancellationToken cancellationToken = default) - => AsClientOrThrow(client).ListResourceTemplatesAsync(cancellationToken); + => AsClientOrThrow(client).ListResourceTemplatesAsync(null, cancellationToken); /// /// Creates an enumerable for asynchronously enumerating all available resource templates from the server. @@ -322,7 +322,7 @@ public static ValueTask> ListResourceTemplatesA [EditorBrowsable(EditorBrowsableState.Never)] public static IAsyncEnumerable EnumerateResourceTemplatesAsync( this IMcpClient client, CancellationToken cancellationToken = default) - => AsClientOrThrow(client).EnumerateResourceTemplatesAsync(cancellationToken); + => AsClientOrThrow(client).EnumerateResourceTemplatesAsync(null, cancellationToken); /// /// Retrieves a list of available resources from the server. @@ -336,7 +336,7 @@ public static IAsyncEnumerable EnumerateResourceTempl /// 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 + /// 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. /// /// @@ -344,7 +344,7 @@ public static IAsyncEnumerable EnumerateResourceTempl /// /// // Get all resources available on the server /// var resources = await client.ListResourcesAsync(); - /// + /// /// // Display information about each resource /// foreach (var resource in resources) /// { @@ -357,7 +357,7 @@ public static IAsyncEnumerable EnumerateResourceTempl [EditorBrowsable(EditorBrowsableState.Never)] public static ValueTask> ListResourcesAsync( this IMcpClient client, CancellationToken cancellationToken = default) - => AsClientOrThrow(client).ListResourcesAsync(cancellationToken); + => AsClientOrThrow(client).ListResourcesAsync(null, cancellationToken); /// /// Creates an enumerable for asynchronously enumerating all available resources from the server. @@ -390,7 +390,7 @@ public static ValueTask> ListResourcesAsync( [EditorBrowsable(EditorBrowsableState.Never)] public static IAsyncEnumerable EnumerateResourcesAsync( this IMcpClient client, CancellationToken cancellationToken = default) - => AsClientOrThrow(client).EnumerateResourcesAsync(cancellationToken); + => AsClientOrThrow(client).EnumerateResourcesAsync(null, cancellationToken); /// /// Reads a resource from the server. @@ -405,7 +405,7 @@ public static IAsyncEnumerable EnumerateResourcesAsync( [EditorBrowsable(EditorBrowsableState.Never)] public static ValueTask ReadResourceAsync( this IMcpClient client, string uri, CancellationToken cancellationToken = default) - => AsClientOrThrow(client).ReadResourceAsync(uri, cancellationToken); + => AsClientOrThrow(client).ReadResourceAsync(uri, null, cancellationToken); /// /// Reads a resource from the server. @@ -419,7 +419,7 @@ public static ValueTask ReadResourceAsync( [EditorBrowsable(EditorBrowsableState.Never)] public static ValueTask ReadResourceAsync( this IMcpClient client, Uri uri, CancellationToken cancellationToken = default) - => AsClientOrThrow(client).ReadResourceAsync(uri, cancellationToken); + => AsClientOrThrow(client).ReadResourceAsync(uri, null, cancellationToken); /// /// Reads a resource from the server. @@ -435,7 +435,7 @@ public static ValueTask ReadResourceAsync( [EditorBrowsable(EditorBrowsableState.Never)] public static ValueTask ReadResourceAsync( this IMcpClient client, string uriTemplate, IReadOnlyDictionary arguments, CancellationToken cancellationToken = default) - => AsClientOrThrow(client).ReadResourceAsync(uriTemplate, arguments, cancellationToken); + => AsClientOrThrow(client).ReadResourceAsync(uriTemplate, arguments, null, cancellationToken); /// /// Requests completion suggestions for a prompt argument or resource reference. @@ -457,7 +457,7 @@ public static ValueTask ReadResourceAsync( /// auto-completion in user interfaces. /// /// - /// When working with resource references, the server will return suggestions relevant to the specified + /// When working with resource references, the server will return suggestions relevant to the specified /// resource URI. /// /// @@ -499,7 +499,7 @@ public static ValueTask CompleteAsync(this IMcpClient client, Re [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); + => AsClientOrThrow(client).SubscribeToResourceAsync(uri, null, cancellationToken); /// /// Subscribes to a resource on the server to receive notifications when it changes. @@ -528,7 +528,7 @@ public static Task SubscribeToResourceAsync(this IMcpClient client, string uri, [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); + => AsClientOrThrow(client).SubscribeToResourceAsync(uri, null, cancellationToken); /// /// Unsubscribes from a resource on the server to stop receiving notifications about its changes. @@ -557,7 +557,7 @@ public static Task SubscribeToResourceAsync(this IMcpClient client, Uri uri, Can [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); + => AsClientOrThrow(client).UnsubscribeFromResourceAsync(uri, null, cancellationToken); /// /// Unsubscribes from a resource on the server to stop receiving notifications about its changes. @@ -585,7 +585,7 @@ public static Task UnsubscribeFromResourceAsync(this IMcpClient client, string u [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); + => AsClientOrThrow(client).UnsubscribeFromResourceAsync(uri, null, cancellationToken); /// /// Invokes a tool on the server. @@ -631,7 +631,7 @@ public static ValueTask CallToolAsync( IProgress? progress = null, JsonSerializerOptions? serializerOptions = null, CancellationToken cancellationToken = default) - => AsClientOrThrow(client).CallToolAsync(toolName, arguments, progress, serializerOptions, cancellationToken); + => AsClientOrThrow(client).CallToolAsync(toolName, arguments, progress, new RequestOptions { JsonSerializerOptions = serializerOptions }, cancellationToken); [MethodImpl(MethodImplOptions.AggressiveInlining)] #pragma warning disable CS0618 // Type or member is obsolete diff --git a/src/ModelContextProtocol.Core/McpEndpointExtensions.cs b/src/ModelContextProtocol.Core/McpEndpointExtensions.cs index 1a5b5c1e2..1bd00989a 100644 --- a/src/ModelContextProtocol.Core/McpEndpointExtensions.cs +++ b/src/ModelContextProtocol.Core/McpEndpointExtensions.cs @@ -56,8 +56,8 @@ public static ValueTask SendRequestAsync( /// 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 + /// 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. /// /// @@ -78,11 +78,11 @@ public static Task SendNotificationAsync(this IMcpEndpoint client, string method /// A task that represents the asynchronous send operation. /// /// - /// This method sends a notification with parameters to the connected endpoint. Notifications are one-way + /// 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 + /// The parameters object is serialized to JSON according to the provided serializer options or the default /// options if none are specified. /// /// @@ -126,7 +126,7 @@ public static Task NotifyProgressAsync( ProgressToken progressToken, ProgressNotificationValue progress, CancellationToken cancellationToken = default) - => AsSessionOrThrow(endpoint).NotifyProgressAsync(progressToken, progress, cancellationToken); + => AsSessionOrThrow(endpoint).NotifyProgressAsync(progressToken, progress, null, cancellationToken); [MethodImpl(MethodImplOptions.AggressiveInlining)] #pragma warning disable CS0618 // Type or member is obsolete diff --git a/src/ModelContextProtocol.Core/Server/McpServerExtensions.cs b/src/ModelContextProtocol.Core/Server/McpServerExtensions.cs index b20865576..a3047dcac 100644 --- a/src/ModelContextProtocol.Core/Server/McpServerExtensions.cs +++ b/src/ModelContextProtocol.Core/Server/McpServerExtensions.cs @@ -31,7 +31,7 @@ public static class McpServerExtensions [EditorBrowsable(EditorBrowsableState.Never)] public static ValueTask SampleAsync( this IMcpServer server, CreateMessageRequestParams request, CancellationToken cancellationToken = default) - => AsServerOrThrow(server).SampleAsync(request, cancellationToken); + => AsServerOrThrow(server).SampleAsync(request, options: null, cancellationToken); /// /// Requests to sample an LLM via the client using the provided chat messages and options. @@ -53,7 +53,7 @@ public static ValueTask SampleAsync( public static Task SampleAsync( this IMcpServer server, IEnumerable messages, ChatOptions? options = default, CancellationToken cancellationToken = default) - => AsServerOrThrow(server).SampleAsync(messages, options, cancellationToken); + => AsServerOrThrow(server).SampleAsync(messages, options, requestOptions: null, cancellationToken); /// /// Creates an wrapper that can be used to send sampling requests to the client. @@ -94,7 +94,7 @@ public static ILoggerProvider AsClientLoggerProvider(this IMcpServer server) [EditorBrowsable(EditorBrowsableState.Never)] public static ValueTask RequestRootsAsync( this IMcpServer server, ListRootsRequestParams request, CancellationToken cancellationToken = default) - => AsServerOrThrow(server).RequestRootsAsync(request, cancellationToken); + => AsServerOrThrow(server).RequestRootsAsync(request, options: null, cancellationToken); /// /// Requests additional information from the user via the client, allowing the server to elicit structured data. From 073f110589738c4418fa8093f8ffa332fec7828d Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Thu, 13 Nov 2025 17:24:33 -0800 Subject: [PATCH 6/8] Minor cleanup --- samples/EverythingServer/Tools/SampleLlmTool.cs | 2 +- samples/TestServerWithHosting/Tools/SampleLlmTool.cs | 2 +- src/ModelContextProtocol.Core/README.md | 6 +++--- src/ModelContextProtocol.Core/Server/McpServer.Methods.cs | 2 +- src/ModelContextProtocol.Core/Server/McpServerExtensions.cs | 6 +++--- src/ModelContextProtocol.Core/TokenProgress.cs | 2 +- tests/ModelContextProtocol.TestServer/Program.cs | 2 +- tests/ModelContextProtocol.TestSseServer/Program.cs | 2 +- .../Server/EmptyCollectionTests.cs | 6 +++--- 9 files changed, 15 insertions(+), 15 deletions(-) diff --git a/samples/EverythingServer/Tools/SampleLlmTool.cs b/samples/EverythingServer/Tools/SampleLlmTool.cs index 1a01f2cd0..f7da46899 100644 --- a/samples/EverythingServer/Tools/SampleLlmTool.cs +++ b/samples/EverythingServer/Tools/SampleLlmTool.cs @@ -15,7 +15,7 @@ public static async Task SampleLLM( CancellationToken cancellationToken) { var samplingParams = CreateRequestSamplingParams(prompt ?? string.Empty, "sampleLLM", maxTokens); - var sampleResult = await server.SampleAsync(samplingParams, options: null, cancellationToken); + var sampleResult = await server.SampleAsync(samplingParams, cancellationToken: cancellationToken); return $"LLM sampling result: {(sampleResult.Content as TextContentBlock)?.Text}"; } diff --git a/samples/TestServerWithHosting/Tools/SampleLlmTool.cs b/samples/TestServerWithHosting/Tools/SampleLlmTool.cs index 5e6c610c7..975929675 100644 --- a/samples/TestServerWithHosting/Tools/SampleLlmTool.cs +++ b/samples/TestServerWithHosting/Tools/SampleLlmTool.cs @@ -18,7 +18,7 @@ public static async Task SampleLLM( CancellationToken cancellationToken) { var samplingParams = CreateRequestSamplingParams(prompt ?? string.Empty, "sampleLLM", maxTokens); - var sampleResult = await thisServer.SampleAsync(samplingParams, options: null, cancellationToken); + var sampleResult = await thisServer.SampleAsync(samplingParams, cancellationToken: cancellationToken); return $"LLM sampling result: {(sampleResult.Content as TextContentBlock)?.Text}"; } diff --git a/src/ModelContextProtocol.Core/README.md b/src/ModelContextProtocol.Core/README.md index f6cffaf68..d62ba8f95 100644 --- a/src/ModelContextProtocol.Core/README.md +++ b/src/ModelContextProtocol.Core/README.md @@ -50,7 +50,7 @@ foreach (var tool in await client.ListToolsAsync()) var result = await client.CallToolAsync( "echo", new Dictionary() { ["message"] = "Hello MCP!" }, - cancellationToken:CancellationToken.None); + cancellationToken: CancellationToken.None); // echo always returns one and only one text content object Console.WriteLine(result.Content.First(c => c.Type == "text").Text); @@ -83,13 +83,13 @@ using System.ComponentModel; var serverOptions = new McpServerOptions(); // Add tools directly -serverOptions.Capabilities.Tools = new() +serverOptions.Capabilities.Tools = new() { ListChanged = true, ToolCollection = [ McpServerTool.Create((string message) => $"hello {message}", new() { - Name = "echo", + Name = "echo", Description = "Echoes the message back to the client." }) ] diff --git a/src/ModelContextProtocol.Core/Server/McpServer.Methods.cs b/src/ModelContextProtocol.Core/Server/McpServer.Methods.cs index 8380cac35..4cb296c8a 100644 --- a/src/ModelContextProtocol.Core/Server/McpServer.Methods.cs +++ b/src/ModelContextProtocol.Core/Server/McpServer.Methods.cs @@ -467,7 +467,7 @@ private sealed class SamplingChatClient(McpServer server) : IChatClient /// public Task GetResponseAsync(IEnumerable messages, ChatOptions? options = null, CancellationToken cancellationToken = default) => - _server.SampleAsync(messages, options, requestOptions: null, cancellationToken); + _server.SampleAsync(messages, options: options, cancellationToken: cancellationToken); /// async IAsyncEnumerable IChatClient.GetStreamingResponseAsync( diff --git a/src/ModelContextProtocol.Core/Server/McpServerExtensions.cs b/src/ModelContextProtocol.Core/Server/McpServerExtensions.cs index a3047dcac..241e7c9c9 100644 --- a/src/ModelContextProtocol.Core/Server/McpServerExtensions.cs +++ b/src/ModelContextProtocol.Core/Server/McpServerExtensions.cs @@ -31,7 +31,7 @@ public static class McpServerExtensions [EditorBrowsable(EditorBrowsableState.Never)] public static ValueTask SampleAsync( this IMcpServer server, CreateMessageRequestParams request, CancellationToken cancellationToken = default) - => AsServerOrThrow(server).SampleAsync(request, options: null, cancellationToken); + => AsServerOrThrow(server).SampleAsync(request, cancellationToken: cancellationToken); /// /// Requests to sample an LLM via the client using the provided chat messages and options. @@ -53,7 +53,7 @@ public static ValueTask SampleAsync( public static Task SampleAsync( this IMcpServer server, IEnumerable messages, ChatOptions? options = default, CancellationToken cancellationToken = default) - => AsServerOrThrow(server).SampleAsync(messages, options, requestOptions: null, cancellationToken); + => AsServerOrThrow(server).SampleAsync(messages, options, cancellationToken: cancellationToken); /// /// Creates an wrapper that can be used to send sampling requests to the client. @@ -94,7 +94,7 @@ public static ILoggerProvider AsClientLoggerProvider(this IMcpServer server) [EditorBrowsable(EditorBrowsableState.Never)] public static ValueTask RequestRootsAsync( this IMcpServer server, ListRootsRequestParams request, CancellationToken cancellationToken = default) - => AsServerOrThrow(server).RequestRootsAsync(request, options: null, cancellationToken); + => AsServerOrThrow(server).RequestRootsAsync(request, cancellationToken: cancellationToken); /// /// Requests additional information from the user via the client, allowing the server to elicit structured data. diff --git a/src/ModelContextProtocol.Core/TokenProgress.cs b/src/ModelContextProtocol.Core/TokenProgress.cs index 5bdd06bc5..d41822c23 100644 --- a/src/ModelContextProtocol.Core/TokenProgress.cs +++ b/src/ModelContextProtocol.Core/TokenProgress.cs @@ -11,6 +11,6 @@ internal sealed class TokenProgress(McpSession session, ProgressToken progressTo /// public void Report(ProgressNotificationValue value) { - _ = session.NotifyProgressAsync(progressToken, value, options: null, CancellationToken.None); + _ = session.NotifyProgressAsync(progressToken, value, cancellationToken: CancellationToken.None); } } diff --git a/tests/ModelContextProtocol.TestServer/Program.cs b/tests/ModelContextProtocol.TestServer/Program.cs index a85205c3c..72f575242 100644 --- a/tests/ModelContextProtocol.TestServer/Program.cs +++ b/tests/ModelContextProtocol.TestServer/Program.cs @@ -193,7 +193,7 @@ private static void ConfigureTools(McpServerOptions options, string? cliArg) throw new McpProtocolException("Missing required arguments 'prompt' and 'maxTokens'", McpErrorCode.InvalidParams); } var sampleResult = await request.Server.SampleAsync(CreateRequestSamplingParams(prompt.ToString(), "sampleLLM", Convert.ToInt32(maxTokens.GetRawText())), - options: null, cancellationToken); + cancellationToken: cancellationToken); return new CallToolResult { diff --git a/tests/ModelContextProtocol.TestSseServer/Program.cs b/tests/ModelContextProtocol.TestSseServer/Program.cs index 384b1a7df..6842702ad 100644 --- a/tests/ModelContextProtocol.TestSseServer/Program.cs +++ b/tests/ModelContextProtocol.TestSseServer/Program.cs @@ -187,7 +187,7 @@ static CreateMessageRequestParams CreateRequestSamplingParams(string context, st throw new McpProtocolException("Missing required arguments 'prompt' and 'maxTokens'", McpErrorCode.InvalidParams); } var sampleResult = await request.Server.SampleAsync(CreateRequestSamplingParams(prompt.ToString(), "sampleLLM", Convert.ToInt32(maxTokens.ToString())), - options: null, cancellationToken); + cancellationToken: cancellationToken); return new CallToolResult { diff --git a/tests/ModelContextProtocol.Tests/Server/EmptyCollectionTests.cs b/tests/ModelContextProtocol.Tests/Server/EmptyCollectionTests.cs index a5b68f8c1..2618f0103 100644 --- a/tests/ModelContextProtocol.Tests/Server/EmptyCollectionTests.cs +++ b/tests/ModelContextProtocol.Tests/Server/EmptyCollectionTests.cs @@ -158,8 +158,8 @@ public async Task ListFails() var client = await CreateMcpClientForServer(); - await Assert.ThrowsAsync(async () => await client.ListToolsAsync(options: null, cancellationToken: TestContext.Current.CancellationToken)); - await Assert.ThrowsAsync(async () => await client.ListPromptsAsync(options: null, TestContext.Current.CancellationToken)); - await Assert.ThrowsAsync(async () => await client.ListResourcesAsync(options: null, TestContext.Current.CancellationToken)); + await Assert.ThrowsAsync(async () => await client.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken)); + await Assert.ThrowsAsync(async () => await client.ListPromptsAsync(cancellationToken: TestContext.Current.CancellationToken)); + await Assert.ThrowsAsync(async () => await client.ListResourcesAsync(cancellationToken: TestContext.Current.CancellationToken)); } } From 65b3a9bb544ec740c09a22676f1d0adec51ed51e Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Mon, 17 Nov 2025 09:13:36 -0800 Subject: [PATCH 7/8] Fix obscure bug in constructor --- src/ModelContextProtocol.Core/Client/McpClient.Methods.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ModelContextProtocol.Core/Client/McpClient.Methods.cs b/src/ModelContextProtocol.Core/Client/McpClient.Methods.cs index 8eb7983cb..71dc383d3 100644 --- a/src/ModelContextProtocol.Core/Client/McpClient.Methods.cs +++ b/src/ModelContextProtocol.Core/Client/McpClient.Methods.cs @@ -569,8 +569,9 @@ async ValueTask SendRequestWithProgressAsync( { Name = toolName, Arguments = ToArgumentsDictionary(arguments, serializerOptions), - ProgressToken = progressToken, Meta = meta, + // Must set ProgressToken after Meta + ProgressToken = progressToken, }, McpJsonUtilities.JsonContext.Default.CallToolRequestParams, McpJsonUtilities.JsonContext.Default.CallToolResult, From dc24e3991efd322fcacb0fef716742f19760d9e1 Mon Sep 17 00:00:00 2001 From: Mike Kistler Date: Mon, 17 Nov 2025 14:01:22 -0800 Subject: [PATCH 8/8] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Server/McpServer.Methods.cs | 36 +++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/src/ModelContextProtocol.Core/Server/McpServer.Methods.cs b/src/ModelContextProtocol.Core/Server/McpServer.Methods.cs index 4cb296c8a..58ace5c05 100644 --- a/src/ModelContextProtocol.Core/Server/McpServer.Methods.cs +++ b/src/ModelContextProtocol.Core/Server/McpServer.Methods.cs @@ -62,7 +62,21 @@ public ValueTask SampleAsync( if (options?.Meta is not null) { - request.Meta = options.Meta; + if (request.Meta is not null) + { + // Merge existing request.Meta and options.Meta, with options.Meta taking precedence + var mergedMeta = new Dictionary(request.Meta); + foreach (var kvp in options.Meta) + { + mergedMeta[kvp.Key] = kvp.Value; + } + request.Meta = mergedMeta; + } + else + { + // Only options.Meta is present + request.Meta = new Dictionary(options.Meta); + } } return SendRequestAsync( @@ -213,7 +227,25 @@ public ValueTask RequestRootsAsync( if (options?.Meta is not null) { - request.Meta = options.Meta; + if (request.Meta is null) + { + // No existing meta, just assign + request.Meta = options.Meta; + } + else + { + // Merge existing and options.Meta, options.Meta takes precedence + var merged = new JsonObject(); + foreach (var kvp in request.Meta) + { + merged[kvp.Key] = kvp.Value; + } + foreach (var kvp in options.Meta) + { + merged[kvp.Key] = kvp.Value; + } + request.Meta = merged; + } } return SendRequestAsync(