Enable .NET SDK code analyzers (#41)

This commit is contained in:
Stephen Toub 2025-03-08 11:18:09 -05:00 • committed by GitHub
commit 587e05f057
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
34 changed files with 301 additions and 151 deletions

View file

@ -1,8 +1,8 @@
namespace McpDotNet.Client;
using McpDotNet.Protocol.Messages;
using McpDotNet.Protocol.Messages;
using McpDotNet.Protocol.Types;
namespace McpDotNet.Client;
/// <summary>
/// Represents an instance of the MCP client connecting to a specific server.
/// Use PingAsync, ListToolsAsync and CallToolAsync instead of SendRequestAsync for common operations.

View file

@ -55,7 +55,7 @@ internal class McpClient : McpJsonRpcEndpoint, IMcpClient
throw new McpClientException("Sampling handler not configured");
}
return await SamplingHandler(request, CancellationTokenSource?.Token ?? CancellationToken.None);
return await SamplingHandler(request, CancellationTokenSource?.Token ?? CancellationToken.None).ConfigureAwait(false);
});
}
if (options.Capabilities?.Roots != null)
@ -69,7 +69,7 @@ internal class McpClient : McpJsonRpcEndpoint, IMcpClient
_logger.RootsHandlerNotConfigured(EndpointName);
throw new McpClientException("Roots handler not configured");
}
return await RootsHandler(request, CancellationTokenSource?.Token ?? CancellationToken.None);
return await RootsHandler(request, CancellationTokenSource?.Token ?? CancellationToken.None).ConfigureAwait(false);
});
}
}

View file

@ -10,6 +10,13 @@ public class McpClientException : Exception
/// </summary>
public int? ErrorCode { get; }
/// <summary>
/// Initializes a new instance of the <see cref="McpClientException"/> class.
/// </summary>
public McpClientException()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="McpClientException"/> class with a specified error message.
/// </summary>

View file

@ -81,7 +81,7 @@ public class McpClientFactory
var transport = _transportFactoryMethod(config);
var client = _clientFactoryMethod(transport, config, _clientOptions);
await client.ConnectAsync(cancellationToken);
await client.ConnectAsync(cancellationToken).ConfigureAwait(false);
_logger.ClientCreated(endpointName);
@ -97,29 +97,36 @@ public class McpClientFactory
var options = string.Join(", ", config.TransportOptions?.Select(kv => $"{kv.Key}={kv.Value}") ?? []);
_logger.CreatingTransport(endpointName, config.TransportType, options);
return config.TransportType.ToLowerInvariant() switch
if (string.Equals(config.TransportType, TransportTypes.StdIo, StringComparison.OrdinalIgnoreCase))
{
TransportTypes.StdIo => new StdioClientTransport(new StdioClientTransportOptions
return new StdioClientTransport(new StdioClientTransportOptions
{
Command = GetCommand(config),
Arguments = config.TransportOptions?.GetValueOrDefault("arguments")?.Split(' '),
WorkingDirectory = config.TransportOptions?.GetValueOrDefault("workingDirectory"),
EnvironmentVariables = config.TransportOptions?
.Where(kv => kv.Key.StartsWith("env:"))
.Where(kv => kv.Key.StartsWith("env:", StringComparison.Ordinal))
.ToDictionary(kv => kv.Key.Substring(4), kv => kv.Value)
}, config, _loggerFactory),
TransportTypes.Sse or "http" => new SseClientTransport(
}, config, _loggerFactory);
}
if (string.Equals(config.TransportType, TransportTypes.Sse, StringComparison.OrdinalIgnoreCase) ||
string.Equals(config.TransportType, "http", StringComparison.OrdinalIgnoreCase))
{
return new SseClientTransport(
new SseClientTransportOptions
{
ConnectionTimeout = TimeSpan.FromSeconds(ParseOrDefault(config.TransportOptions, "connectionTimeout", 30)),
MaxReconnectAttempts = ParseOrDefault(config.TransportOptions, "maxReconnectAttempts", 3),
ReconnectDelay = TimeSpan.FromSeconds(ParseOrDefault(config.TransportOptions, "reconnectDelay", 5)),
AdditionalHeaders = config.TransportOptions?
.Where(kv => kv.Key.StartsWith("header."))
.Where(kv => kv.Key.StartsWith("header.", StringComparison.Ordinal))
.ToDictionary(kv => kv.Key.Substring(7), kv => kv.Value)
}, config, _loggerFactory),
_ => throw new ArgumentException($"Unsupported transport type '{config.TransportType}'.", nameof(config))
};
}, config, _loggerFactory);
}
throw new ArgumentException($"Unsupported transport type '{config.TransportType}'.", nameof(config));
}
private static int ParseOrDefault(Dictionary<string, string>? options, string key, int defaultValue)

