feat: Add output schema support to MCP tools (#153)

This commit is contained in:
Sule-Balogun Olanrewaju 2026-01-11 20:14:48 +00:00 • committed by GitHub
commit c0938a19bd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 605 additions and 55 deletions

View file

@ -16,7 +16,6 @@ use phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\DocBlock\Tags\Deprecated;
use phpDocumentor\Reflection\DocBlock\Tags\Param;
use phpDocumentor\Reflection\DocBlock\Tags\See;
use phpDocumentor\Reflection\DocBlock\Tags\Throws;
use PHPUnit\Framework\TestCase;
class DocBlockParserTest extends TestCase
@ -109,12 +108,6 @@ class DocBlockParserTest extends TestCase
$this->assertInstanceOf(DocBlock::class, $docBlock);
$throwsTags = $docBlock->getTagsByName('throws');
$this->assertCount(1, $throwsTags);
$this->assertInstanceOf(Throws::class, $throwsTags[0]);
$this->assertEquals('\\RuntimeException', (string) $throwsTags[0]->getType());
$this->assertEquals('if processing fails', $throwsTags[0]->getDescription()->render());
$deprecatedTags = $docBlock->getTagsByName('deprecated');
$this->assertCount(1, $deprecatedTags);
$this->assertInstanceOf(Deprecated::class, $deprecatedTags[0]);

View file

@ -70,8 +70,6 @@ class DocBlockTestFixture
*
* @return bool status of the operation
*
* @throws \RuntimeException if processing fails
*
* @deprecated use newMethod() instead
* @see DocBlockTestFixture::newMethod()
*/

View file

@ -11,6 +11,7 @@
namespace Mcp\Tests\Unit\Capability\Discovery;
use Mcp\Capability\Attribute\McpTool;
use Mcp\Capability\Attribute\Schema;
use Mcp\Tests\Unit\Fixtures\Enum\BackedIntEnum;
use Mcp\Tests\Unit\Fixtures\Enum\BackedStringEnum;
@ -337,6 +338,12 @@ class SchemaGeneratorFixture
/**
* Complex nested Schema constraints.
*/
#[McpTool(
outputSchema: [
'type' => 'object',
'additionalProperties' => true,
]
)]
public function complexNestedSchema(
#[Schema(
type: 'object',
@ -399,6 +406,12 @@ class SchemaGeneratorFixture
* Method with no parameters but Schema description.
*/
#[Schema(description: 'Gets server status. Takes no arguments.', properties: [])]
#[McpTool(
outputSchema: [
'type' => 'object',
'additionalProperties' => true,
]
)]
public function noParamsWithSchema(): array
{
return ['status' => 'OK'];
@ -424,4 +437,20 @@ class SchemaGeneratorFixture
public function withParameterNamedRequest(string $_request): void
{
}
// ===== OUTPUT SCHEMA FIXTURES =====
#[McpTool(
outputSchema: [
'type' => 'object',
'properties' => [
'message' => ['type' => 'string'],
],
'required' => ['message'],
'description' => 'The result of the operation',
]
)]
public function returnWithExplicitOutputSchema(): array
{
return ['message' => 'result'];
}
}

View file

@ -346,4 +346,45 @@ final class SchemaGeneratorTest extends TestCase
$this->expectException(InvalidArgumentException::class);
$this->schemaGenerator->generate($method);
}
public function testGenerateOutputSchemaReturnsNullForVoidReturnType(): void
{
$method = new \ReflectionMethod(SchemaGeneratorFixture::class, 'noParams');
$schema = $this->schemaGenerator->generateOutputSchema($method);
$this->assertNull($schema);
}
public function testGenerateOutputSchemaWithReturnDescription(): void
{
$method = new \ReflectionMethod(SchemaGeneratorFixture::class, 'returnWithExplicitOutputSchema');
$schema = $this->schemaGenerator->generateOutputSchema($method);
$this->assertEquals([
'type' => 'object',
'properties' => [
'message' => ['type' => 'string'],
],
'required' => ['message'],
'description' => 'The result of the operation',
], $schema);
}
public function testGenerateOutputSchemaForArrayReturnType(): void
{
$method = new \ReflectionMethod(SchemaGeneratorFixture::class, 'noParamsWithSchema');
$schema = $this->schemaGenerator->generateOutputSchema($method);
$this->assertEquals([
'type' => 'object',
'additionalProperties' => true,
], $schema);
}
public function testGenerateOutputSchemaForComplexNestedSchema(): void
{
$method = new \ReflectionMethod(SchemaGeneratorFixture::class, 'complexNestedSchema');
$schema = $this->schemaGenerator->generateOutputSchema($method);
$this->assertEquals([
'type' => 'object',
'additionalProperties' => true,
], $schema);
}
}