Add CallToolResult<T>, CallToolAsync<T>, and OutputSchema for tools returning CallToolResult#1272
Add CallToolResult<T>, CallToolAsync<T>, and OutputSchema for tools returning CallToolResult#1272
Conversation
…pServerToolCreateOptions - Added Type? OutputSchemaType property to McpServerToolAttribute - Added JsonElement? OutputSchema property to McpServerToolCreateOptions - Updated DeriveOptions to generate schema from OutputSchemaType via AIJsonUtilities.CreateJsonSchema - Updated CreateOutputSchema to use explicit OutputSchema when provided (takes precedence) - OutputSchema forces structured content behavior even if UseStructuredContent is false - Updated Clone() to preserve OutputSchema - Added 8 new tests covering various scenarios - Updated XML documentation Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
src/ModelContextProtocol.Core/Server/AIFunctionMcpServerTool.cs
Outdated
Show resolved
Hide resolved
src/ModelContextProtocol.Core/Server/AIFunctionMcpServerTool.cs
Outdated
Show resolved
Hide resolved
Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
…namicallyAccessedMembers - In DeriveOptions, force UseStructuredContent=true when OutputSchemaType is set - Simplify GetToolDescription to only check UseStructuredContent (no longer needs OutputSchema check) - Remove unnecessary [DynamicallyAccessedMembers] from OutputSchemaType property Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
src/ModelContextProtocol.Core/Server/AIFunctionMcpServerTool.cs
Outdated
Show resolved
Hide resolved
…tests, add using for JsonDocument - Add Debug.Assert in CreateOutputSchema to verify UseStructuredContent is true when OutputSchema is set - Force UseStructuredContent=true in DeriveOptions when OutputSchema is set via options - Update docs to say "to a non-null value" for UseStructuredContent auto-enable - Add using statements for all JsonDocument.Parse calls in tests - Rewrite attribute-based tests to use lambdas instead of reflection - Add test for OutputSchemaType with UseStructuredContent=false to verify it's overridden - Remove OutputSchemaTypeTools helper class (no longer needed) Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
|
@copilot Please give a short explanation of the feature added in this PR from the perspective of a user of the SDK. |
src/ModelContextProtocol.Core/Server/AIFunctionMcpServerTool.cs
Outdated
Show resolved
Hide resolved
…rverTool Replace the ICallToolResultTyped interface (which contained serialization logic) with a minimal ICallToolResultTypedContent interface that only exposes raw content data. The serialization/conversion logic now lives in AIFunctionMcpServerTool's ConvertCallToolResultOfT method where it's consumed. Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
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>
Restore the previous interface design where CallToolResult<T> implements ICallToolResultTyped.ToCallToolResult(JsonSerializerOptions) with the serialization logic inside the generic type. Remove the data-only ICallToolResultTypedContent interface and ConvertCallToolResultOfT helper. Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
src/ModelContextProtocol.Core/Server/AIFunctionMcpServerTool.cs
Outdated
Show resolved
Hide resolved
src/ModelContextProtocol.Core/Server/AIFunctionMcpServerTool.cs
Outdated
Show resolved
Hide resolved
src/ModelContextProtocol.Core/Server/AIFunctionMcpServerTool.cs
Outdated
Show resolved
Hide resolved
src/ModelContextProtocol.Core/Server/AIFunctionMcpServerTool.cs
Outdated
Show resolved
Hide resolved
…minimize diff - Serialize to JsonNode once, use ToJsonString for text (avoid double serialization) - Fix UseStructuredContent + CallToolResult<T>: use T for schema, not full return type - Simplify pattern match: ICallToolResultTyped typed => instead of _ when - Change GetCallToolResultContentType to accept Type instead of MethodInfo - Revert CreateOutputSchema back to original structure (remove EnsureObjectSchema helper) Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
This PR adds three main features:
[McpServerTool]
public static CallToolResult<MyResponse> MyTool(string input)
{
return new() { Content = new MyResponse { Name = "test", Score = 42 } };
}
MyResponse result = await client.CallToolAsync<MyResponse>("my_tool", args);
|
…tOrDefault - CallToolAsync<T> now returns CallToolResult<T> instead of T - No longer throws on IsError; caller can inspect IsError on the result - Use OfType<TextContentBlock>().FirstOrDefault() instead of indexing - Use ToString() instead of ToJsonString(serializerOptions) in ToCallToolResult - Updated tests accordingly Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
|
|
||
| T? content = default; | ||
|
|
||
| if (result.IsError is not true) |
There was a problem hiding this comment.
@copilot if IsError is true, are we now losing any message included in content?
There was a problem hiding this comment.
Let's go back to it returning T. Then someone who wants the details can just use the non generic overload.
There was a problem hiding this comment.
Done in 24bbac3 — CallToolAsync<T> now returns T? and throws McpException with the error message text when IsError is true.
| /// the SDK to handle serialization of a strongly-typed result. | ||
| /// </para> | ||
| /// </remarks> | ||
| public sealed class CallToolResult<T> : ICallToolResultTyped |
| /// </summary> | ||
| private static Type? GetCallToolResultContentType(Type returnType) | ||
| { | ||
| if (returnType.IsGenericType) |
There was a problem hiding this comment.
@copilot why can't all of this schema handling for CallToolResult be done where the rest of the schema detection is done? There's already code unwrapping tasks, creating return schema for the return type, etc. I'm missing why this needs to be done separately.
There was a problem hiding this comment.
Consolidated in 24bbac3 — unified into a single block after all attribute handling:
Type? outputSchemaType = GetCallToolResultContentType(method.ReturnType);
if (outputSchemaType is null && useStructuredContent)
{
outputSchemaType = method.ReturnType;
}
if (outputSchemaType is not null)
{
newOptions.OutputSchema ??= AIJsonUtilities.CreateJsonSchema(outputSchemaType, ...);
}GetCallToolResultContentType always runs first (handles CallToolResult<T> with or without the attribute), then UseStructuredContent fills in the return type only when CallToolResult<T> wasn't detected.
… throws on IsError, consolidate schema handling - CallToolResult<T> now derives from Result (inherits Meta), removed duplicate Meta property - CallToolAsync<T> returns T? instead of CallToolResult<T>, throws McpException on IsError - Consolidated schema handling in DeriveOptions: unified UseStructuredContent and CallToolResult<T> auto-detection into a single block - Updated XML docs for cref changes (Result.Meta) Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
Tools returning
CallToolResultdirectly (to controlIsError,StructuredContent, etc.) had no way to advertise a meaningful output schema — the inferred schema would reflectCallToolResultitself rather than the actual structured content shape.Changes
CallToolResult<T>(new type)CallToolResult<T>that derives fromResult(inheritingMeta) — a peer ofCallToolResultwhereContentisT?instead ofIList<ContentBlock>bool? IsErrorfor error signalingICallToolResultTypedinterface withToCallToolResult(JsonSerializerOptions)that serializesTonce toJsonNode, then usesToString()for the text content blockServer-side:
AIFunctionMcpServerToolCallToolResult<T>as a return type (includingTask<CallToolResult<T>>andValueTask<CallToolResult<T>>)Tto automatically infer theOutputSchema— consolidated into the existing schema detection code alongside task unwrapping and return-type inferenceICallToolResultTyped.ToCallToolResult()for serialization, propagatingIsErrorandMetaMcpServerToolandMcpServerToolAttributeupdated to documentCallToolResult<T>in the return type tableClient-side:
CallToolAsync<T>CallToolAsync<T>onMcpClientthat calls the existingCallToolAsync, then deserializes the result asT?StructuredContent(preferred) or the firstTextContentBlockasTusingOfType<TextContentBlock>().FirstOrDefault()McpExceptionifIsErroristrue— callers who need error details can use the non-genericCallToolAsyncoverloadMcpServerToolCreateOptions.OutputSchemaJsonElement? OutputSchemaallows supplying a pre-built schema directly via optionsOutputSchemaon the options determines whether structured output is used —UseStructuredContentwas removed fromMcpServerToolCreateOptionsUseStructuredContentremains onMcpServerToolAttribute; when set,DeriveOptionsgenerates theOutputSchemafrom the method's return type (usingTfromCallToolResult<T>if applicable)OutputSchematakes precedence overCallToolResult<T>inferencePrecedence
McpServerToolCreateOptions.OutputSchema>CallToolResult<T>type inference >McpServerToolAttribute.UseStructuredContentreturn-type inferenceUsage
Testing
CallToolResult<T>inMcpServerToolTests(schema generation, serialization, IsError/Meta propagation, async methods, null content, explicit override)CallToolResultOfTTests(end-to-end client-server withCallToolAsync<T>, error handling, fallback to text content, schema advertisement, arguments)OutputSchemavia options tests retainedOriginal prompt
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.