-
Notifications
You must be signed in to change notification settings - Fork 560
Description
Is your feature request related to a problem? Please describe.
Currently when registering tools with the [McpServerTool(UseStructuredContent = true)] Attribute, there is no way to decouple the generated output schema from the return value of the Method being registered as a tool.
This is important for targeting OpenAIs App SDK where the Content TextBlock of a ToolResult explicitly is not coupled to the StructuredContent of the ToolResult.
https://developers.openai.com/apps-sdk/build/mcp-server
From their sample:
return {
structuredContent: {
columns: board.columns.map((column) => ({
id: column.id,
title: column.title,
tasks: column.tasks.slice(0, 5) // keep payload concise for the model
}))
},
content: [{ type: "text", text: "Here's your latest board. Drag cards in the component to update status." }],
_meta: {
tasksById: board.tasksById, // full task map for the component only
lastSyncedAt: board.lastSyncedAt
}
};
Describe alternatives you've considered
It's possible to return a explicit CallToolResult where you can explicitly set the Content and StructeredContent, but the output schema generation of course will create a Schema of CallToolResult and not the actual type you want to return in StructuredContent.
so something like
[McpServerTool(UseStructuredContent = true)]
public CallToolResult TestCallToolResult()
{
var structuredContent = new DebugReturnModel {
DebugProp1 = "Hello World",
};
return new CallToolResult {
Content = [
new TextContentBlock {
Text = "Some text independent of the actual structured content.",
},
],
StructuredContent = JsonSerializer.SerializeToNode(structuredContent),
};
}works for separating the StructuredContent from the Content in the Result, but (obviously) fails at generating an Output Schema for DebugReturnModel.
Describe the solution you'd like
I see a couple possibilities to solve this:
Have a generic Version of CallToolResult: CallToolResult<TStructuredContent> where the Output schema generator would generate from TStrcuturedContent.
So for my sample code would look something like:
[McpServerTool(UseStructuredContent = true)]
public CallToolResult<DebugReturnModel> TestCallToolResult()
{
var structuredContent = new DebugReturnModel {
DebugProp1 = "Hello World",
};
return new CallToolResult<DebugReturnModel> {
Content = [
new TextContentBlock {
Text = "Some text independent of the actual structured content.",
},
],
StructuredContent = structuredContent,
};
}(IMHO this would be the best approach as it would eliminate the need to manually serialize the structured content when explicit separation of Content and StructuredContent is required)
Have a generic Version of McpServerTool Attribute: McpServerTool<TStructuredOutput> or have an additional Type parameter for the Attribute like McpServerTool(UseStructuredContent = true, StructuredContentType = typeof(DebugReturnModel))
so resulting code would be like:
[McpServerTool(UseStructuredContent = true, StructuredContentType = typeof(DebugReturnModel))] // one of the two
[McpServerTool<DebugReturnModel>(UseStructuredContent = true)] // one of the two
public CallToolResult TestCallToolResult()
{
var structuredContent = new DebugReturnModel {
DebugProp1 = "Hello World",
};
return new CallToolResult {
Content = [
new TextContentBlock {
Text = "Some text independent of the actual structured content.",
},
],
StructuredContent = JsonSerializer.SerializeToNode(structuredContent),
};
}Have separate attribute for the StructuredContentType like there is also for _meta ([McpMeta]):
[McpStructuredOutput<DebugReturnModel>] or [McpStructuredOutput(typeof(DebugReturnModel))] which would work in conjunction with UseStructuredContent from McpServerTool Attribute.
Additional context
The OpenAI Apps SDK documentation mentions that both Content and StructuredContent will be passed to the model
Your component receives all three fields, but only structuredContent and content are visible to the model.
so duplicating it is basically wasting Tokens.
If there is already a way to achieve the described functionality described with the current package, i would appreciate some guidance.
this is kind of related to the currently open PR: #748