using Microsoft.Extensions.Logging;
using ModelContextProtocol.Protocol;
using ModelContextProtocol.Server;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using System.Text.Json.Nodes;
namespace ModelContextProtocol.Client;
///
/// Represents an instance of a Model Context Protocol (MCP) client session that connects to and communicates with an MCP server.
///
public abstract partial class McpClient : McpSession
{
/// Creates an , connecting it to the specified server.
/// The transport instance used to communicate with the server.
///
/// A client configuration object that specifies client capabilities and protocol version.
/// If , details based on the current process are used.
///
/// A logger factory for creating loggers for clients.
/// The to monitor for cancellation requests. The default is .
/// An that's connected to the specified server.
/// is .
/// An error occurred while connecting to the server over HTTP.
/// The server returned an error response during initialization.
///
///
/// When using an HTTP-based transport (such as ), this method may throw
/// if there is a problem establishing the connection to the MCP server.
///
///
/// If the server requires authentication and credentials are not provided or are invalid, an
/// with an HTTP 401 Unauthorized status code will be thrown.
/// To authenticate with a protected server, configure the
/// property of the transport with appropriate credentials before calling this method.
///
///
public static async Task CreateAsync(
IClientTransport clientTransport,
McpClientOptions? clientOptions = null,
ILoggerFactory? loggerFactory = null,
CancellationToken cancellationToken = default)
{
Throw.IfNull(clientTransport);
var transport = await clientTransport.ConnectAsync(cancellationToken).ConfigureAwait(false);
var endpointName = clientTransport.Name;
var clientSession = new McpClientImpl(transport, endpointName, clientOptions, loggerFactory);
try
{
await clientSession.ConnectAsync(cancellationToken).ConfigureAwait(false);
}
catch (Exception ex) when (ex is not OperationCanceledException and not ClientTransportClosedException)
{
// ConnectAsync already disposed the session (which includes awaiting Completion).
// Check if the transport provided structured completion details indicating
// why the transport closed that aren't already in the original exception chain.
Debug.Assert(clientSession.Completion.IsCompleted, "Completion should already be finished after ConnectAsync's DisposeAsync.");
var completionDetails = await clientSession.Completion.ConfigureAwait(false);
// If the transport closed with a non-graceful error (e.g., server process exited)
// and the completion details carry an exception that's NOT already in the original
// exception chain, throw a ClientTransportClosedException with the structured details so
// callers can programmatically inspect the closure reason (exit code, stderr, etc.).
// When the same exception is already in the chain (e.g., HttpRequestException from
// an HTTP transport), the original exception is more appropriate to re-throw.
if (completionDetails.Exception is { } detailsException &&
!ExceptionChainContains(ex, detailsException))
{
throw new ClientTransportClosedException(completionDetails);
}
throw;
}
return clientSession;
}
///
/// Returns if is the same object as
/// or any exception in its chain.
///
private static bool ExceptionChainContains(Exception exception, Exception target)
{
for (Exception? current = exception; current is not null; current = current.InnerException)
{
if (ReferenceEquals(current, target))
{
return true;
}
}
return false;
}
///
/// Recreates an using an existing transport session without sending a new initialize request.
///
/// The transport instance already configured to connect to the target server.
/// The metadata captured from the original session that should be applied when resuming.
/// Optional client settings that should mirror those used to create the original session.
/// An optional logger factory for diagnostics.
/// The to monitor for cancellation requests. The default is .
/// An bound to the resumed session.
/// , , , or is .
public static async Task ResumeSessionAsync(
IClientTransport clientTransport,
ResumeClientSessionOptions resumeOptions,
McpClientOptions? clientOptions = null,
ILoggerFactory? loggerFactory = null,
CancellationToken cancellationToken = default)
{
Throw.IfNull(clientTransport);
Throw.IfNull(resumeOptions);
Throw.IfNull(resumeOptions.ServerCapabilities);
Throw.IfNull(resumeOptions.ServerInfo);
var transport = await clientTransport.ConnectAsync(cancellationToken).ConfigureAwait(false);
McpClientImpl clientSession = new(transport, clientTransport.Name, clientOptions, loggerFactory);
clientSession.ResumeSession(resumeOptions);
return clientSession;
}
///
/// Sends a ping request to verify server connectivity.
///
/// Optional request options including metadata, serialization settings, and progress tracking.
/// The to monitor for cancellation requests. The default is .
/// A task containing the ping result.
/// The server cannot be reached or returned an error response.
public ValueTask PingAsync(RequestOptions? options = null, CancellationToken cancellationToken = default)
{
return PingAsync(
new PingRequestParams
{
Meta = options?.GetMetaForRequest()
},
cancellationToken);
}
///
/// Sends a ping request to verify server connectivity.
///
/// The request parameters to send in the request.
/// The to monitor for cancellation requests. The default is .
/// A task containing the ping result.
/// is .
/// The server cannot be reached or returned an error response.
public ValueTask PingAsync(
PingRequestParams requestParams,
CancellationToken cancellationToken = default)
{
Throw.IfNull(requestParams);
return SendRequestAsync(
RequestMethods.Ping,
requestParams,
McpJsonUtilities.JsonContext.Default.PingRequestParams,
McpJsonUtilities.JsonContext.Default.PingResult,
cancellationToken: cancellationToken);
}
///
/// Retrieves a list of available tools from the server.
///
/// 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.
/// The request failed or the server returned an error response.
///
///
/// This overload aggregates every page into a single list and does not surface the per-result caching hints
/// ( and ). To read those hints,
/// use the overload, which returns the
/// raw for each page.
///
///
/// The SDK does not perform any internal caching of listing results; every call re-fetches all pages from the server.
/// If you want to cache listing results, do so in your own code using the lower-level
/// overload, which exposes the per-page
/// caching hints and lets you manage pagination so each page can be cached and expired independently.
///
///
public async ValueTask> ListToolsAsync(
RequestOptions? options = null,
CancellationToken cancellationToken = default)
{
ToolCacheClearing?.Invoke();
List? tools = null;
ListToolsRequestParams requestParams = new() { Meta = options?.GetMetaForRequest() };
do
{
var toolResults = await ListToolsAsync(requestParams, cancellationToken).ConfigureAwait(false);
tools ??= new(toolResults.Tools.Count);
foreach (var tool in toolResults.Tools)
{
// Validate x-mcp-header annotations per SEP-2243. The spec requires Streamable HTTP
// clients to exclude tools with invalid annotations and permits other transports
// (e.g., stdio) to ignore the annotations entirely. This client validates on all
// transports so a malformed definition is rejected consistently regardless of transport.
if (!McpHeaderExtractor.ValidateToolSchema(tool, out var rejectionReason))
{
ToolRejected?.Invoke(tool, rejectionReason!);
continue;
}
ToolDiscovered?.Invoke(tool);
tools.Add(new(this, tool, options?.JsonSerializerOptions));
}
requestParams.Cursor = toolResults.NextCursor;
}
while (requestParams.Cursor is not null);
return tools;
}
///
/// Invoked when a tool definition is discovered from a tools/list response.
///
internal Action? ToolDiscovered;
///
/// Invoked when a tool definition is rejected due to invalid x-mcp-header annotations.
///
internal Action? ToolRejected;
///
/// Invoked before enumerating tools to clear any previously cached tool definitions.
///
internal Action? ToolCacheClearing;
///
/// Retrieves a list of available tools from the server.
///
/// The request parameters to send in the request.
/// The to monitor for cancellation requests. The default is .
/// The result of the request as provided by the server.
/// is .
/// The request failed or the server returned an error response.
///
/// The overload retrieves all tools by automatically handling pagination.
/// This overload works with the lower-level and , returning the raw result from the server.
/// Any pagination needs to be managed by the caller.
///
public ValueTask ListToolsAsync(
ListToolsRequestParams requestParams,
CancellationToken cancellationToken = default)
{
Throw.IfNull(requestParams);
return ValidateCacheableResultAsync(RequestMethods.ToolsList, SendRequestAsync(
RequestMethods.ToolsList,
requestParams,
McpJsonUtilities.JsonContext.Default.ListToolsRequestParams,
McpJsonUtilities.JsonContext.Default.ListToolsResult,
cancellationToken: cancellationToken));
}
///
/// Awaits a cacheable result and gives derived clients a chance to emit diagnostics (for example, a
/// SEP-2549 conformance warning) before returning it. Preserves the synchronous argument validation
/// performed by the callers before the request is issued.
///
private async ValueTask ValidateCacheableResultAsync(string method, ValueTask resultTask)
where TResult : ICacheableResult
{
var result = await resultTask.ConfigureAwait(false);
ValidateCacheableResult(method, result);
return result;
}
///
/// Retrieves a list of available prompts from the server.
///
/// 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.
/// The request failed or the server returned an error response.
///
///
/// This overload aggregates every page into a single list and does not surface the per-result caching hints
/// ( and ). To read those hints,
/// use the overload, which returns the
/// raw for each page.
///
///
/// The SDK does not perform any internal caching of listing results; every call re-fetches all pages from the server.
/// If you want to cache listing results, do so in your own code using the lower-level
/// overload, which exposes the per-page
/// caching hints and lets you manage pagination so each page can be cached and expired independently.
///
///
public async ValueTask> ListPromptsAsync(
RequestOptions? options = null,
CancellationToken cancellationToken = default)
{
List? prompts = null;
ListPromptsRequestParams requestParams = new() { Meta = options?.GetMetaForRequest() };
do
{
var promptResults = await ListPromptsAsync(requestParams, cancellationToken).ConfigureAwait(false);
prompts ??= new(promptResults.Prompts.Count);
foreach (var prompt in promptResults.Prompts)
{
prompts.Add(new(this, prompt));
}
requestParams.Cursor = promptResults.NextCursor;
}
while (requestParams.Cursor is not null);
return prompts;
}
///
/// Retrieves a list of available prompts from the server.
///
/// The request parameters to send in the request.
/// The to monitor for cancellation requests. The default is .
/// The result of the request as provided by the server.
/// is .
/// The request failed or the server returned an error response.
///
/// The overload retrieves all prompts by automatically handling pagination.
/// This overload works with the lower-level and , returning the raw result from the server.
/// Any pagination needs to be managed by the caller.
///
public ValueTask ListPromptsAsync(
ListPromptsRequestParams requestParams,
CancellationToken cancellationToken = default)
{
Throw.IfNull(requestParams);
return ValidateCacheableResultAsync(RequestMethods.PromptsList, SendRequestAsync(
RequestMethods.PromptsList,
requestParams,
McpJsonUtilities.JsonContext.Default.ListPromptsRequestParams,
McpJsonUtilities.JsonContext.Default.ListPromptsResult,
cancellationToken: cancellationToken));
}
///
/// Retrieves a specific prompt from the MCP server.
///
/// The name of the prompt to retrieve.
/// Optional arguments for the prompt. The dictionary keys are parameter names, and the values are the argument values.
/// 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.
/// is .
/// is empty or composed entirely of whitespace.
/// The request failed or the server returned an error response.
public ValueTask GetPromptAsync(
string name,
IReadOnlyDictionary? arguments = null,
RequestOptions? options = null,
CancellationToken cancellationToken = default)
{
Throw.IfNullOrWhiteSpace(name);
var serializerOptions = options?.JsonSerializerOptions ?? McpJsonUtilities.DefaultOptions;
serializerOptions.MakeReadOnly();
return GetPromptAsync(
new GetPromptRequestParams
{
Name = name,
Arguments = ToArgumentsDictionary(arguments, serializerOptions),
Meta = options?.GetMetaForRequest(),
},
cancellationToken);
}
///
/// Retrieves a specific prompt from the MCP server.
///
/// The request parameters to send in the request.
/// The to monitor for cancellation requests. The default is .
/// The result of the request as provided by the server.
/// is .
/// The request failed or the server returned an error response.
public ValueTask GetPromptAsync(
GetPromptRequestParams requestParams,
CancellationToken cancellationToken = default)
{
Throw.IfNull(requestParams);
return SendRequestAsync(
RequestMethods.PromptsGet,
requestParams,
McpJsonUtilities.JsonContext.Default.GetPromptRequestParams,
McpJsonUtilities.JsonContext.Default.GetPromptResult,
cancellationToken: cancellationToken);
}
///
/// Retrieves a list of available resource templates from the server.
///
/// 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.
/// The request failed or the server returned an error response.
///
///
/// This overload aggregates every page into a single list and does not surface the per-result caching hints
/// ( and ). To read those hints,
/// use the overload, which returns the
/// raw for each page.
///
///
/// The SDK does not perform any internal caching of listing results; every call re-fetches all pages from the server.
/// If you want to cache listing results, do so in your own code using the lower-level
/// overload, which exposes the per-page
/// caching hints and lets you manage pagination so each page can be cached and expired independently.
///
///
public async ValueTask> ListResourceTemplatesAsync(
RequestOptions? options = null,
CancellationToken cancellationToken = default)
{
List? resourceTemplates = null;
ListResourceTemplatesRequestParams requestParams = new() { Meta = options?.GetMetaForRequest() };
do
{
var templateResults = await ListResourceTemplatesAsync(requestParams, cancellationToken).ConfigureAwait(false);
resourceTemplates ??= new(templateResults.ResourceTemplates.Count);
foreach (var template in templateResults.ResourceTemplates)
{
resourceTemplates.Add(new(this, template));
}
requestParams.Cursor = templateResults.NextCursor;
}
while (requestParams.Cursor is not null);
return resourceTemplates;
}
///
/// Retrieves a list of available resource templates from the server.
///
/// The request parameters to send in the request.
/// The to monitor for cancellation requests. The default is .
/// The result of the request as provided by the server.
/// is .
/// The request failed or the server returned an error response.
///
/// The overload retrieves all resource templates by automatically handling pagination.
/// This overload works with the lower-level and , returning the raw result from the server.
/// Any pagination needs to be managed by the caller.
///
public ValueTask ListResourceTemplatesAsync(
ListResourceTemplatesRequestParams requestParams,
CancellationToken cancellationToken = default)
{
Throw.IfNull(requestParams);
return ValidateCacheableResultAsync(RequestMethods.ResourcesTemplatesList, SendRequestAsync(
RequestMethods.ResourcesTemplatesList,
requestParams,
McpJsonUtilities.JsonContext.Default.ListResourceTemplatesRequestParams,
McpJsonUtilities.JsonContext.Default.ListResourceTemplatesResult,
cancellationToken: cancellationToken));
}
///
/// Retrieves a list of available resources from the server.
///
/// 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.
/// The request failed or the server returned an error response.
///
///
/// This overload aggregates every page into a single list and does not surface the per-result caching hints
/// ( and ). To read those hints,
/// use the overload, which returns the
/// raw for each page.
///
///
/// The SDK does not perform any internal caching of listing results; every call re-fetches all pages from the server.
/// If you want to cache listing results, do so in your own code using the lower-level
/// overload, which exposes the per-page
/// caching hints and lets you manage pagination so each page can be cached and expired independently.
///
///
public async ValueTask> ListResourcesAsync(
RequestOptions? options = null,
CancellationToken cancellationToken = default)
{
List? resources = null;
ListResourcesRequestParams requestParams = new() { Meta = options?.GetMetaForRequest() };
do
{
var resourceResults = await ListResourcesAsync(requestParams, cancellationToken).ConfigureAwait(false);
resources ??= new(resourceResults.Resources.Count);
foreach (var resource in resourceResults.Resources)
{
resources.Add(new(this, resource));
}
requestParams.Cursor = resourceResults.NextCursor;
}
while (requestParams.Cursor is not null);
return resources;
}
///
/// Retrieves a list of available resources from the server.
///
/// The request parameters to send in the request.
/// The to monitor for cancellation requests. The default is .
/// The result of the request as provided by the server.
/// is .
/// The request failed or the server returned an error response.
///
/// The overload retrieves all resources by automatically handling pagination.
/// This overload works with the lower-level and , returning the raw result from the server.
/// Any pagination needs to be managed by the caller.
///
public ValueTask ListResourcesAsync(
ListResourcesRequestParams requestParams,
CancellationToken cancellationToken = default)
{
Throw.IfNull(requestParams);
return ValidateCacheableResultAsync(RequestMethods.ResourcesList, SendRequestAsync(
RequestMethods.ResourcesList,
requestParams,
McpJsonUtilities.JsonContext.Default.ListResourcesRequestParams,
McpJsonUtilities.JsonContext.Default.ListResourcesResult,
cancellationToken: cancellationToken));
}
///
/// Reads a resource from the server.
///
/// The URI of the resource.
/// Optional request options including metadata, serialization settings, and progress tracking.
/// The to monitor for cancellation requests. The default is .
/// is .
/// The request failed or the server returned an error response.
public ValueTask ReadResourceAsync(
Uri uri, RequestOptions? options = null, CancellationToken cancellationToken = default)
{
Throw.IfNull(uri);
return ReadResourceAsync(uri.AbsoluteUri, options, cancellationToken);
}
///
/// Reads a resource from the server.
///
/// The URI of the resource.
/// Optional request options including metadata, serialization settings, and progress tracking.
/// The to monitor for cancellation requests. The default is .
/// is .
/// is empty or composed entirely of whitespace.
/// The request failed or the server returned an error response.
public ValueTask ReadResourceAsync(
string uri, RequestOptions? options = null, CancellationToken cancellationToken = default)
{
Throw.IfNullOrWhiteSpace(uri);
return ReadResourceAsync(new ReadResourceRequestParams
{
Uri = uri,
Meta = options?.GetMetaForRequest(),
}, cancellationToken);
}
///
/// Reads a resource from the server.
///
/// The URI template of the resource.
/// Arguments to use to format .
/// Optional request options including metadata, serialization settings, and progress tracking.
/// The to monitor for cancellation requests. The default is .
/// or is .
/// is empty or composed entirely of whitespace.
/// The request failed or the server returned an error response.
public ValueTask ReadResourceAsync(
string uriTemplate, IReadOnlyDictionary arguments, RequestOptions? options = null, CancellationToken cancellationToken = default)
{
Throw.IfNullOrWhiteSpace(uriTemplate);
Throw.IfNull(arguments);
return ReadResourceAsync(
new ReadResourceRequestParams
{
Uri = UriTemplate.FormatUri(uriTemplate, arguments),
Meta = options?.GetMetaForRequest(),
},
cancellationToken);
}
///
/// Reads a resource from the server.
///
/// The request parameters to send in the request.
/// The to monitor for cancellation requests. The default is .
/// The result of the request.
/// is .
/// The request failed or the server returned an error response.
public ValueTask ReadResourceAsync(
ReadResourceRequestParams requestParams,
CancellationToken cancellationToken = default)
{
Throw.IfNull(requestParams);
return ValidateCacheableResultAsync(RequestMethods.ResourcesRead, SendRequestAsync(
RequestMethods.ResourcesRead,
requestParams,
McpJsonUtilities.JsonContext.Default.ReadResourceRequestParams,
McpJsonUtilities.JsonContext.Default.ReadResourceResult,
cancellationToken: cancellationToken));
}
///
/// Requests completion suggestions for a prompt argument or resource reference.
///
/// The reference object specifying the type and optional URI or name.
/// The name of the argument for which completions are requested.
/// The current value of the argument, used to filter relevant completions.
/// Optional request options including metadata, serialization settings, and progress tracking.
/// The to monitor for cancellation requests. The default is .
/// A containing completion suggestions.
/// or is .
/// is empty or composed entirely of whitespace.
/// The request failed or the server returned an error response.
public ValueTask CompleteAsync(
Reference reference, string argumentName, string argumentValue,
RequestOptions? options = null, CancellationToken cancellationToken = default)
{
Throw.IfNull(reference);
Throw.IfNullOrWhiteSpace(argumentName);
return CompleteAsync(
new CompleteRequestParams
{
Ref = reference,
Argument = new() { Name = argumentName, Value = argumentValue },
Meta = options?.GetMetaForRequest(),
},
cancellationToken);
}
///
/// Requests completion suggestions for a prompt argument or resource reference.
///
/// The request parameters to send in the request.
/// The to monitor for cancellation requests. The default is .
/// The result of the request.
/// is .
/// The request failed or the server returned an error response.
public ValueTask CompleteAsync(
CompleteRequestParams requestParams,
CancellationToken cancellationToken = default)
{
Throw.IfNull(requestParams);
return SendRequestAsync(
RequestMethods.CompletionComplete,
requestParams,
McpJsonUtilities.JsonContext.Default.CompleteRequestParams,
McpJsonUtilities.JsonContext.Default.CompleteResult,
cancellationToken: cancellationToken);
}
///
/// Subscribes to a resource on the server to receive notifications when it changes.
///
/// The URI of the resource to subscribe to.
/// 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.
/// is .
/// The request failed or the server returned an error response.
public Task SubscribeToResourceAsync(Uri uri, RequestOptions? options = null, CancellationToken cancellationToken = default)
{
Throw.IfNull(uri);
return SubscribeToResourceAsync(uri.AbsoluteUri, options, cancellationToken);
}
///
/// Subscribes to a resource on the server to receive notifications when it changes.
///
/// The URI of the resource to which to subscribe.
/// 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.
/// is .
/// is empty or composed entirely of whitespace.
/// The request failed or the server returned an error response.
public Task SubscribeToResourceAsync(string uri, RequestOptions? options = null, CancellationToken cancellationToken = default)
{
Throw.IfNullOrWhiteSpace(uri);
return SubscribeToResourceAsync(
new SubscribeRequestParams
{
Uri = uri,
Meta = options?.GetMetaForRequest(),
},
cancellationToken);
}
///
/// Subscribes to a resource on the server to receive notifications when it changes.
///
/// The request parameters to send in the request.
/// The to monitor for cancellation requests. The default is .
/// The result of the request.
/// is .
/// The request failed or the server returned an error response.
///
///
/// This method subscribes to resource update notifications but does not register a handler.
/// To receive notifications, you must separately call
/// with and filter for the specific resource URI.
/// To unsubscribe, call and dispose the handler registration.
///
///
/// For a simpler API that handles both subscription and notification registration in a single call,
/// use .
///
///
/// The 2026-07-28 protocol revision (SEP-2575) removed resources/subscribe in favor of
/// with resourceSubscriptions. On a session that
/// negotiated that revision or later, this method throws an with
/// .
///
///
public Task SubscribeToResourceAsync(
SubscribeRequestParams requestParams,
CancellationToken cancellationToken = default)
{
Throw.IfNull(requestParams);
return SendRequestAsync(
RequestMethods.ResourcesSubscribe,
requestParams,
McpJsonUtilities.JsonContext.Default.SubscribeRequestParams,
McpJsonUtilities.JsonContext.Default.EmptyResult,
cancellationToken: cancellationToken).AsTask();
}
///
/// Subscribes to a resource on the server and registers a handler for notifications when it changes.
///
/// The URI of the resource to which to subscribe.
/// The handler to invoke when the resource is updated. It receives for the subscribed resource.
/// Optional request options including metadata, serialization settings, and progress tracking.
/// The to monitor for cancellation requests. The default is .
///
/// A task that completes with an that, when disposed, unsubscribes from the resource
/// and removes the notification handler.
///
/// or is .
/// The request failed or the server returned an error response.
///
///
/// This method provides a convenient way to subscribe to resource updates and handle notifications in a single call.
/// The returned manages both the subscription and the notification handler registration.
/// When disposed, it automatically unsubscribes from the resource and removes the handler.
///
///
/// The handler will only be invoked for notifications related to the specified resource URI.
/// Notifications for other resources are filtered out automatically.
///
///
public Task SubscribeToResourceAsync(
Uri uri,
Func handler,
RequestOptions? options = null,
CancellationToken cancellationToken = default)
{
Throw.IfNull(uri);
return SubscribeToResourceAsync(uri.AbsoluteUri, handler, options, cancellationToken);
}
///
/// Subscribes to a resource on the server and registers a handler for notifications when it changes.
///
/// The URI of the resource to which to subscribe.
/// The handler to invoke when the resource is updated. It receives for the subscribed resource.
/// Optional request options including metadata, serialization settings, and progress tracking.
/// The to monitor for cancellation requests. The default is .
///
/// A task that completes with an that, when disposed, unsubscribes from the resource
/// and removes the notification handler.
///
/// or is .
/// is empty or composed entirely of whitespace.
/// The request failed or the server returned an error response.
///
///
/// This method provides a convenient way to subscribe to resource updates and handle notifications in a single call.
/// The returned manages both the subscription and the notification handler registration.
/// When disposed, it automatically unsubscribes from the resource and removes the handler.
///
///
/// The handler will only be invoked for notifications related to the specified resource URI.
/// Notifications for other resources are filtered out automatically.
///
///
public async Task SubscribeToResourceAsync(
string uri,
Func handler,
RequestOptions? options = null,
CancellationToken cancellationToken = default)
{
Throw.IfNullOrWhiteSpace(uri);
Throw.IfNull(handler);
// Register a notification handler that filters for this specific resource
IAsyncDisposable handlerRegistration = RegisterNotificationHandler(
NotificationMethods.ResourceUpdatedNotification,
async (notification, ct) =>
{
if (JsonSerializer.Deserialize(notification.Params, McpJsonUtilities.JsonContext.Default.ResourceUpdatedNotificationParams) is { } resourceUpdate &&
UriTemplate.UriTemplateComparer.Instance.Equals(resourceUpdate.Uri, uri))
{
await handler(resourceUpdate, ct).ConfigureAwait(false);
}
});
try
{
// Subscribe to the resource
await SubscribeToResourceAsync(uri, options, cancellationToken).ConfigureAwait(false);
}
catch
{
// If subscription fails, unregister the handler before propagating the exception
await handlerRegistration.DisposeAsync().ConfigureAwait(false);
throw;
}
// Return a disposable that unsubscribes and removes the handler
return new ResourceSubscription(this, uri, handlerRegistration, options);
}
///
/// Manages a resource subscription, handling both unsubscription and handler disposal.
///
private sealed class ResourceSubscription : IAsyncDisposable
{
private readonly McpClient _client;
private readonly string _uri;
private readonly IAsyncDisposable _handlerRegistration;
private readonly RequestOptions? _options;
private int _disposed;
public ResourceSubscription(McpClient client, string uri, IAsyncDisposable handlerRegistration, RequestOptions? options)
{
_client = client;
_uri = uri;
_handlerRegistration = handlerRegistration;
_options = options;
}
public async ValueTask DisposeAsync()
{
if (Interlocked.Exchange(ref _disposed, 1) != 0)
{
return;
}
try
{
// Unsubscribe from the resource
await _client.UnsubscribeFromResourceAsync(_uri, _options, CancellationToken.None).ConfigureAwait(false);
}
finally
{
// Dispose the notification handler registration
await _handlerRegistration.DisposeAsync().ConfigureAwait(false);
}
}
}
///
/// Unsubscribes from a resource on the server to stop receiving notifications about its changes.
///
/// The URI of the resource to unsubscribe from.
/// 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.
/// is .
/// The request failed or the server returned an error response.
public Task UnsubscribeFromResourceAsync(Uri uri, RequestOptions? options = null, CancellationToken cancellationToken = default)
{
Throw.IfNull(uri);
return UnsubscribeFromResourceAsync(uri.AbsoluteUri, 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 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.
/// is .
/// is empty or composed entirely of whitespace.
/// The request failed or the server returned an error response.
public Task UnsubscribeFromResourceAsync(string uri, RequestOptions? options = null, CancellationToken cancellationToken = default)
{
Throw.IfNullOrWhiteSpace(uri);
return UnsubscribeFromResourceAsync(
new UnsubscribeRequestParams
{
Uri = uri,
Meta = options?.GetMetaForRequest()
},
cancellationToken);
}
///
/// Unsubscribes from a resource on the server to stop receiving notifications about its changes.
///
/// The request parameters to send in the request.
/// The to monitor for cancellation requests. The default is .
/// The result of the request.
/// is .
/// The request failed or the server returned an error response.
///
/// The 2026-07-28 protocol revision (SEP-2575) removed resources/unsubscribe in favor of
/// with resourceSubscriptions. On a session that
/// negotiated that revision or later, this method throws an with
/// .
///
public Task UnsubscribeFromResourceAsync(
UnsubscribeRequestParams requestParams,
CancellationToken cancellationToken = default)
{
Throw.IfNull(requestParams);
return SendRequestAsync(
RequestMethods.ResourcesUnsubscribe,
requestParams,
McpJsonUtilities.JsonContext.Default.UnsubscribeRequestParams,
McpJsonUtilities.JsonContext.Default.EmptyResult,
cancellationToken: cancellationToken).AsTask();
}
///
/// Invokes a tool on the server.
///
/// The name of the tool to invoke.
/// An optional dictionary of arguments to pass to the tool.
/// An optional progress handler for tracking operation progress.
/// Optional request options including metadata, serialization settings, and progress tracking.
/// The to monitor for cancellation requests. The default is .
/// The result of the tool invocation.
/// is .
/// The request failed or the server returned an error response.
public ValueTask CallToolAsync(
string toolName,
IReadOnlyDictionary? arguments = null,
IProgress? progress = null,
RequestOptions? options = null,
CancellationToken cancellationToken = default)
{
Throw.IfNull(toolName);
var serializerOptions = options?.JsonSerializerOptions ?? McpJsonUtilities.DefaultOptions;
serializerOptions.MakeReadOnly();
if (progress is null)
{
return CallToolAsync(
new CallToolRequestParams
{
Name = toolName,
Arguments = ToArgumentsDictionary(arguments, serializerOptions),
Meta = options?.GetMetaForRequest(),
},
cancellationToken);
}
return SendRequestWithProgressAsync(toolName, arguments, progress, options?.GetMetaForRequest(), serializerOptions, cancellationToken);
async ValueTask SendRequestWithProgressAsync(
string toolName,
IReadOnlyDictionary? arguments,
IProgress progress,
JsonObject? meta,
JsonSerializerOptions serializerOptions,
CancellationToken cancellationToken)
{
ProgressToken progressToken = new(Guid.NewGuid().ToString("N"));
await using var _ = RegisterNotificationHandler(NotificationMethods.ProgressNotification,
(notification, cancellationToken) =>
{
if (JsonSerializer.Deserialize(notification.Params, McpJsonUtilities.JsonContext.Default.ProgressNotificationParams) is { } pn &&
pn.ProgressToken == progressToken)
{
progress.Report(pn.Progress);
}
return default;
}).ConfigureAwait(false);
JsonObject metaWithProgress = meta is not null ? (JsonObject)meta.DeepClone() : [];
metaWithProgress["progressToken"] = progressToken.ToString();
return await CallToolAsync(
new()
{
Name = toolName,
Arguments = ToArgumentsDictionary(arguments, serializerOptions),
Meta = metaWithProgress,
},
cancellationToken).ConfigureAwait(false);
}
}
///
/// Invokes a tool on the server.
///
/// The request parameters to send in the request.
/// The to monitor for cancellation requests. The default is .
/// The result of the request.
/// is .
/// The request failed or the server returned an error response.
public ValueTask CallToolAsync(
CallToolRequestParams requestParams,
CancellationToken cancellationToken = default)
{
Throw.IfNull(requestParams);
return SendRequestAsync(
RequestMethods.ToolsCall,
requestParams,
McpJsonUtilities.JsonContext.Default.CallToolRequestParams,
McpJsonUtilities.JsonContext.Default.CallToolResult,
cancellationToken: cancellationToken);
}
///
/// 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 request options including metadata, serialization settings, and progress tracking.
/// The to monitor for cancellation requests. The default is .
/// A task representing the asynchronous operation.
/// The request failed or the server returned an error response.
[Obsolete(Obsoletions.DeprecatedLogging_Message, DiagnosticId = Obsoletions.Deprecated_DiagnosticId, UrlFormat = Obsoletions.Deprecated_Url)]
public Task SetLoggingLevelAsync(LogLevel level, RequestOptions? options = null, CancellationToken cancellationToken = default) =>
SetLoggingLevelAsync(McpServerImpl.ToLoggingLevel(level), options, cancellationToken);
///
/// 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 request options including metadata, serialization settings, and progress tracking.
/// The to monitor for cancellation requests. The default is .
/// A task representing the asynchronous operation.
/// The request failed or the server returned an error response.
[Obsolete(Obsoletions.DeprecatedLogging_Message, DiagnosticId = Obsoletions.Deprecated_DiagnosticId, UrlFormat = Obsoletions.Deprecated_Url)]
public Task SetLoggingLevelAsync(LoggingLevel level, RequestOptions? options = null, CancellationToken cancellationToken = default)
{
return SetLoggingLevelAsync(
new SetLevelRequestParams
{
Level = level,
Meta = options?.GetMetaForRequest()
},
cancellationToken);
}
///
/// Sets the logging level for the server to control which log messages are sent to the client.
///
/// The request parameters to send in the request.
/// The to monitor for cancellation requests. The default is .
/// The result of the request.
/// is .
/// The request failed or the server returned an error response.
[Obsolete(Obsoletions.DeprecatedLogging_Message, DiagnosticId = Obsoletions.Deprecated_DiagnosticId, UrlFormat = Obsoletions.Deprecated_Url)]
public Task SetLoggingLevelAsync(
SetLevelRequestParams requestParams,
CancellationToken cancellationToken = default)
{
Throw.IfNull(requestParams);
return SendRequestAsync(
RequestMethods.LoggingSetLevel,
requestParams,
McpJsonUtilities.JsonContext.Default.SetLevelRequestParams,
McpJsonUtilities.JsonContext.Default.EmptyResult,
cancellationToken: cancellationToken).AsTask();
}
private static Dictionary? ToArgumentsDictionary(
IReadOnlyDictionary? arguments, JsonSerializerOptions options)
{
var typeInfo = options.GetTypeInfo