From 0a0a575a3dc0caa9c2b09890324c7851ea193fb5 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Feb 2026 18:04:09 -0500 Subject: [PATCH] Normalize CallToolResult.StructuredContent to JsonElement? (#1357) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> Co-authored-by: Stephen Toub --- .../Protocol/CallToolResult.cs | 4 +-- .../Server/AIFunctionMcpServerTool.cs | 26 +++++++++++-------- .../Client/McpClientToolTests.cs | 2 +- .../Protocol/CallToolResultTests.cs | 4 +-- .../Protocol/UnknownPropertiesTests.cs | 4 +-- .../Server/McpServerToolTests.cs | 6 ++--- 6 files changed, 25 insertions(+), 21 deletions(-) diff --git a/src/ModelContextProtocol.Core/Protocol/CallToolResult.cs b/src/ModelContextProtocol.Core/Protocol/CallToolResult.cs index cd4c1ab..35dba5b 100644 --- a/src/ModelContextProtocol.Core/Protocol/CallToolResult.cs +++ b/src/ModelContextProtocol.Core/Protocol/CallToolResult.cs @@ -1,5 +1,5 @@ using System.Diagnostics.CodeAnalysis; -using System.Text.Json.Nodes; +using System.Text.Json; using System.Text.Json.Serialization; namespace ModelContextProtocol.Protocol; @@ -41,7 +41,7 @@ public sealed class CallToolResult : Result /// Gets or sets an optional JSON object representing the structured result of the tool call. /// [JsonPropertyName("structuredContent")] - public JsonNode? StructuredContent { get; set; } + public JsonElement? StructuredContent { get; set; } /// /// Gets or sets a value that indicates whether the tool call was unsuccessful. diff --git a/src/ModelContextProtocol.Core/Server/AIFunctionMcpServerTool.cs b/src/ModelContextProtocol.Core/Server/AIFunctionMcpServerTool.cs index f29d8fe..e91bdd2 100644 --- a/src/ModelContextProtocol.Core/Server/AIFunctionMcpServerTool.cs +++ b/src/ModelContextProtocol.Core/Server/AIFunctionMcpServerTool.cs @@ -266,7 +266,7 @@ internal sealed partial class AIFunctionMcpServerTool : McpServerTool object? result; result = await AIFunction.InvokeAsync(arguments, cancellationToken).ConfigureAwait(false); - JsonNode? structuredContent = CreateStructuredResponse(result); + JsonElement? structuredContent = CreateStructuredResponse(result); return result switch { AIContent aiContent => new() @@ -529,7 +529,7 @@ internal sealed partial class AIFunctionMcpServerTool : McpServerTool return outputSchema; } - private JsonNode? CreateStructuredResponse(object? aiFunctionResult) + private JsonElement? CreateStructuredResponse(object? aiFunctionResult) { if (ProtocolTool.OutputSchema is null) { @@ -537,25 +537,29 @@ internal sealed partial class AIFunctionMcpServerTool : McpServerTool return null; } - JsonNode? nodeResult = aiFunctionResult switch + JsonElement? elementResult = aiFunctionResult switch { - JsonNode node => node, - JsonElement jsonElement => JsonSerializer.SerializeToNode(jsonElement, McpJsonUtilities.JsonContext.Default.JsonElement), - _ => JsonSerializer.SerializeToNode(aiFunctionResult, AIFunction.JsonSerializerOptions.GetTypeInfo(typeof(object))), + JsonElement jsonElement => jsonElement, + JsonNode node => JsonSerializer.SerializeToElement(node, McpJsonUtilities.JsonContext.Default.JsonNode), + null => null, + _ => JsonSerializer.SerializeToElement(aiFunctionResult, AIFunction.JsonSerializerOptions.GetTypeInfo(typeof(object))), }; if (_structuredOutputRequiresWrapping) { - return new JsonObject + JsonNode? resultNode = elementResult is { } je + ? JsonSerializer.SerializeToNode(je, McpJsonUtilities.JsonContext.Default.JsonElement) + : null; + return JsonSerializer.SerializeToElement(new JsonObject { - ["result"] = nodeResult - }; + ["result"] = resultNode + }, McpJsonUtilities.JsonContext.Default.JsonObject); } - return nodeResult; + return elementResult; } - private static CallToolResult ConvertAIContentEnumerableToCallToolResult(IEnumerable contentItems, JsonNode? structuredContent) + private static CallToolResult ConvertAIContentEnumerableToCallToolResult(IEnumerable contentItems, JsonElement? structuredContent) { List contentList = []; bool allErrorContent = true; diff --git a/tests/ModelContextProtocol.Tests/Client/McpClientToolTests.cs b/tests/ModelContextProtocol.Tests/Client/McpClientToolTests.cs index f6818fd..e17fb6b 100644 --- a/tests/ModelContextProtocol.Tests/Client/McpClientToolTests.cs +++ b/tests/ModelContextProtocol.Tests/Client/McpClientToolTests.cs @@ -122,7 +122,7 @@ public class McpClientToolTests : ClientServerTestBase new() { Content = [new TextContentBlock { Text = "Regular content" }], - StructuredContent = JsonNode.Parse("{\"key\":\"value\"}") + StructuredContent = JsonElement.Parse("{\"key\":\"value\"}") }; // Tool that returns CallToolResult with Meta diff --git a/tests/ModelContextProtocol.Tests/Protocol/CallToolResultTests.cs b/tests/ModelContextProtocol.Tests/Protocol/CallToolResultTests.cs index 29ecbcb..d66e03b 100644 --- a/tests/ModelContextProtocol.Tests/Protocol/CallToolResultTests.cs +++ b/tests/ModelContextProtocol.Tests/Protocol/CallToolResultTests.cs @@ -12,7 +12,7 @@ public static class CallToolResultTests var original = new CallToolResult { Content = [new TextContentBlock { Text = "Result text" }], - StructuredContent = JsonNode.Parse("""{"temperature":72}"""), + StructuredContent = JsonElement.Parse("""{"temperature":72}"""), IsError = false, Task = new McpTask { @@ -32,7 +32,7 @@ public static class CallToolResultTests var textBlock = Assert.IsType(deserialized.Content[0]); Assert.Equal("Result text", textBlock.Text); Assert.NotNull(deserialized.StructuredContent); - Assert.Equal(72, deserialized.StructuredContent["temperature"]!.GetValue()); + Assert.Equal(72, deserialized.StructuredContent.Value.GetProperty("temperature").GetInt32()); Assert.False(deserialized.IsError); Assert.NotNull(deserialized.Task); Assert.Equal("task-1", deserialized.Task.TaskId); diff --git a/tests/ModelContextProtocol.Tests/Protocol/UnknownPropertiesTests.cs b/tests/ModelContextProtocol.Tests/Protocol/UnknownPropertiesTests.cs index 1501553..05c25b5 100644 --- a/tests/ModelContextProtocol.Tests/Protocol/UnknownPropertiesTests.cs +++ b/tests/ModelContextProtocol.Tests/Protocol/UnknownPropertiesTests.cs @@ -252,7 +252,7 @@ public class UnknownPropertiesTests // Assert Assert.NotNull(deserialized); Assert.NotNull(deserialized.StructuredContent); - Assert.Equal("correctly placed here", deserialized.StructuredContent["result"]?.ToString()); - Assert.Equal(42, (int?)deserialized.StructuredContent["value"]); + Assert.Equal("correctly placed here", deserialized.StructuredContent.Value.GetProperty("result").GetString()); + Assert.Equal(42, deserialized.StructuredContent.Value.GetProperty("value").GetInt32()); } } diff --git a/tests/ModelContextProtocol.Tests/Server/McpServerToolTests.cs b/tests/ModelContextProtocol.Tests/Server/McpServerToolTests.cs index 552eaed..fd62a05 100644 --- a/tests/ModelContextProtocol.Tests/Server/McpServerToolTests.cs +++ b/tests/ModelContextProtocol.Tests/Server/McpServerToolTests.cs @@ -654,11 +654,11 @@ public partial class McpServerToolTests } } - private static void AssertMatchesJsonSchema(JsonElement schemaDoc, JsonNode? value) + private static void AssertMatchesJsonSchema(JsonElement schemaDoc, JsonElement? value) { JsonSchema schema = JsonSerializer.Deserialize(schemaDoc, JsonContext2.Default.JsonSchema)!; EvaluationOptions options = new() { OutputFormat = OutputFormat.List }; - EvaluationResults results = schema.Evaluate(JsonSerializer.SerializeToElement(value, JsonContext2.Default.JsonNode), options); + EvaluationResults results = schema.Evaluate(value!.Value, options); if (!results.IsValid) { IEnumerable errors = (results.Details ?? []) @@ -670,7 +670,7 @@ public partial class McpServerToolTests Schema: {JsonSerializer.Serialize(schema)} Instance: - {value?.ToJsonString() ?? "null"} + {value?.ToString() ?? "null"} Errors: {string.Join(Environment.NewLine, errors)} """);