Clean up and add more comments (#2)

This commit is contained in:
Peder Holdgaard Pedersen 2025-01-02 19:02:24 +01:00 • committed by GitHub
commit 9a244f5770
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 349 additions and 653 deletions

View file

@ -5,6 +5,8 @@ using McpDotNet.Protocol.Messages;
/// <summary>
/// Represents an instance of the MCP client connecting to a specific server.
/// Use PingAsync, ListToolsAsync and CallToolAsync instead of SendRequestAsync for common operations.
/// SendRequestAsync is provided for custom request and those not yet covered explicitly by the client.
/// </summary>
public interface IMcpClient : IAsyncDisposable
{
@ -14,44 +16,58 @@ public interface IMcpClient : IAsyncDisposable
bool IsInitialized { get; }
/// <summary>
/// Gets the capabilities that the server supports.
/// Gets the capabilities supported by the server.
/// </summary>
ServerCapabilities? ServerCapabilities { get; }
/// <summary>
/// Gets the version information of the server.
/// Gets the version and implementation information of the server.
/// </summary>
Implementation? ServerInfo { get; }
/// <summary>
/// Connects to the server.
/// Establishes a connection to the server.
/// </summary>
/// <param name="cancellationToken">A token to cancel the operation.</param>
/// <returns>A task representing the asynchronous operation.</returns>
Task ConnectAsync(CancellationToken cancellationToken = default);
/// <summary>
/// A simple ping to the server to check if it is alive. Task completes when the ping is successful.
/// Sends a ping request to verify server connectivity.
/// </summary>
/// <param name="cancellationToken">A token to cancel the operation.</param>
/// <returns>A task that completes when the ping is successful.</returns>
Task PingAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Queries the server for a list of tools that are available (if any)
/// Retrieves a list of available tools from the server.
/// </summary>
/// <param name="cancellationToken">A token to cancel the operation.</param>
/// <returns>A task containing the server's response with tool information.</returns>
Task<ListToolsResponse> ListToolsAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Calls a tool on the server.
/// Invokes a tool on the server with optional arguments.
/// </summary>
/// <param name="toolName">The name of the tool as given when listing tool definitions.</param>
/// <param name="arguments">Arguments for the tool (if any)</param>
/// <param name="toolName">The name of the tool to call.</param>
/// <param name="arguments">Optional arguments for the tool.</param>
/// <param name="cancellationToken">A token to cancel the operation.</param>
/// <returns>A task containing the tool's response.</returns>
Task<CallToolResponse> CallToolAsync(string toolName, Dictionary<string, object>? arguments = null, CancellationToken cancellationToken = default);
/// <summary>
/// A generic method to send a request to the server and get a response.
/// Sends a generic JSON-RPC request to the server.
/// </summary>
/// <typeparam name="T">The expected response type.</typeparam>
/// <param name="request">The JSON-RPC request to send.</param>
/// <param name="cancellationToken">A token to cancel the operation.</param>
/// <returns>A task containing the server's response.</returns>
Task<T> SendRequestAsync<T>(JsonRpcRequest request, CancellationToken cancellationToken) where T : class;
/// <summary>
/// Registers a handler for a specific notification method.
/// Registers a handler for server notifications of a specific method.
/// </summary>
/// <param name="method">The notification method to handle.</param>
/// <param name="handler">The async handler function to process notifications.</param>
void OnNotification(string method, Func<JsonRpcNotification, Task> handler);
}
}

View file

