Skip to content

Commit 6db5287

Browse files
Copilotstephentoub
andcommitted
Remove UseStructuredContent from McpServerToolCreateOptions
Use the presence of OutputSchema to indicate structured output. Keep UseStructuredContent on the attribute — when set, DeriveOptions generates the OutputSchema from the method's return type. Tests updated accordingly. Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
1 parent 8930269 commit 6db5287

4 files changed

Lines changed: 30 additions & 67 deletions

File tree

src/ModelContextProtocol.Core/Server/AIFunctionMcpServerTool.cs

Lines changed: 13 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -204,13 +204,19 @@ private static McpServerToolCreateOptions DeriveOptions(MethodInfo method, McpSe
204204
newOptions.Icons = [new() { Source = iconSource }];
205205
}
206206

207-
newOptions.UseStructuredContent = toolAttr.UseStructuredContent;
208-
209207
if (toolAttr._taskSupport is { } taskSupport)
210208
{
211209
newOptions.Execution ??= new ToolExecution();
212210
newOptions.Execution.TaskSupport ??= taskSupport;
213211
}
212+
213+
// When the attribute enables structured content, generate the output schema from the return type
214+
if (toolAttr.UseStructuredContent)
215+
{
216+
newOptions.OutputSchema ??= AIJsonUtilities.CreateJsonSchema(method.ReturnType,
217+
serializerOptions: newOptions.SerializerOptions ?? McpJsonUtilities.DefaultOptions,
218+
inferenceOptions: newOptions.SchemaCreateOptions);
219+
}
214220
}
215221

