diff --git a/src/Common/Polyfills/System/Collections/Generic/CollectionExtensions.cs b/src/Common/Polyfills/System/Collections/Generic/CollectionExtensions.cs index 4848134..ccfbd39 100644 --- a/src/Common/Polyfills/System/Collections/Generic/CollectionExtensions.cs +++ b/src/Common/Polyfills/System/Collections/Generic/CollectionExtensions.cs @@ -1,3 +1,5 @@ +using ModelContextProtocol.Utils; + namespace System.Collections.Generic; internal static class CollectionExtensions @@ -9,10 +11,7 @@ internal static class CollectionExtensions public static TValue GetValueOrDefault(this IReadOnlyDictionary dictionary, TKey key, TValue defaultValue) { - if (dictionary is null) - { - throw new ArgumentNullException(nameof(dictionary)); - } + Throw.IfNull(dictionary); return dictionary.TryGetValue(key, out TValue? value) ? value : defaultValue; } diff --git a/src/Common/Polyfills/System/IO/StreamExtensions.cs b/src/Common/Polyfills/System/IO/StreamExtensions.cs new file mode 100644 index 0000000..c566a97 --- /dev/null +++ b/src/Common/Polyfills/System/IO/StreamExtensions.cs @@ -0,0 +1,36 @@ +using ModelContextProtocol.Utils; +using System.Buffers; +using System.Runtime.InteropServices; + +namespace System.IO; + +internal static class StreamExtensions +{ + public static ValueTask WriteAsync(this Stream stream, ReadOnlyMemory buffer, CancellationToken cancellationToken) + { + Throw.IfNull(stream); + + if (MemoryMarshal.TryGetArray(buffer, out ArraySegment segment)) + { + return new ValueTask(stream.WriteAsync(segment.Array, segment.Offset, segment.Count, cancellationToken)); + } + else + { + return WriteAsyncCore(stream, buffer, cancellationToken); + + static async ValueTask WriteAsyncCore(Stream stream, ReadOnlyMemory buffer, CancellationToken cancellationToken) + { + byte[] array = ArrayPool.Shared.Rent(buffer.Length); + try + { + buffer.Span.CopyTo(array); + await stream.WriteAsync(array, 0, buffer.Length, cancellationToken).ConfigureAwait(false); + } + finally + { + ArrayPool.Shared.Return(array); + } + } + } + } +} \ No newline at end of file diff --git a/src/Common/Polyfills/System/IO/TextReaderExtensions.cs b/src/Common/Polyfills/System/IO/TextReaderExtensions.cs index b5a61df..985f335 100644 --- a/src/Common/Polyfills/System/IO/TextReaderExtensions.cs +++ b/src/Common/Polyfills/System/IO/TextReaderExtensions.cs @@ -4,7 +4,21 @@ internal static class TextReaderExtensions { public static Task ReadLineAsync(this TextReader reader, CancellationToken cancellationToken) { - _ = cancellationToken; + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + return reader.ReadLineAsync(); } + + public static Task ReadToEndAsync(this TextReader reader, CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + + return reader.ReadToEndAsync(); + } } \ No newline at end of file diff --git a/src/Common/Polyfills/System/IO/TextWriterExtensions.cs b/src/Common/Polyfills/System/IO/TextWriterExtensions.cs index b111948..928d070 100644 --- a/src/Common/Polyfills/System/IO/TextWriterExtensions.cs +++ b/src/Common/Polyfills/System/IO/TextWriterExtensions.cs @@ -1,3 +1,4 @@ +using ModelContextProtocol.Utils; using System.Runtime.InteropServices; namespace System.IO; @@ -6,10 +7,7 @@ internal static class TextWriterExtensions { public static async Task WriteLineAsync(this TextWriter writer, ReadOnlyMemory value, CancellationToken cancellationToken) { - if (writer is null) - { - throw new ArgumentNullException(nameof(writer)); - } + Throw.IfNull(writer); if (value.IsEmpty) { diff --git a/src/Common/Polyfills/System/Net/Http/HttpClientExtensions.cs b/src/Common/Polyfills/System/Net/Http/HttpClientExtensions.cs index 5b00858..80a959a 100644 --- a/src/Common/Polyfills/System/Net/Http/HttpClientExtensions.cs +++ b/src/Common/Polyfills/System/Net/Http/HttpClientExtensions.cs @@ -1,13 +1,12 @@ +using ModelContextProtocol.Utils; + namespace System.Net.Http; internal static class HttpClientExtensions { public static async Task ReadAsStreamAsync(this HttpContent content, CancellationToken cancellationToken) { - if (content is null) - { - throw new ArgumentNullException(nameof(content)); - } + Throw.IfNull(content); cancellationToken.ThrowIfCancellationRequested(); return await content.ReadAsStreamAsync(); @@ -15,10 +14,7 @@ internal static class HttpClientExtensions public static async Task ReadAsStringAsync(this HttpContent content, CancellationToken cancellationToken) { - if (content is null) - { - throw new ArgumentNullException(nameof(content)); - } + Throw.IfNull(content); cancellationToken.ThrowIfCancellationRequested(); return await content.ReadAsStringAsync(); diff --git a/src/Common/Polyfills/System/Threading/CancellationTokenSourceExtensions.cs b/src/Common/Polyfills/System/Threading/CancellationTokenSourceExtensions.cs index 93e34fd..3c485f1 100644 --- a/src/Common/Polyfills/System/Threading/CancellationTokenSourceExtensions.cs +++ b/src/Common/Polyfills/System/Threading/CancellationTokenSourceExtensions.cs @@ -1,13 +1,12 @@ +using ModelContextProtocol.Utils; + namespace System.Threading.Tasks; internal static class CancellationTokenSourceExtensions { public static Task CancelAsync(this CancellationTokenSource cancellationTokenSource) { - if (cancellationTokenSource is null) - { - throw new ArgumentNullException(nameof(cancellationTokenSource)); - } + Throw.IfNull(cancellationTokenSource); cancellationTokenSource.Cancel(); return Task.CompletedTask; diff --git a/src/Common/Polyfills/System/Threading/ForceYielding.cs b/src/Common/Polyfills/System/Threading/ForceYielding.cs new file mode 100644 index 0000000..a25baa9 --- /dev/null +++ b/src/Common/Polyfills/System/Threading/ForceYielding.cs @@ -0,0 +1,17 @@ +using System.Runtime.CompilerServices; + +namespace System.Threading; + +/// +/// await default(ForceYielding) to provide the same behavior as +/// await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding). +/// +internal readonly struct ForceYielding : INotifyCompletion, ICriticalNotifyCompletion +{ + public ForceYielding GetAwaiter() => this; + + public bool IsCompleted => false; + public void OnCompleted(Action continuation) => ThreadPool.QueueUserWorkItem(a => ((Action)a!)(), continuation); + public void UnsafeOnCompleted(Action continuation) => ThreadPool.UnsafeQueueUserWorkItem(a => ((Action)a!)(), continuation); + public void GetResult() { } +} \ No newline at end of file diff --git a/src/Common/Polyfills/System/Threading/Tasks/TaskExtensions.cs b/src/Common/Polyfills/System/Threading/Tasks/TaskExtensions.cs index 6f8e2bf..fbdeffe 100644 --- a/src/Common/Polyfills/System/Threading/Tasks/TaskExtensions.cs +++ b/src/Common/Polyfills/System/Threading/Tasks/TaskExtensions.cs @@ -1,3 +1,5 @@ +using ModelContextProtocol.Utils; + namespace System.Threading.Tasks; internal static class TaskExtensions @@ -15,10 +17,7 @@ internal static class TaskExtensions public static async Task WaitAsync(this Task task, TimeSpan timeout, CancellationToken cancellationToken) { - if (task is null) - { - throw new ArgumentNullException(nameof(task)); - } + Throw.IfNull(task); if (timeout < TimeSpan.Zero && timeout != Timeout.InfiniteTimeSpan) { diff --git a/src/ModelContextProtocol/Client/McpClientExtensions.cs b/src/ModelContextProtocol/Client/McpClientExtensions.cs index 5d18d0b..9c2ca38 100644 --- a/src/ModelContextProtocol/Client/McpClientExtensions.cs +++ b/src/ModelContextProtocol/Client/McpClientExtensions.cs @@ -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; /// @@ -439,7 +437,7 @@ public static class McpClientExtensions } private static JsonRpcRequest CreateRequest(string method, Dictionary? parameters) => - new JsonRpcRequest + new() { Method = method, Params = parameters diff --git a/src/ModelContextProtocol/Configuration/McpServerBuilderExtensions.Transports.cs b/src/ModelContextProtocol/Configuration/McpServerBuilderExtensions.Transports.cs index 178d824..1f357d3 100644 --- a/src/ModelContextProtocol/Configuration/McpServerBuilderExtensions.Transports.cs +++ b/src/ModelContextProtocol/Configuration/McpServerBuilderExtensions.Transports.cs @@ -30,10 +30,7 @@ public static partial class McpServerBuilderExtensions /// The builder instance. public static IMcpServerBuilder WithHttpListenerSseServerTransport(this IMcpServerBuilder builder) { - if (builder is null) - { - throw new ArgumentNullException(nameof(builder)); - } + Throw.IfNull(builder); builder.Services.AddSingleton(); builder.Services.AddHostedService(); diff --git a/src/ModelContextProtocol/Configuration/McpServerOptionsSetup.cs b/src/ModelContextProtocol/Configuration/McpServerOptionsSetup.cs index 1121726..f4509d5 100644 --- a/src/ModelContextProtocol/Configuration/McpServerOptionsSetup.cs +++ b/src/ModelContextProtocol/Configuration/McpServerOptionsSetup.cs @@ -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 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 diff --git a/src/ModelContextProtocol/Protocol/Transport/HttpListenerServerProvider.cs b/src/ModelContextProtocol/Protocol/Transport/HttpListenerServerProvider.cs index eee8499..2dc81d9 100644 --- a/src/ModelContextProtocol/Protocol/Transport/HttpListenerServerProvider.cs +++ b/src/ModelContextProtocol/Protocol/Transport/HttpListenerServerProvider.cs @@ -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? _messageHandler; @@ -31,7 +34,7 @@ internal class HttpListenerServerProvider : IDisposable public Task GetSseEndpointUri() { - return Task.FromResult($"http://localhost:{_port}{_sseEndpoint}"); + return Task.FromResult($"http://localhost:{_port}{SseEndpoint}"); } public Task InitializeMessageHandler(Func 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 { diff --git a/src/ModelContextProtocol/Protocol/Transport/HttpListenerSseServerTransport.cs b/src/ModelContextProtocol/Protocol/Transport/HttpListenerSseServerTransport.cs index f385476..7bf9f0e 100644 --- a/src/ModelContextProtocol/Protocol/Transport/HttpListenerSseServerTransport.cs +++ b/src/ModelContextProtocol/Protocol/Transport/HttpListenerSseServerTransport.cs @@ -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 /// Validates the and extracts from it the server name to use. 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; } diff --git a/src/ModelContextProtocol/Protocol/Transport/StdioClientTransport.cs b/src/ModelContextProtocol/Protocol/Transport/StdioClientTransport.cs index d8986de..5479a30 100644 --- a/src/ModelContextProtocol/Protocol/Transport/StdioClientTransport.cs +++ b/src/ModelContextProtocol/Protocol/Transport/StdioClientTransport.cs @@ -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; /// diff --git a/src/ModelContextProtocol/Protocol/Transport/StdioServerTransport.cs b/src/ModelContextProtocol/Protocol/Transport/StdioServerTransport.cs index 2e81634..d63fe38 100644 --- a/src/ModelContextProtocol/Protocol/Transport/StdioServerTransport.cs +++ b/src/ModelContextProtocol/Protocol/Transport/StdioServerTransport.cs @@ -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; /// diff --git a/src/ModelContextProtocol/Shared/McpJsonRpcEndpoint.cs b/src/ModelContextProtocol/Shared/McpJsonRpcEndpoint.cs index c8fc983..caf2fd0 100644 --- a/src/ModelContextProtocol/Shared/McpJsonRpcEndpoint.cs +++ b/src/ModelContextProtocol/Shared/McpJsonRpcEndpoint.cs @@ -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 { diff --git a/src/ModelContextProtocol/Utils/Throw.cs b/src/ModelContextProtocol/Utils/Throw.cs index 524bda3..537990f 100644 --- a/src/ModelContextProtocol/Utils/Throw.cs +++ b/src/ModelContextProtocol/Utils/Throw.cs @@ -21,7 +21,7 @@ internal static class Throw { if (arg is null || arg.AsSpan().IsWhiteSpace()) { - ThrowArgumentNullOrWhiteSpaceException(arg); + ThrowArgumentNullOrWhiteSpaceException(parameterName); } } diff --git a/tests/ModelContextProtocol.TestServer/Program.cs b/tests/ModelContextProtocol.TestServer/Program.cs index 5cf3078..a59e1ec 100644 --- a/tests/ModelContextProtocol.TestServer/Program.cs +++ b/tests/ModelContextProtocol.TestServer/Program.cs @@ -45,7 +45,7 @@ internal static class Program GetCompletionHandler = ConfigureCompletion(), }; - var loggerFactory = CreateLoggerFactory(); + using var loggerFactory = CreateLoggerFactory(); await using IMcpServer server = McpServerFactory.Create(new StdioServerTransport("TestServer", loggerFactory), options, loggerFactory); Log.Logger.Information("Server initialized."); @@ -266,7 +266,7 @@ internal static class Program }; } - private static HashSet _subscribedResources = new(); + private static readonly HashSet _subscribedResources = new(); private static readonly object _subscribedResourcesLock = new(); private static ResourcesCapability ConfigureResources() diff --git a/tests/ModelContextProtocol.TestSseServer/Program.cs b/tests/ModelContextProtocol.TestSseServer/Program.cs index ae58174..7e9f5e4 100644 --- a/tests/ModelContextProtocol.TestSseServer/Program.cs +++ b/tests/ModelContextProtocol.TestSseServer/Program.cs @@ -29,7 +29,7 @@ internal class Program { Console.WriteLine("Starting server..."); - McpServerOptions options = new McpServerOptions() + McpServerOptions options = new() { ServerInfo = new Implementation() { Name = "TestServer", Version = "1.0.0" }, Capabilities = new ServerCapabilities() @@ -236,11 +236,10 @@ internal class Program { throw new McpServerException("Missing required argument 'uri'"); } - ResourceContents? contents = resourceContents.FirstOrDefault(r => r.Uri == request.Params.Uri); - if (contents is null) - { + + ResourceContents? contents = resourceContents.FirstOrDefault(r => r.Uri == request.Params.Uri) ?? throw new McpServerException("Resource not found"); - } + return Task.FromResult(new ReadResourceResult() { Contents = [contents] @@ -347,7 +346,7 @@ internal class Program }, }; - var loggerFactory = CreateLoggerFactory(); + using var loggerFactory = CreateLoggerFactory(); server = McpServerFactory.Create(new HttpListenerSseServerTransport("TestServer", 3001, loggerFactory), options, loggerFactory); Console.WriteLine("Server initialized."); @@ -357,10 +356,7 @@ internal class Program Console.WriteLine("Server started."); // Run until process is stopped by the client (parent process) - while (true) - { - await Task.Delay(1000); - } + await Task.Delay(Timeout.Infinite); } const string MCP_TINY_IMAGE = diff --git a/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsToolsTests.cs b/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsToolsTests.cs index 42c11e4..c169d1e 100644 --- a/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsToolsTests.cs +++ b/tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsToolsTests.cs @@ -220,18 +220,17 @@ public class McpServerBuilderExtensionsToolsTests Assert.Contains("'NotRegisteredTool'", exception.Message); } - // TODO: https://github.com/dotnet/extensions/issues/6124 - //[Fact] - //public async Task Throws_Exception_Missing_Parameter() - //{ - // _builder.Object.WithTools(typeof(EchoTool)); + [Fact(Skip = "https://github.com/dotnet/extensions/issues/6124")] + public async Task Throws_Exception_Missing_Parameter() + { + _builder.Object.WithTools(typeof(EchoTool)); - // var serviceProvider = _services.BuildServiceProvider(); - // var options = serviceProvider.GetRequiredService>().Value; + var serviceProvider = _services.BuildServiceProvider(); + var options = serviceProvider.GetRequiredService>().Value; - // var exception = await Assert.ThrowsAsync(async () => await options.CallToolHandler!(new(Mock.Of(), new() { Name = "Echo" }), CancellationToken.None)); - // Assert.Equal("Missing required argument 'message'.", exception.Message); - //} + var exception = await Assert.ThrowsAsync(async () => await options.CallToolHandler!(new(Mock.Of(), new() { Name = "Echo" }), CancellationToken.None)); + Assert.Equal("Missing required argument 'message'.", exception.Message); + } [Fact] public void Throws_Exception_For_Null_Types() @@ -357,26 +356,6 @@ public class McpServerBuilderExtensionsToolsTests } } - public interface IDependendService - { - } - - private class EchoToolWithDi - { - public EchoToolWithDi(IDependendService service) - { - - } - - [McpTool, Description("Echoes the input back to the client.")] -#pragma warning disable CA1822 // Mark members as static - public Task Echo(string message) -#pragma warning restore CA1822 // Mark members as static - { - return Task.FromResult("hello " + message); - } - } - public class ComplexObject { public string? Name { get; set; } diff --git a/tests/ModelContextProtocol.Tests/EverythingSseServerFixture.cs b/tests/ModelContextProtocol.Tests/EverythingSseServerFixture.cs index d39f8dd..fdd701d 100644 --- a/tests/ModelContextProtocol.Tests/EverythingSseServerFixture.cs +++ b/tests/ModelContextProtocol.Tests/EverythingSseServerFixture.cs @@ -4,8 +4,8 @@ namespace ModelContextProtocol.Tests; public class EverythingSseServerFixture : IAsyncDisposable { - private int _port; - private string _containerName; + private readonly int _port; + private readonly string _containerName; public static bool IsDockerAvailable => _isDockerAvailable ??= CheckIsDockerAvailable(); private static bool? _isDockerAvailable; diff --git a/tests/ModelContextProtocol.Tests/Protocol/ProtocolTypeTests.cs b/tests/ModelContextProtocol.Tests/Protocol/ProtocolTypeTests.cs index 25cda95..a5c4c6a 100644 --- a/tests/ModelContextProtocol.Tests/Protocol/ProtocolTypeTests.cs +++ b/tests/ModelContextProtocol.Tests/Protocol/ProtocolTypeTests.cs @@ -44,9 +44,11 @@ public static class ProtocolTypeTests public static void ToolInputSchema_AcceptsValidSchemaDocuments(string validSchema) { using var document = JsonDocument.Parse(validSchema); - var tool = new Tool(); + Tool tool = new() + { + InputSchema = document.RootElement + }; - tool.InputSchema = document.RootElement; Assert.True(JsonElement.DeepEquals(document.RootElement, tool.InputSchema)); } } diff --git a/tests/ModelContextProtocol.Tests/Transport/StdioServerTransportTests.cs b/tests/ModelContextProtocol.Tests/Transport/StdioServerTransportTests.cs index a150650..82ce821 100644 --- a/tests/ModelContextProtocol.Tests/Transport/StdioServerTransportTests.cs +++ b/tests/ModelContextProtocol.Tests/Transport/StdioServerTransportTests.cs @@ -64,7 +64,7 @@ public class StdioServerTransportTests TextWriter oldOut = Console.Out; try { - var output = new StringWriter(); + using var output = new StringWriter(); Console.SetIn(new StringReader("")); Console.SetOut(output); diff --git a/tests/ModelContextProtocol.Tests/Utils/MockHttpHandler.cs b/tests/ModelContextProtocol.Tests/Utils/MockHttpHandler.cs index 24968cb..5e58a6c 100644 --- a/tests/ModelContextProtocol.Tests/Utils/MockHttpHandler.cs +++ b/tests/ModelContextProtocol.Tests/Utils/MockHttpHandler.cs @@ -8,14 +8,12 @@ public class MockHttpHandler : HttpMessageHandler { if (RequestHandler == null) throw new InvalidOperationException($"No {nameof(RequestHandler)} was set! Please set handler first and make request afterwards."); - - if (cancellationToken.IsCancellationRequested) - throw new OperationCanceledException(cancellationToken); + + cancellationToken.ThrowIfCancellationRequested(); var result = await RequestHandler.Invoke(request); - - if (cancellationToken.IsCancellationRequested) - throw new OperationCanceledException(cancellationToken); + + cancellationToken.ThrowIfCancellationRequested(); return result; }