Fully stabilize now-stable protocol properties (#1686)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
8e7f28c237
commit
5d00eedebb
4 changed files with 4 additions and 162 deletions
|
|
@ -1,4 +1,3 @@
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using ModelContextProtocol.Client;
|
using ModelContextProtocol.Client;
|
||||||
using ModelContextProtocol.Server;
|
using ModelContextProtocol.Server;
|
||||||
|
|
@ -83,15 +82,6 @@ public sealed class ClientCapabilities
|
||||||
/// interoperability. Clients advertise extension support via this field during the initialization handshake.
|
/// interoperability. Clients advertise extension support via this field during the initialization handshake.
|
||||||
/// </para>
|
/// </para>
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[JsonIgnore]
|
|
||||||
public IDictionary<string, object>? Extensions
|
|
||||||
{
|
|
||||||
get => ExtensionsCore;
|
|
||||||
set => ExtensionsCore = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// See ExperimentalInternalPropertyTests.cs before modifying this property.
|
|
||||||
[JsonInclude]
|
|
||||||
[JsonPropertyName("extensions")]
|
[JsonPropertyName("extensions")]
|
||||||
internal IDictionary<string, object>? ExtensionsCore { get; set; }
|
public IDictionary<string, object>? Extensions { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,17 +35,8 @@ public abstract class RequestParams
|
||||||
/// the value is the client's response to that input request.
|
/// the value is the client's response to that input request.
|
||||||
/// </para>
|
/// </para>
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[JsonIgnore]
|
|
||||||
public IDictionary<string, InputResponse>? InputResponses
|
|
||||||
{
|
|
||||||
get => InputResponsesCore;
|
|
||||||
set => InputResponsesCore = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// See ExperimentalInternalPropertyTests.cs before modifying this property.
|
|
||||||
[JsonInclude]
|
|
||||||
[JsonPropertyName("inputResponses")]
|
[JsonPropertyName("inputResponses")]
|
||||||
internal IDictionary<string, InputResponse>? InputResponsesCore { get; set; }
|
public IDictionary<string, InputResponse>? InputResponses { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets opaque request state echoed back from a previous <see cref="InputRequiredResult"/>.
|
/// Gets or sets opaque request state echoed back from a previous <see cref="InputRequiredResult"/>.
|
||||||
|
|
@ -57,17 +48,8 @@ public abstract class RequestParams
|
||||||
/// exact value without modification.
|
/// exact value without modification.
|
||||||
/// </para>
|
/// </para>
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[JsonIgnore]
|
|
||||||
public string? RequestState
|
|
||||||
{
|
|
||||||
get => RequestStateCore;
|
|
||||||
set => RequestStateCore = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// See ExperimentalInternalPropertyTests.cs before modifying this property.
|
|
||||||
[JsonInclude]
|
|
||||||
[JsonPropertyName("requestState")]
|
[JsonPropertyName("requestState")]
|
||||||
internal string? RequestStateCore { get; set; }
|
public string? RequestState { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the opaque token that will be attached to any subsequent progress notifications.
|
/// Gets the opaque token that will be attached to any subsequent progress notifications.
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using ModelContextProtocol.Server;
|
using ModelContextProtocol.Server;
|
||||||
|
|
||||||
|
|
@ -81,15 +80,6 @@ public sealed class ServerCapabilities
|
||||||
/// interoperability. Servers advertise extension support via this field during the initialization handshake.
|
/// interoperability. Servers advertise extension support via this field during the initialization handshake.
|
||||||
/// </para>
|
/// </para>
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[JsonIgnore]
|
|
||||||
public IDictionary<string, object>? Extensions
|
|
||||||
{
|
|
||||||
get => ExtensionsCore;
|
|
||||||
set => ExtensionsCore = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// See ExperimentalInternalPropertyTests.cs before modifying this property.
|
|
||||||
[JsonInclude]
|
|
||||||
[JsonPropertyName("extensions")]
|
[JsonPropertyName("extensions")]
|
||||||
internal IDictionary<string, object>? ExtensionsCore { get; set; }
|
public IDictionary<string, object>? Extensions { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,120 +0,0 @@
|
||||||
using System.Text.Json;
|
|
||||||
using System.Text.Json.Nodes;
|
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
using ModelContextProtocol.Protocol;
|
|
||||||
|
|
||||||
namespace ModelContextProtocol.Tests;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Validates that the internal property pattern used for experimental properties
|
|
||||||
/// produces the expected serialization behavior for SDK consumers using source generators.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// <para>
|
|
||||||
/// Experimental properties (e.g. <see cref="ServerCapabilities.Extensions"/>, <see cref="ClientCapabilities.Extensions"/>)
|
|
||||||
/// use an internal <c>*Core</c> property for serialization. A consumer's source-generated
|
|
||||||
/// <see cref="JsonSerializerContext"/> cannot see internal members, so experimental data is
|
|
||||||
/// silently dropped unless the consumer chains the SDK's resolver into their options.
|
|
||||||
/// </para>
|
|
||||||
/// <para>
|
|
||||||
/// These tests depend on <see cref="ServerCapabilities.Extensions"/> and <see cref="ClientCapabilities.Extensions"/>
|
|
||||||
/// 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.
|
|
||||||
/// </para>
|
|
||||||
/// </remarks>
|
|
||||||
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<string, object> { ["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<string, object> { ["io.test"] = new JsonObject { ["enabled"] = true } }
|
|
||||||
},
|
|
||||||
McpJsonUtilities.DefaultOptions);
|
|
||||||
Assert.Contains("\"extensions\"", json);
|
|
||||||
|
|
||||||
var options = new JsonSerializerOptions
|
|
||||||
{
|
|
||||||
TypeInfoResolverChain = { ConsumerJsonContext.Default }
|
|
||||||
};
|
|
||||||
var deserialized = JsonSerializer.Deserialize<ServerCapabilities>(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<string, object> { ["io.test"] = new JsonObject { ["enabled"] = true } }
|
|
||||||
};
|
|
||||||
|
|
||||||
string json = JsonSerializer.Serialize(capabilities, options);
|
|
||||||
Assert.Contains("\"extensions\"", json);
|
|
||||||
Assert.Contains("\"tools\"", json);
|
|
||||||
|
|
||||||
var deserialized = JsonSerializer.Deserialize<ServerCapabilities>(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<string, object> { ["io.test"] = new JsonObject { ["enabled"] = true } }
|
|
||||||
};
|
|
||||||
|
|
||||||
string json = JsonSerializer.Serialize(capabilities, McpJsonUtilities.DefaultOptions);
|
|
||||||
Assert.Contains("\"extensions\"", json);
|
|
||||||
|
|
||||||
var deserialized = JsonSerializer.Deserialize<ClientCapabilities>(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;
|
|
||||||
Loading…
Reference in a new issue