laravel/mcp Reverts Structured Content Object Cast (PR #340)
Contributed by pushpak1300
Part of the mcp v1.0.1 release
PR #340 in laravel/mcp is a full revert of PR #339, which had updated HasStructuredContent "so structured content always gets returned correctly." With this revert, structured tool content is once again merged into the response payload as a plain PHP array — no (object) cast and no object-shaped output for list-style data. For anyone upgrading from v0.9.6, that means structured content behaves exactly as it did there. If you never touched an interim build that included #339, you will see no behavioral change at all; if you did, this release undoes it.
What changed
The revert touches three files, all moving back to the pre-#339 state:
src/Server/Concerns/HasStructuredContent.php—mergeStructuredContent()now returnsarray_merge($baseArray, ['structuredContent' => $this->structuredContent]). The(object)cast that forced object serialization is gone.src/Server/Testing/TestResponse.php— thestructuredContent()accessor again treats only arrays as valid structured content. The extra! is_object($structuredContent)condition introduced by #339 has been removed, so the testing helpers recognize arrays only.- The test suite — assertions that expected
(object) [...]now expect plain[...], and the dedicated test covering "casts structured content as object when an indexed array is used" was deleted entirely.
Why the cast existed in the first place
The Model Context Protocol specification requires the structuredContent field of a tool result to be an object. PHP handles the common case naturally: an associative array such as ['temperature' => 22.5, 'humidity' => 65] encodes to {"temperature":22.5,"humidity":65} — a JSON object — with no cast required. The edge case is a sequential list: ['value1', 'value2'] encodes as ["value1","value2"], a JSON array, which does not satisfy the spec's object requirement. PR #339 attempted to close that gap by casting the whole structuredContent value to stdClass before merging, guaranteeing object output even for indexed arrays.
Why it was reverted
While the cast did produce object-shaped JSON for list-style content, it changed the payload everywhere else too. Structured content suddenly flowed through the server internals and the testing layer as an object rather than an array, which rippled outward: TestResponse needed a new object-aware check, every affected test needed (object) casts, and code comparing structured content against arrays stopped matching. Rather than keep adjusting the surrounding code to accommodate one edge case, the maintainers rolled the change back in full. Structured content now passes through verbatim, preserving predictable array semantics end to end.
What the output looks like now
Here is the exact usage pattern from the trait's own test suite:
$tool = new class {
use HasStructuredContent;
};
$tool->setStructuredContent(['value1', 'value2']);
$tool->mergeStructuredContent([
'content' => [['type' => 'text', 'text' => 'value1, value2']],
'isError' => false,
]);
After this release, the merged result contains structuredContent => ['value1', 'value2'], which encodes over the wire as a JSON array:
{
"result": {
"content": [{ "type": "text", "text": "value1, value2" }],
"isError": false,
"structuredContent": ["value1", "value2"]
}
}
Under the interim #339 build, that same payload briefly produced "structuredContent": {"0": "value1", "1": "value2"} instead. For associative arrays — by far the most common case — the output is identical with or without the cast.
Upgrade impact
- Upgrading directly from v0.9.6: nothing to do. The array passthrough matches what you already have; no code or test changes are required.
- Coming from an interim build that included #339: remove any
(object)casts you added aroundstructuredContentin your own assertions. TheTestResponsehelpers compare against arrays again, so object-based expectations will fail. - Relying on lists being coerced to objects: if your MCP client enforces the spec's object requirement strictly, switch your structured content from a sequential list to an associative array (or give the values string keys) so it encodes as a JSON object on its own.
Takeaways
- PR #340 fully reverts PR #339;
structuredContentis no longer cast to(object)before being merged into tool results. - For anyone upgrading from v0.9.6, the effective behavior is unchanged — structured content is passed through as a plain array.
- Associative arrays serialize identically either way; indexed arrays once again serialize as JSON arrays rather than
{"0": ...}objects. - Test expectations written against the interim object-cast behavior should be reverted to plain array comparisons.
- No breaking changes are expected for tools returning keyed, associative structured content — the overwhelmingly common pattern.
Categories: fix (3), upgrade notes (11)