From ee37372117054b11b43eebcd48451edc1608997e Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 17 Feb 2026 08:17:07 +0000
Subject: [PATCH 1/6] Initial plan
From 31e2e479f92e045fa542ac7d485c83c81847dd81 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 17 Feb 2026 08:22:46 +0000
Subject: [PATCH 2/6] Add MCP Extensions support to ClientCapabilities and
ServerCapabilities
Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
---
docs/list-of-diagnostics.md | 2 +-
.../Protocol/ClientCapabilities.cs | 20 +++++++++++++++++++
.../Protocol/ServerCapabilities.cs | 20 +++++++++++++++++++
3 files changed, 41 insertions(+), 1 deletion(-)
diff --git a/docs/list-of-diagnostics.md b/docs/list-of-diagnostics.md
index 386b90bc2..573d97fea 100644
--- a/docs/list-of-diagnostics.md
+++ b/docs/list-of-diagnostics.md
@@ -23,4 +23,4 @@ If you use experimental APIs, you will get one of the diagnostics shown below. T
| Diagnostic ID | Description |
| :------------ | :---------- |
-| `MCPEXP001` | MCP task-related APIs are experimental. Tasks provide a mechanism for asynchronous long-running operations that can be polled for status and results. See [MCP Tasks specification](https://modelcontextprotocol.io/specification/draft/basic/utilities/tasks) for details. |
\ No newline at end of file
+| `MCPEXP001` | MCP experimental APIs including Tasks and Extensions. Tasks provide a mechanism for asynchronous long-running operations that can be polled for status and results (see [MCP Tasks specification](https://modelcontextprotocol.io/specification/draft/basic/utilities/tasks)). Extensions provide a framework for extending the Model Context Protocol while maintaining interoperability (see [SEP-2133](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2133)). |
\ No newline at end of file
diff --git a/src/ModelContextProtocol.Core/Protocol/ClientCapabilities.cs b/src/ModelContextProtocol.Core/Protocol/ClientCapabilities.cs
index cb85ef5e3..199bd53e3 100644
--- a/src/ModelContextProtocol.Core/Protocol/ClientCapabilities.cs
+++ b/src/ModelContextProtocol.Core/Protocol/ClientCapabilities.cs
@@ -1,4 +1,5 @@
using System.ComponentModel;
+using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization;
using ModelContextProtocol.Client;
using ModelContextProtocol.Server;
@@ -82,4 +83,23 @@ public sealed class ClientCapabilities
///
[JsonPropertyName("tasks")]
public McpTasksCapability? Tasks { get; set; }
+
+ ///
+ /// Gets or sets optional MCP extensions that the client supports.
+ ///
+ ///
+ ///
+ /// Keys are extension identifiers in reverse domain notation with an extension name
+ /// (e.g., "io.modelcontextprotocol/oauth-client-credentials"), and values are
+ /// per-extension settings objects. An empty object indicates support with no additional settings.
+ ///
+ ///
+ /// Extensions provide a framework for extending the Model Context Protocol while maintaining
+ /// interoperability. Both clients and servers advertise extension support via this field during
+ /// the initialization handshake.
+ ///
+ ///
+ [JsonPropertyName("extensions")]
+ [Experimental(Experimentals.Tasks_DiagnosticId, UrlFormat = Experimentals.Tasks_Url)] // SEP-2133: https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2133
+ public IDictionary? Extensions { get; set; }
}
diff --git a/src/ModelContextProtocol.Core/Protocol/ServerCapabilities.cs b/src/ModelContextProtocol.Core/Protocol/ServerCapabilities.cs
index 499819662..a17e8ef63 100644
--- a/src/ModelContextProtocol.Core/Protocol/ServerCapabilities.cs
+++ b/src/ModelContextProtocol.Core/Protocol/ServerCapabilities.cs
@@ -1,4 +1,5 @@
using System.ComponentModel;
+using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization;
using ModelContextProtocol.Server;
@@ -81,4 +82,23 @@ public sealed class ServerCapabilities
///
[JsonPropertyName("tasks")]
public McpTasksCapability? Tasks { get; set; }
+
+ ///
+ /// Gets or sets optional MCP extensions that the server supports.
+ ///
+ ///
+ ///
+ /// Keys are extension identifiers in reverse domain notation with an extension name
+ /// (e.g., "io.modelcontextprotocol/apps"), and values are per-extension settings
+ /// objects. An empty object indicates support with no additional settings.
+ ///
+ ///
+ /// Extensions provide a framework for extending the Model Context Protocol while maintaining
+ /// interoperability. Both clients and servers advertise extension support via this field during
+ /// the initialization handshake.
+ ///
+ ///
+ [JsonPropertyName("extensions")]
+ [Experimental(Experimentals.Tasks_DiagnosticId, UrlFormat = Experimentals.Tasks_Url)] // SEP-2133: https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2133
+ public IDictionary? Extensions { get; set; }
}
From f6526ba2f4b5632066498f20b6e6e42c24d52c8c Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 17 Feb 2026 08:41:12 +0000
Subject: [PATCH 3/6] Add dedicated Extensions constants to Experimentals class
Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
---
src/Common/Experimentals.cs | 15 +++++++++++++++
.../Protocol/ClientCapabilities.cs | 2 +-
.../Protocol/ServerCapabilities.cs | 2 +-
3 files changed, 17 insertions(+), 2 deletions(-)
diff --git a/src/Common/Experimentals.cs b/src/Common/Experimentals.cs
index 2ccde0796..e38d3deef 100644
--- a/src/Common/Experimentals.cs
+++ b/src/Common/Experimentals.cs
@@ -35,4 +35,19 @@ internal static class Experimentals
/// URL for the experimental MCP Tasks feature.
///
public const string Tasks_Url = "https://github.com/modelcontextprotocol/csharp-sdk/blob/main/docs/list-of-diagnostics.md#mcpexp001";
+
+ ///
+ /// Diagnostic ID for the experimental MCP Extensions feature.
+ ///
+ public const string Extensions_DiagnosticId = "MCPEXP001";
+
+ ///
+ /// Message for the experimental MCP Extensions feature.
+ ///
+ public const string Extensions_Message = "The Extensions feature is experimental per the MCP specification and is subject to change.";
+
+ ///
+ /// URL for the experimental MCP Extensions feature.
+ ///
+ public const string Extensions_Url = "https://github.com/modelcontextprotocol/csharp-sdk/blob/main/docs/list-of-diagnostics.md#mcpexp001";
}
diff --git a/src/ModelContextProtocol.Core/Protocol/ClientCapabilities.cs b/src/ModelContextProtocol.Core/Protocol/ClientCapabilities.cs
index 199bd53e3..132dafc2e 100644
--- a/src/ModelContextProtocol.Core/Protocol/ClientCapabilities.cs
+++ b/src/ModelContextProtocol.Core/Protocol/ClientCapabilities.cs
@@ -100,6 +100,6 @@ public sealed class ClientCapabilities
///
///
[JsonPropertyName("extensions")]
- [Experimental(Experimentals.Tasks_DiagnosticId, UrlFormat = Experimentals.Tasks_Url)] // SEP-2133: https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2133
+ [Experimental(Experimentals.Extensions_DiagnosticId, UrlFormat = Experimentals.Extensions_Url)] // SEP-2133: https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2133
public IDictionary? Extensions { get; set; }
}
diff --git a/src/ModelContextProtocol.Core/Protocol/ServerCapabilities.cs b/src/ModelContextProtocol.Core/Protocol/ServerCapabilities.cs
index a17e8ef63..aec553609 100644
--- a/src/ModelContextProtocol.Core/Protocol/ServerCapabilities.cs
+++ b/src/ModelContextProtocol.Core/Protocol/ServerCapabilities.cs
@@ -99,6 +99,6 @@ public sealed class ServerCapabilities
///
///
[JsonPropertyName("extensions")]
- [Experimental(Experimentals.Tasks_DiagnosticId, UrlFormat = Experimentals.Tasks_Url)] // SEP-2133: https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2133
+ [Experimental(Experimentals.Extensions_DiagnosticId, UrlFormat = Experimentals.Extensions_Url)] // SEP-2133: https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2133
public IDictionary? Extensions { get; set; }
}
From f0153c69a3c68e033e89e06c23d9c46bbf850628 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 17 Feb 2026 08:43:13 +0000
Subject: [PATCH 4/6] Add clarifying documentation for shared diagnostic ID
Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
---
src/Common/Experimentals.cs | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/src/Common/Experimentals.cs b/src/Common/Experimentals.cs
index e38d3deef..0268aab84 100644
--- a/src/Common/Experimentals.cs
+++ b/src/Common/Experimentals.cs
@@ -39,6 +39,12 @@ internal static class Experimentals
///
/// Diagnostic ID for the experimental MCP Extensions feature.
///
+ ///
+ /// This uses the same diagnostic ID as because both
+ /// Tasks and Extensions are covered by the same MCPEXP001 diagnostic for experimental
+ /// MCP features. Having separate constants improves code clarity while maintaining a
+ /// single diagnostic suppression point.
+ ///
public const string Extensions_DiagnosticId = "MCPEXP001";
///
From b314d3c09ae9025a377abd7f210669a17a81e5f7 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 17 Feb 2026 08:52:38 +0000
Subject: [PATCH 5/6] Add serialization roundtrip tests for Extensions property
and update message
Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
---
src/Common/Experimentals.cs | 2 +-
.../Protocol/CapabilitiesTests.cs | 269 ++++++++++++++++++
2 files changed, 270 insertions(+), 1 deletion(-)
create mode 100644 tests/ModelContextProtocol.Tests/Protocol/CapabilitiesTests.cs
diff --git a/src/Common/Experimentals.cs b/src/Common/Experimentals.cs
index 0268aab84..ec2c7c550 100644
--- a/src/Common/Experimentals.cs
+++ b/src/Common/Experimentals.cs
@@ -50,7 +50,7 @@ internal static class Experimentals
///
/// Message for the experimental MCP Extensions feature.
///
- public const string Extensions_Message = "The Extensions feature is experimental per the MCP specification and is subject to change.";
+ public const string Extensions_Message = "The Extensions feature is part of a future MCP specification version that has not yet been ratified and is subject to change.";
///
/// URL for the experimental MCP Extensions feature.
diff --git a/tests/ModelContextProtocol.Tests/Protocol/CapabilitiesTests.cs b/tests/ModelContextProtocol.Tests/Protocol/CapabilitiesTests.cs
new file mode 100644
index 000000000..875283e7c
--- /dev/null
+++ b/tests/ModelContextProtocol.Tests/Protocol/CapabilitiesTests.cs
@@ -0,0 +1,269 @@
+using ModelContextProtocol.Protocol;
+using System.Text.Json;
+
+namespace ModelContextProtocol.Tests.Protocol;
+
+public static class CapabilitiesTests
+{
+ [Fact]
+ public static void ClientCapabilities_ExtensionsProperty_SerializationRoundTrip()
+ {
+ // Arrange - Use raw JSON instead of objects for source generation compatibility
+ string json = """
+ {
+ "extensions": {
+ "io.modelcontextprotocol/oauth-client-credentials": {},
+ "io.modelcontextprotocol/test-extension": {
+ "setting1": "value1",
+ "setting2": 42
+ }
+ }
+ }
+ """;
+
+ // Act - Deserialize from JSON
+ var deserialized = JsonSerializer.Deserialize(json, McpJsonUtilities.DefaultOptions);
+
+ // Assert
+ Assert.NotNull(deserialized);
+ Assert.NotNull(deserialized.Extensions);
+ Assert.Equal(2, deserialized.Extensions.Count);
+ Assert.True(deserialized.Extensions.ContainsKey("io.modelcontextprotocol/oauth-client-credentials"));
+ Assert.True(deserialized.Extensions.ContainsKey("io.modelcontextprotocol/test-extension"));
+
+ // Act - Serialize back to JSON
+ string roundtrippedJson = JsonSerializer.Serialize(deserialized, McpJsonUtilities.DefaultOptions);
+
+ // Assert - Deserialize again to verify
+ var deserialized2 = JsonSerializer.Deserialize(roundtrippedJson, McpJsonUtilities.DefaultOptions);
+ Assert.NotNull(deserialized2);
+ Assert.NotNull(deserialized2.Extensions);
+ Assert.Equal(2, deserialized2.Extensions.Count);
+ }
+
+ [Fact]
+ public static void ClientCapabilities_ExtensionsProperty_DeserializesCorrectly()
+ {
+ // Arrange
+ string json = """
+ {
+ "extensions": {
+ "io.modelcontextprotocol/test": {}
+ }
+ }
+ """;
+
+ // Act
+ var capabilities = JsonSerializer.Deserialize(json, McpJsonUtilities.DefaultOptions);
+
+ // Assert
+ Assert.NotNull(capabilities);
+ Assert.NotNull(capabilities.Extensions);
+ Assert.Single(capabilities.Extensions);
+ Assert.True(capabilities.Extensions.ContainsKey("io.modelcontextprotocol/test"));
+ }
+
+ [Fact]
+ public static void ClientCapabilities_WithoutExtensions_DeserializesWithNullExtensions()
+ {
+ // Arrange
+ string json = "{}";
+
+ // Act
+ var capabilities = JsonSerializer.Deserialize(json, McpJsonUtilities.DefaultOptions);
+
+ // Assert
+ Assert.NotNull(capabilities);
+ Assert.Null(capabilities.Extensions);
+ }
+
+ [Fact]
+ public static void ClientCapabilities_WithEmptyExtensions_DeserializesAsEmptyDictionary()
+ {
+ // Arrange
+ string json = """
+ {
+ "extensions": {}
+ }
+ """;
+
+ // Act
+ var capabilities = JsonSerializer.Deserialize(json, McpJsonUtilities.DefaultOptions);
+
+ // Assert
+ Assert.NotNull(capabilities);
+ Assert.NotNull(capabilities.Extensions);
+ Assert.Empty(capabilities.Extensions);
+ }
+
+ [Fact]
+ public static void ServerCapabilities_ExtensionsProperty_SerializationRoundTrip()
+ {
+ // Arrange - Use raw JSON instead of objects for source generation compatibility
+ string json = """
+ {
+ "extensions": {
+ "io.modelcontextprotocol/apps": {},
+ "io.modelcontextprotocol/custom": {
+ "option": 42,
+ "enabled": true
+ }
+ }
+ }
+ """;
+
+ // Act - Deserialize from JSON
+ var deserialized = JsonSerializer.Deserialize(json, McpJsonUtilities.DefaultOptions);
+
+ // Assert
+ Assert.NotNull(deserialized);
+ Assert.NotNull(deserialized.Extensions);
+ Assert.Equal(2, deserialized.Extensions.Count);
+ Assert.True(deserialized.Extensions.ContainsKey("io.modelcontextprotocol/apps"));
+ Assert.True(deserialized.Extensions.ContainsKey("io.modelcontextprotocol/custom"));
+
+ // Act - Serialize back to JSON
+ string roundtrippedJson = JsonSerializer.Serialize(deserialized, McpJsonUtilities.DefaultOptions);
+
+ // Assert - Deserialize again to verify
+ var deserialized2 = JsonSerializer.Deserialize(roundtrippedJson, McpJsonUtilities.DefaultOptions);
+ Assert.NotNull(deserialized2);
+ Assert.NotNull(deserialized2.Extensions);
+ Assert.Equal(2, deserialized2.Extensions.Count);
+ }
+
+ [Fact]
+ public static void ServerCapabilities_ExtensionsProperty_DeserializesCorrectly()
+ {
+ // Arrange
+ string json = """
+ {
+ "extensions": {
+ "io.modelcontextprotocol/test": {}
+ }
+ }
+ """;
+
+ // Act
+ var capabilities = JsonSerializer.Deserialize(json, McpJsonUtilities.DefaultOptions);
+
+ // Assert
+ Assert.NotNull(capabilities);
+ Assert.NotNull(capabilities.Extensions);
+ Assert.Single(capabilities.Extensions);
+ Assert.True(capabilities.Extensions.ContainsKey("io.modelcontextprotocol/test"));
+ }
+
+ [Fact]
+ public static void ServerCapabilities_WithoutExtensions_DeserializesWithNullExtensions()
+ {
+ // Arrange
+ string json = "{}";
+
+ // Act
+ var capabilities = JsonSerializer.Deserialize(json, McpJsonUtilities.DefaultOptions);
+
+ // Assert
+ Assert.NotNull(capabilities);
+ Assert.Null(capabilities.Extensions);
+ }
+
+ [Fact]
+ public static void ServerCapabilities_WithEmptyExtensions_DeserializesAsEmptyDictionary()
+ {
+ // Arrange
+ string json = """
+ {
+ "extensions": {}
+ }
+ """;
+
+ // Act
+ var capabilities = JsonSerializer.Deserialize(json, McpJsonUtilities.DefaultOptions);
+
+ // Assert
+ Assert.NotNull(capabilities);
+ Assert.NotNull(capabilities.Extensions);
+ Assert.Empty(capabilities.Extensions);
+ }
+
+ [Fact]
+ public static void ClientCapabilities_ExtensionsWithComplexValues_RoundTrips()
+ {
+ // Arrange
+ string json = """
+ {
+ "extensions": {
+ "io.modelcontextprotocol/complex": {
+ "stringValue": "test",
+ "numberValue": 123,
+ "boolValue": true,
+ "arrayValue": [1, 2, 3]
+ }
+ }
+ }
+ """;
+
+ // Act - Deserialize from JSON
+ var deserialized = JsonSerializer.Deserialize(json, McpJsonUtilities.DefaultOptions);
+
+ // Assert
+ Assert.NotNull(deserialized);
+ Assert.NotNull(deserialized.Extensions);
+ Assert.Single(deserialized.Extensions);
+ Assert.True(deserialized.Extensions.ContainsKey("io.modelcontextprotocol/complex"));
+
+ // Verify the complex value can be accessed as JsonElement
+ var complexValue = deserialized.Extensions["io.modelcontextprotocol/complex"];
+ Assert.NotNull(complexValue);
+
+ // Act - Serialize back to JSON
+ string roundtrippedJson = JsonSerializer.Serialize(deserialized, McpJsonUtilities.DefaultOptions);
+
+ // Assert - Verify it can deserialize again
+ var deserialized2 = JsonSerializer.Deserialize(roundtrippedJson, McpJsonUtilities.DefaultOptions);
+ Assert.NotNull(deserialized2);
+ Assert.NotNull(deserialized2.Extensions);
+ Assert.Single(deserialized2.Extensions);
+ }
+
+ [Fact]
+ public static void ServerCapabilities_ExtensionsWithComplexValues_RoundTrips()
+ {
+ // Arrange
+ string json = """
+ {
+ "extensions": {
+ "io.modelcontextprotocol/complex": {
+ "stringValue": "test",
+ "numberValue": 456,
+ "boolValue": false,
+ "arrayValue": ["a", "b", "c"]
+ }
+ }
+ }
+ """;
+
+ // Act - Deserialize from JSON
+ var deserialized = JsonSerializer.Deserialize(json, McpJsonUtilities.DefaultOptions);
+
+ // Assert
+ Assert.NotNull(deserialized);
+ Assert.NotNull(deserialized.Extensions);
+ Assert.Single(deserialized.Extensions);
+ Assert.True(deserialized.Extensions.ContainsKey("io.modelcontextprotocol/complex"));
+
+ // Verify the complex value can be accessed as JsonElement
+ var complexValue = deserialized.Extensions["io.modelcontextprotocol/complex"];
+ Assert.NotNull(complexValue);
+
+ // Act - Serialize back to JSON
+ string roundtrippedJson = JsonSerializer.Serialize(deserialized, McpJsonUtilities.DefaultOptions);
+
+ // Assert - Verify it can deserialize again
+ var deserialized2 = JsonSerializer.Deserialize(roundtrippedJson, McpJsonUtilities.DefaultOptions);
+ Assert.NotNull(deserialized2);
+ Assert.NotNull(deserialized2.Extensions);
+ Assert.Single(deserialized2.Extensions);
+ }
+}
From b19aefda8cce5f5525b7eb8786501b5dd0fa71fc Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 17 Feb 2026 09:18:53 +0000
Subject: [PATCH 6/6] Split capabilities tests into separate files by type
Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
---
.../Protocol/CapabilitiesTests.cs | 269 ------------------
.../Protocol/ClientCapabilitiesTests.cs | 138 +++++++++
.../Protocol/ServerCapabilitiesTests.cs | 138 +++++++++
3 files changed, 276 insertions(+), 269 deletions(-)
delete mode 100644 tests/ModelContextProtocol.Tests/Protocol/CapabilitiesTests.cs
create mode 100644 tests/ModelContextProtocol.Tests/Protocol/ClientCapabilitiesTests.cs
create mode 100644 tests/ModelContextProtocol.Tests/Protocol/ServerCapabilitiesTests.cs
diff --git a/tests/ModelContextProtocol.Tests/Protocol/CapabilitiesTests.cs b/tests/ModelContextProtocol.Tests/Protocol/CapabilitiesTests.cs
deleted file mode 100644
index 875283e7c..000000000
--- a/tests/ModelContextProtocol.Tests/Protocol/CapabilitiesTests.cs
+++ /dev/null
@@ -1,269 +0,0 @@
-using ModelContextProtocol.Protocol;
-using System.Text.Json;
-
-namespace ModelContextProtocol.Tests.Protocol;
-
-public static class CapabilitiesTests
-{
- [Fact]
- public static void ClientCapabilities_ExtensionsProperty_SerializationRoundTrip()
- {
- // Arrange - Use raw JSON instead of objects for source generation compatibility
- string json = """
- {
- "extensions": {
- "io.modelcontextprotocol/oauth-client-credentials": {},
- "io.modelcontextprotocol/test-extension": {
- "setting1": "value1",
- "setting2": 42
- }
- }
- }
- """;
-
- // Act - Deserialize from JSON
- var deserialized = JsonSerializer.Deserialize(json, McpJsonUtilities.DefaultOptions);
-
- // Assert
- Assert.NotNull(deserialized);
- Assert.NotNull(deserialized.Extensions);
- Assert.Equal(2, deserialized.Extensions.Count);
- Assert.True(deserialized.Extensions.ContainsKey("io.modelcontextprotocol/oauth-client-credentials"));
- Assert.True(deserialized.Extensions.ContainsKey("io.modelcontextprotocol/test-extension"));
-
- // Act - Serialize back to JSON
- string roundtrippedJson = JsonSerializer.Serialize(deserialized, McpJsonUtilities.DefaultOptions);
-
- // Assert - Deserialize again to verify
- var deserialized2 = JsonSerializer.Deserialize(roundtrippedJson, McpJsonUtilities.DefaultOptions);
- Assert.NotNull(deserialized2);
- Assert.NotNull(deserialized2.Extensions);
- Assert.Equal(2, deserialized2.Extensions.Count);
- }
-
- [Fact]
- public static void ClientCapabilities_ExtensionsProperty_DeserializesCorrectly()
- {
- // Arrange
- string json = """
- {
- "extensions": {
- "io.modelcontextprotocol/test": {}
- }
- }
- """;
-
- // Act
- var capabilities = JsonSerializer.Deserialize(json, McpJsonUtilities.DefaultOptions);
-
- // Assert
- Assert.NotNull(capabilities);
- Assert.NotNull(capabilities.Extensions);
- Assert.Single(capabilities.Extensions);
- Assert.True(capabilities.Extensions.ContainsKey("io.modelcontextprotocol/test"));
- }
-
- [Fact]
- public static void ClientCapabilities_WithoutExtensions_DeserializesWithNullExtensions()
- {
- // Arrange
- string json = "{}";
-
- // Act
- var capabilities = JsonSerializer.Deserialize(json, McpJsonUtilities.DefaultOptions);
-
- // Assert
- Assert.NotNull(capabilities);
- Assert.Null(capabilities.Extensions);
- }
-
- [Fact]
- public static void ClientCapabilities_WithEmptyExtensions_DeserializesAsEmptyDictionary()
- {
- // Arrange
- string json = """
- {
- "extensions": {}
- }
- """;
-
- // Act
- var capabilities = JsonSerializer.Deserialize(json, McpJsonUtilities.DefaultOptions);
-
- // Assert
- Assert.NotNull(capabilities);
- Assert.NotNull(capabilities.Extensions);
- Assert.Empty(capabilities.Extensions);
- }
-
- [Fact]
- public static void ServerCapabilities_ExtensionsProperty_SerializationRoundTrip()
- {
- // Arrange - Use raw JSON instead of objects for source generation compatibility
- string json = """
- {
- "extensions": {
- "io.modelcontextprotocol/apps": {},
- "io.modelcontextprotocol/custom": {
- "option": 42,
- "enabled": true
- }
- }
- }
- """;
-
- // Act - Deserialize from JSON
- var deserialized = JsonSerializer.Deserialize(json, McpJsonUtilities.DefaultOptions);
-
- // Assert
- Assert.NotNull(deserialized);
- Assert.NotNull(deserialized.Extensions);
- Assert.Equal(2, deserialized.Extensions.Count);
- Assert.True(deserialized.Extensions.ContainsKey("io.modelcontextprotocol/apps"));
- Assert.True(deserialized.Extensions.ContainsKey("io.modelcontextprotocol/custom"));
-
- // Act - Serialize back to JSON
- string roundtrippedJson = JsonSerializer.Serialize(deserialized, McpJsonUtilities.DefaultOptions);
-
- // Assert - Deserialize again to verify
- var deserialized2 = JsonSerializer.Deserialize(roundtrippedJson, McpJsonUtilities.DefaultOptions);
- Assert.NotNull(deserialized2);
- Assert.NotNull(deserialized2.Extensions);
- Assert.Equal(2, deserialized2.Extensions.Count);
- }
-
- [Fact]
- public static void ServerCapabilities_ExtensionsProperty_DeserializesCorrectly()
- {
- // Arrange
- string json = """
- {
- "extensions": {
- "io.modelcontextprotocol/test": {}
- }
- }
- """;
-
- // Act
- var capabilities = JsonSerializer.Deserialize(json, McpJsonUtilities.DefaultOptions);
-
- // Assert
- Assert.NotNull(capabilities);
- Assert.NotNull(capabilities.Extensions);
- Assert.Single(capabilities.Extensions);
- Assert.True(capabilities.Extensions.ContainsKey("io.modelcontextprotocol/test"));
- }
-
- [Fact]
- public static void ServerCapabilities_WithoutExtensions_DeserializesWithNullExtensions()
- {
- // Arrange
- string json = "{}";
-
- // Act
- var capabilities = JsonSerializer.Deserialize(json, McpJsonUtilities.DefaultOptions);
-
- // Assert
- Assert.NotNull(capabilities);
- Assert.Null(capabilities.Extensions);
- }
-
- [Fact]
- public static void ServerCapabilities_WithEmptyExtensions_DeserializesAsEmptyDictionary()
- {
- // Arrange
- string json = """
- {
- "extensions": {}
- }
- """;
-
- // Act
- var capabilities = JsonSerializer.Deserialize(json, McpJsonUtilities.DefaultOptions);
-
- // Assert
- Assert.NotNull(capabilities);
- Assert.NotNull(capabilities.Extensions);
- Assert.Empty(capabilities.Extensions);
- }
-
- [Fact]
- public static void ClientCapabilities_ExtensionsWithComplexValues_RoundTrips()
- {
- // Arrange
- string json = """
- {
- "extensions": {
- "io.modelcontextprotocol/complex": {
- "stringValue": "test",
- "numberValue": 123,
- "boolValue": true,
- "arrayValue": [1, 2, 3]
- }
- }
- }
- """;
-
- // Act - Deserialize from JSON
- var deserialized = JsonSerializer.Deserialize(json, McpJsonUtilities.DefaultOptions);
-
- // Assert
- Assert.NotNull(deserialized);
- Assert.NotNull(deserialized.Extensions);
- Assert.Single(deserialized.Extensions);
- Assert.True(deserialized.Extensions.ContainsKey("io.modelcontextprotocol/complex"));
-
- // Verify the complex value can be accessed as JsonElement
- var complexValue = deserialized.Extensions["io.modelcontextprotocol/complex"];
- Assert.NotNull(complexValue);
-
- // Act - Serialize back to JSON
- string roundtrippedJson = JsonSerializer.Serialize(deserialized, McpJsonUtilities.DefaultOptions);
-
- // Assert - Verify it can deserialize again
- var deserialized2 = JsonSerializer.Deserialize(roundtrippedJson, McpJsonUtilities.DefaultOptions);
- Assert.NotNull(deserialized2);
- Assert.NotNull(deserialized2.Extensions);
- Assert.Single(deserialized2.Extensions);
- }
-
- [Fact]
- public static void ServerCapabilities_ExtensionsWithComplexValues_RoundTrips()
- {
- // Arrange
- string json = """
- {
- "extensions": {
- "io.modelcontextprotocol/complex": {
- "stringValue": "test",
- "numberValue": 456,
- "boolValue": false,
- "arrayValue": ["a", "b", "c"]
- }
- }
- }
- """;
-
- // Act - Deserialize from JSON
- var deserialized = JsonSerializer.Deserialize(json, McpJsonUtilities.DefaultOptions);
-
- // Assert
- Assert.NotNull(deserialized);
- Assert.NotNull(deserialized.Extensions);
- Assert.Single(deserialized.Extensions);
- Assert.True(deserialized.Extensions.ContainsKey("io.modelcontextprotocol/complex"));
-
- // Verify the complex value can be accessed as JsonElement
- var complexValue = deserialized.Extensions["io.modelcontextprotocol/complex"];
- Assert.NotNull(complexValue);
-
- // Act - Serialize back to JSON
- string roundtrippedJson = JsonSerializer.Serialize(deserialized, McpJsonUtilities.DefaultOptions);
-
- // Assert - Verify it can deserialize again
- var deserialized2 = JsonSerializer.Deserialize(roundtrippedJson, McpJsonUtilities.DefaultOptions);
- Assert.NotNull(deserialized2);
- Assert.NotNull(deserialized2.Extensions);
- Assert.Single(deserialized2.Extensions);
- }
-}
diff --git a/tests/ModelContextProtocol.Tests/Protocol/ClientCapabilitiesTests.cs b/tests/ModelContextProtocol.Tests/Protocol/ClientCapabilitiesTests.cs
new file mode 100644
index 000000000..b7b2766a7
--- /dev/null
+++ b/tests/ModelContextProtocol.Tests/Protocol/ClientCapabilitiesTests.cs
@@ -0,0 +1,138 @@
+using ModelContextProtocol.Protocol;
+using System.Text.Json;
+
+namespace ModelContextProtocol.Tests.Protocol;
+
+public static class ClientCapabilitiesTests
+{
+ [Fact]
+ public static void ExtensionsProperty_SerializationRoundTrip()
+ {
+ // Arrange - Use raw JSON instead of objects for source generation compatibility
+ string json = """
+ {
+ "extensions": {
+ "io.modelcontextprotocol/oauth-client-credentials": {},
+ "io.modelcontextprotocol/test-extension": {
+ "setting1": "value1",
+ "setting2": 42
+ }
+ }
+ }
+ """;
+
+ // Act - Deserialize from JSON
+ var deserialized = JsonSerializer.Deserialize(json, McpJsonUtilities.DefaultOptions);
+
+ // Assert
+ Assert.NotNull(deserialized);
+ Assert.NotNull(deserialized.Extensions);
+ Assert.Equal(2, deserialized.Extensions.Count);
+ Assert.True(deserialized.Extensions.ContainsKey("io.modelcontextprotocol/oauth-client-credentials"));
+ Assert.True(deserialized.Extensions.ContainsKey("io.modelcontextprotocol/test-extension"));
+
+ // Act - Serialize back to JSON
+ string roundtrippedJson = JsonSerializer.Serialize(deserialized, McpJsonUtilities.DefaultOptions);
+
+ // Assert - Deserialize again to verify
+ var deserialized2 = JsonSerializer.Deserialize(roundtrippedJson, McpJsonUtilities.DefaultOptions);
+ Assert.NotNull(deserialized2);
+ Assert.NotNull(deserialized2.Extensions);
+ Assert.Equal(2, deserialized2.Extensions.Count);
+ }
+
+ [Fact]
+ public static void ExtensionsProperty_DeserializesCorrectly()
+ {
+ // Arrange
+ string json = """
+ {
+ "extensions": {
+ "io.modelcontextprotocol/test": {}
+ }
+ }
+ """;
+
+ // Act
+ var capabilities = JsonSerializer.Deserialize(json, McpJsonUtilities.DefaultOptions);
+
+ // Assert
+ Assert.NotNull(capabilities);
+ Assert.NotNull(capabilities.Extensions);
+ Assert.Single(capabilities.Extensions);
+ Assert.True(capabilities.Extensions.ContainsKey("io.modelcontextprotocol/test"));
+ }
+
+ [Fact]
+ public static void WithoutExtensions_DeserializesWithNullExtensions()
+ {
+ // Arrange
+ string json = "{}";
+
+ // Act
+ var capabilities = JsonSerializer.Deserialize(json, McpJsonUtilities.DefaultOptions);
+
+ // Assert
+ Assert.NotNull(capabilities);
+ Assert.Null(capabilities.Extensions);
+ }
+
+ [Fact]
+ public static void WithEmptyExtensions_DeserializesAsEmptyDictionary()
+ {
+ // Arrange
+ string json = """
+ {
+ "extensions": {}
+ }
+ """;
+
+ // Act
+ var capabilities = JsonSerializer.Deserialize(json, McpJsonUtilities.DefaultOptions);
+
+ // Assert
+ Assert.NotNull(capabilities);
+ Assert.NotNull(capabilities.Extensions);
+ Assert.Empty(capabilities.Extensions);
+ }
+
+ [Fact]
+ public static void ExtensionsWithComplexValues_RoundTrips()
+ {
+ // Arrange
+ string json = """
+ {
+ "extensions": {
+ "io.modelcontextprotocol/complex": {
+ "stringValue": "test",
+ "numberValue": 123,
+ "boolValue": true,
+ "arrayValue": [1, 2, 3]
+ }
+ }
+ }
+ """;
+
+ // Act - Deserialize from JSON
+ var deserialized = JsonSerializer.Deserialize(json, McpJsonUtilities.DefaultOptions);
+
+ // Assert
+ Assert.NotNull(deserialized);
+ Assert.NotNull(deserialized.Extensions);
+ Assert.Single(deserialized.Extensions);
+ Assert.True(deserialized.Extensions.ContainsKey("io.modelcontextprotocol/complex"));
+
+ // Verify the complex value can be accessed as JsonElement
+ var complexValue = deserialized.Extensions["io.modelcontextprotocol/complex"];
+ Assert.NotNull(complexValue);
+
+ // Act - Serialize back to JSON
+ string roundtrippedJson = JsonSerializer.Serialize(deserialized, McpJsonUtilities.DefaultOptions);
+
+ // Assert - Verify it can deserialize again
+ var deserialized2 = JsonSerializer.Deserialize(roundtrippedJson, McpJsonUtilities.DefaultOptions);
+ Assert.NotNull(deserialized2);
+ Assert.NotNull(deserialized2.Extensions);
+ Assert.Single(deserialized2.Extensions);
+ }
+}
diff --git a/tests/ModelContextProtocol.Tests/Protocol/ServerCapabilitiesTests.cs b/tests/ModelContextProtocol.Tests/Protocol/ServerCapabilitiesTests.cs
new file mode 100644
index 000000000..30f1e7bf7
--- /dev/null
+++ b/tests/ModelContextProtocol.Tests/Protocol/ServerCapabilitiesTests.cs
@@ -0,0 +1,138 @@
+using ModelContextProtocol.Protocol;
+using System.Text.Json;
+
+namespace ModelContextProtocol.Tests.Protocol;
+
+public static class ServerCapabilitiesTests
+{
+ [Fact]
+ public static void ExtensionsProperty_SerializationRoundTrip()
+ {
+ // Arrange - Use raw JSON instead of objects for source generation compatibility
+ string json = """
+ {
+ "extensions": {
+ "io.modelcontextprotocol/apps": {},
+ "io.modelcontextprotocol/custom": {
+ "option": 42,
+ "enabled": true
+ }
+ }
+ }
+ """;
+
+ // Act - Deserialize from JSON
+ var deserialized = JsonSerializer.Deserialize(json, McpJsonUtilities.DefaultOptions);
+
+ // Assert
+ Assert.NotNull(deserialized);
+ Assert.NotNull(deserialized.Extensions);
+ Assert.Equal(2, deserialized.Extensions.Count);
+ Assert.True(deserialized.Extensions.ContainsKey("io.modelcontextprotocol/apps"));
+ Assert.True(deserialized.Extensions.ContainsKey("io.modelcontextprotocol/custom"));
+
+ // Act - Serialize back to JSON
+ string roundtrippedJson = JsonSerializer.Serialize(deserialized, McpJsonUtilities.DefaultOptions);
+
+ // Assert - Deserialize again to verify
+ var deserialized2 = JsonSerializer.Deserialize(roundtrippedJson, McpJsonUtilities.DefaultOptions);
+ Assert.NotNull(deserialized2);
+ Assert.NotNull(deserialized2.Extensions);
+ Assert.Equal(2, deserialized2.Extensions.Count);
+ }
+
+ [Fact]
+ public static void ExtensionsProperty_DeserializesCorrectly()
+ {
+ // Arrange
+ string json = """
+ {
+ "extensions": {
+ "io.modelcontextprotocol/test": {}
+ }
+ }
+ """;
+
+ // Act
+ var capabilities = JsonSerializer.Deserialize(json, McpJsonUtilities.DefaultOptions);
+
+ // Assert
+ Assert.NotNull(capabilities);
+ Assert.NotNull(capabilities.Extensions);
+ Assert.Single(capabilities.Extensions);
+ Assert.True(capabilities.Extensions.ContainsKey("io.modelcontextprotocol/test"));
+ }
+
+ [Fact]
+ public static void WithoutExtensions_DeserializesWithNullExtensions()
+ {
+ // Arrange
+ string json = "{}";
+
+ // Act
+ var capabilities = JsonSerializer.Deserialize(json, McpJsonUtilities.DefaultOptions);
+
+ // Assert
+ Assert.NotNull(capabilities);
+ Assert.Null(capabilities.Extensions);
+ }
+
+ [Fact]
+ public static void WithEmptyExtensions_DeserializesAsEmptyDictionary()
+ {
+ // Arrange
+ string json = """
+ {
+ "extensions": {}
+ }
+ """;
+
+ // Act
+ var capabilities = JsonSerializer.Deserialize(json, McpJsonUtilities.DefaultOptions);
+
+ // Assert
+ Assert.NotNull(capabilities);
+ Assert.NotNull(capabilities.Extensions);
+ Assert.Empty(capabilities.Extensions);
+ }
+
+ [Fact]
+ public static void ExtensionsWithComplexValues_RoundTrips()
+ {
+ // Arrange
+ string json = """
+ {
+ "extensions": {
+ "io.modelcontextprotocol/complex": {
+ "stringValue": "test",
+ "numberValue": 456,
+ "boolValue": false,
+ "arrayValue": ["a", "b", "c"]
+ }
+ }
+ }
+ """;
+
+ // Act - Deserialize from JSON
+ var deserialized = JsonSerializer.Deserialize(json, McpJsonUtilities.DefaultOptions);
+
+ // Assert
+ Assert.NotNull(deserialized);
+ Assert.NotNull(deserialized.Extensions);
+ Assert.Single(deserialized.Extensions);
+ Assert.True(deserialized.Extensions.ContainsKey("io.modelcontextprotocol/complex"));
+
+ // Verify the complex value can be accessed as JsonElement
+ var complexValue = deserialized.Extensions["io.modelcontextprotocol/complex"];
+ Assert.NotNull(complexValue);
+
+ // Act - Serialize back to JSON
+ string roundtrippedJson = JsonSerializer.Serialize(deserialized, McpJsonUtilities.DefaultOptions);
+
+ // Assert - Verify it can deserialize again
+ var deserialized2 = JsonSerializer.Deserialize(roundtrippedJson, McpJsonUtilities.DefaultOptions);
+ Assert.NotNull(deserialized2);
+ Assert.NotNull(deserialized2.Extensions);
+ Assert.Single(deserialized2.Extensions);
+ }
+}