Skip to content

[Capability] Harden structuredContent extraction - #417

Open
chr-hertel wants to merge 3 commits into
modelcontextprotocol:mainfrom
chr-hertel:feat/resource-link-content-review
Open

[Capability] Harden structuredContent extraction#417
chr-hertel wants to merge 3 commits into
modelcontextprotocol:mainfrom
chr-hertel:feat/resource-link-content-review

Conversation

@chr-hertel

@chr-hertel chr-hertel commented Aug 14, 2026

Copy link
Copy Markdown
Member

Follow-up to #400.

  • structuredContent must be a JSON object. extractStructuredContent() returned PHP lists as-is, and the object branch returned whatever JsonSerializable produced — both contradict the method's array<string, mixed>|null signature and get the tool call rejected by strict clients. Tool::fromArray() already requires an outputSchema of type object; this aligns the runtime path with it.
  • ResourceLink::fromArray() validated optional fields inconsistently: description/mimeType reached the constructor as a TypeError, size was silently (int)-cast, annotations lacked the is_array() guard icons had. Now mirrors ResourceDefinition::fromArray().
  • Test that a typed resource_link block keeps its optional fields through PromptResultFormatter.
  • Document outputSchema/structuredContent, previously undocumented.

Drops testExtractStructuredContentReturnsArrayDirectlyForArrayOutputSchema: it contradicted c0938a1, which pinned ToolOutputSchema to type: 'object' and added the matching fromArray() check in the same commit.

BC: tools returning a list no longer advertise structuredContent; the value stays in content.

🤖 Generated with Claude Code

@cancan101

Copy link
Copy Markdown
Contributor

Did you want me to do a merge /rebase to resolve conflicts?

@chr-hertel

Copy link
Copy Markdown
Member Author

@cancan101 no, all good - just a follow up. your commits will be dropped after merging #400 in a sec

Three follow-ups from review of the resource_link work:

* PromptResultFormatter: add a regression test proving the optional
  fields of a typed resource_link block (title, description, mimeType,
  size, annotations, _meta) survive formatting. The delegation to
  ResourceLink::fromArray() already landed while rebasing onto the
  PromptResultFormatter refactor, but the existing test only supplied
  uri/name and so could not catch a reintroduced field drop.

* ResourceLink::fromArray(): validate optional fields consistently, the
  way the equivalent ResourceDefinition::fromArray() already does.
  description, mimeType and size now raise InvalidArgumentException
  instead of surfacing a TypeError (or silently coercing, for size's
  (int) cast); annotations goes through Annotations::tryFromArray(),
  which carries the is_array() guard that icons already had; icons uses
  Icon::listFromArray() so a non-array entry is reported in context.

* ToolReference::extractStructuredContent(): never emit a list as
  structuredContent. The Content guard added earlier fixed only one
  instance of the real invariant - structuredContent must be a JSON
  object, and a PHP list can never be one. Tool::fromArray() already
  enforces the matching rule by rejecting an outputSchema whose type is
  not "object", so this aligns the runtime path with it. The test
  asserting the opposite for an array-typed outputSchema contradicted
  the phpstan type and the fromArray() check introduced alongside it in
  the same commit, so it is replaced by tests for the two list shapes.
@chr-hertel
chr-hertel force-pushed the feat/resource-link-content-review branch from 64b3a3d to fa158fc Compare August 14, 2026 21:25
`extractStructuredContent()` guards raw array results against being emitted
as a JSON array, but the object branch handed back whatever `json_decode()`
produced. A `JsonSerializable` returning a list or a scalar slipped straight
through, producing exactly the `structuredContent` the array guard exists to
prevent — and a return value that contradicts the method's own
`array<string, mixed>|null` signature.

Check the decoded value before returning it, and cover the object branch,
which had no tests at all.
`outputSchema` and `structuredContent` were undocumented: the tool return
value docs covered only the `content` side, and the schema generation
section is about tool parameters.

Add a "Structured Output" subsection covering how to declare the schema,
which return values populate `structuredContent`, and why a list has to be
wrapped in a key to get structured output at all.
@chr-hertel chr-hertel changed the title [Schema] Add resource_link content block [Capability] Harden structuredContent extraction, tighten ResourceLink validation Aug 14, 2026
@chr-hertel chr-hertel changed the title [Capability] Harden structuredContent extraction, tighten ResourceLink validation [Capability] Harden structuredContent extraction Aug 14, 2026
@chr-hertel chr-hertel modified the milestones: 0.8.0, 0.9.0 Aug 14, 2026
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.

2 participants