From 982138eef5b6b2be133ff17c2737301164adb6b9 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 18 Dec 2025 23:07:00 +0000
Subject: [PATCH 01/12] Initial plan
From 6a070b3e7722722a02a567c28d5d7e5b3d2fb3bf Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 18 Dec 2025 23:27:49 +0000
Subject: [PATCH 02/12] Fix ToJsonObject to support anonymous types in
dictionary values
- Updated ToJsonObject to use JsonSerializer.SerializeToNode(object, Type, JsonSerializerOptions) instead of strongly-typed JsonTypeInfo
- Added DefaultJsonTypeInfoResolver to McpJsonUtilities.DefaultOptions to enable reflection-based serialization for user-defined types
- Fixed bug in FunctionResultContent serialization where it was serializing 'content' instead of 'resultContent.Result'
- Simplified default case in ToContentBlock to avoid serializing unsupported AIContent types
- Added comprehensive tests for anonymous types in AdditionalProperties
- All tests passing on .NET 8, 9, and 10
Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
---
.../AIContentExtensions.cs | 11 +-
.../McpJsonUtilities.cs | 8 +-
.../AIContentExtensionsAnonymousTypeTests.cs | 135 ++++++++++++++++++
.../RegressionTests.cs | 31 ++++
4 files changed, 179 insertions(+), 6 deletions(-)
create mode 100644 tests/ModelContextProtocol.Tests/AIContentExtensionsAnonymousTypeTests.cs
create mode 100644 tests/ModelContextProtocol.Tests/RegressionTests.cs
diff --git a/src/ModelContextProtocol.Core/AIContentExtensions.cs b/src/ModelContextProtocol.Core/AIContentExtensions.cs
index b1ba32bf4..48e526a35 100644
--- a/src/ModelContextProtocol.Core/AIContentExtensions.cs
+++ b/src/ModelContextProtocol.Core/AIContentExtensions.cs
@@ -1,6 +1,7 @@
using Microsoft.Extensions.AI;
using ModelContextProtocol.Client;
using ModelContextProtocol.Protocol;
+using System.Diagnostics.CodeAnalysis;
#if !NET
using System.Runtime.InteropServices;
#endif
@@ -138,8 +139,10 @@ public static class AIContentExtensions
}
/// Converts the specified dictionary to a .
+ [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access", Justification = "DefaultOptions includes fallback to reflection-based serialization when available.")]
+ [UnconditionalSuppressMessage("ReflectionAnalysis", "IL3050:RequiresDynamicCode", Justification = "DefaultOptions includes fallback to reflection-based serialization when available.")]
internal static JsonObject? ToJsonObject(this IReadOnlyDictionary properties) =>
- JsonSerializer.SerializeToNode(properties, McpJsonUtilities.JsonContext.Default.IReadOnlyDictionaryStringObject) as JsonObject;
+ JsonSerializer.SerializeToNode(properties, typeof(IReadOnlyDictionary), McpJsonUtilities.DefaultOptions) as JsonObject;
internal static AdditionalPropertiesDictionary ToAdditionalProperties(this JsonObject obj)
{
@@ -271,7 +274,7 @@ public static IList ToPromptMessages(this ChatMessage chatMessage
EmbeddedResourceBlock resourceContent => resourceContent.Resource.ToAIContent(),
ToolUseContentBlock toolUse => FunctionCallContent.CreateFromParsedArguments(toolUse.Input, toolUse.Id, toolUse.Name,
- static json => JsonSerializer.Deserialize(json, McpJsonUtilities.JsonContext.Default.IDictionaryStringObject)),
+ static json => JsonSerializer.Deserialize(json, McpJsonUtilities.DefaultOptions.GetTypeInfo>())),
ToolResultContentBlock toolResult => new FunctionResultContent(
toolResult.ToolUseId,
@@ -414,13 +417,13 @@ public static ContentBlock ToContentBlock(this AIContent content)
Content =
resultContent.Result is AIContent c ? [c.ToContentBlock()] :
resultContent.Result is IEnumerable ec ? [.. ec.Select(c => c.ToContentBlock())] :
- [new TextContentBlock { Text = JsonSerializer.Serialize(content, McpJsonUtilities.DefaultOptions.GetTypeInfo