From f75b8f857a9f4b3863fc1960205fc97307b9e0fc Mon Sep 17 00:00:00 2001 From: Eirik Tsarpalis Date: Wed, 1 Jul 2026 19:19:52 +0300 Subject: [PATCH] Fully stabilize now-stable protocol properties (remove internal *Core plumbing) Removes the internal *Core JSON plumbing from ClientCapabilities.Extensions, ServerCapabilities.Extensions, RequestParams.InputResponses, and RequestParams.RequestState, converting them to plain public [JsonPropertyName] auto-properties. PR #1642 removed [Experimental] from these but left the workaround plumbing from #1301, so consumer-defined source-gen contexts still silently dropped them. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../Protocol/ClientCapabilities.cs | 12 +- .../Protocol/RequestParams.cs | 22 +--- .../Protocol/ServerCapabilities.cs | 12 +- .../ExperimentalPropertySerializationTests.cs | 120 ------------------ 4 files changed, 4 insertions(+), 162 deletions(-) delete mode 100644 tests/ModelContextProtocol.Tests/ExperimentalPropertySerializationTests.cs diff --git a/src/ModelContextProtocol.Core/Protocol/ClientCapabilities.cs b/src/ModelContextProtocol.Core/Protocol/ClientCapabilities.cs index 22245f335..2828602fc 100644 --- a/src/ModelContextProtocol.Core/Protocol/ClientCapabilities.cs +++ b/src/ModelContextProtocol.Core/Protocol/ClientCapabilities.cs @@ -1,4 +1,3 @@ -using System.ComponentModel; using System.Text.Json.Serialization; using ModelContextProtocol.Client; using ModelContextProtocol.Server; @@ -83,15 +82,6 @@ public sealed class ClientCapabilities /// interoperability. Clients advertise extension support via this field during the initialization handshake. /// /// - [JsonIgnore] - public IDictionary? Extensions - { - get => ExtensionsCore; - set => ExtensionsCore = value; - } - - // See ExperimentalInternalPropertyTests.cs before modifying this property. - [JsonInclude] [JsonPropertyName("extensions")] - internal IDictionary? ExtensionsCore { get; set; } + public IDictionary? Extensions { get; set; } } diff --git a/src/ModelContextProtocol.Core/Protocol/RequestParams.cs b/src/ModelContextProtocol.Core/Protocol/RequestParams.cs index 67a8f00ba..37872ec5b 100644 --- a/src/ModelContextProtocol.Core/Protocol/RequestParams.cs +++ b/src/ModelContextProtocol.Core/Protocol/RequestParams.cs @@ -35,17 +35,8 @@ private protected RequestParams() /// the value is the client's response to that input request. /// /// - [JsonIgnore] - public IDictionary? InputResponses - { - get => InputResponsesCore; - set => InputResponsesCore = value; - } - - // See ExperimentalInternalPropertyTests.cs before modifying this property. - [JsonInclude] [JsonPropertyName("inputResponses")] - internal IDictionary? InputResponsesCore { get; set; } + public IDictionary? InputResponses { get; set; } /// /// Gets or sets opaque request state echoed back from a previous . @@ -57,17 +48,8 @@ public IDictionary? InputResponses /// exact value without modification. /// /// - [JsonIgnore] - public string? RequestState - { - get => RequestStateCore; - set => RequestStateCore = value; - } - - // See ExperimentalInternalPropertyTests.cs before modifying this property. - [JsonInclude] [JsonPropertyName("requestState")] - internal string? RequestStateCore { get; set; } + public string? RequestState { get; set; } /// /// Gets the opaque token that will be attached to any subsequent progress notifications. diff --git a/src/ModelContextProtocol.Core/Protocol/ServerCapabilities.cs b/src/ModelContextProtocol.Core/Protocol/ServerCapabilities.cs index bfc6318d6..95cfa078d 100644 --- a/src/ModelContextProtocol.Core/Protocol/ServerCapabilities.cs +++ b/src/ModelContextProtocol.Core/Protocol/ServerCapabilities.cs @@ -1,4 +1,3 @@ -using System.ComponentModel; using System.Text.Json.Serialization; using ModelContextProtocol.Server; @@ -81,15 +80,6 @@ public sealed class ServerCapabilities /// interoperability. Servers advertise extension support via this field during the initialization handshake. /// /// - [JsonIgnore] - public IDictionary? Extensions - { - get => ExtensionsCore; - set => ExtensionsCore = value; - } - - // See ExperimentalInternalPropertyTests.cs before modifying this property. - [JsonInclude] [JsonPropertyName("extensions")] - internal IDictionary? ExtensionsCore { get; set; } + public IDictionary? Extensions { get; set; } } diff --git a/tests/ModelContextProtocol.Tests/ExperimentalPropertySerializationTests.cs b/tests/ModelContextProtocol.Tests/ExperimentalPropertySerializationTests.cs deleted file mode 100644 index 866a59c61..000000000 --- a/tests/ModelContextProtocol.Tests/ExperimentalPropertySerializationTests.cs +++ /dev/null @@ -1,120 +0,0 @@ -using System.Text.Json; -using System.Text.Json.Nodes; -using System.Text.Json.Serialization; -using ModelContextProtocol.Protocol; - -namespace ModelContextProtocol.Tests; - -/// -/// Validates that the internal property pattern used for experimental properties -/// produces the expected serialization behavior for SDK consumers using source generators. -/// -/// -/// -/// Experimental properties (e.g. , ) -/// use an internal *Core property for serialization. A consumer's source-generated -/// cannot see internal members, so experimental data is -/// silently dropped unless the consumer chains the SDK's resolver into their options. -/// -/// -/// These tests depend on and -/// being experimental. When those APIs stabilize, update these tests to reference whatever -/// experimental properties exist at that time, or remove them entirely if no experimental -/// APIs remain. -/// -/// -public class ExperimentalPropertySerializationTests -{ - [Fact] - public void ExperimentalProperties_Dropped_WithConsumerContextOnly() - { - var options = new JsonSerializerOptions - { - TypeInfoResolverChain = { ConsumerJsonContext.Default } - }; - - var capabilities = new ServerCapabilities - { - Tools = new ToolsCapability(), - Extensions = new Dictionary { ["io.test"] = new JsonObject { ["enabled"] = true } } - }; - - string json = JsonSerializer.Serialize(capabilities, options); - Assert.DoesNotContain("\"extensions\"", json); - Assert.Contains("\"tools\"", json); - } - - [Fact] - public void ExperimentalProperties_IgnoredOnDeserialize_WithConsumerContextOnly() - { - string json = JsonSerializer.Serialize( - new ServerCapabilities - { - Tools = new ToolsCapability(), - Extensions = new Dictionary { ["io.test"] = new JsonObject { ["enabled"] = true } } - }, - McpJsonUtilities.DefaultOptions); - Assert.Contains("\"extensions\"", json); - - var options = new JsonSerializerOptions - { - TypeInfoResolverChain = { ConsumerJsonContext.Default } - }; - var deserialized = JsonSerializer.Deserialize(json, options)!; - Assert.NotNull(deserialized.Tools); - Assert.Null(deserialized.Extensions); - } - - [Fact] - public void ExperimentalProperties_RoundTrip_WhenSdkResolverIsChained() - { - var options = new JsonSerializerOptions - { - TypeInfoResolverChain = - { - McpJsonUtilities.DefaultOptions.TypeInfoResolver!, - ConsumerJsonContext.Default, - } - }; - - var capabilities = new ServerCapabilities - { - Tools = new ToolsCapability(), - Extensions = new Dictionary { ["io.test"] = new JsonObject { ["enabled"] = true } } - }; - - string json = JsonSerializer.Serialize(capabilities, options); - Assert.Contains("\"extensions\"", json); - Assert.Contains("\"tools\"", json); - - var deserialized = JsonSerializer.Deserialize(json, options)!; - Assert.NotNull(deserialized.Tools); - Assert.NotNull(deserialized.Extensions); - Assert.True(deserialized.Extensions.ContainsKey("io.test")); - } - - [Fact] - public void ExperimentalProperties_RoundTrip_WithDefaultOptions() - { - var capabilities = new ClientCapabilities - { - Extensions = new Dictionary { ["io.test"] = new JsonObject { ["enabled"] = true } } - }; - - string json = JsonSerializer.Serialize(capabilities, McpJsonUtilities.DefaultOptions); - Assert.Contains("\"extensions\"", json); - - var deserialized = JsonSerializer.Deserialize(json, McpJsonUtilities.DefaultOptions)!; - Assert.NotNull(deserialized.Extensions); - Assert.True(deserialized.Extensions.ContainsKey("io.test")); - } -} - -[JsonSerializable(typeof(Tool))] -[JsonSerializable(typeof(ServerCapabilities))] -[JsonSerializable(typeof(ClientCapabilities))] -[JsonSerializable(typeof(CallToolResult))] -[JsonSerializable(typeof(CallToolRequestParams))] -[JsonSerializable(typeof(CreateMessageRequestParams))] -[JsonSerializable(typeof(ElicitRequestParams))] -internal partial class ConsumerJsonContext : JsonSerializerContext;