Skip to content

fix(mcp): fix ignored output format - #8542

Open
vincentchalamon wants to merge 4 commits into
4.4from
reproducer/mcp-output-format-ignored
Open

vincentchalamon wants to merge 4 commits into
4.4from
reproducer/mcp-output-format-ignored

Conversation

@vincentchalamon

Copy link
Copy Markdown
Contributor
Q A
Branch? 4.4
Tickets N/A
License MIT
Doc PR N/A

StructuredContentProcessor serializes a tool result with

    $format = $request->getRequestFormat('') ?: 'jsonld';

and never reads $operation->getOutputFormats(), although $operation is in scope
and FormatsResourceMetadataCollectionFactory has populated it. The MCP route
carries no _format placeholder or default either, so getRequestFormat() is always
empty and the expression is a constant 'jsonld' on every call.

McpFormatTool declares outputFormats: ['json' => ['application/json']]. The first
test asserts the metadata honours it (passes); the two others assert the payload
and structuredContent honour it (both fail, showing the JSON-LD envelope).

Declaring the format on the operation exercises the same code path as
api_platform.mcp.format, which is implemented as withOutputFormats() on the
operation -- so this single reproducer covers both ways of configuring it.
…result

Read $operation->getOutputFormats() before falling back to the request format
and to jsonld. The change is strictly additive: when no format is declared the
array is empty and the previous expression applies unchanged, which is why the
26 existing MCP functional tests are untouched.

Covers api_platform.mcp.format as well, since FormatsResourceMetadataCollectionFactory
implements that option by calling withOutputFormats() on the operation.
MCP operations that declare their own formats never went through
normalizeFormats(), which every HTTP operation gets in normalize(). The list form
-- ['json'], meaning "reuse the already configured json format" -- was therefore
left as [0 => 'json'] instead of being resolved to ['json' => ['application/json']],
and the call failed once the processor started reading it.

The loop now mirrors normalize() exactly, with the MCP format standing in for the
resource-level defaults. Behaviour is unchanged when no format is declared: the
operation still receives api_platform.mcp.format, which is what the 26 existing
MCP functional tests exercise.

The map form ['json' => ['application/json']] keeps passing through untouched --
normalizeFormats() accepts it as an inline mime-type declaration, which is the
documented way to use a format that is not registered globally.

McpFormatListTool covers the list form so both shapes stay green.
@vincentchalamon vincentchalamon self-assigned this Sep 18, 2026
Two CI failures, both mine.

PHPStan: process() receives the base Operation type, which does not declare
getOutputFormats() -- only HttpOperation does. The lookup now sits behind the
same McpTool/McpResource check the structuredContent flag already used, hoisted
into a local so both read it.

PHPUnit: the MongoDB test app loads routing_mongodb.yml, the only environment
that does not import routing_test.php and therefore has no /mcp route, so the
tests answered 404 instead of skipping. McpTest guards on isMongoDB() for the
same reason; McpFormatTest now does too, via RecreateSchemaTrait.
@vincentchalamon
vincentchalamon marked this pull request as ready for review September 18, 2026 09:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant