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
18 changed files with 301 additions and 151 deletions

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.

View file

@ -1,6 +1,6 @@
namespace McpDotNet.Protocol.Types;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization;
namespace McpDotNet.Protocol.Types;
/// <summary>
/// Describes the name and version of an MCP implementation.

View file

@ -1,6 +1,6 @@
namespace McpDotNet.Protocol.Types;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization;
namespace McpDotNet.Protocol.Types;
/// <summary>
/// Parameters for an initialization request sent to the server.

View file

@ -1,6 +1,6 @@
namespace McpDotNet.Protocol.Types;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization;
namespace McpDotNet.Protocol.Types;
/// <summary>
/// Result of the initialization request sent to the server.

View file

@ -59,21 +59,25 @@ public class ModelPreferences
{
bool valid = true;
List<string> errors = [];
if (CostPriority < 0 || CostPriority > 1)
if (CostPriority is < 0 or > 1)
{
errors.Add("CostPriority must be between 0 and 1");
valid = false;
}
if (SpeedPriority < 0 || SpeedPriority > 1)
if (SpeedPriority is < 0 or > 1)
{
errors.Add("SpeedPriority must be between 0 and 1");
valid = false;
}
if (IntelligencePriority < 0 || IntelligencePriority > 1)
if (IntelligencePriority is < 0 or > 1)
{
errors.Add("IntelligencePriority must be between 0 and 1");
valid = false;
}
if (!valid)
{
errorMessage = string.Join(", ", errors);
@ -82,6 +86,7 @@ public class ModelPreferences
{
errorMessage = "";
}
return valid;
}
}

View file

@ -1,6 +1,6 @@
namespace McpDotNet.Protocol.Types;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization;
namespace McpDotNet.Protocol.Types;
/// <summary>
/// Represents a known resource that the server is capable of reading.

View file

@ -1,6 +1,6 @@
namespace McpDotNet.Protocol.Types;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization;
namespace McpDotNet.Protocol.Types;
/// <summary>
/// Represents the capabilities that a server may support.