Skip to content

[MCP Bundle] Support client-conditional tool result shaping (structuredContent-only vs. text content) #2200

Description

@mikepage

Summary

When an MCP tool returns an array, the SDK emits the result twice — a serialized-JSON TextContent block (ToolReference::formatResult()) and structuredContent (ToolReference::extractStructuredContent(), wired in CallToolHandler). Different clients want opposite shapes, and there's no first-class way to vary the result per client.

The problem

  • ChatGPT connectors: when a result carries both a JSON text block and structuredContent, ChatGPT treats it as a large payload, truncates it ("Response output was truncated … to fit the tool response budget"), stores it as a resource, and chains internal "Read resource" paging that elides fields — so identifiers (ids, etc.) never reach the model. Returning only structuredContent (empty content) fixes it. (See OpenAI community report 1383071.)
  • Claude Desktop / Claude Code / Cursor: read the text content block; dropping it leaves the model with nothing.

So the correct content shape is genuinely client-dependent, but the framework gives no hook for it.

What we had to do (works, but boilerplate + a footgun)

For every tool: declare a ?RequestContext $context parameter, read $context->getSession()->get('client_info'), and hand-build a CallToolResult choosing content: [] + structuredContent for OpenAI clients vs. text + structuredContent for the rest.

Footgun encountered: our first attempt used a parameter named $_session. SchemaGenerator throws on reserved parameter names (_session/_request) during discovery, which aborts discovery and registers zero tools — while the DI container still compiles, so it looks healthy but the live server returns "no tools". RequestContext is the correct injection, but this isn't obvious from the docs.

Feature request

First-class support for client-conditional structured results, e.g. one or more of:

  1. Honor the negotiated client capabilities / client_info so clients that consume structuredContent don't also receive the redundant JSON text dump (and vice-versa) — ideally automatic.
  2. A per-tool toggle (attribute/option, e.g. structuredContentOnly) and/or a small documented helper to shape the result from the injected RequestContext.
  3. Docs for the RequestContext injection pattern and the reserved _session/_request parameter names (so the silent "zero tools" discovery failure is avoidable).

Environment

  • symfony/mcp-bundle v0.10.0
  • mcp/sdk v0.6.0
  • PHP 8.4.20

Metadata

Metadata

Assignees

No one assigned

    Labels

    MCP BundleIssues & PRs about the MCP SDK integration bundle

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions