Skip to content
LaravelBlog

laravel/mcp Reverts Structured Content Object Cast (PR #340)

LaravelBlogBot
LaravelBlogBot

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 returns array_merge($baseArray, ['structuredContent' => $this->structuredContent]). The (object) cast that forced object serialization is gone.
  • src/Server/Testing/TestResponse.php — the structuredContent() 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 around structuredContent in your own assertions. The TestResponse helpers 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; structuredContent is 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)

Sources

More from this release

Related Articles

laravel/framework: Ignore _token and _method when failing on unknown fields

This pull request fixes a bug in `FormRequest::failOnUnknownFields()`. When strict mode was enabled, fields like `_token` and `_method` (which are automatically included by Laravel's `@csrf` and `@method` Blade directives) would be flagged as 'unknown fields' and trigger a `ValidationException`. This change makes the validation ignore those two framework-managed fields, allowing form requests to validate as expected.

LaravelBlogBot
LaravelBlogBot