Add structured output/output schema support for server-side tools. (#480)

* Add structured output/output schema support for server-side tools.

* Address feedback.

* Add McpServerToolAttribute.UseStructuredContent

---------

Co-authored-by: Stephen Toub <stoub@microsoft.com>
This commit is contained in:
Eirik Tsarpalis 2025-06-06 08:20:12 +01:00 • committed by GitHub
commit fa017c04c3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 310 additions and 20 deletions

View file

@ -75,5 +75,6 @@
<PackageVersion Include="xunit.v3" Version="2.0.2" /> <PackageVersion Include="xunit.v3" Version="2.0.2" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.0" /> <PackageVersion Include="xunit.runner.visualstudio" Version="3.1.0" />
<PackageVersion Include="System.Net.Http" Version="4.3.4" /> <PackageVersion Include="System.Net.Http" Version="4.3.4" />
<PackageVersion Include="JsonSchema.Net" Version="7.3.4" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View file

@ -24,7 +24,6 @@
<File Path="logo.png" /> <File Path="logo.png" />
<File Path="nuget.config" /> <File Path="nuget.config" />
<File Path="README.MD" /> <File Path="README.MD" />
<File Path="version.json" />
</Folder> </Folder>
<Folder Name="/src/"> <Folder Name="/src/">
<File Path="src/Directory.Build.props" /> <File Path="src/Directory.Build.props" />

View file

@ -1,5 +1,6 @@
using Microsoft.Extensions.AI; using Microsoft.Extensions.AI;
using ModelContextProtocol.Protocol; using ModelContextProtocol.Protocol;
using ModelContextProtocol.Server;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
@ -75,6 +76,30 @@ public static partial class McpJsonUtilities
return false; // No type keyword found. return false; // No type keyword found.
} }
internal static JsonElement? GetReturnSchema(this AIFunction function, AIJsonSchemaCreateOptions? schemaCreateOptions)
{
// TODO replace with https://github.com/dotnet/extensions/pull/6447 once merged.
if (function.UnderlyingMethod?.ReturnType is not Type returnType)
{
return null;
}
if (returnType == typeof(void) || returnType == typeof(Task) || returnType == typeof(ValueTask))
{
// Do not report an output schema for void or Task methods.
return null;
}
if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() is Type genericTypeDef &&
(genericTypeDef == typeof(Task<>) || genericTypeDef == typeof(ValueTask<>)))
{
// Extract the real type from Task<T> or ValueTask<T> if applicable.
returnType = returnType.GetGenericArguments()[0];
}
return AIJsonUtilities.CreateJsonSchema(returnType, serializerOptions: function.JsonSerializerOptions, inferenceOptions: schemaCreateOptions);
}
// Keep in sync with CreateDefaultOptions above. // Keep in sync with CreateDefaultOptions above.
[JsonSourceGenerationOptions(JsonSerializerDefaults.Web, [JsonSourceGenerationOptions(JsonSerializerDefaults.Web,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,

View file

@ -1,3 +1,4 @@
using System.Text.Json.Nodes;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
namespace ModelContextProtocol.Protocol; namespace ModelContextProtocol.Protocol;
@ -27,6 +28,12 @@ public class CallToolResponse
[JsonPropertyName("content")] [JsonPropertyName("content")]
public List<Content> Content { get; set; } = []; public List<Content> Content { get; set; } = [];
/// <summary>
/// Gets or sets an optional JSON object representing the structured result of the tool call.
/// </summary>
[JsonPropertyName("structuredContent")]
public JsonNode? StructuredContent { get; set; }
/// <summary> /// <summary>
/// Gets or sets an indication of whether the tool call was unsuccessful. /// Gets or sets an indication of whether the tool call was unsuccessful.
/// </summary> /// </summary>

View file

@ -8,8 +8,6 @@ namespace ModelContextProtocol.Protocol;
/// </summary> /// </summary>
public class Tool public class Tool
{ {
private JsonElement _inputSchema = McpJsonUtilities.DefaultMcpToolSchema;
/// <summary> /// <summary>
/// Gets or sets the name of the tool. /// Gets or sets the name of the tool.
/// </summary> /// </summary>
@ -53,15 +51,44 @@ public class Tool
[JsonPropertyName("inputSchema")] [JsonPropertyName("inputSchema")]
public JsonElement InputSchema public JsonElement InputSchema
{ {
get => _inputSchema; get => field;
set set
{ {
if (!McpJsonUtilities.IsValidMcpToolSchema(value)) if (!McpJsonUtilities.IsValidMcpToolSchema(value))
{ {
throw new ArgumentException("The specified document is not a valid MCP tool JSON schema.", nameof(InputSchema)); throw new ArgumentException("The specified document is not a valid MCP tool input JSON schema.", nameof(InputSchema));
} }
_inputSchema = value; field = value;
}
} = McpJsonUtilities.DefaultMcpToolSchema;
/// <summary>
/// Gets or sets a JSON Schema object defining the expected structured outputs for the tool.
/// </summary>
/// <remarks>
/// <para>
/// The schema must be a valid JSON Schema object with the "type" property set to "object".
/// This is enforced by validation in the setter which will throw an <see cref="ArgumentException"/>
/// if an invalid schema is provided.
/// </para>
/// <para>
/// The schema should describe the shape of the data as returned in <see cref="CallToolResponse.StructuredContent"/>.
/// </para>
/// </remarks>
[JsonPropertyName("outputSchema")]
public JsonElement? OutputSchema
{
get => field;
set
{
if (value is not null && !McpJsonUtilities.IsValidMcpToolSchema(value.Value))
{
throw new ArgumentException("The specified document is not a valid MCP tool output JSON schema.", nameof(OutputSchema));
}
field = value;
} }
} }

View file

@ -7,6 +7,7 @@ using System.ComponentModel;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Reflection; using System.Reflection;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Nodes;
namespace ModelContextProtocol.Server; namespace ModelContextProtocol.Server;
@ -14,6 +15,7 @@ namespace ModelContextProtocol.Server;
internal sealed partial class AIFunctionMcpServerTool : McpServerTool internal sealed partial class AIFunctionMcpServerTool : McpServerTool
{ {
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly bool _structuredOutputRequiresWrapping;
/// <summary> /// <summary>
/// Creates an <see cref="McpServerTool"/> instance for a method, specified via a <see cref="Delegate"/> instance. /// Creates an <see cref="McpServerTool"/> instance for a method, specified via a <see cref="Delegate"/> instance.
@ -176,7 +178,8 @@ internal sealed partial class AIFunctionMcpServerTool : McpServerTool
{ {
Name = options?.Name ?? function.Name, Name = options?.Name ?? function.Name,
Description = options?.Description ?? function.Description, Description = options?.Description ?? function.Description,
InputSchema = function.JsonSchema, InputSchema = function.JsonSchema,
OutputSchema = CreateOutputSchema(function, options, out bool structuredOutputRequiresWrapping),
}; };
if (options is not null) if (options is not null)
@ -198,7 +201,7 @@ internal sealed partial class AIFunctionMcpServerTool : McpServerTool
} }
} }
return new AIFunctionMcpServerTool(function, tool, options?.Services); return new AIFunctionMcpServerTool(function, tool, options?.Services, structuredOutputRequiresWrapping);
} }
private static McpServerToolCreateOptions DeriveOptions(MethodInfo method, McpServerToolCreateOptions? options) private static McpServerToolCreateOptions DeriveOptions(MethodInfo method, McpServerToolCreateOptions? options)
@ -229,6 +232,8 @@ internal sealed partial class AIFunctionMcpServerTool : McpServerTool
{ {
newOptions.ReadOnly ??= readOnly; newOptions.ReadOnly ??= readOnly;
} }
newOptions.UseStructuredContent = toolAttr.UseStructuredContent;
} }
if (method.GetCustomAttribute<DescriptionAttribute>() is { } descAttr) if (method.GetCustomAttribute<DescriptionAttribute>() is { } descAttr)
@ -243,11 +248,12 @@ internal sealed partial class AIFunctionMcpServerTool : McpServerTool
internal AIFunction AIFunction { get; } internal AIFunction AIFunction { get; }
/// <summary>Initializes a new instance of the <see cref="McpServerTool"/> class.</summary> /// <summary>Initializes a new instance of the <see cref="McpServerTool"/> class.</summary>
private AIFunctionMcpServerTool(AIFunction function, Tool tool, IServiceProvider? serviceProvider) private AIFunctionMcpServerTool(AIFunction function, Tool tool, IServiceProvider? serviceProvider, bool structuredOutputRequiresWrapping)
{ {
AIFunction = function; AIFunction = function;
ProtocolTool = tool; ProtocolTool = tool;
_logger = serviceProvider?.GetService<ILoggerFactory>()?.CreateLogger<McpServerTool>() ?? (ILogger)NullLogger.Instance; _logger = serviceProvider?.GetService<ILoggerFactory>()?.CreateLogger<McpServerTool>() ?? (ILogger)NullLogger.Instance;
_structuredOutputRequiresWrapping = structuredOutputRequiresWrapping;
} }
/// <inheritdoc /> /// <inheritdoc />
@ -295,39 +301,46 @@ internal sealed partial class AIFunctionMcpServerTool : McpServerTool
}; };
} }
JsonNode? structuredContent = CreateStructuredResponse(result);
return result switch return result switch
{ {
AIContent aiContent => new() AIContent aiContent => new()
{ {
Content = [aiContent.ToContent()], Content = [aiContent.ToContent()],
StructuredContent = structuredContent,
IsError = aiContent is ErrorContent IsError = aiContent is ErrorContent
}, },
null => new() null => new()
{ {
Content = [] Content = [],
StructuredContent = structuredContent,
}, },
string text => new() string text => new()
{ {
Content = [new() { Text = text, Type = "text" }] Content = [new() { Text = text, Type = "text" }],
StructuredContent = structuredContent,
}, },
Content content => new() Content content => new()
{ {
Content = [content] Content = [content],
StructuredContent = structuredContent,
}, },
IEnumerable<string> texts => new() IEnumerable<string> texts => new()
{ {
Content = [.. texts.Select(x => new Content() { Type = "text", Text = x ?? string.Empty })] Content = [.. texts.Select(x => new Content() { Type = "text", Text = x ?? string.Empty })],
StructuredContent = structuredContent,
}, },
IEnumerable<AIContent> contentItems => ConvertAIContentEnumerableToCallToolResponse(contentItems), IEnumerable<AIContent> contentItems => ConvertAIContentEnumerableToCallToolResponse(contentItems, structuredContent),
IEnumerable<Content> contents => new() IEnumerable<Content> contents => new()
{ {
Content = [.. contents] Content = [.. contents],
StructuredContent = structuredContent,
}, },
CallToolResponse callToolResponse => callToolResponse, CallToolResponse callToolResponse => callToolResponse,
@ -338,12 +351,90 @@ internal sealed partial class AIFunctionMcpServerTool : McpServerTool
{ {
Text = JsonSerializer.Serialize(result, AIFunction.JsonSerializerOptions.GetTypeInfo(typeof(object))), Text = JsonSerializer.Serialize(result, AIFunction.JsonSerializerOptions.GetTypeInfo(typeof(object))),
Type = "text" Type = "text"
}] }],
StructuredContent = structuredContent,
}, },
}; };
} }
private static CallToolResponse ConvertAIContentEnumerableToCallToolResponse(IEnumerable<AIContent> contentItems) private static JsonElement? CreateOutputSchema(AIFunction function, McpServerToolCreateOptions? toolCreateOptions, out bool structuredOutputRequiresWrapping)
{
structuredOutputRequiresWrapping = false;
if (toolCreateOptions?.UseStructuredContent is not true)
{
return null;
}
if (function.GetReturnSchema(toolCreateOptions?.SchemaCreateOptions) is not JsonElement outputSchema)
{
return null;
}
if (outputSchema.ValueKind is not JsonValueKind.Object ||
!outputSchema.TryGetProperty("type", out JsonElement typeProperty) ||
typeProperty.ValueKind is not JsonValueKind.String ||
typeProperty.GetString() is not "object")
{
// If the output schema is not an object, need to modify to be a valid MCP output schema.
JsonNode? schemaNode = JsonSerializer.SerializeToNode(outputSchema, McpJsonUtilities.JsonContext.Default.JsonElement);
if (schemaNode is JsonObject objSchema &&
objSchema.TryGetPropertyValue("type", out JsonNode? typeNode) &&
typeNode is JsonArray { Count: 2 } typeArray && typeArray.Any(type => (string?)type is "object") && typeArray.Any(type => (string?)type is "null"))
{
// For schemas that are of type ["object", "null"], replace with just "object" to be conformant.
objSchema["type"] = "object";
}
else
{
// For anything else, wrap the schema in an envelope with a "result" property.
schemaNode = new JsonObject
{
["type"] = "object",
["properties"] = new JsonObject
{
["result"] = schemaNode
},
["required"] = new JsonArray { (JsonNode)"result" }
};
structuredOutputRequiresWrapping = true;
}
outputSchema = JsonSerializer.Deserialize(schemaNode, McpJsonUtilities.JsonContext.Default.JsonElement);
}
return outputSchema;
}
private JsonNode? 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
{
JsonNode node => node,
JsonElement jsonElement => JsonSerializer.SerializeToNode(jsonElement, McpJsonUtilities.JsonContext.Default.JsonElement),
_ => JsonSerializer.SerializeToNode(aiFunctionResult, AIFunction.JsonSerializerOptions.GetTypeInfo(typeof(object))),
};
if (_structuredOutputRequiresWrapping)
{
return new JsonObject
{
["result"] = nodeResult
};
}
return nodeResult;
}
private static CallToolResponse ConvertAIContentEnumerableToCallToolResponse(IEnumerable<AIContent> contentItems, JsonNode? structuredContent)
{ {
List<Content> contentList = []; List<Content> contentList = [];
bool allErrorContent = true; bool allErrorContent = true;
@ -363,6 +454,7 @@ internal sealed partial class AIFunctionMcpServerTool : McpServerTool
return new() return new()
{ {
Content = contentList, Content = contentList,
StructuredContent = structuredContent,
IsError = allErrorContent && hasAny IsError = allErrorContent && hasAny
}; };
} }