View file

@ -17,6 +17,11 @@ public static partial class McpServerBuilderExtensions
/// <param name="handler">The handler.</param>
public static IMcpServerBuilder WithListToolsHandler(this IMcpServerBuilder builder, Func<RequestContext<ListToolsRequestParams>, CancellationToken, Task<ListToolsResult>> handler)
{
if (builder is null)
{
throw new ArgumentNullException(nameof(builder));
}
builder.Services.Configure<McpServerDelegates>(s => s.ListToolsHandler = handler);
return builder;
}
@ -28,6 +33,11 @@ public static partial class McpServerBuilderExtensions
/// <param name="handler">The handler.</param>
public static IMcpServerBuilder WithCallToolHandler(this IMcpServerBuilder builder, Func<RequestContext<CallToolRequestParams>, CancellationToken, Task<CallToolResponse>> handler)
{
if (builder is null)
{
throw new ArgumentNullException(nameof(builder));
}
builder.Services.Configure<McpServerDelegates>(s => s.CallToolHandler = handler);
return builder;
}
@ -39,6 +49,11 @@ public static partial class McpServerBuilderExtensions
/// <param name="handler">The handler.</param>
public static IMcpServerBuilder WithListPromptsHandler(this IMcpServerBuilder builder, Func<RequestContext<ListPromptsRequestParams>, CancellationToken, Task<ListPromptsResult>> handler)
{
if (builder is null)
{
throw new ArgumentNullException(nameof(builder));
}
builder.Services.Configure<McpServerDelegates>(s => s.ListPromptsHandler = handler);
return builder;
}
@ -50,6 +65,11 @@ public static partial class McpServerBuilderExtensions
/// <param name="handler">The handler.</param>
public static IMcpServerBuilder WithGetPromptHandler(this IMcpServerBuilder builder, Func<RequestContext<GetPromptRequestParams>, CancellationToken, Task<GetPromptResult>> handler)
{
if (builder is null)
{
throw new ArgumentNullException(nameof(builder));
}
builder.Services.Configure<McpServerDelegates>(s => s.GetPromptHandler = handler);
return builder;
}
@ -61,6 +81,11 @@ public static partial class McpServerBuilderExtensions
/// <param name="handler">The handler.</param>
public static IMcpServerBuilder WithListResourcesHandler(this IMcpServerBuilder builder, Func<RequestContext<ListResourcesRequestParams>, CancellationToken, Task<ListResourcesResult>> handler)
{
if (builder is null)
{
throw new ArgumentNullException(nameof(builder));
}
builder.Services.Configure<McpServerDelegates>(s => s.ListResourcesHandler = handler);
return builder;
}
@ -72,6 +97,11 @@ public static partial class McpServerBuilderExtensions
/// <param name="handler">The handler.</param>
public static IMcpServerBuilder WithReadResourceHandler(this IMcpServerBuilder builder, Func<RequestContext<ReadResourceRequestParams>, CancellationToken, Task<ReadResourceResult>> handler)
{
if (builder is null)
{
throw new ArgumentNullException(nameof(builder));
}
builder.Services.Configure<McpServerDelegates>(s => s.ReadResourceHandler = handler);
return builder;
}
@ -83,6 +113,11 @@ public static partial class McpServerBuilderExtensions
/// <param name="handler">The handler.</param>
public static IMcpServerBuilder WithGetCompletionHandler(this IMcpServerBuilder builder, Func<RequestContext<CompleteRequestParams>, CancellationToken, Task<CompleteResult>> handler)
{
if (builder is null)
{
throw new ArgumentNullException(nameof(builder));
}
builder.Services.Configure<McpServerDelegates>(s => s.GetCompletionHandler = handler);
return builder;
}
@ -94,6 +129,11 @@ public static partial class McpServerBuilderExtensions
/// <param name="handler">The handler.</param>
public static IMcpServerBuilder WithSubscribeToResourcesHandler(this IMcpServerBuilder builder, Func<RequestContext<string>, CancellationToken, Task> handler)
{
if (builder is null)
{
throw new ArgumentNullException(nameof(builder));
}
builder.Services.Configure<McpServerDelegates>(s => s.SubscribeToResourcesHandler = handler);
return builder;
}
@ -105,6 +145,11 @@ public static partial class McpServerBuilderExtensions
/// <param name="handler">The handler.</param>
public static IMcpServerBuilder WithUnsubscribeFromResourcesHandler(this IMcpServerBuilder builder, Func<RequestContext<string>, CancellationToken, Task> handler)
{
if (builder is null)
{
throw new ArgumentNullException(nameof(builder));
}
builder.Services.Configure<McpServerDelegates>(s => s.UnsubscribeFromResourcesHandler = handler);
return builder;
}

