Overhaul McpClientFactory/McpServerFactory (#87)

Handlers are now specified before connecting the client/server. Otherwise, race conditions exist. Handlers move into being a part of the McpClientOptions/McpServerOptions, so that they're provided to the factories. Where relevant, the handlers are specified as part of the capability descriptors, so that they go hand in hand.

The factories are no longer stateful. Instead of allocating a factory and then calling a create method on it, you just call a static factory method.
This commit is contained in:
Stephen Toub 2025-03-16 12:08:52 -04:00 • committed by GitHub
commit 08b0a04dab
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -4,8 +4,10 @@ using McpDotNet.Client;
using McpDotNet.Logging;
using McpDotNet.Protocol.Messages;
using McpDotNet.Protocol.Transport;
using McpDotNet.Utils;
using McpDotNet.Utils.Json;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
namespace McpDotNet.Shared;
@ -24,7 +26,7 @@ internal abstract class McpJsonRpcEndpoint : IAsyncDisposable
private readonly Dictionary<string, Func<JsonRpcRequest, Task<object?>>> _requestHandlers = [];
private int _nextRequestId;
private readonly JsonSerializerOptions _jsonOptions;
private readonly ILogger<McpClient> _logger;
private readonly ILogger _logger;
private bool _isDisposed;
/// <summary>
@ -32,19 +34,18 @@ internal abstract class McpJsonRpcEndpoint : IAsyncDisposable
/// </summary>
/// <param name="transport">An MCP transport implementation.</param>
/// <param name="loggerFactory">The logger factory.</param>
protected McpJsonRpcEndpoint(ITransport transport, ILoggerFactory loggerFactory)
protected McpJsonRpcEndpoint(ITransport transport, ILoggerFactory? loggerFactory = null)
{
if (transport is null)
{
throw new ArgumentNullException(nameof(transport));
}
Throw.IfNull(transport);
loggerFactory ??= NullLoggerFactory.Instance;
_transport = transport;
_pendingRequests = new();
_notificationHandlers = new();
_nextRequestId = 1;
_jsonOptions = JsonSerializerOptionsExtensions.DefaultOptions;
_logger = loggerFactory.CreateLogger<McpClient>();
_logger = (ILogger?)loggerFactory?.CreateLogger<McpClient>() ?? NullLogger.Instance;
}
/// <summary>
@ -282,10 +283,7 @@ internal abstract class McpJsonRpcEndpoint : IAsyncDisposable
public Task SendMessageAsync(IJsonRpcMessage message, CancellationToken cancellationToken = default)
{
if (message is null)
{
throw new ArgumentNullException(nameof(message));
}
Throw.IfNull(message);
if (!_transport.IsConnected)
{
@ -333,15 +331,8 @@ internal abstract class McpJsonRpcEndpoint : IAsyncDisposable
/// <param name="handler">Handler to be called when a request with specified method identifier is received</param>
protected void SetRequestHandler<TRequest, TResponse>(string method, Func<TRequest?, Task<TResponse>> handler)
{
if (method is null)
{
throw new ArgumentNullException(nameof(method));
}
if (handler is null)
{
throw new ArgumentNullException(nameof(handler));
}
Throw.IfNull(method);
Throw.IfNull(handler);
_requestHandlers[method] = async (request) =>
{