Skip to content

Conversation

@bigdevlarry
Copy link
Contributor

@bigdevlarry bigdevlarry commented Nov 16, 2025

The changes add comprehensive support for outputSchema in the Tools definition, enabling MCP servers to specify the expected structure and types of tool responses.

This includes:

Added outputSchema parameter to Builder::addTool() method
Updated the Tool class constructor and JSON serialization to support outputSchema
Added validation for outputSchema structure and type requirements
Enhanced existing tests to verify outputSchema functionality works correctly
Fixed related type annotation issues and code style problems

Screenshot 2025-12-06 at 23 13 03 Screenshot 2025-12-06 at 23 12 44

Motivation and Context

How Has This Been Tested?

Breaking Changes

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • [x ] I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

@bigdevlarry
Copy link
Contributor Author

Hi @chr-hertel, & @CodeWithKyrian the OutputSchema support PR is back up

@bigdevlarry bigdevlarry force-pushed the add-support-for-output-schema branch from ec01d9a to a2d7b6d Compare November 16, 2025 23:43
@bigdevlarry bigdevlarry force-pushed the add-support-for-output-schema branch from 9234613 to b3b69cd Compare November 29, 2025 19:08
@bigdevlarry
Copy link
Contributor Author

Hi @chr-hertel, @Nyholm can I get some eyes on here for the output schema support. Ty

@Nyholm
Copy link
Contributor

Nyholm commented Dec 2, 2025

Interesting. I do like the idea. I'll spend some more time to read the code.

Copy link
Member

@chr-hertel chr-hertel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general this looks pretty good already, just one thing i noticed with one test scenario. structuredContent should be optional in response payload

Comment on lines 8 to 11
"isError": false,
"structuredContent": {
"result": "System status: OK (discovered)"
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder a bit about this change here - it's not adding value to me. spec is unclear about it, but i think we shouldn't just add it always.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chr-hertel this has now been actioned, so structrued content will only be added if outputschema is present and explicility specified

@bigdevlarry
Copy link
Contributor Author

@chr-hertel To make structuredContent optional in the response payload, I'll have to change the behavior so that outputSchema is only present when explicitly provided in the #[McpTool] attribute, not auto-generated from return types or DocBlocks. When an outputSchema is explicitly provided, structuredContent must be included in the response (as required by the MCP spec). When no outputSchema is provided, neither outputSchema nor structuredContent is included.

@bigdevlarry bigdevlarry force-pushed the add-support-for-output-schema branch 2 times, most recently from e8de647 to 329533b Compare December 6, 2025 20:53
@chr-hertel
Copy link
Member

The case that i was pointing out was mostly just returning a string, but still had that result => '...' structure on top - i just can't find any reference for that in the spec or do i miss sth?

@bigdevlarry bigdevlarry force-pushed the add-support-for-output-schema branch from c8f19cc to 2772869 Compare December 6, 2025 21:10
@bigdevlarry bigdevlarry force-pushed the add-support-for-output-schema branch from 4e96598 to 13d5b77 Compare December 6, 2025 22:52
@bigdevlarry
Copy link
Contributor Author

bigdevlarry commented Dec 6, 2025

The case that i was pointing out was mostly just returning a string, but still had that result => '...' structure on top - i just can't find any reference for that in the spec or do i miss sth?

Good shout ! I was working with the wrong assumption that the result wrapper was in the spec. structuredContent now matches the outputSchema properties directly, no more top level result wrapping. The structure is as in the MCP spec example. I've updated the diagram on the PR to reflect the test as well

}

return $toolExecutionResult;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't we leave that to implementations of the handler? I'm not sure we should alter the content at any point in the sdk.

}

return $value;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure to understand this already happens in formatResult if our goal is to return a structured content maybe that we should add a condition above?