View file

@ -1,4 +1,5 @@
using System.Reflection;
using System.Globalization;
using System.Reflection;
using System.Text.Json;
using McpDotNet.Configuration;
using McpDotNet.Protocol.Types;
@ -37,6 +38,11 @@ public static partial class McpServerBuilderExtensions
/// <param name="toolTypes">Types with marked methods to add as tools to the server.</param>
public static IMcpServerBuilder WithTools(this IMcpServerBuilder builder, params Type[] toolTypes)
{
if (builder is null)
{
throw new ArgumentNullException(nameof(builder));
}
if (toolTypes is null || toolTypes.Length == 0)
throw new ArgumentException("At least one tool type must be provided.", nameof(toolTypes));
@ -54,7 +60,7 @@ public static partial class McpServerBuilderExtensions
var tool = CreateTool(method, attribute);
tools.Add(tool);
callbacks.Add(tool.Name, async (request, cancellationToken) => await CallTool(request, method, cancellationToken));
callbacks.Add(tool.Name, async (request, cancellationToken) => await CallTool(request, method, cancellationToken).ConfigureAwait(false));
// register type because method is not static and so we need an instance
if (!method.IsStatic)
@ -68,7 +74,7 @@ public static partial class McpServerBuilderExtensions
builder.WithCallToolHandler(async (request, cancellationToken) =>
{
if (request.Params != null && callbacks.TryGetValue(request.Params.Name, out var callback))
return await callback(request, cancellationToken);
return await callback(request, cancellationToken).ConfigureAwait(false);
throw new McpServerException($"Unknown tool: {request.Params?.Name}");
});
@ -219,7 +225,7 @@ public static partial class McpServerBuilderExtensions
if (value is JsonElement element)
value = JsonSerializer.Deserialize(element.GetRawText(), parameter.ParameterType);
parameters.Add(Convert.ChangeType(value, parameter.ParameterType));
parameters.Add(Convert.ChangeType(value, parameter.ParameterType, CultureInfo.InvariantCulture));
}
else
{

View file

@ -15,6 +15,11 @@ public static partial class McpServerBuilderExtensions
/// <param name="builder">The builder instance.</param>
public static IMcpServerBuilder WithStdioServerTransport(this IMcpServerBuilder builder)
{
if (builder is null)
{
throw new ArgumentNullException(nameof(builder));
}
builder.Services.AddSingleton<IServerTransport, StdioServerTransport>();
return builder;
}

View file

@ -23,6 +23,6 @@ public class McpServerHostedService : BackgroundService
/// <inheritdoc />
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await _server.StartAsync(stoppingToken);
await _server.StartAsync(stoppingToken).ConfigureAwait(false);
}
}

