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

View file

@ -21,6 +21,7 @@ use Mcp\Schema\JsonRpc\Error;
use Mcp\Schema\JsonRpc\Response;
use Mcp\Schema\Request\CallToolRequest;
use Mcp\Schema\Result\CallToolResult;
use Mcp\Schema\Tool;
use Mcp\Server\Handler\Request\CallToolHandler;
use Mcp\Server\Session\SessionInterface;
use PHPUnit\Framework\MockObject\MockObject;
@ -59,7 +60,9 @@ class CallToolHandlerTest extends TestCase
public function testHandleSuccessfulToolCall(): void
{
$request = $this->createCallToolRequest('greet_user', ['name' => 'John']);
$toolReference = $this->createMock(ToolReference::class);
$toolReference = $this->createToolReference('greet_user', function () {
return 'Hello, John!';
});
$expectedResult = new CallToolResult([new TextContent('Hello, John!')]);
$this->registry
@ -92,7 +95,9 @@ class CallToolHandlerTest extends TestCase
public function testHandleToolCallWithEmptyArguments(): void
{
$request = $this->createCallToolRequest('simple_tool', []);
$toolReference = $this->createMock(ToolReference::class);
$toolReference = $this->createToolReference('greet_user', function () {
return 'Hello, John!';
});
$expectedResult = new CallToolResult([new TextContent('Simple result')]);
$this->registry
@ -129,7 +134,9 @@ class CallToolHandlerTest extends TestCase
'null_param' => null,
];
$request = $this->createCallToolRequest('complex_tool', $arguments);
$toolReference = $this->createMock(ToolReference::class);
$toolReference = $this->createToolReference('greet_user', function () {
return 'Hello, John!';
});
$expectedResult = new CallToolResult([new TextContent('Complex result')]);
$this->registry
@ -182,7 +189,9 @@ class CallToolHandlerTest extends TestCase
$request = $this->createCallToolRequest('failing_tool', ['param' => 'value']);
$exception = new ToolCallException('Tool execution failed');
$toolReference = $this->createMock(ToolReference::class);
$toolReference = $this->createToolReference('greet_user', function () {
return 'Hello, John!';
});
$this->registry
->expects($this->once())
->method('getTool')
@ -217,7 +226,9 @@ class CallToolHandlerTest extends TestCase
$request = $this->createCallToolRequest('null_tool', []);
$expectedResult = new CallToolResult([]);
$toolReference = $this->createMock(ToolReference::class);
$toolReference = $this->createToolReference('greet_user', function () {
return 'Hello, John!';
});
$this->registry
->expects($this->once())
->method('getTool')
@ -254,7 +265,9 @@ class CallToolHandlerTest extends TestCase
$request = $this->createCallToolRequest('test_tool', ['key1' => 'value1', 'key2' => 42]);
$exception = new ToolCallException('Custom error message');
$toolReference = $this->createMock(ToolReference::class);
$toolReference = $this->createToolReference('greet_user', function () {
return 'Hello, John!';
});
$this->registry
->expects($this->once())
->method('getTool')
@ -297,7 +310,9 @@ class CallToolHandlerTest extends TestCase
$request = $this->createCallToolRequest('failing_tool', ['param' => 'value']);
$exception = new \RuntimeException('Internal database connection failed');
$toolReference = $this->createMock(ToolReference::class);
$toolReference = $this->createToolReference('greet_user', function () {
return 'Hello, John!';
});
$this->registry
->expects($this->once())
->method('getTool')
@ -324,7 +339,10 @@ class CallToolHandlerTest extends TestCase
$request = $this->createCallToolRequest('tool-with_special.chars', []);
$expectedResult = new CallToolResult([new TextContent('Special tool result')]);
$toolReference = $this->createMock(ToolReference::class);
$toolReference = $this->createToolReference('greet_user', function () {
return 'Hello, John!';
});
$this->registry
->expects($this->once())
->method('getTool')
@ -359,7 +377,9 @@ class CallToolHandlerTest extends TestCase
$request = $this->createCallToolRequest('unicode_tool', $arguments);
$expectedResult = new CallToolResult([new TextContent('Unicode handled')]);
$toolReference = $this->createMock(ToolReference::class);
$toolReference = $this->createToolReference('greet_user', function () {
return 'Hello, John!';
});
$this->registry
->expects($this->once())
->method('getTool')
@ -387,7 +407,9 @@ class CallToolHandlerTest extends TestCase
public function testHandleReturnsStructuredContentResult(): void
{
$request = $this->createCallToolRequest('structured_tool', ['query' => 'php']);
$toolReference = $this->createMock(ToolReference::class);
$toolReference = $this->createToolReference('greet_user', function () {
return 'Hello, John!';
});
$structuredResult = new CallToolResult([new TextContent('Rendered results')], false, ['result' => 'Rendered results']);
$this->registry
@ -416,7 +438,9 @@ class CallToolHandlerTest extends TestCase
public function testHandleReturnsCallToolResult(): void
{
$request = $this->createCallToolRequest('result_tool', ['query' => 'php']);
$toolReference = $this->createMock(ToolReference::class);
$toolReference = $this->createToolReference('greet_user', function () {
return 'Hello, John!';
});
$callToolResult = new CallToolResult([new TextContent('Error result')], true);
$this->registry
@ -457,4 +481,22 @@ class CallToolHandlerTest extends TestCase
],
]);
}
private function createToolReference(
string $name,
callable $handler,
?array $outputSchema = null,
array $methodsToMock = ['formatResult'],
): ToolReference&MockObject {
$tool = new Tool($name, ['type' => 'object', 'properties' => [], 'required' => null], null, null, null, null, $outputSchema);
$builder = $this->getMockBuilder(ToolReference::class)
->setConstructorArgs([$tool, $handler]);
if (!empty($methodsToMock)) {
$builder->onlyMethods($methodsToMock);
}
return $builder->getMock();
}
}