public static function success(array $content, ?array $meta = null, ?array $structuredContent = null): self
{
return new self($content, false, null, $meta);
return new self($content, false, $meta, $structuredContent);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure to why this is needed content can be a structured content since #93 or am I missing something?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missed that PR. We can use the existing structure. Thanks for flagging

* required: string[]|null
* }
* @phpstan-type ToolOutputSchema array{
* type: 'object',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* type: 'object',
* type: 'object',

Can't the schema be a list? object type is not specified in the spec for the output.

if (isset($data['outputSchema']['properties']) && \is_array($data['outputSchema']['properties']) && empty($data['outputSchema']['properties'])) {
$data['outputSchema']['properties'] = new \stdClass();
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I advise against validating this here we should leave that to the user discretion, it'll make the sdk more portable and subject to specification changes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was following the validation pattern we've for the inputSchema setup above. Also, if we don't add validation for outputSchema, how can we have a structured output format? Users might use a structure we don't support, don't you think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I'm actually not a huge fan of the above inputSchema implementation either :).

I think it's the responsability of another abstraction to do this, and its also part of a bigger discussion around json schema and validation (I suggested already that these should not be coded into the php-sdk as they're quite complicated subjects).


$result = $rawResult;
if (!$rawResult instanceof CallToolResult) {
$result = new CallToolResult($reference->formatResult($rawResult), structuredContent: $structuredContent);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd try not to change this part of the code, I understand we want to comply with the spec that says:

Servers MUST provide structured results that conform to this schema.

If possible I think it should be handled in the formatResult.

My personal opinion would be to throw if the handler doesn't return a structured content.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we're trying to comply with what the spec says here. I also think it reads better having it here otherwise, @chr-hertel feels otherwise

"text": "{\n \"result\": 50,\n \"operation\": \"10 multiply 5\",\n \"precision\": 2,\n \"within_bounds\": true\n}"
}
],
"isError": false
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should probably not change

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just indentation, no code change

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know but why as we didn't change anything to the json serialization this is weird to me and pollutes the diff

}
],
"isError": false
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should probably not change

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just indentation, no code change

"text": "{\n \"success\": true,\n \"config\": {\n \"app\": {\n \"name\": \"TestApp\",\n \"env\": \"development\",\n \"debug\": true,\n \"url\": \"https://example.com\",\n \"port\": 8080\n },\n \"generated_at\": \"2025-01-01T00:00:00+00:00\",\n \"version\": \"1.0.0\",\n \"features\": {\n \"logging\": true,\n \"caching\": false,\n \"analytics\": false,\n \"rate_limiting\": false\n }\n },\n \"validation\": {\n \"app_name_valid\": true,\n \"url_valid\": true,\n \"port_in_range\": true\n }\n}"
}
],
"isError": false
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should probably not change (same for the other snapshots not sure why they change)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just indentation, no code change

'type' => 'object',
'additionalProperties' => true,
]
)]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

on a general matter I think (if possible) that we should avoid to change existing fixtures but instead add new ones, as for that we need to change existing tests.

@soyuka
Copy link
Contributor

soyuka commented Dec 8, 2025

Nice addition we definitely miss this! Thanks!

@chr-hertel
Copy link
Member

@bigdevlarry we have a misunderstanding here: my point was that output schema - in my understanding - is only needed for more complex structures - not for simple string responses for example.

compared with the typescript-sdk:

simple tool response
image

structured tool response
image


my take-away here is: there's no need for the structuredContent keyword in the response, if we only return a string - but feel free to point out what i miss

@bigdevlarry
Copy link
Contributor Author

bigdevlarry commented Dec 8, 2025

@chr-hertel At the moment, if structuredContent returns a primitive (string, int, bool, etc.), extractStructuredContent() method in the src/Capability/Registry/ToolReference.php returns null, and structuredContent is not included in the response. So image 2, looks valid where you've get_weather because the outputSchema returns an object/array. If this is still not the intended behaviour, maybe you can point out what to add/remove in the extractStructuredContent method
Screenshot 2025-12-08 at 21 31 59

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.

4 participants