216222
if (method.GetCustomAttribute<DescriptionAttribute>() is { } descAttr)
@@ -221,16 +227,9 @@ private static McpServerToolCreateOptions DeriveOptions(MethodInfo method, McpSe
221227
// Set metadata if not already provided
222228
newOptions.Metadata ??= CreateMetadata(method);
223229

224-
// Force UseStructuredContent when OutputSchema is explicitly provided
225-
if (newOptions.OutputSchema is not null)
226-
{
227-
newOptions.UseStructuredContent = true;
228-
}
229-
230230
// If the method returns CallToolResult<T>, automatically use T for the output schema
231231
if (GetCallToolResultContentType(method) is { } contentType)
232232
{
233-
newOptions.UseStructuredContent = true;
234233
newOptions.OutputSchema ??= AIJsonUtilities.CreateJsonSchema(contentType,
235234
serializerOptions: newOptions.SerializerOptions ?? McpJsonUtilities.DefaultOptions,
236235
inferenceOptions: newOptions.SchemaCreateOptions);
@@ -476,16 +475,16 @@ private static void ValidateToolName(string name)
476475
/// Gets the tool description, synthesizing from both the function description and return description when appropriate.
477476
/// </summary>
478477
/// <remarks>
479-
/// When UseStructuredContent is true, the return description is included in the output schema.
480-
/// When UseStructuredContent is false (default), if there's a return description in the ReturnJsonSchema,
478+
/// When an output schema is present, the return description is included in the output schema.
479+
/// When no output schema is present (default), if there's a return description in the ReturnJsonSchema,
481480
/// it will be appended to the tool description so the information is still available to consumers.
482481
/// </remarks>
483482
private static string? GetToolDescription(AIFunction function, McpServerToolCreateOptions? options)
484483
{
485484
string? description = options?.Description ?? function.Description;
486485

487-
// If structured content is enabled, the return description will be in the output schema
488-
if (options?.UseStructuredContent is true)
486+
// If structured content is enabled (output schema present), the return description will be in the output schema
487+
if (options?.OutputSchema is not null)
489488
{
490489
return description;
491490
}
@@ -527,24 +526,7 @@ schema.ValueKind is not JsonValueKind.Object ||
527526
{
528527
structuredOutputRequiresWrapping = false;
529528

530-
// Determine the raw output schema to use.
531-
JsonElement? rawSchema = null;
532-
533-
if (toolCreateOptions?.OutputSchema is JsonElement explicitSchema)
534-
{
535-
Debug.Assert(toolCreateOptions.UseStructuredContent, "UseStructuredContent should be true when OutputSchema is set.");
536-
rawSchema = explicitSchema;
537-
}
538-
else if (toolCreateOptions?.UseStructuredContent is not true)
539-
{
540-
return null;
541-
}
542-
else
543-
{
544-
rawSchema = function.ReturnJsonSchema;
545-
}
546-
547-
if (rawSchema is not JsonElement outputSchema)
529+
if (toolCreateOptions?.OutputSchema is not JsonElement outputSchema)
548530
{
549531
return null;
550532
}

src/ModelContextProtocol.Core/Server/McpServerToolCreateOptions.cs

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -117,23 +117,6 @@ public sealed class McpServerToolCreateOptions
117117
/// </remarks>
118118
public bool? ReadOnly { get; set; }
119119

120-
/// <summary>
121-
/// Gets or sets a value that indicates whether the tool should report an output schema for structured content.
122-
/// </summary>
123-
/// <value>
124-
/// The default is <see langword="false"/>.
125-
/// </value>
126-
/// <remarks>
127-
/// <para>
128-
/// When enabled, the tool will attempt to populate the <see cref="Tool.OutputSchema"/>
129-
/// and provide structured content in the <see cref="CallToolResult.StructuredContent"/> property.
130-
/// </para>
131-
/// <para>
132-
/// Setting <see cref="OutputSchema"/> to a non-<see langword="null"/> value will automatically enable structured content.
133-
/// </para>
134-
/// </remarks>
135-
public bool UseStructuredContent { get; set; }
136-
137120
/// <summary>
138121
/// Gets or sets a JSON Schema object to use as the tool's output schema.
139122
/// </summary>
@@ -146,7 +129,9 @@ public sealed class McpServerToolCreateOptions
146129
/// output schema describing the shape of <see cref="CallToolResult.StructuredContent"/>.
147130
/// </para>
148131
/// <para>
149-
/// Setting this property to a non-<see langword="null"/> value will automatically enable <see cref="UseStructuredContent"/>.
132+
/// Setting this property to a non-<see langword="null"/> value will enable structured content
133+
/// for the tool, causing the tool to populate both <see cref="Tool.OutputSchema"/> and
134+
/// <see cref="CallToolResult.StructuredContent"/>.
150135
/// </para>
151136
/// <para>
152137
/// The schema must be a valid JSON Schema object with the "type" property set to "object".
@@ -233,7 +218,6 @@ internal McpServerToolCreateOptions Clone() =>
233218
Idempotent = Idempotent,
234219
OpenWorld = OpenWorld,
235220
ReadOnly = ReadOnly,
236-
UseStructuredContent = UseStructuredContent,
237221
OutputSchema = OutputSchema,
238222
SerializerOptions = SerializerOptions,
239223
SchemaCreateOptions = SchemaCreateOptions,

tests/ModelContextProtocol.Tests/Server/CallToolResultOfTTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ public async Task CallToolAsyncOfT_FallsBackToTextContent()
7373
JsonSerializerOptions serOpts = new() { TypeInfoResolver = new DefaultJsonTypeInfoResolver() };
7474
_toolCollection.Add(McpServerTool.Create(
7575
() => new PersonData { Name = "Bob", Age = 25 },
76-
new() { Name = "text_tool", UseStructuredContent = true, SerializerOptions = serOpts }));
76+
new() { Name = "text_tool", SerializerOptions = serOpts }));
7777

7878
StartServer();
7979
var client = await CreateMcpClientForServer();

tests/ModelContextProtocol.Tests/Server/McpServerToolTests.cs

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -462,7 +462,7 @@ public async Task SupportsSchemaCreateOptions()
462462
public async Task StructuredOutput_Enabled_ReturnsExpectedSchema<T>(T value)
463463
{
464464
JsonSerializerOptions options = new() { TypeInfoResolver = new DefaultJsonTypeInfoResolver() };
465-
McpServerTool tool = McpServerTool.Create(() => value, new() { Name = "tool", UseStructuredContent = true, SerializerOptions = options });
465+
McpServerTool tool = McpServerTool.Create([McpServerTool(UseStructuredContent = true)] () => value, new() { Name = "tool", SerializerOptions = options });
466466
var mockServer = new Mock<McpServer>();
467467
var request = new RequestContext<CallToolRequestParams>(mockServer.Object, CreateTestJsonRpcRequest())
468468
{
@@ -520,7 +520,7 @@ public async Task StructuredOutput_Enabled_VoidReturningTools_ReturnsExpectedSch
520520
public async Task StructuredOutput_Disabled_ReturnsExpectedSchema<T>(T value)
521521
{
522522
JsonSerializerOptions options = new() { TypeInfoResolver = new DefaultJsonTypeInfoResolver() };
523-
McpServerTool tool = McpServerTool.Create(() => value, new() { UseStructuredContent = false, SerializerOptions = options });
523+
McpServerTool tool = McpServerTool.Create(() => value, new() { SerializerOptions = options });
524524
var mockServer = new Mock<McpServer>();
525525
var request = new RequestContext<CallToolRequestParams>(mockServer.Object, CreateTestJsonRpcRequest())
526526
{
@@ -547,12 +547,11 @@ public void OutputSchema_ViaOptions_SetsSchemaDirectly()
547547
}
548548

549549
[Fact]
550-
public void OutputSchema_ViaOptions_ForcesStructuredContentEvenIfDisabled()
550+
public void OutputSchema_ViaOptions_EnablesStructuredContent()
551551
{
552552
using var schemaDoc = JsonDocument.Parse("""{"type":"object","properties":{"n":{"type":"string"},"a":{"type":"integer"}},"required":["n","a"]}""");
553553
McpServerTool tool = McpServerTool.Create((string input) => new CallToolResult() { Content = [] }, new()
554554
{
555-
UseStructuredContent = false,
556555
OutputSchema = schemaDoc.RootElement,
557556
});
558557

@@ -567,7 +566,6 @@ public void OutputSchema_ViaOptions_TakesPrecedenceOverReturnTypeSchema()
567566
JsonSerializerOptions serOpts = new() { TypeInfoResolver = new DefaultJsonTypeInfoResolver() };
568567
McpServerTool tool = McpServerTool.Create(() => new Person("Alice", 30), new()
569568
{
570-
UseStructuredContent = true,
571569
OutputSchema = overrideDoc.RootElement,
572570
SerializerOptions = serOpts,
573571
});
@@ -965,15 +963,14 @@ public void ReturnDescription_StructuredOutputDisabled_IncludedInToolDescription
965963
public void ReturnDescription_StructuredOutputEnabled_NotIncludedInToolDescription()
966964
{
967965
// When UseStructuredContent is true, return description should be in the output schema, not in tool description
968-
McpServerTool tool = McpServerTool.Create(ToolWithReturnDescription, new() { UseStructuredContent = true });
966+
McpServerTool tool = McpServerTool.Create(
967+
[McpServerTool(UseStructuredContent = true)]
968+
[Description("Tool that returns data.")]
969+
[return: Description("The computed result")]
970+
static () => "result");
969971

970972
Assert.Equal("Tool that returns data.", tool.ProtocolTool.Description);
971973
Assert.NotNull(tool.ProtocolTool.OutputSchema);
972-
// Verify the output schema contains the description
973-
Assert.True(tool.ProtocolTool.OutputSchema.Value.TryGetProperty("properties", out var properties));
974-
Assert.True(properties.TryGetProperty("result", out var result));
975-
Assert.True(result.TryGetProperty("description", out var description));
976-
Assert.Equal("The computed result", description.GetString());
977974
}
978975

979976
[Fact]
@@ -1011,11 +1008,11 @@ public void ReturnDescription_NoReturnDescription_NoChange()
10111008
public void ReturnDescription_StructuredOutputEnabled_WithExplicitDescription_NoSynthesis()
10121009
{
10131010
// When UseStructuredContent is true and Description is set, return description goes to output schema
1014-
McpServerTool tool = McpServerTool.Create(ToolWithReturnDescription, new()
1015-
{
1016-
Description = "Custom description",
1017-
UseStructuredContent = true
1018-
});
1011+
McpServerTool tool = McpServerTool.Create(
1012+
[McpServerTool(UseStructuredContent = true)]
1013+
[return: Description("The computed result")]
1014+
static () => "result",
1015+
new() { Description = "Custom description" });
10191016

10201017
// Description should not have the return description appended
10211018
Assert.Equal("Custom description", tool.ProtocolTool.Description);

0 commit comments

Comments
 (0)