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. |
…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.
| /// </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>
|
@copilot Provide bullets that capture breaking changes made in this PR. There can be 2 categories of breaking changes: API changes, and behavioral changes. These bullets will be included into upcoming release notes as described in |
API Breaking Changes
Behavioral Breaking Changes
|
|
@stephentoub Do you think we should defer this until after v1.0, since now we think it will not create an breaking changes? |
Yup, that was my suggestion offline. Thanks. |
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.