Skip to content

Commit 8befc1f

Browse files
Address feedback.
1 parent bb2bf87 commit 8befc1f

File tree

3 files changed

+32
-28
lines changed

3 files changed

+32
-28
lines changed

src/ModelContextProtocol.Core/McpJsonUtilities.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using Microsoft.Extensions.AI;
22
using ModelContextProtocol.Protocol;
3+
using ModelContextProtocol.Server;
34
using System.Diagnostics.CodeAnalysis;
45
using System.Text.Json;
56
using System.Text.Json.Serialization;
@@ -75,6 +76,30 @@ internal static bool IsValidMcpToolSchema(JsonElement element)
7576
return false; // No type keyword found.
7677
}
7778

79+
internal static JsonElement? GetReturnSchema(this AIFunction function, AIJsonSchemaCreateOptions? schemaCreateOptions)
80+
{
81+
// TODO replace with https://github.com/dotnet/extensions/pull/6447 once merged.
82+
if (function.UnderlyingMethod?.ReturnType is not Type returnType)
83+
{
84+
return null;
85+
}
86+
87+
if (returnType == typeof(void) || returnType == typeof(Task) || returnType == typeof(ValueTask))
88+
{
89+
// Do not report an output schema for void or Task methods.
90+
return null;
91+
}
92+
93+
if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() is Type genericTypeDef &&
94+
(genericTypeDef == typeof(Task<>) || genericTypeDef == typeof(ValueTask<>)))
95+
{
96+
// Extract the real type from Task<T> or ValueTask<T> if applicable.
97+
returnType = returnType.GetGenericArguments()[0];
98+
}
99+
100+
return AIJsonUtilities.CreateJsonSchema(returnType, serializerOptions: function.JsonSerializerOptions, inferenceOptions: schemaCreateOptions);
101+
}
102+
78103
// Keep in sync with CreateDefaultOptions above.
79104
[JsonSourceGenerationOptions(JsonSerializerDefaults.Web,
80105
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,

src/ModelContextProtocol.Core/Protocol/CallToolResponse.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public class CallToolResponse
3232
/// Gets or sets an optional JSON object representing the structured result of the tool call.
3333
/// </summary>
3434
[JsonPropertyName("structuredContent")]
35-
public JsonObject? StructuredContent { get; set; }
35+
public JsonNode? StructuredContent { get; set; }
3636

3737
/// <summary>
3838
/// Gets or sets an indication of whether the tool call was unsuccessful.

src/ModelContextProtocol.Core/Server/AIFunctionMcpServerTool.cs

Lines changed: 6 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ public override async ValueTask<CallToolResponse> InvokeAsync(
299299
};
300300
}
301301

302-
JsonObject? structuredContent = CreateStructuredResponse(result);
302+
JsonNode? structuredContent = CreateStructuredResponse(result);
303303
return result switch
304304
{
305305
AIContent aiContent => new()
@@ -357,35 +357,18 @@ public override async ValueTask<CallToolResponse> InvokeAsync(
357357

358358
private static JsonElement? CreateOutputSchema(AIFunction function, McpServerToolCreateOptions? toolCreateOptions, out bool structuredOutputRequiresWrapping)
359359
{
360-
// TODO replace with https://github.com/dotnet/extensions/pull/6447 once merged.
361-
362360
structuredOutputRequiresWrapping = false;
363361

364362
if (toolCreateOptions?.UseStructuredContent is not true)
365363
{
366364
return null;
367365
}
368366

369-
if (function.UnderlyingMethod?.ReturnType is not Type returnType)
370-
{
371-
return null;
372-
}
373-
374-
if (returnType == typeof(void) || returnType == typeof(Task) || returnType == typeof(ValueTask))
367+
if (function.GetReturnSchema(toolCreateOptions?.SchemaCreateOptions) is not JsonElement outputSchema)
375368
{
376-
// Do not report an output schema for void or Task methods.
377369
return null;
378370
}
379371

380-
if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() is Type genericTypeDef &&
381-
(genericTypeDef == typeof(Task<>) || genericTypeDef == typeof(ValueTask<>)))
382-
{
383-
// Extract the real type from Task<T> or ValueTask<T> if applicable.
384-
returnType = returnType.GetGenericArguments()[0];
385-
}
386-
387-
JsonElement outputSchema = AIJsonUtilities.CreateJsonSchema(returnType, serializerOptions: function.JsonSerializerOptions, inferenceOptions: toolCreateOptions?.SchemaCreateOptions);
388-
389372
if (outputSchema.ValueKind is not JsonValueKind.Object ||
390373
!outputSchema.TryGetProperty("type", out JsonElement typeProperty) ||
391374
typeProperty.ValueKind is not JsonValueKind.String ||
@@ -423,10 +406,11 @@ typeProperty.ValueKind is not JsonValueKind.String ||
423406
return outputSchema;
424407
}
425408

426-
private JsonObject? CreateStructuredResponse(object? aiFunctionResult)
409+
private JsonNode? CreateStructuredResponse(object? aiFunctionResult)
427410
{
428411
if (ProtocolTool.OutputSchema is null)
429412
{
413+
// Only provide structured responses if the tool has an output schema defined.
430414
return null;
431415
}
432416

@@ -445,15 +429,10 @@ typeProperty.ValueKind is not JsonValueKind.String ||
445429
};
446430
}
447431

448-
if (nodeResult is JsonObject jsonObject)
449-
{
450-
return jsonObject;
451-
}
452-
453-
throw new InvalidOperationException("The result of the AIFunction does not match its declared output schema.");
432+
return nodeResult;
454433
}
455434

456-
private static CallToolResponse ConvertAIContentEnumerableToCallToolResponse(IEnumerable<AIContent> contentItems, JsonObject? structuredContent)
435+
private static CallToolResponse ConvertAIContentEnumerableToCallToolResponse(IEnumerable<AIContent> contentItems, JsonNode? structuredContent)
457436
{
458437
List<Content> contentList = [];
459438
bool allErrorContent = true;

0 commit comments

Comments
 (0)