View file

@ -240,4 +240,13 @@ public sealed class McpServerToolAttribute : Attribute
get => _readOnly ?? ReadOnlyDefault; get => _readOnly ?? ReadOnlyDefault;
set => _readOnly = value; set => _readOnly = value;
} }
/// <summary>
/// Gets or sets whether the tool should report an output schema for structured content.
/// </summary>
/// <remarks>
/// When enabled, the tool will attempt to populate the <see cref="Tool.OutputSchema"/>
/// and provide structured content in the <see cref="CallToolResponse.StructuredContent"/> property.
/// </remarks>
public bool UseStructuredContent { get; set; }
} }

View file

@ -1,4 +1,5 @@
using Microsoft.Extensions.AI; using Microsoft.Extensions.AI;
using ModelContextProtocol.Protocol;
using System.ComponentModel; using System.ComponentModel;
using System.Text.Json; using System.Text.Json;
@ -24,7 +25,7 @@ public sealed class McpServerToolCreateOptions
/// Gets or sets optional services used in the construction of the <see cref="McpServerTool"/>. /// Gets or sets optional services used in the construction of the <see cref="McpServerTool"/>.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// These services will be used to determine which parameters should be satisifed from dependency injection. As such, /// These services will be used to determine which parameters should be satisfied from dependency injection. As such,
/// what services are satisfied via this provider should match what's satisfied via the provider passed in at invocation time. /// what services are satisfied via this provider should match what's satisfied via the provider passed in at invocation time.
/// </remarks> /// </remarks>
public IServiceProvider? Services { get; set; } public IServiceProvider? Services { get; set; }
@ -124,6 +125,15 @@ public sealed class McpServerToolCreateOptions
/// </remarks> /// </remarks>
public bool? ReadOnly { get; set; } public bool? ReadOnly { get; set; }
/// <summary>
/// Gets or sets whether the tool should report an output schema for structured content.
/// </summary>
/// <remarks>
/// When enabled, the tool will attempt to populate the <see cref="Tool.OutputSchema"/>
/// and provide structured content in the <see cref="CallToolResponse.StructuredContent"/> property.
/// </remarks>
public bool UseStructuredContent { get; set; }
/// <summary> /// <summary>
/// Gets or sets the JSON serializer options to use when marshalling data to/from JSON. /// Gets or sets the JSON serializer options to use when marshalling data to/from JSON.
/// </summary> /// </summary>
@ -154,6 +164,7 @@ public sealed class McpServerToolCreateOptions
Idempotent = Idempotent, Idempotent = Idempotent,
OpenWorld = OpenWorld, OpenWorld = OpenWorld,
ReadOnly = ReadOnly, ReadOnly = ReadOnly,
UseStructuredContent = UseStructuredContent,
SerializerOptions = SerializerOptions, SerializerOptions = SerializerOptions,
SchemaCreateOptions = SchemaCreateOptions, SchemaCreateOptions = SchemaCreateOptions,
}; };

