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 <stoub@microsoft.com>
This commit is contained in:
parent
a2ee0c805a
commit
0a0a575a3d
6 changed files with 25 additions and 21 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Text.Json.Nodes;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace ModelContextProtocol.Protocol;
|
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.
|
/// Gets or sets an optional JSON object representing the structured result of the tool call.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonPropertyName("structuredContent")]
|
[JsonPropertyName("structuredContent")]
|
||||||
public JsonNode? StructuredContent { get; set; }
|
public JsonElement? StructuredContent { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value that indicates whether the tool call was unsuccessful.
|
/// Gets or sets a value that indicates whether the tool call was unsuccessful.
|
||||||
|
|
|
||||||
|
|
@ -266,7 +266,7 @@ internal sealed partial class AIFunctionMcpServerTool : McpServerTool
|
||||||
object? result;
|
object? result;
|
||||||
result = await AIFunction.InvokeAsync(arguments, cancellationToken).ConfigureAwait(false);
|
result = await AIFunction.InvokeAsync(arguments, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
JsonNode? structuredContent = CreateStructuredResponse(result);
|
JsonElement? structuredContent = CreateStructuredResponse(result);
|
||||||
return result switch
|
return result switch
|
||||||
{
|
{
|
||||||
AIContent aiContent => new()
|
AIContent aiContent => new()
|
||||||
|
|
@ -529,7 +529,7 @@ internal sealed partial class AIFunctionMcpServerTool : McpServerTool
|
||||||
return outputSchema;
|
return outputSchema;
|
||||||
}
|
}
|
||||||
|
|
||||||
private JsonNode? CreateStructuredResponse(object? aiFunctionResult)
|
private JsonElement? CreateStructuredResponse(object? aiFunctionResult)
|
||||||
{
|
{
|
||||||
if (ProtocolTool.OutputSchema is null)
|
if (ProtocolTool.OutputSchema is null)
|
||||||
{
|
{
|
||||||
|
|
@ -537,25 +537,29 @@ internal sealed partial class AIFunctionMcpServerTool : McpServerTool
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonNode? nodeResult = aiFunctionResult switch
|
JsonElement? elementResult = aiFunctionResult switch
|
||||||
{
|
{
|
||||||
JsonNode node => node,
|
JsonElement jsonElement => jsonElement,
|
||||||
JsonElement jsonElement => JsonSerializer.SerializeToNode(jsonElement, McpJsonUtilities.JsonContext.Default.JsonElement),
|
JsonNode node => JsonSerializer.SerializeToElement(node, McpJsonUtilities.JsonContext.Default.JsonNode),
|
||||||
_ => JsonSerializer.SerializeToNode(aiFunctionResult, AIFunction.JsonSerializerOptions.GetTypeInfo(typeof(object))),
|
null => null,
|
||||||
|
_ => JsonSerializer.SerializeToElement(aiFunctionResult, AIFunction.JsonSerializerOptions.GetTypeInfo(typeof(object))),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (_structuredOutputRequiresWrapping)
|
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<AIContent> contentItems, JsonNode? structuredContent)
|
private static CallToolResult ConvertAIContentEnumerableToCallToolResult(IEnumerable<AIContent> contentItems, JsonElement? structuredContent)
|
||||||
{
|
{
|
||||||
List<ContentBlock> contentList = [];
|
List<ContentBlock> contentList = [];
|
||||||
bool allErrorContent = true;
|
bool allErrorContent = true;
|
||||||
|
|
|
||||||
|
|
@ -122,7 +122,7 @@ public class McpClientToolTests : ClientServerTestBase
|
||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
Content = [new TextContentBlock { Text = "Regular content" }],
|
Content = [new TextContentBlock { Text = "Regular content" }],
|
||||||
StructuredContent = JsonNode.Parse("{\"key\":\"value\"}")
|
StructuredContent = JsonElement.Parse("{\"key\":\"value\"}")
|
||||||
};
|
};
|
||||||
|
|
||||||
// Tool that returns CallToolResult with Meta
|
// Tool that returns CallToolResult with Meta
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ public static class CallToolResultTests
|
||||||
var original = new CallToolResult
|
var original = new CallToolResult
|
||||||
{
|
{
|
||||||
Content = [new TextContentBlock { Text = "Result text" }],
|
Content = [new TextContentBlock { Text = "Result text" }],
|
||||||
StructuredContent = JsonNode.Parse("""{"temperature":72}"""),
|
StructuredContent = JsonElement.Parse("""{"temperature":72}"""),
|
||||||
IsError = false,
|
IsError = false,
|
||||||
Task = new McpTask
|
Task = new McpTask
|
||||||
{
|
{
|
||||||
|
|
@ -32,7 +32,7 @@ public static class CallToolResultTests
|
||||||
var textBlock = Assert.IsType<TextContentBlock>(deserialized.Content[0]);
|
var textBlock = Assert.IsType<TextContentBlock>(deserialized.Content[0]);
|
||||||
Assert.Equal("Result text", textBlock.Text);
|
Assert.Equal("Result text", textBlock.Text);
|
||||||
Assert.NotNull(deserialized.StructuredContent);
|
Assert.NotNull(deserialized.StructuredContent);
|
||||||
Assert.Equal(72, deserialized.StructuredContent["temperature"]!.GetValue<int>());
|
Assert.Equal(72, deserialized.StructuredContent.Value.GetProperty("temperature").GetInt32());
|
||||||
Assert.False(deserialized.IsError);
|
Assert.False(deserialized.IsError);
|
||||||
Assert.NotNull(deserialized.Task);
|
Assert.NotNull(deserialized.Task);
|
||||||
Assert.Equal("task-1", deserialized.Task.TaskId);
|
Assert.Equal("task-1", deserialized.Task.TaskId);
|
||||||
|
|
|
||||||
|
|
@ -252,7 +252,7 @@ public class UnknownPropertiesTests
|
||||||
// Assert
|
// Assert
|
||||||
Assert.NotNull(deserialized);
|
Assert.NotNull(deserialized);
|
||||||
Assert.NotNull(deserialized.StructuredContent);
|
Assert.NotNull(deserialized.StructuredContent);
|
||||||
Assert.Equal("correctly placed here", deserialized.StructuredContent["result"]?.ToString());
|
Assert.Equal("correctly placed here", deserialized.StructuredContent.Value.GetProperty("result").GetString());
|
||||||
Assert.Equal(42, (int?)deserialized.StructuredContent["value"]);
|
Assert.Equal(42, deserialized.StructuredContent.Value.GetProperty("value").GetInt32());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)!;
|
JsonSchema schema = JsonSerializer.Deserialize(schemaDoc, JsonContext2.Default.JsonSchema)!;
|
||||||
EvaluationOptions options = new() { OutputFormat = OutputFormat.List };
|
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)
|
if (!results.IsValid)
|
||||||
{
|
{
|
||||||
IEnumerable<string> errors = (results.Details ?? [])
|
IEnumerable<string> errors = (results.Details ?? [])
|
||||||
|
|
@ -670,7 +670,7 @@ public partial class McpServerToolTests
|
||||||
Schema:
|
Schema:
|
||||||
{JsonSerializer.Serialize(schema)}
|
{JsonSerializer.Serialize(schema)}
|
||||||
Instance:
|
Instance:
|
||||||
{value?.ToJsonString() ?? "null"}
|
{value?.ToString() ?? "null"}
|
||||||
Errors:
|
Errors:
|
||||||
{string.Join(Environment.NewLine, errors)}
|
{string.Join(Environment.NewLine, errors)}
|
||||||
""");
|
""");
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue