From 6e587b8498862c4f1ec8ef21d442d3003f258b1c Mon Sep 17 00:00:00 2001 From: Dogukan Demir Date: Fri, 1 Aug 2025 23:06:37 +0200 Subject: [PATCH 1/6] Elicitation extension methods to improve code readability and reduce potential string comparison errors --- .../ElicitResultExtensions.cs | 45 +++++++ .../Server/McpServerExtensions.cs | 17 +++ .../ElicitResultExtensionsTests.cs | 118 ++++++++++++++++++ .../Server/McpServerTests.cs | 25 ++++ 4 files changed, 205 insertions(+) create mode 100644 src/ModelContextProtocol.Core/ExtensionMethods/ElicitResultExtensions.cs create mode 100644 tests/ModelContextProtocol.Tests/ExtensionMethods/ElicitResultExtensionsTests.cs diff --git a/src/ModelContextProtocol.Core/ExtensionMethods/ElicitResultExtensions.cs b/src/ModelContextProtocol.Core/ExtensionMethods/ElicitResultExtensions.cs new file mode 100644 index 00000000..517a04ff --- /dev/null +++ b/src/ModelContextProtocol.Core/ExtensionMethods/ElicitResultExtensions.cs @@ -0,0 +1,45 @@ +using ModelContextProtocol.Protocol; + +namespace ModelContextProtocol.ExtensionMethods; + +/// +/// Provides extension methods for interacting with an instance. +/// +public static class ElicitResultExtensions +{ + /// + /// Determines whether given represents an accepted action. + /// + /// Elicit result to check. + /// if the action is "accept"; otherwise, . + /// Thrown when is . + public static bool IsAccepted(this ElicitResult result) + { + Throw.IfNull(result); + return string.Equals(result.Action, "accept", StringComparison.OrdinalIgnoreCase); + } + + /// + /// Determines whether given represents a declined action. + /// + /// Elicit result to check. + /// if the action is "decline"; otherwise, . + /// Thrown when is . + public static bool IsDeclined(this ElicitResult result) + { + Throw.IfNull(result); + return string.Equals(result.Action, "decline", StringComparison.OrdinalIgnoreCase); + } + + /// + /// Determines whether given represents a cancelled action. + /// + /// Elicit result to check. + /// if the action is "cancel"; otherwise, . + /// Thrown when is . + public static bool IsCancelled(this ElicitResult result) + { + Throw.IfNull(result); + return string.Equals(result.Action, "cancel", StringComparison.OrdinalIgnoreCase); + } +} diff --git a/src/ModelContextProtocol.Core/Server/McpServerExtensions.cs b/src/ModelContextProtocol.Core/Server/McpServerExtensions.cs index 277ed737..94569d97 100644 --- a/src/ModelContextProtocol.Core/Server/McpServerExtensions.cs +++ b/src/ModelContextProtocol.Core/Server/McpServerExtensions.cs @@ -234,6 +234,23 @@ public static ValueTask ElicitAsync( cancellationToken: cancellationToken); } + /// + /// Determines whether client supports elicitation capability. + /// + /// McpServer instance to check. + /// + /// if client supports elicitation requests; otherwise, . + /// + /// is . + /// + /// When , the server can call to request additional information from the user via the client. + /// + public static bool SupportsElicitation(this IMcpServer server) + { + Throw.IfNull(server); + return server.ClientCapabilities?.Elicitation is not null; + } + private static void ThrowIfSamplingUnsupported(IMcpServer server) { if (server.ClientCapabilities?.Sampling is null) diff --git a/tests/ModelContextProtocol.Tests/ExtensionMethods/ElicitResultExtensionsTests.cs b/tests/ModelContextProtocol.Tests/ExtensionMethods/ElicitResultExtensionsTests.cs new file mode 100644 index 00000000..13383705 --- /dev/null +++ b/tests/ModelContextProtocol.Tests/ExtensionMethods/ElicitResultExtensionsTests.cs @@ -0,0 +1,118 @@ +using ModelContextProtocol.ExtensionMethods; +using ModelContextProtocol.Protocol; + +namespace ModelContextProtocol.Tests.ExtensionMethods; + +public class ElicitResultExtensionsTests +{ + [Fact] + public void IsAccepted_Throws_ArgumentNullException_If_Result_Is_Null() + { + // Arrange + ElicitResult? result = null; + + // Act & Assert + Assert.Throws(() => result!.IsAccepted()); + } + + [Fact] + public void IsDeclined_Throws_ArgumentNullException_If_Result_Is_Null() + { + // Arrange + ElicitResult? result = null; + + // Act & Assert + Assert.Throws(() => result!.IsDeclined()); + } + + [Fact] + public void IsCancelled_Throws_ArgumentNullException_If_Result_Is_Null() + { + // Arrange + ElicitResult? result = null; + + // Act & Assert + Assert.Throws(() => result!.IsCancelled()); + } + + [Theory] + [InlineData("accept")] + [InlineData("ACCEPT")] + [InlineData("Accept")] + [InlineData("AccEpt")] + public void IsAccepted_Returns_True_For_VariousActions(string action) + { + // Arrange + var result = new ElicitResult { Action = action }; + + // Act & Assert + Assert.True(result.IsAccepted()); + } + + [Theory] + [InlineData("decline")] + [InlineData("cancel")] + [InlineData("unknown")] + public void IsAccepted_Returns_False_For_NonAcceptedActions(string action) + { + // Arrange + var result = new ElicitResult { Action = action }; + + // Act & Assert + Assert.False(result.IsAccepted()); + } + + [Theory] + [InlineData("accept")] + [InlineData("cancel")] + [InlineData("unknown")] + public void IsDeclined_Returns_False_For_NonDeclinedActions(string action) + { + // Arrange + var result = new ElicitResult { Action = action }; + + // Act & Assert + Assert.False(result.IsDeclined()); + } + + [Theory] + [InlineData("accept")] + [InlineData("decline")] + [InlineData("unknown")] + public void IsCancelled_Returns_False_For_NonCancelledActions(string action) + { + // Arrange + var result = new ElicitResult { Action = action }; + + // Act & Assert + Assert.False(result.IsCancelled()); + } + + [Theory] + [InlineData("decline")] + [InlineData("DECLINE")] + [InlineData("Decline")] + [InlineData("DecLine")] + public void IsDeclined_Returns_True_For_VariousActions(string action) + { + // Arrange + var result = new ElicitResult { Action = action }; + + // Act & Assert + Assert.True(result.IsDeclined()); + } + + [Theory] + [InlineData("cancel")] + [InlineData("CANCEL")] + [InlineData("Cancel")] + [InlineData("CanCel")] + public void IsCancelled_Returns_True_For_VariousActions(string action) + { + // Arrange + var result = new ElicitResult { Action = action }; + + // Act & Assert + Assert.True(result.IsCancelled()); + } +} \ No newline at end of file diff --git a/tests/ModelContextProtocol.Tests/Server/McpServerTests.cs b/tests/ModelContextProtocol.Tests/Server/McpServerTests.cs index 6750b2ca..790559c1 100644 --- a/tests/ModelContextProtocol.Tests/Server/McpServerTests.cs +++ b/tests/ModelContextProtocol.Tests/Server/McpServerTests.cs @@ -177,6 +177,31 @@ public async Task ElicitAsync_Should_Throw_Exception_If_Client_Does_Not_Support_ await Assert.ThrowsAsync(async () => await server.ElicitAsync(new ElicitRequestParams(), CancellationToken.None)); } + [Fact] + public void SupportsElicitation_Should_Throw_ArgumentNullException_If_Server_Is_Null() + { + // Arrange + IMcpServer? server = null; + + // Act & Assert + Assert.Throws(() => server!.SupportsElicitation()); + } + + [Fact] + public async Task SupportsElicitation_Should_Return_False_If_ElicitationCapability_Is_Not_Set() + { + // Arrange + await using var transport = new TestServerTransport(); + await using var server = McpServerFactory.Create(transport, _options, LoggerFactory); + SetClientCapabilities(server, new ClientCapabilities { Elicitation = null }); + + // Act + var supportsElicitation = server.SupportsElicitation(); + + // Assert + Assert.False(supportsElicitation); + } + [Fact] public async Task ElicitAsync_Should_SendRequest() { From 8c1cff522d44521b5e333e6c5588f29c425e5cca Mon Sep 17 00:00:00 2001 From: Dogukan Demir Date: Fri, 1 Aug 2025 23:54:04 +0200 Subject: [PATCH 2/6] Replace ElicitResult extension methods with JsonIgnore properties --- .../ElicitResultExtensions.cs | 45 ----- .../Protocol/ElicitResult.cs | 21 +++ .../ElicitResultExtensionsTests.cs | 118 ------------- .../Protocol/ElicitResultTests.cs | 160 ++++++++++++++++++ 4 files changed, 181 insertions(+), 163 deletions(-) delete mode 100644 src/ModelContextProtocol.Core/ExtensionMethods/ElicitResultExtensions.cs delete mode 100644 tests/ModelContextProtocol.Tests/ExtensionMethods/ElicitResultExtensionsTests.cs create mode 100644 tests/ModelContextProtocol.Tests/Protocol/ElicitResultTests.cs diff --git a/src/ModelContextProtocol.Core/ExtensionMethods/ElicitResultExtensions.cs b/src/ModelContextProtocol.Core/ExtensionMethods/ElicitResultExtensions.cs deleted file mode 100644 index 517a04ff..00000000 --- a/src/ModelContextProtocol.Core/ExtensionMethods/ElicitResultExtensions.cs +++ /dev/null @@ -1,45 +0,0 @@ -using ModelContextProtocol.Protocol; - -namespace ModelContextProtocol.ExtensionMethods; - -/// -/// Provides extension methods for interacting with an instance. -/// -public static class ElicitResultExtensions -{ - /// - /// Determines whether given represents an accepted action. - /// - /// Elicit result to check. - /// if the action is "accept"; otherwise, . - /// Thrown when is . - public static bool IsAccepted(this ElicitResult result) - { - Throw.IfNull(result); - return string.Equals(result.Action, "accept", StringComparison.OrdinalIgnoreCase); - } - - /// - /// Determines whether given represents a declined action. - /// - /// Elicit result to check. - /// if the action is "decline"; otherwise, . - /// Thrown when is . - public static bool IsDeclined(this ElicitResult result) - { - Throw.IfNull(result); - return string.Equals(result.Action, "decline", StringComparison.OrdinalIgnoreCase); - } - - /// - /// Determines whether given represents a cancelled action. - /// - /// Elicit result to check. - /// if the action is "cancel"; otherwise, . - /// Thrown when is . - public static bool IsCancelled(this ElicitResult result) - { - Throw.IfNull(result); - return string.Equals(result.Action, "cancel", StringComparison.OrdinalIgnoreCase); - } -} diff --git a/src/ModelContextProtocol.Core/Protocol/ElicitResult.cs b/src/ModelContextProtocol.Core/Protocol/ElicitResult.cs index 9e7f1c34..5a738b30 100644 --- a/src/ModelContextProtocol.Core/Protocol/ElicitResult.cs +++ b/src/ModelContextProtocol.Core/Protocol/ElicitResult.cs @@ -47,4 +47,25 @@ public sealed class ElicitResult : Result /// [JsonPropertyName("content")] public IDictionary? Content { get; set; } + + /// + /// Gets a value indicating whether the user accepted the elicitation request. + /// + /// if the action is "accept"; otherwise, . + [JsonIgnore] + public bool IsAccepted => string.Equals(Action, "accept", StringComparison.OrdinalIgnoreCase); + + /// + /// Gets a value indicating whether the user declined the elicitation request. + /// + /// if the action is "decline"; otherwise, . + [JsonIgnore] + public bool IsDeclined => string.Equals(Action, "decline", StringComparison.OrdinalIgnoreCase); + + /// + /// Gets a value indicating whether the user cancelled the elicitation request. + /// + /// if the action is "cancel"; otherwise, . + [JsonIgnore] + public bool IsCancelled => string.Equals(Action, "cancel", StringComparison.OrdinalIgnoreCase); } diff --git a/tests/ModelContextProtocol.Tests/ExtensionMethods/ElicitResultExtensionsTests.cs b/tests/ModelContextProtocol.Tests/ExtensionMethods/ElicitResultExtensionsTests.cs deleted file mode 100644 index 13383705..00000000 --- a/tests/ModelContextProtocol.Tests/ExtensionMethods/ElicitResultExtensionsTests.cs +++ /dev/null @@ -1,118 +0,0 @@ -using ModelContextProtocol.ExtensionMethods; -using ModelContextProtocol.Protocol; - -namespace ModelContextProtocol.Tests.ExtensionMethods; - -public class ElicitResultExtensionsTests -{ - [Fact] - public void IsAccepted_Throws_ArgumentNullException_If_Result_Is_Null() - { - // Arrange - ElicitResult? result = null; - - // Act & Assert - Assert.Throws(() => result!.IsAccepted()); - } - - [Fact] - public void IsDeclined_Throws_ArgumentNullException_If_Result_Is_Null() - { - // Arrange - ElicitResult? result = null; - - // Act & Assert - Assert.Throws(() => result!.IsDeclined()); - } - - [Fact] - public void IsCancelled_Throws_ArgumentNullException_If_Result_Is_Null() - { - // Arrange - ElicitResult? result = null; - - // Act & Assert - Assert.Throws(() => result!.IsCancelled()); - } - - [Theory] - [InlineData("accept")] - [InlineData("ACCEPT")] - [InlineData("Accept")] - [InlineData("AccEpt")] - public void IsAccepted_Returns_True_For_VariousActions(string action) - { - // Arrange - var result = new ElicitResult { Action = action }; - - // Act & Assert - Assert.True(result.IsAccepted()); - } - - [Theory] - [InlineData("decline")] - [InlineData("cancel")] - [InlineData("unknown")] - public void IsAccepted_Returns_False_For_NonAcceptedActions(string action) - { - // Arrange - var result = new ElicitResult { Action = action }; - - // Act & Assert - Assert.False(result.IsAccepted()); - } - - [Theory] - [InlineData("accept")] - [InlineData("cancel")] - [InlineData("unknown")] - public void IsDeclined_Returns_False_For_NonDeclinedActions(string action) - { - // Arrange - var result = new ElicitResult { Action = action }; - - // Act & Assert - Assert.False(result.IsDeclined()); - } - - [Theory] - [InlineData("accept")] - [InlineData("decline")] - [InlineData("unknown")] - public void IsCancelled_Returns_False_For_NonCancelledActions(string action) - { - // Arrange - var result = new ElicitResult { Action = action }; - - // Act & Assert - Assert.False(result.IsCancelled()); - } - - [Theory] - [InlineData("decline")] - [InlineData("DECLINE")] - [InlineData("Decline")] - [InlineData("DecLine")] - public void IsDeclined_Returns_True_For_VariousActions(string action) - { - // Arrange - var result = new ElicitResult { Action = action }; - - // Act & Assert - Assert.True(result.IsDeclined()); - } - - [Theory] - [InlineData("cancel")] - [InlineData("CANCEL")] - [InlineData("Cancel")] - [InlineData("CanCel")] - public void IsCancelled_Returns_True_For_VariousActions(string action) - { - // Arrange - var result = new ElicitResult { Action = action }; - - // Act & Assert - Assert.True(result.IsCancelled()); - } -} \ No newline at end of file diff --git a/tests/ModelContextProtocol.Tests/Protocol/ElicitResultTests.cs b/tests/ModelContextProtocol.Tests/Protocol/ElicitResultTests.cs new file mode 100644 index 00000000..1176c318 --- /dev/null +++ b/tests/ModelContextProtocol.Tests/Protocol/ElicitResultTests.cs @@ -0,0 +1,160 @@ +using System.Text.Json; +using ModelContextProtocol.Protocol; + +namespace ModelContextProtocol.Tests.Protocol; + +public class ElicitResultTests +{ + [Theory] + [InlineData("accept")] + [InlineData("Accept")] + [InlineData("ACCEPT")] + [InlineData("AccEpt")] + public void IsAccepted_Returns_True_For_VariousAcceptedActions(string action) + { + // Arrange + var result = new ElicitResult { Action = action }; + + // Act + var isAccepted = result.IsAccepted; + + // Assert + Assert.True(isAccepted); + } + + [Theory] + [InlineData("decline")] + [InlineData("Decline")] + [InlineData("DECLINE")] + [InlineData("DecLine")] + public void IsDeclined_Returns_True_For_VariousDeclinedActions(string action) + { + // Arrange + var result = new ElicitResult { Action = action }; + + // Act + var isDeclined = result.IsDeclined; + + // Assert + Assert.True(isDeclined); + } + + [Theory] + [InlineData("cancel")] + [InlineData("Cancel")] + [InlineData("CANCEL")] + [InlineData("CanCel")] + public void IsCancelled_Returns_True_For_VariousCancelledActions(string action) + { + // Arrange + var result = new ElicitResult { Action = action }; + + // Act + var isCancelled = result.IsCancelled; + + // Assert + Assert.True(isCancelled); + } + + [Fact] + public void IsAccepted_Returns_False_For_DefaultAction() + { + // Arrange + var result = new ElicitResult(); + + // Act & Assert + Assert.False(result.IsAccepted); + } + + [Fact] + public void IsDeclined_Returns_False_For_DefaultAction() + { + // Arrange + var result = new ElicitResult(); + + // Act & Assert + Assert.False(result.IsDeclined); + } + + [Fact] + public void IsCancelled_Returns_True_For_DefaultAction() + { + // Arrange + var result = new ElicitResult(); + + // Act & Assert + Assert.True(result.IsCancelled); + } + + [Fact] + public void IsAccepted_Returns_False_For_Null_Action() + { + // Arrange + var result = new ElicitResult { Action = null! }; + + // Act & Assert + Assert.False(result.IsAccepted); + } + + [Fact] + public void IsDeclined_Returns_False_For_Null_Action() + { + // Arrange + var result = new ElicitResult { Action = null! }; + + // Act & Assert + Assert.False(result.IsDeclined); + } + + [Fact] + public void IsCancelled_Returns_False_For_Null_Action() + { + // Arrange + var result = new ElicitResult { Action = null! }; + + // Act & Assert + Assert.False(result.IsCancelled); + } + + [Theory] + [InlineData("accept")] + [InlineData("decline")] + [InlineData("cancel")] + [InlineData("unknown")] + public void JsonSerialization_ExcludesJsonIgnoredProperties(string action) + { + // Arrange + var result = new ElicitResult { Action = action }; + + // Act + var json = JsonSerializer.Serialize(result, McpJsonUtilities.DefaultOptions); + + // Assert + Assert.DoesNotContain("IsAccepted", json); + Assert.DoesNotContain("IsDeclined", json); + Assert.DoesNotContain("IsCancelled", json); + Assert.Contains($"\"action\":\"{action}\"", json); + } + + [Theory] + [InlineData("accept", true, false, false)] + [InlineData("decline", false, true, false)] + [InlineData("cancel", false, false, true)] + [InlineData("unknown", false, false, false)] + public void JsonRoundTrip_PreservesActionAndComputedProperties(string action, bool isAccepted, bool isDeclined, bool isCancelled) + { + // Arrange + var result = new ElicitResult { Action = action }; + + // Act + var json = JsonSerializer.Serialize(result, McpJsonUtilities.DefaultOptions); + var deserialized = JsonSerializer.Deserialize(json, McpJsonUtilities.DefaultOptions); + + // Assert + Assert.NotNull(deserialized); + Assert.Equal(action, deserialized.Action); + Assert.Equal(isAccepted, deserialized.IsAccepted); + Assert.Equal(isDeclined, deserialized.IsDeclined); + Assert.Equal(isCancelled, deserialized.IsCancelled); + } +} \ No newline at end of file From 6d1bf76a2683a08d4ea649b7267f81ede592b44b Mon Sep 17 00:00:00 2001 From: Dogukan Demir Date: Wed, 6 Aug 2025 22:28:34 +0200 Subject: [PATCH 3/6] Add ClientSupportsRoots and ClientSupportsSampling methods rename SupportsElicitation to ClientSupportsElicitation --- .../Server/McpServerExtensions.cs | 36 +++++- .../Server/McpServerTests.cs | 105 +++++++++++++++++- 2 files changed, 135 insertions(+), 6 deletions(-) diff --git a/src/ModelContextProtocol.Core/Server/McpServerExtensions.cs b/src/ModelContextProtocol.Core/Server/McpServerExtensions.cs index 94569d97..92dfc7fd 100644 --- a/src/ModelContextProtocol.Core/Server/McpServerExtensions.cs +++ b/src/ModelContextProtocol.Core/Server/McpServerExtensions.cs @@ -245,12 +245,46 @@ public static ValueTask ElicitAsync( /// /// When , the server can call to request additional information from the user via the client. /// - public static bool SupportsElicitation(this IMcpServer server) + public static bool ClientSupportsElicitation(this IMcpServer server) { Throw.IfNull(server); return server.ClientCapabilities?.Elicitation is not null; } + /// + /// Determines whether client supports roots capability. + /// + /// McpServer instance to check. + /// + /// if client supports roots requests; otherwise, . + /// + /// is . + /// + /// When , the server can call to request the list of roots exposed by the client. + /// + public static bool ClientSupportsRoots(this IMcpServer server) + { + Throw.IfNull(server); + return server.ClientCapabilities?.Roots is not null; + } + + /// + /// Determines whether client supports sampling capability. + /// + /// McpServer instance to check. + /// + /// if client supports sampling requests; otherwise, . + /// + /// is . + /// + /// When , the server can call sampling methods to request LLM sampling via the client. + /// + public static bool ClientSupportsSampling(this IMcpServer server) + { + Throw.IfNull(server); + return server.ClientCapabilities?.Sampling is not null; + } + private static void ThrowIfSamplingUnsupported(IMcpServer server) { if (server.ClientCapabilities?.Sampling is null) diff --git a/tests/ModelContextProtocol.Tests/Server/McpServerTests.cs b/tests/ModelContextProtocol.Tests/Server/McpServerTests.cs index 790559c1..150101ad 100644 --- a/tests/ModelContextProtocol.Tests/Server/McpServerTests.cs +++ b/tests/ModelContextProtocol.Tests/Server/McpServerTests.cs @@ -178,17 +178,17 @@ public async Task ElicitAsync_Should_Throw_Exception_If_Client_Does_Not_Support_ } [Fact] - public void SupportsElicitation_Should_Throw_ArgumentNullException_If_Server_Is_Null() + public void ClientSupportsElicitation_Should_Throw_ArgumentNullException_If_Server_Is_Null() { // Arrange IMcpServer? server = null; // Act & Assert - Assert.Throws(() => server!.SupportsElicitation()); + Assert.Throws(() => server!.ClientSupportsElicitation()); } [Fact] - public async Task SupportsElicitation_Should_Return_False_If_ElicitationCapability_Is_Not_Set() + public async Task ClientSupportsElicitation_Should_Return_False_If_ElicitationCapability_Is_Not_Set() { // Arrange await using var transport = new TestServerTransport(); @@ -196,10 +196,105 @@ public async Task SupportsElicitation_Should_Return_False_If_ElicitationCapabili SetClientCapabilities(server, new ClientCapabilities { Elicitation = null }); // Act - var supportsElicitation = server.SupportsElicitation(); + var clientSupportsElicitation = server.ClientSupportsElicitation(); // Assert - Assert.False(supportsElicitation); + Assert.False(clientSupportsElicitation); + } + + [Fact] + public async Task ClientSupportsElicitation_Should_Return_True_If_ElicitationCapability_Is_Set() + { + // Arrange + await using var transport = new TestServerTransport(); + await using var server = McpServerFactory.Create(transport, _options, LoggerFactory); + SetClientCapabilities(server, new ClientCapabilities { Elicitation = new ElicitationCapability() }); + + // Act + var clientSupportsElicitation = server.ClientSupportsElicitation(); + + // Assert + Assert.True(clientSupportsElicitation); + } + + [Fact] + public void ClientSupportsRoots_Should_Throw_ArgumentNullException_If_Server_Is_Null() + { + // Arrange + IMcpServer? server = null; + + // Act & Assert + Assert.Throws(() => server!.ClientSupportsRoots()); + } + + [Fact] + public async Task ClientSupportsRoots_Should_Return_False_If_RootsCapability_Is_Not_Set() + { + // Arrange + await using var transport = new TestServerTransport(); + await using var server = McpServerFactory.Create(transport, _options, LoggerFactory); + SetClientCapabilities(server, new ClientCapabilities { Roots = null }); + + // Act + var clientSupportsRoots = server.ClientSupportsRoots(); + + // Assert + Assert.False(clientSupportsRoots); + } + + [Fact] + public async Task ClientSupportsRoots_Should_Return_True_If_RootsCapability_Is_Set() + { + // Arrange + await using var transport = new TestServerTransport(); + await using var server = McpServerFactory.Create(transport, _options, LoggerFactory); + SetClientCapabilities(server, new ClientCapabilities { Roots = new RootsCapability() }); + + // Act + var clientSupportsRoots = server.ClientSupportsRoots(); + + // Assert + Assert.True(clientSupportsRoots); + } + + [Fact] + public void ClientSupportsSampling_Should_Throw_ArgumentNullException_If_Server_Is_Null() + { + // Arrange + IMcpServer? server = null; + + // Act & Assert + Assert.Throws(() => server!.ClientSupportsSampling()); + } + + [Fact] + public async Task ClientSupportsSampling_Should_Return_False_If_SamplingCapability_Is_Not_Set() + { + // Arrange + await using var transport = new TestServerTransport(); + await using var server = McpServerFactory.Create(transport, _options, LoggerFactory); + SetClientCapabilities(server, new ClientCapabilities { Sampling = null }); + + // Act + var clientSupportsSampling = server.ClientSupportsSampling(); + + // Assert + Assert.False(clientSupportsSampling); + } + + [Fact] + public async Task ClientSupportsSampling_Should_Return_True_If_SamplingCapability_Is_Set() + { + // Arrange + await using var transport = new TestServerTransport(); + await using var server = McpServerFactory.Create(transport, _options, LoggerFactory); + SetClientCapabilities(server, new ClientCapabilities { Sampling = new SamplingCapability() }); + + // Act + var clientSupportsSampling = server.ClientSupportsSampling(); + + // Assert + Assert.True(clientSupportsSampling); } [Fact] From c3b6d810cbbd36710bc60b17a7f59000a34f920e Mon Sep 17 00:00:00 2001 From: Dogukan Demir Date: Wed, 6 Aug 2025 22:35:25 +0200 Subject: [PATCH 4/6] rename ThrowIfXXXUnsupported methods to ThrowIfClientXXXUnsupported --- .../Server/McpServerExtensions.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/ModelContextProtocol.Core/Server/McpServerExtensions.cs b/src/ModelContextProtocol.Core/Server/McpServerExtensions.cs index 92dfc7fd..2c194d65 100644 --- a/src/ModelContextProtocol.Core/Server/McpServerExtensions.cs +++ b/src/ModelContextProtocol.Core/Server/McpServerExtensions.cs @@ -30,7 +30,7 @@ public static ValueTask SampleAsync( this IMcpServer server, CreateMessageRequestParams request, CancellationToken cancellationToken = default) { Throw.IfNull(server); - ThrowIfSamplingUnsupported(server); + ThrowIfClientSamplingUnsupported(server); return server.SendRequestAsync( RequestMethods.SamplingCreateMessage, @@ -164,7 +164,7 @@ public static async Task SampleAsync( public static IChatClient AsSamplingChatClient(this IMcpServer server) { Throw.IfNull(server); - ThrowIfSamplingUnsupported(server); + ThrowIfClientSamplingUnsupported(server); return new SamplingChatClient(server); } @@ -198,7 +198,7 @@ public static ValueTask RequestRootsAsync( this IMcpServer server, ListRootsRequestParams request, CancellationToken cancellationToken = default) { Throw.IfNull(server); - ThrowIfRootsUnsupported(server); + ThrowIfClientRootsUnsupported(server); return server.SendRequestAsync( RequestMethods.RootsList, @@ -224,7 +224,7 @@ public static ValueTask ElicitAsync( this IMcpServer server, ElicitRequestParams request, CancellationToken cancellationToken = default) { Throw.IfNull(server); - ThrowIfElicitationUnsupported(server); + ThrowIfClientElicitationUnsupported(server); return server.SendRequestAsync( RequestMethods.ElicitationCreate, @@ -285,7 +285,7 @@ public static bool ClientSupportsSampling(this IMcpServer server) return server.ClientCapabilities?.Sampling is not null; } - private static void ThrowIfSamplingUnsupported(IMcpServer server) + private static void ThrowIfClientSamplingUnsupported(IMcpServer server) { if (server.ClientCapabilities?.Sampling is null) { @@ -298,7 +298,7 @@ private static void ThrowIfSamplingUnsupported(IMcpServer server) } } - private static void ThrowIfRootsUnsupported(IMcpServer server) + private static void ThrowIfClientRootsUnsupported(IMcpServer server) { if (server.ClientCapabilities?.Roots is null) { @@ -311,7 +311,7 @@ private static void ThrowIfRootsUnsupported(IMcpServer server) } } - private static void ThrowIfElicitationUnsupported(IMcpServer server) + private static void ThrowIfClientElicitationUnsupported(IMcpServer server) { if (server.ClientCapabilities?.Elicitation is null) { From 122b4d49e1a5f3adc89ba2adadff0c2c65567ab3 Mon Sep 17 00:00:00 2001 From: Dogukan Demir Date: Thu, 14 Aug 2025 23:39:20 +0200 Subject: [PATCH 5/6] rename IsCancelled to IsCanceled - American English --- src/ModelContextProtocol.Core/Protocol/ElicitResult.cs | 2 +- .../Protocol/ElicitResultTests.cs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ModelContextProtocol.Core/Protocol/ElicitResult.cs b/src/ModelContextProtocol.Core/Protocol/ElicitResult.cs index 5a738b30..705380b6 100644 --- a/src/ModelContextProtocol.Core/Protocol/ElicitResult.cs +++ b/src/ModelContextProtocol.Core/Protocol/ElicitResult.cs @@ -67,5 +67,5 @@ public sealed class ElicitResult : Result /// /// if the action is "cancel"; otherwise, . [JsonIgnore] - public bool IsCancelled => string.Equals(Action, "cancel", StringComparison.OrdinalIgnoreCase); + public bool IsCanceled => string.Equals(Action, "cancel", StringComparison.OrdinalIgnoreCase); } diff --git a/tests/ModelContextProtocol.Tests/Protocol/ElicitResultTests.cs b/tests/ModelContextProtocol.Tests/Protocol/ElicitResultTests.cs index 1176c318..283e30dd 100644 --- a/tests/ModelContextProtocol.Tests/Protocol/ElicitResultTests.cs +++ b/tests/ModelContextProtocol.Tests/Protocol/ElicitResultTests.cs @@ -50,7 +50,7 @@ public void IsCancelled_Returns_True_For_VariousCancelledActions(string action) var result = new ElicitResult { Action = action }; // Act - var isCancelled = result.IsCancelled; + var isCancelled = result.IsCanceled; // Assert Assert.True(isCancelled); @@ -83,7 +83,7 @@ public void IsCancelled_Returns_True_For_DefaultAction() var result = new ElicitResult(); // Act & Assert - Assert.True(result.IsCancelled); + Assert.True(result.IsCanceled); } [Fact] @@ -113,7 +113,7 @@ public void IsCancelled_Returns_False_For_Null_Action() var result = new ElicitResult { Action = null! }; // Act & Assert - Assert.False(result.IsCancelled); + Assert.False(result.IsCanceled); } [Theory] @@ -132,7 +132,7 @@ public void JsonSerialization_ExcludesJsonIgnoredProperties(string action) // Assert Assert.DoesNotContain("IsAccepted", json); Assert.DoesNotContain("IsDeclined", json); - Assert.DoesNotContain("IsCancelled", json); + Assert.DoesNotContain("IsCanceled", json); Assert.Contains($"\"action\":\"{action}\"", json); } @@ -155,6 +155,6 @@ public void JsonRoundTrip_PreservesActionAndComputedProperties(string action, bo Assert.Equal(action, deserialized.Action); Assert.Equal(isAccepted, deserialized.IsAccepted); Assert.Equal(isDeclined, deserialized.IsDeclined); - Assert.Equal(isCancelled, deserialized.IsCancelled); + Assert.Equal(isCancelled, deserialized.IsCanceled); } } \ No newline at end of file From a3d2d363a6a474e00ca9cd0c6f0f75ccc7f340a3 Mon Sep 17 00:00:00 2001 From: Dogukan Demir Date: Fri, 15 Aug 2025 00:27:52 +0200 Subject: [PATCH 6/6] use canceled in xml comment --- src/ModelContextProtocol.Core/Protocol/ElicitResult.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ModelContextProtocol.Core/Protocol/ElicitResult.cs b/src/ModelContextProtocol.Core/Protocol/ElicitResult.cs index 705380b6..cd44a583 100644 --- a/src/ModelContextProtocol.Core/Protocol/ElicitResult.cs +++ b/src/ModelContextProtocol.Core/Protocol/ElicitResult.cs @@ -63,7 +63,7 @@ public sealed class ElicitResult : Result public bool IsDeclined => string.Equals(Action, "decline", StringComparison.OrdinalIgnoreCase); /// - /// Gets a value indicating whether the user cancelled the elicitation request. + /// Gets a value indicating whether the user canceled the elicitation request. /// /// if the action is "cancel"; otherwise, . [JsonIgnore]