using Microsoft.Extensions.AI;
using Microsoft.Extensions.Logging;
using ModelContextProtocol.Protocol;
using ModelContextProtocol.Server;
using System.Runtime.CompilerServices;
using System.Text.Json;
namespace ModelContextProtocol.Client;
///
/// Represents an instance of a Model Context Protocol (MCP) client session that connects to and communicates with an MCP server.
///
#pragma warning disable CS0618 // Type or member is obsolete
public abstract partial class McpClient : McpSession, IMcpClient
#pragma warning restore CS0618 // Type or member is obsolete
{
/// Creates an , connecting it to the specified server.
/// The transport instance used to communicate with the server.
///
/// A client configuration object which specifies client capabilities and protocol version.
/// If , details based on the current process will be employed.
///
/// A logger factory for creating loggers for clients.
/// The to monitor for cancellation requests. The default is .
/// An that's connected to the specified server.
/// is .
/// is .
public static async Task CreateAsync(
IClientTransport clientTransport,
McpClientOptions? clientOptions = null,
ILoggerFactory? loggerFactory = null,
CancellationToken cancellationToken = default)
{
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
{
await clientSession.DisposeAsync().ConfigureAwait(false);
throw;
}
return clientSession;
}
///
/// Sends a ping request to verify server connectivity.
///
/// 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)
{
var opts = McpJsonUtilities.DefaultOptions;
opts.MakeReadOnly();
return SendRequestAsync(
RequestMethods.Ping,
parameters: null,
serializerOptions: opts,
cancellationToken: cancellationToken).AsTask();
}
///
/// Retrieves a list of available tools from the server.
///
/// The serializer options governing tool parameter serialization. If null, the default options will be used.
/// The to monitor for cancellation requests. The default is .
/// A list of all available tools as instances.
public async ValueTask> ListToolsAsync(
JsonSerializerOptions? serializerOptions = null,
CancellationToken cancellationToken = default)
{
serializerOptions ??= McpJsonUtilities.DefaultOptions;
serializerOptions.MakeReadOnly();
List? tools = null;
string? cursor = null;
do
{
var toolResults = await SendRequestAsync(
RequestMethods.ToolsList,
new() { Cursor = cursor },
McpJsonUtilities.JsonContext.Default.ListToolsRequestParams,
McpJsonUtilities.JsonContext.Default.ListToolsResult,
cancellationToken: cancellationToken).ConfigureAwait(false);
tools ??= new List(toolResults.Tools.Count);
foreach (var tool in toolResults.Tools)
{
tools.Add(new McpClientTool(this, tool, serializerOptions));
}
cursor = toolResults.NextCursor;
}
while (cursor is not null);
return tools;
}
///
/// Creates an enumerable for asynchronously enumerating all available tools from the server.
///
/// The serializer options governing tool parameter serialization. If null, the default options will be used.
/// The to monitor for cancellation requests. The default is .
/// An asynchronous sequence of all available tools as instances.
public async IAsyncEnumerable EnumerateToolsAsync(
JsonSerializerOptions? serializerOptions = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
serializerOptions ??= McpJsonUtilities.DefaultOptions;
serializerOptions.MakeReadOnly();
string? cursor = null;
do
{
var toolResults = await SendRequestAsync(
RequestMethods.ToolsList,
new() { Cursor = cursor },
McpJsonUtilities.JsonContext.Default.ListToolsRequestParams,
McpJsonUtilities.JsonContext.Default.ListToolsResult,
cancellationToken: cancellationToken).ConfigureAwait(false);
foreach (var tool in toolResults.Tools)
{
yield return new McpClientTool(this, tool, serializerOptions);
}
cursor = toolResults.NextCursor;
}
while (cursor is not null);
}
///
/// Retrieves a list of available prompts from the server.
///
/// The to monitor for cancellation requests. The default is .
/// A list of all available prompts as instances.
public async ValueTask> ListPromptsAsync(
CancellationToken cancellationToken = default)
{
List? prompts = null;
string? cursor = null;
do
{
var promptResults = await SendRequestAsync(
RequestMethods.PromptsList,
new() { Cursor = cursor },
McpJsonUtilities.JsonContext.Default.ListPromptsRequestParams,
McpJsonUtilities.JsonContext.Default.ListPromptsResult,
cancellationToken: cancellationToken).ConfigureAwait(false);
prompts ??= new List(promptResults.Prompts.Count);
foreach (var prompt in promptResults.Prompts)
{
prompts.Add(new McpClientPrompt(this, prompt));
}
cursor = promptResults.NextCursor;
}
while (cursor is not null);
return prompts;
}
///
/// Creates an enumerable for asynchronously enumerating all available prompts from the server.
///
/// The to monitor for cancellation requests. The default is .
/// An asynchronous sequence of all available prompts as instances.
public async IAsyncEnumerable EnumeratePromptsAsync(
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
string? cursor = null;
do
{
var promptResults = await SendRequestAsync(
RequestMethods.PromptsList,
new() { Cursor = cursor },
McpJsonUtilities.JsonContext.Default.ListPromptsRequestParams,
McpJsonUtilities.JsonContext.Default.ListPromptsResult,
cancellationToken: cancellationToken).ConfigureAwait(false);
foreach (var prompt in promptResults.Prompts)
{
yield return new(this, prompt);
}
cursor = promptResults.NextCursor;
}
while (cursor is not null);
}
///
/// Retrieves a specific prompt from the MCP server.
///
/// The name of the prompt to retrieve.
/// Optional arguments for the prompt. Keys are parameter names, and values are the argument values.
/// The serialization options governing argument serialization.
/// The to monitor for cancellation requests. The default is .
/// A task containing the prompt's result with content and messages.
public ValueTask GetPromptAsync(
string name,
IReadOnlyDictionary? arguments = null,
JsonSerializerOptions? serializerOptions = null,
CancellationToken cancellationToken = default)
{
Throw.IfNullOrWhiteSpace(name);
serializerOptions ??= McpJsonUtilities.DefaultOptions;
serializerOptions.MakeReadOnly();
return SendRequestAsync(
RequestMethods.PromptsGet,
new() { Name = name, Arguments = ToArgumentsDictionary(arguments, serializerOptions) },
McpJsonUtilities.JsonContext.Default.GetPromptRequestParams,
McpJsonUtilities.JsonContext.Default.GetPromptResult,
cancellationToken: cancellationToken);
}
///
/// Retrieves a list of available resource templates from the server.
///
/// The to monitor for cancellation requests. The default is .
/// A list of all available resource templates as instances.
public async ValueTask> ListResourceTemplatesAsync(
CancellationToken cancellationToken = default)
{
List? resourceTemplates = null;
string? cursor = null;
do
{
var templateResults = await SendRequestAsync(
RequestMethods.ResourcesTemplatesList,
new() { Cursor = cursor },
McpJsonUtilities.JsonContext.Default.ListResourceTemplatesRequestParams,
McpJsonUtilities.JsonContext.Default.ListResourceTemplatesResult,
cancellationToken: cancellationToken).ConfigureAwait(false);
resourceTemplates ??= new List(templateResults.ResourceTemplates.Count);
foreach (var template in templateResults.ResourceTemplates)
{
resourceTemplates.Add(new McpClientResourceTemplate(this, template));
}
cursor = templateResults.NextCursor;
}
while (cursor is not null);
return resourceTemplates;
}
///
/// Creates an enumerable for asynchronously enumerating all available resource templates from the server.
///
/// The to monitor for cancellation requests. The default is .
/// An asynchronous sequence of all available resource templates as instances.
public async IAsyncEnumerable EnumerateResourceTemplatesAsync(
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
string? cursor = null;
do
{
var templateResults = await SendRequestAsync(
RequestMethods.ResourcesTemplatesList,
new() { Cursor = cursor },
McpJsonUtilities.JsonContext.Default.ListResourceTemplatesRequestParams,
McpJsonUtilities.JsonContext.Default.ListResourceTemplatesResult,
cancellationToken: cancellationToken).ConfigureAwait(false);
foreach (var templateResult in templateResults.ResourceTemplates)
{
yield return new McpClientResourceTemplate(this, templateResult);
}
cursor = templateResults.NextCursor;
}
while (cursor is not null);
}
///
/// Retrieves a list of available resources from the server.
///
/// The to monitor for cancellation requests. The default is .
/// A list of all available resources as instances.
public async ValueTask> ListResourcesAsync(
CancellationToken cancellationToken = default)
{
List? resources = null;
string? cursor = null;
do
{
var resourceResults = await SendRequestAsync(
RequestMethods.ResourcesList,
new() { Cursor = cursor },
McpJsonUtilities.JsonContext.Default.ListResourcesRequestParams,
McpJsonUtilities.JsonContext.Default.ListResourcesResult,
cancellationToken: cancellationToken).ConfigureAwait(false);
resources ??= new List(resourceResults.Resources.Count);
foreach (var resource in resourceResults.Resources)
{
resources.Add(new McpClientResource(this, resource));
}
cursor = resourceResults.NextCursor;
}
while (cursor is not null);
return resources;
}
///
/// Creates an enumerable for asynchronously enumerating all available resources from the server.
///
/// The to monitor for cancellation requests. The default is .
/// An asynchronous sequence of all available resources as instances.
public async IAsyncEnumerable EnumerateResourcesAsync(
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
string? cursor = null;
do
{
var resourceResults = await SendRequestAsync(
RequestMethods.ResourcesList,
new() { Cursor = cursor },
McpJsonUtilities.JsonContext.Default.ListResourcesRequestParams,
McpJsonUtilities.JsonContext.Default.ListResourcesResult,
cancellationToken: cancellationToken).ConfigureAwait(false);
foreach (var resource in resourceResults.Resources)
{
yield return new McpClientResource(this, resource);
}
cursor = resourceResults.NextCursor;
}
while (cursor is not null);
}
///
/// Reads a resource from the server.
///
/// The uri of the resource.
/// The to monitor for cancellation requests. The default is .
public ValueTask ReadResourceAsync(
string uri, CancellationToken cancellationToken = default)
{
Throw.IfNullOrWhiteSpace(uri);
return SendRequestAsync(
RequestMethods.ResourcesRead,
new() { Uri = uri },
McpJsonUtilities.JsonContext.Default.ReadResourceRequestParams,
McpJsonUtilities.JsonContext.Default.ReadResourceResult,
cancellationToken: cancellationToken);
}
///
/// Reads a resource from the server.
///
/// The uri of the resource.
/// The to monitor for cancellation requests. The default is .
public ValueTask ReadResourceAsync(
Uri uri, CancellationToken cancellationToken = default)
{
Throw.IfNull(uri);
return ReadResourceAsync(uri.ToString(), cancellationToken);
}
///
/// Reads a resource from the server.
///
/// The uri template of the resource.
/// Arguments to use to format .
/// The to monitor for cancellation requests. The default is .
public ValueTask ReadResourceAsync(
string uriTemplate, IReadOnlyDictionary arguments, CancellationToken cancellationToken = default)
{
Throw.IfNullOrWhiteSpace(uriTemplate);
Throw.IfNull(arguments);
return SendRequestAsync(
RequestMethods.ResourcesRead,
new() { Uri = UriTemplate.FormatUri(uriTemplate, arguments) },
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.
/// The to monitor for cancellation requests. The default is .
/// A containing completion suggestions.
public ValueTask CompleteAsync(Reference reference, string argumentName, string argumentValue, CancellationToken cancellationToken = default)
{
Throw.IfNull(reference);
Throw.IfNullOrWhiteSpace(argumentName);
return SendRequestAsync(
RequestMethods.CompletionComplete,
new()
{
Ref = reference,
Argument = new Argument { Name = argumentName, Value = argumentValue }
},
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 which to subscribe.
/// The to monitor for cancellation requests. The default is .
/// A task that represents the asynchronous operation.
public Task SubscribeToResourceAsync(string uri, CancellationToken cancellationToken = default)
{
Throw.IfNullOrWhiteSpace(uri);
return SendRequestAsync(
RequestMethods.ResourcesSubscribe,
new() { Uri = uri },
McpJsonUtilities.JsonContext.Default.SubscribeRequestParams,
McpJsonUtilities.JsonContext.Default.EmptyResult,
cancellationToken: cancellationToken).AsTask();
}
///
/// Subscribes to a resource on the server to receive notifications when it changes.
///
/// The URI of the resource to which to subscribe.
/// The to monitor for cancellation requests. The default is .
/// A task that represents the asynchronous operation.
public Task SubscribeToResourceAsync(Uri uri, CancellationToken cancellationToken = default)
{
Throw.IfNull(uri);
return SubscribeToResourceAsync(uri.ToString(), cancellationToken);
}
///
/// Unsubscribes from a resource on the server to stop receiving notifications about its changes.
///
/// The URI of the resource to unsubscribe from.
/// The to monitor for cancellation requests. The default is .
/// A task that represents the asynchronous operation.
public Task UnsubscribeFromResourceAsync(string uri, CancellationToken cancellationToken = default)
{
Throw.IfNullOrWhiteSpace(uri);
return SendRequestAsync(
RequestMethods.ResourcesUnsubscribe,
new() { Uri = uri },
McpJsonUtilities.JsonContext.Default.UnsubscribeRequestParams,
McpJsonUtilities.JsonContext.Default.EmptyResult,
cancellationToken: cancellationToken).AsTask();
}
///
/// Unsubscribes from a resource on the server to stop receiving notifications about its changes.
///
/// The URI of the resource to unsubscribe from.
/// The to monitor for cancellation requests. The default is .
/// A task that represents the asynchronous operation.
public Task UnsubscribeFromResourceAsync(Uri uri, CancellationToken cancellationToken = default)
{
Throw.IfNull(uri);
return UnsubscribeFromResourceAsync(uri.ToString(), cancellationToken);
}
///
/// Invokes a tool on the server.
///
/// 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.
/// JSON serializer options.
/// A cancellation token.
/// The from the tool execution.
public ValueTask CallToolAsync(
string toolName,
IReadOnlyDictionary? arguments = null,
IProgress? progress = null,
JsonSerializerOptions? serializerOptions = null,
CancellationToken cancellationToken = default)
{
Throw.IfNull(toolName);
serializerOptions ??= McpJsonUtilities.DefaultOptions;
serializerOptions.MakeReadOnly();
if (progress is not null)
{
return SendRequestWithProgressAsync(toolName, arguments, progress, serializerOptions, cancellationToken);
}
return SendRequestAsync(
RequestMethods.ToolsCall,
new()
{
Name = toolName,
Arguments = ToArgumentsDictionary(arguments, serializerOptions),
},
McpJsonUtilities.JsonContext.Default.CallToolRequestParams,
McpJsonUtilities.JsonContext.Default.CallToolResult,
cancellationToken: cancellationToken);
async ValueTask SendRequestWithProgressAsync(
string toolName,
IReadOnlyDictionary? arguments,
IProgress progress,
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);
return await SendRequestAsync(
RequestMethods.ToolsCall,
new()
{
Name = toolName,
Arguments = ToArgumentsDictionary(arguments, serializerOptions),
ProgressToken = progressToken,
},
McpJsonUtilities.JsonContext.Default.CallToolRequestParams,
McpJsonUtilities.JsonContext.Default.CallToolResult,
cancellationToken: cancellationToken).ConfigureAwait(false);
}
}
///
/// 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.
/// The to monitor for cancellation requests. The default is .
/// A task representing the asynchronous operation.
public Task SetLoggingLevel(LoggingLevel level, CancellationToken cancellationToken = default)
{
return SendRequestAsync(
RequestMethods.LoggingSetLevel,
new() { Level = level },
McpJsonUtilities.JsonContext.Default.SetLevelRequestParams,
McpJsonUtilities.JsonContext.Default.EmptyResult,
cancellationToken: cancellationToken).AsTask();
}
///
/// 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.
/// 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);
/// Convers a dictionary with values to a dictionary with values.
private static Dictionary? ToArgumentsDictionary(
IReadOnlyDictionary? arguments, JsonSerializerOptions options)
{
var typeInfo = options.GetTypeInfo();
Dictionary? result = null;
if (arguments is not null)
{
result = new(arguments.Count);
foreach (var kvp in arguments)
{
result.Add(kvp.Key, kvp.Value is JsonElement je ? je : JsonSerializer.SerializeToElement(kvp.Value, typeInfo));
}
}
return result;
}
}