View file

@ -1,6 +1,6 @@
namespace McpDotNet.Protocol.Messages;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization;
namespace McpDotNet.Protocol.Messages;
/// <summary>
/// An error response message in the JSON-RPC protocol.

View file

@ -1,6 +1,6 @@
namespace McpDotNet.Protocol.Messages;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization;
namespace McpDotNet.Protocol.Messages;
/// <summary>
/// Detailed error information for JSON-RPC error responses.

View file

@ -1,6 +1,6 @@
namespace McpDotNet.Protocol.Messages;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization;
namespace McpDotNet.Protocol.Messages;
/// <summary>
/// A request message in the JSON-RPC protocol.

View file

@ -1,6 +1,6 @@
namespace McpDotNet.Protocol.Messages;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization;
namespace McpDotNet.Protocol.Messages;
/// <summary>
/// A notification message in the JSON-RPC protocol (a request that doesn't expect a response).

View file

@ -23,5 +23,5 @@ public record JsonRpcResponse : IJsonRpcMessageWithId
/// The result of the method invocation.
/// </summary>
[JsonPropertyName("result")]
public required object Result { get; init; }
public required object? Result { get; init; }
}

View file

@ -1,6 +1,6 @@
namespace McpDotNet.Protocol.Messages;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization;
namespace McpDotNet.Protocol.Messages;
/// <summary>
/// Represents a JSON-RPC request identifier which can be either a string or a number.

View file

