Address some static analysis and TODOs (#12)

This commit is contained in:
Stephen Toub 2025-03-20 23:09:38 -04:00 • committed by GitHub
commit 33b9582199
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 132 additions and 123 deletions

View file

@ -6,8 +6,6 @@ using Microsoft.Extensions.AI;
using System.Runtime.CompilerServices;
using System.Text.Json;
#pragma warning disable CA1508 // Avoid dead conditional code
namespace ModelContextProtocol.Client;
/// <summary>
@ -439,7 +437,7 @@ public static class McpClientExtensions
}
private static JsonRpcRequest CreateRequest(string method, Dictionary<string, object?>? parameters) =>
new JsonRpcRequest
new()
{
Method = method,
Params = parameters

View file

@ -30,10 +30,7 @@ public static partial class McpServerBuilderExtensions
/// <param name="builder">The builder instance.</param>
public static IMcpServerBuilder WithHttpListenerSseServerTransport(this IMcpServerBuilder builder)
{
if (builder is null)
{
throw new ArgumentNullException(nameof(builder));
}
Throw.IfNull(builder);
builder.Services.AddSingleton<IServerTransport, HttpListenerSseServerTransport>();
builder.Services.AddHostedService<McpServerHostedService>();

View file

@ -2,6 +2,7 @@
using ModelContextProtocol.Protocol.Types;
using ModelContextProtocol.Server;
using Microsoft.Extensions.Options;
using ModelContextProtocol.Utils;
namespace ModelContextProtocol.Configuration;
@ -9,10 +10,7 @@ internal sealed class McpServerOptionsSetup(IOptions<McpServerHandlers> serverHa
{
public void Configure(McpServerOptions options)
{
if (options is null)
{
throw new ArgumentNullException(nameof(options));
}
Throw.IfNull(options);
var assemblyName = Assembly.GetEntryAssembly()?.GetName();
options.ServerInfo = new Implementation

View file

@ -11,9 +11,12 @@ namespace ModelContextProtocol.Protocol.Transport;
[ExcludeFromCodeCoverage]
internal class HttpListenerServerProvider : IDisposable
{
private static readonly byte[] s_accepted = "Accepted"u8.ToArray();
private const string SseEndpoint = "/sse";
private const string MessageEndpoint = "/message";
private readonly int _port;
private readonly string _sseEndpoint = "/sse";
private readonly string _messageEndpoint = "/message";
private HttpListener? _listener;
private CancellationTokenSource? _cts;
private Func<string, CancellationToken, bool>? _messageHandler;
@ -31,7 +34,7 @@ internal class HttpListenerServerProvider : IDisposable
public Task<string> GetSseEndpointUri()
{
return Task.FromResult($"http://localhost:{_port}{_sseEndpoint}");
return Task.FromResult($"http://localhost:{_port}{SseEndpoint}");
}
public Task InitializeMessageHandler(Func<string, CancellationToken, bool> messageHandler)
@ -131,12 +134,12 @@ internal class HttpListenerServerProvider : IDisposable
throw new McpServerException("Request is null");
// Handle SSE connection
if (request.HttpMethod == "GET" && request.Url?.LocalPath == _sseEndpoint)
if (request.HttpMethod == "GET" && request.Url?.LocalPath == SseEndpoint)
{
await HandleSseConnectionAsync(context, cancellationToken).ConfigureAwait(false);
}
// Handle message POST
else if (request.HttpMethod == "POST" && request.Url?.LocalPath == _messageEndpoint)
else if (request.HttpMethod == "POST" && request.Url?.LocalPath == MessageEndpoint)
{
await HandleMessageAsync(context, cancellationToken).ConfigureAwait(false);
}
@ -167,9 +170,6 @@ internal class HttpListenerServerProvider : IDisposable
response.Headers.Add("Cache-Control", "no-cache");
response.Headers.Add("Connection", "keep-alive");
// Create a unique ID for this client
var clientId = Guid.NewGuid().ToString();
// Get the output stream and create a StreamWriter
var outputStream = response.OutputStream;
_streamWriter = new StreamWriter(outputStream, Encoding.UTF8) { AutoFlush = true };
@ -179,7 +179,7 @@ internal class HttpListenerServerProvider : IDisposable
{
// Immediately send the "endpoint" event with the POST URL
await _streamWriter.WriteLineAsync("event: endpoint").ConfigureAwait(false);
await _streamWriter.WriteLineAsync($"data: {_messageEndpoint}").ConfigureAwait(false);
await _streamWriter.WriteLineAsync($"data: {MessageEndpoint}").ConfigureAwait(false);
await _streamWriter.WriteLineAsync().ConfigureAwait(false); // blank line to end an SSE message
await _streamWriter.FlushAsync(cancellationToken).ConfigureAwait(false);
@ -222,10 +222,7 @@ internal class HttpListenerServerProvider : IDisposable
string requestBody;
using (var reader = new StreamReader(request.InputStream, request.ContentEncoding))
{
// TODO: Add cancellation token and netstandard2.0 support (polyfill?)
#pragma warning disable CA2016 // Forward the 'CancellationToken' parameter to methods
requestBody = await reader.ReadToEndAsync().ConfigureAwait(false);
#pragma warning restore CA2016 // Forward the 'CancellationToken' parameter to methods
requestBody = await reader.ReadToEndAsync(cancellationToken).ConfigureAwait(false);
}
// Process the message asynchronously
@ -233,12 +230,9 @@ internal class HttpListenerServerProvider : IDisposable
{
// Return 202 Accepted
response.StatusCode = 202;
// Write "accepted" response
// TODO: Use WriteAsync, add cancellation token and netstandard2.0 support (polyfill?)
byte[] buffer = Encoding.UTF8.GetBytes("Accepted");
#pragma warning disable CA1835 // Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync'
await response.OutputStream.WriteAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
#pragma warning restore CA1835 // Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync'
await response.OutputStream.WriteAsync(s_accepted, cancellationToken).ConfigureAwait(false);
}
else
{

View file

@ -4,6 +4,7 @@ using ModelContextProtocol.Protocol.Messages;
using ModelContextProtocol.Utils.Json;
using ModelContextProtocol.Logging;
using ModelContextProtocol.Server;
using ModelContextProtocol.Utils;
namespace ModelContextProtocol.Protocol.Transport;
@ -160,15 +161,8 @@ public sealed class HttpListenerSseServerTransport : TransportBase, IServerTrans
/// <summary>Validates the <paramref name="serverOptions"/> and extracts from it the server name to use.</summary>
private static string GetServerName(McpServerOptions serverOptions)
{
if (serverOptions is null)
{
throw new ArgumentNullException(nameof(serverOptions));
}
if (serverOptions.ServerInfo is null)
{
throw new ArgumentNullException($"{nameof(serverOptions)}.{nameof(serverOptions.ServerInfo)}");
}
Throw.IfNull(serverOptions);
Throw.IfNull(serverOptions.ServerInfo);
return serverOptions.ServerInfo.Name;
}

View file

@ -8,8 +8,6 @@ using ModelContextProtocol.Utils.Json;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
#pragma warning disable CA2213 // Disposable fields should be disposed
namespace ModelContextProtocol.Protocol.Transport;
/// <summary>

View file

@ -8,9 +8,6 @@ using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
#pragma warning disable CA2208 // Instantiate argument exceptions correctly
#pragma warning disable CA2213 // Disposable fields should be disposed
namespace ModelContextProtocol.Protocol.Transport;
/// <summary>

View file

@ -87,7 +87,7 @@ internal abstract class McpJsonRpcEndpoint : IAsyncDisposable
#if NET
await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding);
#else
await Task.Yield(); // TODO: Fix this
await default(ForceYielding);
#endif
try
{

View file

@ -21,7 +21,7 @@ internal static class Throw
{
if (arg is null || arg.AsSpan().IsWhiteSpace())
{
ThrowArgumentNullOrWhiteSpaceException(arg);
ThrowArgumentNullOrWhiteSpaceException(parameterName);
}
}