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/Common/Experimentals.cs b/src/Common/Experimentals.cs
index 2ccde0796..ec2c7c550 100644
--- a/src/Common/Experimentals.cs
+++ b/src/Common/Experimentals.cs
@@ -35,4 +35,25 @@ 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.
+ ///
+ ///
+ /// 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";
+
+ ///
+ /// Message for the experimental MCP Extensions feature.
+ ///
+ 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.
+ ///
+ 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 9841e3da0..77b2bef9f 100644
--- a/src/ModelContextProtocol.Core/Protocol/ClientCapabilities.cs
+++ b/src/ModelContextProtocol.Core/Protocol/ClientCapabilities.cs
@@ -93,4 +93,31 @@ public McpTasksCapability? Tasks
[JsonInclude]
[JsonPropertyName("tasks")]
internal McpTasksCapability? TasksCore { 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. Clients advertise extension support via this field during the initialization handshake.
+ ///
+ ///
+ [Experimental(Experimentals.Extensions_DiagnosticId, UrlFormat = Experimentals.Extensions_Url)]
+ [JsonIgnore]
+ public IDictionary? Extensions
+ {
+ get => ExtensionsCore;
+ set => ExtensionsCore = value;
+ }
+
+ // See ExperimentalInternalPropertyTests.cs before modifying this property.
+ [JsonInclude]
+ [JsonPropertyName("extensions")]
+ internal IDictionary? ExtensionsCore { get; set; }
}
diff --git a/src/ModelContextProtocol.Core/Protocol/ServerCapabilities.cs b/src/ModelContextProtocol.Core/Protocol/ServerCapabilities.cs
index d4e55653e..d4e23a66f 100644
--- a/src/ModelContextProtocol.Core/Protocol/ServerCapabilities.cs
+++ b/src/ModelContextProtocol.Core/Protocol/ServerCapabilities.cs
@@ -92,4 +92,31 @@ public McpTasksCapability? Tasks
[JsonInclude]
[JsonPropertyName("tasks")]
internal McpTasksCapability? TasksCore { 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. Servers advertise extension support via this field during the initialization handshake.
+ ///
+ ///
+ [Experimental(Experimentals.Extensions_DiagnosticId, UrlFormat = Experimentals.Extensions_Url)]
+ [JsonIgnore]
+ public IDictionary? Extensions
+ {
+ get => ExtensionsCore;
+ set => ExtensionsCore = value;
+ }
+
+ // See ExperimentalInternalPropertyTests.cs before modifying this property.
+ [JsonInclude]
+ [JsonPropertyName("extensions")]
+ internal IDictionary? ExtensionsCore { get; set; }
}
diff --git a/tests/ModelContextProtocol.Tests/Protocol/ClientCapabilitiesTests.cs b/tests/ModelContextProtocol.Tests/Protocol/ClientCapabilitiesTests.cs
index 188326a62..cacb7e84e 100644
--- a/tests/ModelContextProtocol.Tests/Protocol/ClientCapabilitiesTests.cs
+++ b/tests/ModelContextProtocol.Tests/Protocol/ClientCapabilitiesTests.cs
@@ -21,7 +21,11 @@ public static void ClientCapabilities_SerializationRoundTrip_PreservesAllPropert
Form = new FormElicitationCapability(),
Url = new UrlElicitationCapability()
},
- Tasks = new McpTasksCapability()
+ Tasks = new McpTasksCapability(),
+ Extensions = new Dictionary
+ {
+ ["io.modelcontextprotocol/test"] = new object()
+ }
};
string json = JsonSerializer.Serialize(original, McpJsonUtilities.DefaultOptions);
@@ -37,6 +41,8 @@ public static void ClientCapabilities_SerializationRoundTrip_PreservesAllPropert
Assert.NotNull(deserialized.Elicitation.Form);
Assert.NotNull(deserialized.Elicitation.Url);
Assert.NotNull(deserialized.Tasks);
+ Assert.NotNull(deserialized.Extensions);
+ Assert.True(deserialized.Extensions.ContainsKey("io.modelcontextprotocol/test"));
}
[Fact]
@@ -53,5 +59,42 @@ public static void ClientCapabilities_SerializationRoundTrip_WithMinimalProperti
Assert.Null(deserialized.Sampling);
Assert.Null(deserialized.Elicitation);
Assert.Null(deserialized.Tasks);
+ Assert.Null(deserialized.Extensions);
+ }
+
+ [Fact]
+ public static void ClientCapabilities_Extensions_DeserializesFromJson()
+ {
+ string json = """
+ {
+ "extensions": {
+ "io.modelcontextprotocol/oauth-client-credentials": {},
+ "io.modelcontextprotocol/test-extension": {
+ "setting1": "value1",
+ "setting2": 42
+ }
+ }
+ }
+ """;
+
+ var deserialized = JsonSerializer.Deserialize(json, McpJsonUtilities.DefaultOptions);
+
+ 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"));
+ }
+
+ [Fact]
+ public static void ClientCapabilities_Extensions_EmptyObjectDeserializesAsEmptyDictionary()
+ {
+ string json = """{"extensions": {}}""";
+
+ var deserialized = JsonSerializer.Deserialize(json, McpJsonUtilities.DefaultOptions);
+
+ Assert.NotNull(deserialized);
+ Assert.NotNull(deserialized.Extensions);
+ Assert.Empty(deserialized.Extensions);
}
}
diff --git a/tests/ModelContextProtocol.Tests/Protocol/ServerCapabilitiesTests.cs b/tests/ModelContextProtocol.Tests/Protocol/ServerCapabilitiesTests.cs
index ca698ef2b..a6f8265f1 100644
--- a/tests/ModelContextProtocol.Tests/Protocol/ServerCapabilitiesTests.cs
+++ b/tests/ModelContextProtocol.Tests/Protocol/ServerCapabilitiesTests.cs
@@ -15,7 +15,11 @@ public static void ServerCapabilities_SerializationRoundTrip_PreservesAllPropert
Resources = new ResourcesCapability { Subscribe = true, ListChanged = true },
Tools = new ToolsCapability { ListChanged = false },
Completions = new CompletionsCapability(),
- Tasks = new McpTasksCapability()
+ Tasks = new McpTasksCapability(),
+ Extensions = new Dictionary
+ {
+ ["io.modelcontextprotocol/apps"] = new object()
+ }
};
string json = JsonSerializer.Serialize(original, McpJsonUtilities.DefaultOptions);
@@ -32,6 +36,8 @@ public static void ServerCapabilities_SerializationRoundTrip_PreservesAllPropert
Assert.False(deserialized.Tools.ListChanged);
Assert.NotNull(deserialized.Completions);
Assert.NotNull(deserialized.Tasks);
+ Assert.NotNull(deserialized.Extensions);
+ Assert.True(deserialized.Extensions.ContainsKey("io.modelcontextprotocol/apps"));
}
[Fact]
@@ -50,5 +56,42 @@ public static void ServerCapabilities_SerializationRoundTrip_WithMinimalProperti
Assert.Null(deserialized.Tools);
Assert.Null(deserialized.Completions);
Assert.Null(deserialized.Tasks);
+ Assert.Null(deserialized.Extensions);
+ }
+
+ [Fact]
+ public static void ServerCapabilities_Extensions_DeserializesFromJson()
+ {
+ string json = """
+ {
+ "extensions": {
+ "io.modelcontextprotocol/apps": {},
+ "io.modelcontextprotocol/custom": {
+ "option": 42,
+ "enabled": true
+ }
+ }
+ }
+ """;
+
+ var deserialized = JsonSerializer.Deserialize(json, McpJsonUtilities.DefaultOptions);
+
+ 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"));
+ }
+
+ [Fact]
+ public static void ServerCapabilities_Extensions_EmptyObjectDeserializesAsEmptyDictionary()
+ {
+ string json = """{"extensions": {}}""";
+
+ var deserialized = JsonSerializer.Deserialize(json, McpJsonUtilities.DefaultOptions);
+
+ Assert.NotNull(deserialized);
+ Assert.NotNull(deserialized.Extensions);
+ Assert.Empty(deserialized.Extensions);
}
}