@ -1,8 +1,8 @@
namespace McpDotNet.Protocol.Messages;
using System.Text.Json;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace McpDotNet.Protocol.Messages;
/// <summary>
/// JSON converter for RequestId that handles both string and number values.
/// </summary>
@ -11,20 +11,22 @@ public class RequestIdConverter : JsonConverter<RequestId>
/// <inheritdoc />
public override RequestId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
switch (reader.TokenType)
return reader.TokenType switch
{
case JsonTokenType.String:
return RequestId.FromString(reader.GetString()!);
case JsonTokenType.Number:
return RequestId.FromNumber(reader.GetInt64());
default:
throw new JsonException("RequestId must be either a string or a number");
}
JsonTokenType.String => RequestId.FromString(reader.GetString()!),
JsonTokenType.Number => RequestId.FromNumber(reader.GetInt64()),
_ => throw new JsonException("RequestId must be either a string or a number"),
};
}
/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, RequestId value, JsonSerializerOptions options)
{
if (writer is null)
{
throw new ArgumentNullException(nameof(writer));
}
if (value.IsString)
writer.WriteStringValue(value.AsString);
else

View file

@ -7,6 +7,7 @@ using McpDotNet.Logging;
using McpDotNet.Protocol.Messages;
using McpDotNet.Utils.Json;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
namespace McpDotNet.Protocol.Transport;
@ -21,7 +22,7 @@ public sealed class SseClientTransport : TransportBase, IClientTransport
private Uri? _messageEndpoint;
private readonly CancellationTokenSource _connectionCts;
private Task? _receiveTask;
private readonly ILogger<SseClientTransport> _logger;
private readonly ILogger _logger;
private readonly McpServerConfig _serverConfig;
private readonly JsonSerializerOptions _jsonOptions;
private readonly TaskCompletionSource<bool> _connectionEstablished;
@ -35,15 +36,25 @@ public sealed class SseClientTransport : TransportBase, IClientTransport
/// <param name="transportOptions">Configuration options for the transport.</param>
/// <param name="serverConfig">The configuration object indicating which server to connect to.</param>
/// <param name="loggerFactory">Logger factory for creating loggers.</param>
public SseClientTransport(SseClientTransportOptions transportOptions, McpServerConfig serverConfig, ILoggerFactory loggerFactory)
public SseClientTransport(SseClientTransportOptions transportOptions, McpServerConfig serverConfig, ILoggerFactory? loggerFactory)
: base(loggerFactory)
{
if (transportOptions is null)
{
throw new ArgumentNullException(nameof(transportOptions));
}
if (serverConfig is null)
{
throw new ArgumentNullException(nameof(serverConfig));
}
_options = transportOptions;
_serverConfig = serverConfig;
_sseEndpoint = new Uri(serverConfig.Location!);
_httpClient = new HttpClient();
_connectionCts = new CancellationTokenSource();
_logger = loggerFactory.CreateLogger<SseClientTransport>();
_logger = loggerFactory is not null ? loggerFactory.CreateLogger<SseClientTransport>() : NullLogger.Instance;
_jsonOptions = JsonSerializerOptionsExtensions.DefaultOptions;
_connectionEstablished = new TaskCompletionSource<bool>();
}
@ -64,7 +75,7 @@ public sealed class SseClientTransport : TransportBase, IClientTransport
_logger.TransportReadingMessages(EndpointName);
await _connectionEstablished.Task.WaitAsync(_options.ConnectionTimeout, cancellationToken);
await _connectionEstablished.Task.WaitAsync(_options.ConnectionTimeout, cancellationToken).ConfigureAwait(false);
}
catch (McpTransportException)
{
@ -74,7 +85,7 @@ public sealed class SseClientTransport : TransportBase, IClientTransport
catch (Exception ex)
{
_logger.TransportConnectFailed(EndpointName, ex);
await CloseAsync();
await CloseAsync().ConfigureAwait(false);
throw new McpTransportException("Failed to connect transport", ex);
}
}
@ -104,11 +115,11 @@ public sealed class SseClientTransport : TransportBase, IClientTransport
_messageEndpoint,
content,
cancellationToken
);
).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
var responseContent = await response.Content.ReadAsStringAsync(cancellationToken);
var responseContent = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
// Check if the message was an initialize request
if (message is JsonRpcRequest request && request.Method == "initialize")
@ -125,7 +136,7 @@ public sealed class SseClientTransport : TransportBase, IClientTransport
throw new McpTransportException("Failed to initialize client");
_logger.TransportReceivedMessageParsed(EndpointName, messageId);
await WriteMessageAsync(initializeResponse, cancellationToken);
await WriteMessageAsync(initializeResponse, cancellationToken).ConfigureAwait(false);
_logger.TransportMessageWritten(EndpointName, messageId);
}
return;
@ -146,9 +157,9 @@ public sealed class SseClientTransport : TransportBase, IClientTransport
/// <inheritdoc/>
public async Task CloseAsync()
{
await _connectionCts.CancelAsync();
await _connectionCts.CancelAsync().ConfigureAwait(false);
if (_receiveTask != null)
await _receiveTask;
await _receiveTask.ConfigureAwait(false);
_httpClient.Dispose();
_connectionCts.Dispose();
@ -158,7 +169,7 @@ public sealed class SseClientTransport : TransportBase, IClientTransport
/// <inheritdoc/>
public override async ValueTask DisposeAsync()
{
await CloseAsync();
await CloseAsync().ConfigureAwait(false);
GC.SuppressFinalize(this);
}
@ -181,16 +192,16 @@ public sealed class SseClientTransport : TransportBase, IClientTransport
request,
HttpCompletionOption.ResponseHeadersRead,
cancellationToken
);
).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
using var stream = await response.Content.ReadAsStreamAsync(cancellationToken);
using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
// Reset reconnect attempts on successful connection
reconnectAttempts = 0;
await foreach (SseItem<string> sseEvent in SseParser.Create(stream).EnumerateAsync(cancellationToken))
await foreach (SseItem<string> sseEvent in SseParser.Create(stream).EnumerateAsync(cancellationToken).ConfigureAwait(false))
{
switch (sseEvent.EventType)
{
@ -199,7 +210,7 @@ public sealed class SseClientTransport : TransportBase, IClientTransport
break;
case "message":
await ProcessSseMessage(sseEvent.Data, cancellationToken);
await ProcessSseMessage(sseEvent.Data, cancellationToken).ConfigureAwait(false);
break;
}
}
@ -224,7 +235,7 @@ public sealed class SseClientTransport : TransportBase, IClientTransport
throw new McpTransportException("Exceeded reconnect limit", ex);
}
await Task.Delay(_options.ReconnectDelay, cancellationToken);
await Task.Delay(_options.ReconnectDelay, cancellationToken).ConfigureAwait(false);
}
}
}
@ -253,7 +264,7 @@ public sealed class SseClientTransport : TransportBase, IClientTransport
}
_logger.TransportReceivedMessageParsed(EndpointName, messageId);
await WriteMessageAsync(message, cancellationToken);
await WriteMessageAsync(message, cancellationToken).ConfigureAwait(false);
_logger.TransportMessageWritten(EndpointName, messageId);
}
catch (JsonException ex)
@ -282,7 +293,7 @@ public sealed class SseClientTransport : TransportBase, IClientTransport
{
// If the endpoint is a relative URI, we need to combine it with the relative path of the SSE endpoint
var hostUrl = _sseEndpoint.AbsoluteUri;
if (hostUrl.EndsWith("/sse"))
if (hostUrl.EndsWith("/sse", StringComparison.Ordinal))
hostUrl = hostUrl[..^4];
var endpointUri = hostUrl + data;

View file

@ -108,7 +108,7 @@ public sealed class StdioClientTransport : TransportBase, IClientTransport
_process.BeginErrorReadLine();
// Start reading messages in the background
_readTask = Task.Run(async () => await ReadMessagesAsync(_shutdownCts.Token), CancellationToken.None);
_readTask = Task.Run(async () => await ReadMessagesAsync(_shutdownCts.Token).ConfigureAwait(false), CancellationToken.None);
_logger.TransportReadingMessages(EndpointName);
SetConnected(true);
@ -116,7 +116,7 @@ public sealed class StdioClientTransport : TransportBase, IClientTransport
catch (Exception ex)
{
_logger.TransportConnectFailed(EndpointName, ex);
await CleanupAsync(cancellationToken);
await CleanupAsync(cancellationToken).ConfigureAwait(false);
throw new McpTransportException("Failed to connect transport", ex);
}
}
@ -142,8 +142,8 @@ public sealed class StdioClientTransport : TransportBase, IClientTransport
_logger.TransportSendingMessage(EndpointName, id, json);
// Write the message followed by a newline
await _process!.StandardInput.WriteLineAsync(json.AsMemory(), cancellationToken);
await _process.StandardInput.FlushAsync(cancellationToken);
await _process!.StandardInput.WriteLineAsync(json.AsMemory(), cancellationToken).ConfigureAwait(false);
await _process.StandardInput.FlushAsync(cancellationToken).ConfigureAwait(false);
_logger.TransportSentMessage(EndpointName, id);
}
@ -157,7 +157,7 @@ public sealed class StdioClientTransport : TransportBase, IClientTransport
/// <inheritdoc/>
public override async ValueTask DisposeAsync()
{
await CleanupAsync(CancellationToken.None);
await CleanupAsync(CancellationToken.None).ConfigureAwait(false);
GC.SuppressFinalize(this);
}
@ -172,7 +172,7 @@ public sealed class StdioClientTransport : TransportBase, IClientTransport
while (!cancellationToken.IsCancellationRequested && !_process.HasExited)
{
_logger.TransportWaitingForMessage(EndpointName);
var line = await reader.ReadLineAsync(cancellationToken);
var line = await reader.ReadLineAsync(cancellationToken).ConfigureAwait(false);
if (line == null)
{
_logger.TransportEndOfStream(EndpointName);
@ -197,7 +197,7 @@ public sealed class StdioClientTransport : TransportBase, IClientTransport
messageId = messageWithId.Id.ToString();
}
_logger.TransportReceivedMessageParsed(EndpointName, messageId);
await WriteMessageAsync(message, cancellationToken);
await WriteMessageAsync(message, cancellationToken).ConfigureAwait(false);
_logger.TransportMessageWritten(EndpointName, messageId);
}
else
@ -224,7 +224,7 @@ public sealed class StdioClientTransport : TransportBase, IClientTransport
}
finally
{
await CleanupAsync(cancellationToken);
await CleanupAsync(cancellationToken).ConfigureAwait(false);
}
}
@ -258,7 +258,7 @@ public sealed class StdioClientTransport : TransportBase, IClientTransport
}
if (_shutdownCts != null)
await _shutdownCts.CancelAsync();
await _shutdownCts.CancelAsync().ConfigureAwait(false);
_shutdownCts?.Dispose();
_shutdownCts = null;
@ -267,7 +267,7 @@ public sealed class StdioClientTransport : TransportBase, IClientTransport
try
{
_logger.TransportWaitingForReadTask(EndpointName);
await _readTask.WaitAsync(TimeSpan.FromSeconds(5), cancellationToken);
await _readTask.WaitAsync(TimeSpan.FromSeconds(5), cancellationToken).ConfigureAwait(false);
}
catch (TimeoutException)
{

View file

@ -4,6 +4,7 @@ using McpDotNet.Protocol.Messages;
using McpDotNet.Server;
using McpDotNet.Utils.Json;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
namespace McpDotNet.Protocol.Transport;
@ -13,7 +14,7 @@ namespace McpDotNet.Protocol.Transport;
public sealed class StdioServerTransport : TransportBase, IServerTransport
{
private readonly string _serverName;
private readonly ILogger<StdioClientTransport> _logger;
private readonly ILogger _logger;
private readonly JsonSerializerOptions _jsonOptions;
private Task? _readTask;
private CancellationTokenSource? _shutdownCts;
@ -25,8 +26,8 @@ public sealed class StdioServerTransport : TransportBase, IServerTransport
/// </summary>
/// <param name="serverOptions">The server options.</param>
/// <param name="loggerFactory">A logger factory for creating loggers.</param>
public StdioServerTransport(McpServerOptions serverOptions, ILoggerFactory loggerFactory)
: this(serverOptions.ServerInfo.Name, loggerFactory)
public StdioServerTransport(McpServerOptions serverOptions, ILoggerFactory? loggerFactory)
: this(serverOptions is not null ? serverOptions.ServerInfo.Name : throw new ArgumentNullException(nameof(serverOptions)), loggerFactory)
{
}
@ -35,11 +36,11 @@ public sealed class StdioServerTransport : TransportBase, IServerTransport
/// </summary>
/// <param name="serverName">The name of the server.</param>
/// <param name="loggerFactory">A logger factory for creating loggers.</param>
public StdioServerTransport(string serverName, ILoggerFactory loggerFactory)
public StdioServerTransport(string serverName, ILoggerFactory? loggerFactory)
: base(loggerFactory)
{
_serverName = serverName;
_logger = loggerFactory.CreateLogger<StdioClientTransport>();
_logger = loggerFactory is not null ? loggerFactory.CreateLogger<StdioClientTransport>() : NullLogger.Instance;
_jsonOptions = JsonSerializerOptionsExtensions.DefaultOptions;
}
@ -48,7 +49,7 @@ public sealed class StdioServerTransport : TransportBase, IServerTransport
{
_shutdownCts = new CancellationTokenSource();
_readTask = Task.Run(async () => await ReadMessagesAsync(_shutdownCts.Token), CancellationToken.None);
_readTask = Task.Run(async () => await ReadMessagesAsync(_shutdownCts.Token).ConfigureAwait(false), CancellationToken.None);
SetConnected(true);
@ -76,11 +77,8 @@ public sealed class StdioServerTransport : TransportBase, IServerTransport
var json = JsonSerializer.Serialize(message, _jsonOptions);
_logger.TransportSendingMessage(EndpointName, id, json);
using (Console.OpenStandardOutput())
{
await Console.Out.WriteLineAsync(json.AsMemory(), cancellationToken);
await Console.Out.FlushAsync(cancellationToken);
}
await Console.Out.WriteLineAsync(json.AsMemory(), cancellationToken).ConfigureAwait(false);
await Console.Out.FlushAsync(cancellationToken).ConfigureAwait(false);
_logger.TransportSentMessage(EndpointName, id);
}
@ -94,7 +92,7 @@ public sealed class StdioServerTransport : TransportBase, IServerTransport
/// <inheritdoc/>
public override async ValueTask DisposeAsync()
{
await CleanupAsync(CancellationToken.None);
await CleanupAsync(CancellationToken.None).ConfigureAwait(false);
GC.SuppressFinalize(this);
}
@ -110,7 +108,7 @@ public sealed class StdioServerTransport : TransportBase, IServerTransport
using (Console.OpenStandardInput())
{
var reader = Console.In;
var line = await reader.ReadLineAsync(cancellationToken);
var line = await reader.ReadLineAsync(cancellationToken).ConfigureAwait(false);
if (line == null)
{
_logger.TransportEndOfStream(EndpointName);
@ -135,7 +133,7 @@ public sealed class StdioServerTransport : TransportBase, IServerTransport
messageId = messageWithId.Id.ToString();
}
_logger.TransportReceivedMessageParsed(EndpointName, messageId);
await WriteMessageAsync(message, cancellationToken);
await WriteMessageAsync(message, cancellationToken).ConfigureAwait(false);
_logger.TransportMessageWritten(EndpointName, messageId);
}
else
@ -163,7 +161,7 @@ public sealed class StdioServerTransport : TransportBase, IServerTransport
}
finally
{
await CleanupAsync(cancellationToken);
await CleanupAsync(cancellationToken).ConfigureAwait(false);
}
}
@ -180,7 +178,7 @@ public sealed class StdioServerTransport : TransportBase, IServerTransport
try
{
_logger.TransportWaitingForReadTask(EndpointName);
await _readTask.WaitAsync(TimeSpan.FromSeconds(5), cancellationToken);
await _readTask.WaitAsync(TimeSpan.FromSeconds(5), cancellationToken).ConfigureAwait(false);
}
catch (TimeoutException)
{

View file

@ -2,6 +2,7 @@
using Microsoft.Extensions.Logging;
using McpDotNet.Logging;
using McpDotNet.Protocol.Messages;
using Microsoft.Extensions.Logging.Abstractions;
namespace McpDotNet.Protocol.Transport;
@ -11,13 +12,13 @@ namespace McpDotNet.Protocol.Transport;
public abstract class TransportBase : ITransport
{
private readonly Channel<IJsonRpcMessage> _messageChannel;
private readonly ILogger<TransportBase> _logger;
private readonly ILogger _logger;
private bool _isConnected;
/// <summary>
/// Initializes a new instance of the <see cref="TransportBase"/> class.
/// </summary>
protected TransportBase(ILoggerFactory loggerFactory)
protected TransportBase(ILoggerFactory? loggerFactory)
{
// Unbounded channel to prevent blocking on writes
_messageChannel = Channel.CreateUnbounded<IJsonRpcMessage>(new UnboundedChannelOptions
@ -25,7 +26,7 @@ public abstract class TransportBase : ITransport
SingleReader = true,
SingleWriter = true,
});
_logger = loggerFactory.CreateLogger<TransportBase>();
_logger = loggerFactory is not null ? loggerFactory.CreateLogger<TransportBase>() : NullLogger.Instance;
}
/// <inheritdoc/>
@ -53,7 +54,7 @@ public abstract class TransportBase : ITransport
}
_logger.TransportWritingMessageToChannel(message);
await _messageChannel.Writer.WriteAsync(message, cancellationToken);
await _messageChannel.Writer.WriteAsync(message, cancellationToken).ConfigureAwait(false);
_logger.TransportMessageWrittenToChannel();
}

View file

@ -12,7 +12,7 @@ public class Completion
/// An array of completion values. Must not exceed 100 items.
/// </summary>
[JsonPropertyName("values")]
public string[] Values { get; set; } = new string[0];
public string[] Values { get; set; } = [];
/// <summary>
/// The total number of completion options available. This can exceed the number of values actually sent in the response.

Some files were not shown because too many files have changed in this diff Show more