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; } }