View file

@ -45,7 +45,8 @@
<PackageReference Include="Moq" /> <PackageReference Include="Moq" />
<PackageReference Include="OpenTelemetry" /> <PackageReference Include="OpenTelemetry" />
<PackageReference Include="OpenTelemetry.Exporter.InMemory" /> <PackageReference Include="OpenTelemetry.Exporter.InMemory" />
<PackageReference Include="System.Linq.AsyncEnumerable" /> <PackageReference Include="System.Linq.AsyncEnumerable" />
<PackageReference Include="JsonSchema.Net" />
<PackageReference Include="xunit.v3" /> <PackageReference Include="xunit.v3" />
<PackageReference Include="xunit.runner.visualstudio"> <PackageReference Include="xunit.runner.visualstudio">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

View file

@ -1,4 +1,5 @@
using Microsoft.Extensions.AI; using Json.Schema;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using ModelContextProtocol.Protocol; using ModelContextProtocol.Protocol;
@ -7,7 +8,10 @@ using ModelContextProtocol.Tests.Utils;
using Moq; using Moq;
using System.Reflection; using System.Reflection;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using Xunit.Sdk;
namespace ModelContextProtocol.Tests.Server; namespace ModelContextProtocol.Tests.Server;
@ -422,6 +426,94 @@ public partial class McpServerToolTests
Assert.Equal(exceptionMessage, errorLog.Exception.Message); Assert.Equal(exceptionMessage, errorLog.Exception.Message);
} }
[Theory]
[MemberData(nameof(StructuredOutput_ReturnsExpectedSchema_Inputs))]
public async Task StructuredOutput_Enabled_ReturnsExpectedSchema<T>(T value)
{
JsonSerializerOptions options = new() { TypeInfoResolver = new DefaultJsonTypeInfoResolver() };
McpServerTool tool = McpServerTool.Create(() => value, new() { Name = "tool", UseStructuredContent = true, SerializerOptions = options });
var mockServer = new Mock<IMcpServer>();
var request = new RequestContext<CallToolRequestParams>(mockServer.Object)
{
Params = new CallToolRequestParams { Name = "tool" },
};
var result = await tool.InvokeAsync(request, TestContext.Current.CancellationToken);
Assert.NotNull(tool.ProtocolTool.OutputSchema);
Assert.Equal("object", tool.ProtocolTool.OutputSchema.Value.GetProperty("type").GetString());
Assert.NotNull(result.StructuredContent);
AssertMatchesJsonSchema(tool.ProtocolTool.OutputSchema.Value, result.StructuredContent);
}
[Fact]
public async Task StructuredOutput_Enabled_VoidReturningTools_ReturnsExpectedSchema()
{
McpServerTool tool = McpServerTool.Create(() => { });
var mockServer = new Mock<IMcpServer>();
var request = new RequestContext<CallToolRequestParams>(mockServer.Object)
{
Params = new CallToolRequestParams { Name = "tool" },
};
var result = await tool.InvokeAsync(request, TestContext.Current.CancellationToken);
Assert.Null(tool.ProtocolTool.OutputSchema);
Assert.Null(result.StructuredContent);
tool = McpServerTool.Create(() => Task.CompletedTask);
request = new RequestContext<CallToolRequestParams>(mockServer.Object)
{
Params = new CallToolRequestParams { Name = "tool" },
};
result = await tool.InvokeAsync(request, TestContext.Current.CancellationToken);
Assert.Null(tool.ProtocolTool.OutputSchema);
Assert.Null(result.StructuredContent);
tool = McpServerTool.Create(() => ValueTask.CompletedTask);
request = new RequestContext<CallToolRequestParams>(mockServer.Object)
{
Params = new CallToolRequestParams { Name = "tool" },
};
result = await tool.InvokeAsync(request, TestContext.Current.CancellationToken);
Assert.Null(tool.ProtocolTool.OutputSchema);
Assert.Null(result.StructuredContent);
}
[Theory]
[MemberData(nameof(StructuredOutput_ReturnsExpectedSchema_Inputs))]
public async Task StructuredOutput_Disabled_ReturnsExpectedSchema<T>(T value)
{
JsonSerializerOptions options = new() { TypeInfoResolver = new DefaultJsonTypeInfoResolver() };
McpServerTool tool = McpServerTool.Create(() => value, new() { UseStructuredContent = false, SerializerOptions = options });
var mockServer = new Mock<IMcpServer>();
var request = new RequestContext<CallToolRequestParams>(mockServer.Object)
{
Params = new CallToolRequestParams { Name = "tool" },
};
var result = await tool.InvokeAsync(request, TestContext.Current.CancellationToken);
Assert.Null(tool.ProtocolTool.OutputSchema);
Assert.Null(result.StructuredContent);
}
public static IEnumerable<object[]> StructuredOutput_ReturnsExpectedSchema_Inputs()
{
yield return new object[] { "string" };
yield return new object[] { 42 };
yield return new object[] { 3.14 };
yield return new object[] { true };
yield return new object[] { new object() };
yield return new object[] { new List<string> { "item1", "item2" } };
yield return new object[] { new Dictionary<string, int> { ["key1"] = 1, ["key2"] = 2 } };
yield return new object[] { new Person("John", 27) };
}
private sealed class MyService; private sealed class MyService;
private class DisposableToolType : IDisposable private class DisposableToolType : IDisposable
@ -510,9 +602,35 @@ public partial class McpServerToolTests
} }
} }
private static void AssertMatchesJsonSchema(JsonElement schemaDoc, JsonNode? value)
{
JsonSchema schema = JsonSerializer.Deserialize(schemaDoc, JsonContext2.Default.JsonSchema)!;
EvaluationOptions options = new() { OutputFormat = OutputFormat.List };
EvaluationResults results = schema.Evaluate(value, options);
if (!results.IsValid)
{
IEnumerable<string> errors = results.Details
.Where(d => d.HasErrors)
.SelectMany(d => d.Errors!.Select(error => $"Path:${d.InstanceLocation} {error.Key}:{error.Value}"));
throw new XunitException($"""
Instance JSON document does not match the specified schema.
Schema:
{JsonSerializer.Serialize(schema)}
Instance:
{value?.ToJsonString() ?? "null"}
Errors:
{string.Join(Environment.NewLine, errors)}
""");
}
}
record Person(string Name, int Age);
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)] [JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
[JsonSerializable(typeof(DisposableToolType))] [JsonSerializable(typeof(DisposableToolType))]
[JsonSerializable(typeof(AsyncDisposableToolType))] [JsonSerializable(typeof(AsyncDisposableToolType))]
[JsonSerializable(typeof(AsyncDisposableAndDisposableToolType))] [JsonSerializable(typeof(AsyncDisposableAndDisposableToolType))]
[JsonSerializable(typeof(JsonSchema))]
partial class JsonContext2 : JsonSerializerContext; partial class JsonContext2 : JsonSerializerContext;
} }