Update to .NET 10.x packages (#957)
This commit is contained in:
parent
d4ee78797b
commit
b993ae0e61
9 changed files with 298 additions and 174 deletions
|
|
@ -1,72 +1,69 @@
|
||||||
<Project>
|
<Project>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||||
<System9Version>9.0.10</System9Version>
|
<System8Version>8.0.22</System8Version>
|
||||||
<System10Version>10.0.0-rc.2.25502.107</System10Version>
|
<System9Version>9.0.11</System9Version>
|
||||||
<MicrosoftExtensionsAIVersion>9.10.2</MicrosoftExtensionsAIVersion>
|
<System10Version>10.0.0</System10Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<!-- Product dependencies netstandard -->
|
<!-- Product dependencies shared -->
|
||||||
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
|
<ItemGroup>
|
||||||
<PackageVersion Include="Microsoft.Bcl.Memory" Version="$(System9Version)" />
|
<PackageVersion Include="Microsoft.Extensions.AI" Version="$(System10Version)" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.1" />
|
<PackageVersion Include="Microsoft.Extensions.AI.Abstractions" Version="$(System10Version)" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.3" />
|
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="$(System10Version)" />
|
||||||
<PackageVersion Include="System.Diagnostics.DiagnosticSource" Version="8.0.1" />
|
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="$(System10Version)" />
|
||||||
<PackageVersion Include="System.IO.Pipelines" Version="8.0.0" />
|
|
||||||
<PackageVersion Include="System.Text.Json" Version="8.0.6" />
|
|
||||||
<PackageVersion Include="System.Threading.Channels" Version="8.0.0" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<!-- Product dependencies LTS -->
|
<!-- Product dependencies < .NET 10 -->
|
||||||
|
<ItemGroup Condition="!$([MSBuild]::IsTargetFrameworkCompatible($(TargetFramework), 'net10.0'))">
|
||||||
|
<PackageVersion Include="System.Net.ServerSentEvents" Version="$(System10Version)" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!-- Product dependencies .NET Standard -->
|
||||||
|
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
|
||||||
|
<PackageVersion Include="Microsoft.Bcl.Memory" Version="$(System10Version)" />
|
||||||
|
<PackageVersion Include="System.Diagnostics.DiagnosticSource" Version="$(System10Version)" />
|
||||||
|
<PackageVersion Include="System.IO.Pipelines" Version="$(System10Version)" />
|
||||||
|
<PackageVersion Include="System.Text.Json" Version="$(System10Version)" />
|
||||||
|
<PackageVersion Include="System.Threading.Channels" Version="$(System10Version)" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!-- Product dependencies .NET 8 -->
|
||||||
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
|
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
|
||||||
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.15" />
|
<PackageVersion Include="System.IO.Pipelines" Version="$(System10Version)" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.1" />
|
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="$(System8Version)" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.3" />
|
|
||||||
<PackageVersion Include="System.IO.Pipelines" Version="8.0.0" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<!-- Product dependencies .NET 9 -->
|
<!-- Product dependencies .NET 9 -->
|
||||||
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
|
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
|
||||||
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="$(System9Version)" />
|
|
||||||
<PackageVersion Include="Microsoft.IdentityModel.Tokens" Version="8.14.0" />
|
<PackageVersion Include="Microsoft.IdentityModel.Tokens" Version="8.14.0" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="$(System9Version)" />
|
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="$(System9Version)" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="$(System9Version)" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<!-- Product dependencies .NET 10 -->
|
<!-- Product dependencies .NET 10 -->
|
||||||
<ItemGroup Condition="'$(TargetFramework)' == 'net10.0'">
|
<ItemGroup Condition="'$(TargetFramework)' == 'net10.0'">
|
||||||
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="$(System10Version)" />
|
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="$(System10Version)" />
|
||||||
<PackageVersion Include="Microsoft.IdentityModel.Tokens" Version="8.14.0" />
|
<PackageVersion Include="Microsoft.IdentityModel.Tokens" Version="8.14.0" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="$(System10Version)" />
|
|
||||||
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="$(System10Version)" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<!-- Product dependencies shared -->
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageVersion Include="Microsoft.Extensions.AI.Abstractions" Version="$(MicrosoftExtensionsAIVersion)" />
|
|
||||||
<PackageVersion Include="Microsoft.Extensions.AI" Version="$(MicrosoftExtensionsAIVersion)" />
|
|
||||||
<PackageVersion Include="System.Net.ServerSentEvents" Version="$(System10Version)" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
||||||
<!-- Build Infra & Packaging -->
|
<!-- Build Infra & Packaging -->
|
||||||
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
|
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
|
||||||
|
|
||||||
<!-- Testing dependencies -->
|
<!-- Testing dependencies -->
|
||||||
<PackageVersion Include="Anthropic.SDK" Version="5.6.0" />
|
<PackageVersion Include="Anthropic.SDK" Version="5.8.0" />
|
||||||
<PackageVersion Include="coverlet.collector" Version="6.0.4">
|
<PackageVersion Include="coverlet.collector" Version="6.0.4">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</PackageVersion>
|
</PackageVersion>
|
||||||
<PackageVersion Include="GitHubActionsTestLogger" Version="2.4.1" />
|
<PackageVersion Include="GitHubActionsTestLogger" Version="2.4.1" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.AI.OpenAI" Version="9.10.2-preview.1.25552.1" />
|
<PackageVersion Include="Microsoft.Extensions.AI.OpenAI" Version="10.0.0-preview.1.25559.3" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="$(System9Version)" />
|
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="$(System10Version)" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="$(System9Version)" />
|
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="$(System10Version)" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="$(System9Version)" />
|
<PackageVersion Include="Microsoft.Extensions.Logging" Version="$(System10Version)" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="$(System9Version)" />
|
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="$(System10Version)" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Options" Version="$(System9Version)" />
|
<PackageVersion Include="Microsoft.Extensions.Options" Version="$(System10Version)" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.TimeProvider.Testing" Version="9.10.0" />
|
<PackageVersion Include="Microsoft.Extensions.TimeProvider.Testing" Version="$(System10Version)" />
|
||||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||||
<PackageVersion Include="Moq" Version="4.20.72" />
|
<PackageVersion Include="Moq" Version="4.20.72" />
|
||||||
<PackageVersion Include="OpenTelemetry" Version="1.12.0" />
|
<PackageVersion Include="OpenTelemetry" Version="1.12.0" />
|
||||||
|
|
|
||||||
|
|
@ -1,125 +0,0 @@
|
||||||
// Licensed to the .NET Foundation under one or more agreements.
|
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
|
||||||
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
#if !NET9_0_OR_GREATER
|
|
||||||
using System.Reflection;
|
|
||||||
#endif
|
|
||||||
using System.Text.Json;
|
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
#if !NET9_0_OR_GREATER
|
|
||||||
using ModelContextProtocol;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// NOTE:
|
|
||||||
// This is a workaround for lack of System.Text.Json's JsonStringEnumConverter<T>
|
|
||||||
// 9.x support for JsonStringEnumMemberNameAttribute. Once all builds use the System.Text.Json 9.x
|
|
||||||
// version, this whole file can be removed. Note that the type is public so that external source
|
|
||||||
// generators can use it, so removing it is a potential breaking change.
|
|
||||||
|
|
||||||
namespace ModelContextProtocol
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// A JSON converter for enums that allows customizing the serialized string value of enum members
|
|
||||||
/// using the <see cref="JsonStringEnumMemberNameAttribute"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="TEnum">The enum type to convert.</typeparam>
|
|
||||||
/// <remarks>
|
|
||||||
/// This is a temporary workaround for lack of System.Text.Json's JsonStringEnumConverter<T>
|
|
||||||
/// 9.x support for custom enum member naming. It will be replaced by the built-in functionality
|
|
||||||
/// once .NET 9 is fully adopted.
|
|
||||||
/// </remarks>
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
public sealed class CustomizableJsonStringEnumConverter<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] TEnum> :
|
|
||||||
JsonStringEnumConverter<TEnum> where TEnum : struct, Enum
|
|
||||||
{
|
|
||||||
#if !NET9_0_OR_GREATER
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="CustomizableJsonStringEnumConverter{TEnum}"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// The converter automatically detects any enum members decorated with <see cref="JsonStringEnumMemberNameAttribute"/>
|
|
||||||
/// and uses those values during serialization and deserialization.
|
|
||||||
/// </remarks>
|
|
||||||
public CustomizableJsonStringEnumConverter() :
|
|
||||||
base(namingPolicy: ResolveNamingPolicy())
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
private static JsonNamingPolicy? ResolveNamingPolicy()
|
|
||||||
{
|
|
||||||
var map = typeof(TEnum).GetFields(BindingFlags.Public | BindingFlags.Static)
|
|
||||||
.Select(f => (f.Name, AttributeName: f.GetCustomAttribute<JsonStringEnumMemberNameAttribute>()?.Name))
|
|
||||||
.Where(pair => pair.AttributeName != null)
|
|
||||||
.ToDictionary(pair => pair.Name, pair => pair.AttributeName);
|
|
||||||
|
|
||||||
return map.Count > 0 ? new EnumMemberNamingPolicy(map!) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class EnumMemberNamingPolicy(Dictionary<string, string> map) : JsonNamingPolicy
|
|
||||||
{
|
|
||||||
public override string ConvertName(string name) =>
|
|
||||||
map.TryGetValue(name, out string? newName) ?
|
|
||||||
newName :
|
|
||||||
name;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A JSON converter for enums that allows customizing the serialized string value of enum members
|
|
||||||
/// using the <see cref="JsonStringEnumMemberNameAttribute"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// This is a temporary workaround for lack of System.Text.Json's JsonStringEnumConverter<T>
|
|
||||||
/// 9.x support for custom enum member naming. It will be replaced by the built-in functionality
|
|
||||||
/// once .NET 9 is fully adopted.
|
|
||||||
/// </remarks>
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
[RequiresUnreferencedCode("Requires unreferenced code to instantiate the generic enum converter.")]
|
|
||||||
[RequiresDynamicCode("Requires dynamic code to instantiate the generic enum converter.")]
|
|
||||||
public sealed class CustomizableJsonStringEnumConverter : JsonConverterFactory
|
|
||||||
{
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override bool CanConvert(Type typeToConvert) => typeToConvert.IsEnum;
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options)
|
|
||||||
{
|
|
||||||
Type converterType = typeof(CustomizableJsonStringEnumConverter<>).MakeGenericType(typeToConvert)!;
|
|
||||||
var factory = (JsonConverterFactory)Activator.CreateInstance(converterType)!;
|
|
||||||
return factory.CreateConverter(typeToConvert, options);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#if !NET9_0_OR_GREATER
|
|
||||||
namespace System.Text.Json.Serialization
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Determines the custom string value that should be used when serializing an enum member using JSON.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// This attribute is a temporary workaround for lack of System.Text.Json's support for custom enum member naming
|
|
||||||
/// in versions prior to .NET 9. It works together with <see cref="CustomizableJsonStringEnumConverter{TEnum}"/>
|
|
||||||
/// to provide customized string representations of enum values during JSON serialization and deserialization.
|
|
||||||
/// </remarks>
|
|
||||||
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
|
|
||||||
internal sealed class JsonStringEnumMemberNameAttribute : Attribute
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Creates new attribute instance with a specified enum member name.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="name">The name to apply to the current enum member when serialized to JSON.</param>
|
|
||||||
public JsonStringEnumMemberNameAttribute(string name)
|
|
||||||
{
|
|
||||||
Name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the custom JSON name of the enum member.
|
|
||||||
/// </summary>
|
|
||||||
public string Name { get; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
@ -13,13 +13,8 @@ internal static class Diagnostics
|
||||||
internal static Meter Meter { get; } = new("Experimental.ModelContextProtocol");
|
internal static Meter Meter { get; } = new("Experimental.ModelContextProtocol");
|
||||||
|
|
||||||
internal static Histogram<double> CreateDurationHistogram(string name, string description, bool longBuckets) =>
|
internal static Histogram<double> CreateDurationHistogram(string name, string description, bool longBuckets) =>
|
||||||
Meter.CreateHistogram<double>(name, "s", description
|
Meter.CreateHistogram<double>(name, "s", description, advice: longBuckets ? LongSecondsBucketBoundaries : ShortSecondsBucketBoundaries);
|
||||||
#if NET9_0_OR_GREATER
|
|
||||||
, advice: longBuckets ? LongSecondsBucketBoundaries : ShortSecondsBucketBoundaries
|
|
||||||
#endif
|
|
||||||
);
|
|
||||||
|
|
||||||
#if NET9_0_OR_GREATER
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Follows boundaries from http.server.request.duration/http.client.request.duration
|
/// Follows boundaries from http.server.request.duration/http.client.request.duration
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -36,7 +31,6 @@ internal static class Diagnostics
|
||||||
{
|
{
|
||||||
HistogramBucketBoundaries = [0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 2, 5, 10, 30, 60, 120, 300],
|
HistogramBucketBoundaries = [0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 2, 5, 10, 30, 60, 120, 300],
|
||||||
};
|
};
|
||||||
#endif
|
|
||||||
|
|
||||||
internal static ActivityContext ExtractActivityContext(this DistributedContextPropagator propagator, JsonRpcMessage message)
|
internal static ActivityContext ExtractActivityContext(this DistributedContextPropagator propagator, JsonRpcMessage message)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ public static partial class McpJsonUtilities
|
||||||
// Add a converter for user-defined enums, if reflection is enabled by default.
|
// Add a converter for user-defined enums, if reflection is enabled by default.
|
||||||
if (JsonSerializer.IsReflectionEnabledByDefault)
|
if (JsonSerializer.IsReflectionEnabledByDefault)
|
||||||
{
|
{
|
||||||
options.Converters.Add(new CustomizableJsonStringEnumConverter());
|
options.Converters.Add(new JsonStringEnumConverter());
|
||||||
}
|
}
|
||||||
|
|
||||||
options.MakeReadOnly();
|
options.MakeReadOnly();
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ namespace ModelContextProtocol.Protocol;
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
|
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[JsonConverter(typeof(CustomizableJsonStringEnumConverter<ContextInclusion>))]
|
[JsonConverter(typeof(JsonStringEnumConverter<ContextInclusion>))]
|
||||||
public enum ContextInclusion
|
public enum ContextInclusion
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ namespace ModelContextProtocol.Protocol;
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// These map to syslog message severities, as specified in <see href="https://datatracker.ietf.org/doc/html/rfc5424#section-6.2.1">RFC-5424</see>.
|
/// These map to syslog message severities, as specified in <see href="https://datatracker.ietf.org/doc/html/rfc5424#section-6.2.1">RFC-5424</see>.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[JsonConverter(typeof(CustomizableJsonStringEnumConverter<LoggingLevel>))]
|
[JsonConverter(typeof(JsonStringEnumConverter<LoggingLevel>))]
|
||||||
public enum LoggingLevel
|
public enum LoggingLevel
|
||||||
{
|
{
|
||||||
/// <summary>Detailed debug information, typically only valuable to developers.</summary>
|
/// <summary>Detailed debug information, typically only valuable to developers.</summary>
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ namespace ModelContextProtocol.Protocol;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents the type of role in the Model Context Protocol conversation.
|
/// Represents the type of role in the Model Context Protocol conversation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonConverter(typeof(CustomizableJsonStringEnumConverter<Role>))]
|
[JsonConverter(typeof(JsonStringEnumConverter<Role>))]
|
||||||
public enum Role
|
public enum Role
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -309,7 +309,7 @@ public partial class ElicitationTypedTests : ClientServerTestBase
|
||||||
Assert.Contains(typeof(string).FullName!, ex.Message);
|
Assert.Contains(typeof(string).FullName!, ex.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
[JsonConverter(typeof(CustomizableJsonStringEnumConverter<SampleRole>))]
|
[JsonConverter(typeof(JsonStringEnumConverter<SampleRole>))]
|
||||||
|
|
||||||
public enum SampleRole
|
public enum SampleRole
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,258 @@
|
||||||
|
using System.Text.Json;
|
||||||
|
using ModelContextProtocol.Protocol;
|
||||||
|
|
||||||
|
namespace ModelContextProtocol.Tests.Protocol;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests that custom JSON converters properly handle unknown additional properties
|
||||||
|
/// for forward compatibility with future protocol versions.
|
||||||
|
/// </summary>
|
||||||
|
public class UnknownPropertiesTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void ContentBlock_DeserializationWithUnknownProperty_SkipsProperty()
|
||||||
|
{
|
||||||
|
// Arrange - JSON with unknown "unknownField" property
|
||||||
|
const string Json = """
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "Hello, world!",
|
||||||
|
"unknownField": "should be skipped"
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var deserialized = JsonSerializer.Deserialize<ContentBlock>(Json, McpJsonUtilities.DefaultOptions);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(deserialized);
|
||||||
|
var textBlock = Assert.IsType<TextContentBlock>(deserialized);
|
||||||
|
Assert.Equal("Hello, world!", textBlock.Text);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ContentBlock_DeserializationWithStructuredContentInContent_SkipsProperty()
|
||||||
|
{
|
||||||
|
// Arrange - This was the actual bug case: structuredContent incorrectly placed
|
||||||
|
// inside a ContentBlock instead of at CallToolResult level
|
||||||
|
const string Json = """
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "Result text",
|
||||||
|
"structuredContent": {
|
||||||
|
"data": "this should be ignored"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
// Act - Should not throw
|
||||||
|
var deserialized = JsonSerializer.Deserialize<ContentBlock>(Json, McpJsonUtilities.DefaultOptions);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(deserialized);
|
||||||
|
var textBlock = Assert.IsType<TextContentBlock>(deserialized);
|
||||||
|
Assert.Equal("Result text", textBlock.Text);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ContentBlock_DeserializationWithMultipleUnknownProperties_SkipsAll()
|
||||||
|
{
|
||||||
|
// Arrange - JSON with multiple unknown properties
|
||||||
|
const string Json = """
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"data": "base64data",
|
||||||
|
"mimeType": "image/png",
|
||||||
|
"futureProperty1": "value1",
|
||||||
|
"futureProperty2": {"nested": "object"},
|
||||||
|
"futureProperty3": [1, 2, 3]
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var deserialized = JsonSerializer.Deserialize<ContentBlock>(Json, McpJsonUtilities.DefaultOptions);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(deserialized);
|
||||||
|
var imageBlock = Assert.IsType<ImageContentBlock>(deserialized);
|
||||||
|
Assert.Equal("base64data", imageBlock.Data);
|
||||||
|
Assert.Equal("image/png", imageBlock.MimeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Reference_DeserializationWithUnknownProperty_SkipsProperty()
|
||||||
|
{
|
||||||
|
// Arrange - JSON with unknown "metadata" property
|
||||||
|
const string Json = """
|
||||||
|
{
|
||||||
|
"type": "ref/prompt",
|
||||||
|
"name": "test-prompt",
|
||||||
|
"metadata": {
|
||||||
|
"version": "2.0",
|
||||||
|
"author": "future"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var deserialized = JsonSerializer.Deserialize<Reference>(Json, McpJsonUtilities.DefaultOptions);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(deserialized);
|
||||||
|
var promptRef = Assert.IsType<PromptReference>(deserialized);
|
||||||
|
Assert.Equal("test-prompt", promptRef.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Reference_DeserializationWithMultipleUnknownProperties_SkipsAll()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
const string Json = """
|
||||||
|
{
|
||||||
|
"type": "ref/resource",
|
||||||
|
"uri": "file:///test.txt",
|
||||||
|
"extraField1": "ignored",
|
||||||
|
"extraField2": 123,
|
||||||
|
"extraField3": true
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var deserialized = JsonSerializer.Deserialize<Reference>(Json, McpJsonUtilities.DefaultOptions);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(deserialized);
|
||||||
|
var resourceRef = Assert.IsType<ResourceTemplateReference>(deserialized);
|
||||||
|
Assert.Equal("file:///test.txt", resourceRef.Uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ResourceContents_DeserializationWithUnknownProperty_SkipsProperty()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
const string Json = """
|
||||||
|
{
|
||||||
|
"uri": "file:///document.txt",
|
||||||
|
"text": "File contents here",
|
||||||
|
"mimeType": "text/plain",
|
||||||
|
"futureEncoding": "utf-16",
|
||||||
|
"futureChecksum": "abc123"
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var deserialized = JsonSerializer.Deserialize<ResourceContents>(Json, McpJsonUtilities.DefaultOptions);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(deserialized);
|
||||||
|
var textResource = Assert.IsType<TextResourceContents>(deserialized);
|
||||||
|
Assert.Equal("file:///document.txt", textResource.Uri);
|
||||||
|
Assert.Equal("File contents here", textResource.Text);
|
||||||
|
Assert.Equal("text/plain", textResource.MimeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ProgressNotificationParams_DeserializationWithUnknownProperty_SkipsProperty()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
const string Json = """
|
||||||
|
{
|
||||||
|
"progressToken": "token123",
|
||||||
|
"progress": 50,
|
||||||
|
"newProgressFormat": "percentage",
|
||||||
|
"estimatedTimeRemaining": 30
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var deserialized = JsonSerializer.Deserialize<ProgressNotificationParams>(Json, McpJsonUtilities.DefaultOptions);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(deserialized);
|
||||||
|
Assert.Equal("token123", deserialized.ProgressToken.ToString());
|
||||||
|
Assert.Equal(50.0f, deserialized.Progress.Progress);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void PrimitiveSchemaDefinition_DeserializationWithUnknownProperty_SkipsProperty()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
const string Json = """
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "A test string",
|
||||||
|
"futureValidation": "regex:.*",
|
||||||
|
"futureMaxLength": 100
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var deserialized = JsonSerializer.Deserialize<ElicitRequestParams.PrimitiveSchemaDefinition>(Json, McpJsonUtilities.DefaultOptions);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(deserialized);
|
||||||
|
var stringSchema = Assert.IsType<ElicitRequestParams.StringSchema>(deserialized);
|
||||||
|
Assert.Equal("A test string", stringSchema.Description);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CallToolResult_WithContentBlockContainingUnknownProperties_Succeeds()
|
||||||
|
{
|
||||||
|
// Arrange - Simulates the real-world bug scenario: a malformed response where
|
||||||
|
// structuredContent was incorrectly nested inside content blocks
|
||||||
|
const string Json = """
|
||||||
|
{
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "Tool executed successfully",
|
||||||
|
"structuredContent": {
|
||||||
|
"result": "this was incorrectly placed here"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"isError": false
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
// Act - Should not throw an exception
|
||||||
|
var deserialized = JsonSerializer.Deserialize<CallToolResult>(Json, McpJsonUtilities.DefaultOptions);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(deserialized);
|
||||||
|
Assert.Single(deserialized.Content);
|
||||||
|
var textBlock = Assert.IsType<TextContentBlock>(deserialized.Content[0]);
|
||||||
|
Assert.Equal("Tool executed successfully", textBlock.Text);
|
||||||
|
Assert.False(deserialized.IsError);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CallToolResult_WithStructuredContentAtCorrectLevel_PreservesProperty()
|
||||||
|
{
|
||||||
|
// Arrange - Correct placement of structuredContent at CallToolResult level
|
||||||
|
const string Json = """
|
||||||
|
{
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": "Tool executed"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"structuredContent": {
|
||||||
|
"result": "correctly placed here",
|
||||||
|
"value": 42
|
||||||
|
},
|
||||||
|
"isError": false
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var deserialized = JsonSerializer.Deserialize<CallToolResult>(Json, McpJsonUtilities.DefaultOptions);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(deserialized);
|
||||||
|
Assert.NotNull(deserialized.StructuredContent);
|
||||||
|
Assert.Equal("correctly placed here", deserialized.StructuredContent["result"]?.ToString());
|
||||||
|
Assert.Equal(42, (int?)deserialized.StructuredContent["value"]);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue