Skip to content
4 changes: 2 additions & 2 deletions src/ModelContextProtocol.Core/Protocol/CallToolResult.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -41,7 +41,7 @@ public sealed class CallToolResult : Result
/// Gets or sets an optional JSON object representing the structured result of the tool call.
/// </summary>
[JsonPropertyName("structuredContent")]
public JsonNode? StructuredContent { get; set; }
public JsonElement? StructuredContent { get; set; }

/// <summary>
/// Gets or sets a value that indicates whether the tool call was unsuccessful.
Expand Down
26 changes: 15 additions & 11 deletions src/ModelContextProtocol.Core/Server/AIFunctionMcpServerTool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ public override async ValueTask<CallToolResult> InvokeAsync(
object? result;
result = await AIFunction.InvokeAsync(arguments, cancellationToken).ConfigureAwait(false);

JsonNode? structuredContent = CreateStructuredResponse(result);
JsonElement? structuredContent = CreateStructuredResponse(result);
return result switch
{
AIContent aiContent => new()
Expand Down Expand Up @@ -529,33 +529,37 @@ typeProperty.ValueKind is not JsonValueKind.String ||
return outputSchema;
}

private JsonNode? CreateStructuredResponse(object? aiFunctionResult)
private JsonElement? CreateStructuredResponse(object? aiFunctionResult)
{
if (ProtocolTool.OutputSchema is null)
{
// Only provide structured responses if the tool has an output schema defined.
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<AIContent> contentItems, JsonNode? structuredContent)
private static CallToolResult ConvertAIContentEnumerableToCallToolResult(IEnumerable<AIContent> contentItems, JsonElement? structuredContent)
{
List<ContentBlock> contentList = [];
bool allErrorContent = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ public static CallToolResult StructuredContentTool() =>
new()
{
Content = [new TextContentBlock { Text = "Regular content" }],
StructuredContent = JsonNode.Parse("{\"key\":\"value\"}")
StructuredContent = JsonElement.Parse("{\"key\":\"value\"}")
};

// Tool that returns CallToolResult with Meta
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public static void CallToolResult_SerializationRoundTrip_PreservesAllProperties(
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
{
Expand All @@ -32,7 +32,7 @@ public static void CallToolResult_SerializationRoundTrip_PreservesAllProperties(
var textBlock = Assert.IsType<TextContentBlock>(deserialized.Content[0]);
Assert.Equal("Result text", textBlock.Text);
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.NotNull(deserialized.Task);
Assert.Equal("task-1", deserialized.Task.TaskId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ public void CallToolResult_WithStructuredContentAtCorrectLevel_PreservesProperty
// 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());
}
}
6 changes: 3 additions & 3 deletions tests/ModelContextProtocol.Tests/Server/McpServerToolTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -654,11 +654,11 @@ public object InstanceMethod()
}
}

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<string> errors = (results.Details ?? [])
Expand All @@ -670,7 +670,7 @@ Instance JSON document does not match the specified schema.
Schema:
{JsonSerializer.Serialize(schema)}
Instance:
{value?.ToJsonString() ?? "null"}
{value?.ToString() ?? "null"}
Errors:
{string.Join(Environment.NewLine, errors)}
""");
Expand Down