@ -1,5 +1,4 @@
// Client/McpClientOptions.cs
namespace McpDotNet.Client;
namespace McpDotNet.Client;
using McpDotNet.Protocol.Types;
using McpDotNet.Protocol.Transport;
@ -9,10 +8,8 @@ using System.Diagnostics;
using System.Text.Json;
using McpDotNet.Utils.Json;
/// <summary>
/// Main MCP client implementation that handles protocol lifecycle and message routing.
/// </summary>
public class McpClient : IMcpClient
/// </inheritdoc>
internal class McpClient : IMcpClient
{
private readonly IMcpTransport _transport;
private readonly McpClientOptions _options;
@ -24,21 +21,20 @@ public class McpClient : IMcpClient
private CancellationTokenSource? _cts;
private readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions().ConfigureForMcp();
/// <summary>
/// Gets whether the client is connected and initialized.
/// </summary>
/// </inheritdoc>
public bool IsInitialized => _isInitialized;
/// <summary>
/// Gets the server's capabilities after initialization.
/// </summary>
/// </inheritdoc>
public ServerCapabilities? ServerCapabilities { get; private set; }
/// <summary>
/// Gets information about the server implementation after initialization.
/// </summary>
/// </inheritdoc>
public Implementation? ServerInfo { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="McpClient"/> class.
/// </summary>
/// <param name="transport">An MCP transport implementation.</param>
/// <param name="options">Options for the client, defining protocol version and capabilities.</param>
public McpClient(IMcpTransport transport, McpClientOptions options)
{
_transport = transport;
@ -48,9 +44,7 @@ public class McpClient : IMcpClient
_nextRequestId = 1;
}
/// <summary>
/// Connects to the server and performs the initialization sequence.
/// </summary>
/// </inheritdoc>
public async Task ConnectAsync(CancellationToken cancellationToken = default)
{
if (_isInitialized)
@ -128,6 +122,7 @@ public class McpClient : IMcpClient
}
}
/// </inheritdoc>
public async Task PingAsync(CancellationToken cancellationToken)
{
await SendRequestAsync<dynamic>(
@ -139,6 +134,7 @@ public class McpClient : IMcpClient
).ConfigureAwait(false);
}
/// </inheritdoc>
public async Task<ListToolsResponse> ListToolsAsync(CancellationToken cancellationToken)
{
return await SendRequestAsync<ListToolsResponse>(
@ -151,6 +147,7 @@ public class McpClient : IMcpClient
).ConfigureAwait(false);
}
/// </inheritdoc>
public async Task<CallToolResponse> CallToolAsync(string toolName, Dictionary<string, object>? arguments = null,
CancellationToken cancellationToken = default)
{
@ -218,6 +215,7 @@ public class McpClient : IMcpClient
}
}
/// </inheritdoc>
public async Task<T> SendRequestAsync<T>(JsonRpcRequest request, CancellationToken cancellationToken) where T : class
{
if (!_transport.IsConnected)
@ -275,9 +273,7 @@ public class McpClient : IMcpClient
await _transport.SendMessageAsync(notification, cancellationToken).ConfigureAwait(false);
}
/// <summary>
/// Registers a handler for notifications of a specific method.
/// </summary>
/// </inheritdoc>
public void OnNotification(string method, Func<JsonRpcNotification,Task> handler)
{
var handlers = _notificationHandlers.GetOrAdd(method, _ => new());

View file

@ -1,5 +1,4 @@
// Client/McpClientOptions.cs
namespace McpDotNet.Client;
namespace McpDotNet.Client;
/// <summary>
/// Represents errors that occur in the MCP client.
@ -11,15 +10,29 @@ public class McpClientException : Exception
/// </summary>
public int? ErrorCode { get; }
/// <summary>
/// Initializes a new instance of the <see cref="McpClientException"/> class with a specified error message.
/// </summary>
/// <param name="message">The message that describes the error.</param>
public McpClientException(string message) : base(message)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="McpClientException"/> class with a specified error message and error code.
/// </summary>
/// <param name="message">The message that describes the error.</param>
/// <param name="errorCode">The error code associated with the JSON-RPC error response.</param>
public McpClientException(string message, int errorCode) : base(message)
{
ErrorCode = errorCode;
}
/// <summary>
/// Initializes a new instance of the <see cref="McpClientException"/> class with a specified error message and a reference to the inner exception that is the cause of this exception.
/// </summary>
/// <param name="message">The message that describes the error.</param>
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference if no inner exception is specified.</param>
public McpClientException(string message, Exception innerException) : base(message, innerException)
{
}

View file

@ -1,11 +1,13 @@
// Client/McpClientFactory.cs
namespace McpDotNet.Client;
namespace McpDotNet.Client;
using McpDotNet.Configuration;
using McpDotNet.Protocol.Transport;
/// <summary>
/// Factory for creating MCP clients based on configuration.
/// Factory for creating MCP clients based on configuration. It caches clients for reuse, so it is safe to call GetClientAsync multiple times.
/// Call GetClientAsync to get a client for a specific server (by ID), which will create a new client and connect if it doesn't already exist.
/// All server configurations must be passed in the constructor.
/// Capabilities (as defined in client options) are shared across all clients, as the client host can always decide not to use certain capabilities.
/// </summary>
public class McpClientFactory
{
@ -15,6 +17,15 @@ public class McpClientFactory
private readonly Func<McpServerConfig, IMcpTransport> _transportFactoryMethod;
private readonly Func<IMcpTransport, McpClientOptions, IMcpClient> _clientFactoryMethod;
/// <summary>
/// Initializes a new instance of the <see cref="McpClientFactory"/> class.
/// It is not necessary to pass factory methods for creating transports and clients, as default implementations are provided.
/// Custom factory methods can be provided for mocking or to use custom transport or client implementations.
/// </summary>
/// <param name="serverConfigs">Configuration objects for each server the factory should support.</param>
/// <param name="clientOptions">A configuration object which specifies client capabilities and protocol version.</param>
/// <param name="transportFactoryMethod">An optional factory method which returns transport implementations based on a server configuration.</param>
/// <param name="clientFactoryMethod">An optional factory method which creates a client based on client options and transport implementation. </param>
public McpClientFactory(
IEnumerable<McpServerConfig> serverConfigs,
McpClientOptions clientOptions,
@ -28,8 +39,12 @@ public class McpClientFactory
}
/// <summary>
/// Gets or creates a client for the specified server.
/// Gets or creates a client for the specified server. The first time a server is requested, a new client is created and connected.
/// Note that this will often spawn the server process during connection, so in some cases you want to call this method only when needed.
/// In other cases, you may want to call it ahead of time to ensure the server is ready when needed, as it may take some time to start up.
/// </summary>
/// <param name="serverId">The ID of the server to connect to. It must have been passed in the serverConfigs when constructing the factory.</param>
/// <param name="cancellationToken">A token to cancel the operation.</param>
public async Task<IMcpClient> GetClientAsync(string serverId, CancellationToken cancellationToken = default)
{
if (!_serverConfigs.TryGetValue(serverId, out var config))

View file

@ -1,10 +1,11 @@
// Client/McpClientOptions.cs
using McpDotNet.Protocol.Types;
namespace McpDotNet.Client;
using global::McpDotNet.Protocol.Types;
/// <summary>
/// Configuration options for the MCP client.
/// Configuration options for the MCP client. This is passed to servers during the initialization sequence, letting them know about the client's capabilities and
/// protocol version.
/// <see href="https://spec.modelcontextprotocol.io/specification/basic/lifecycle/">See the protocol specification for details on capability negotiation</see>
/// </summary>
public record McpClientOptions
{