1: <?php
2:
3: declare(strict_types=1);
4:
5: /**
6: * This file is part of the Nexus MCP SDK package.
7: *
8: * (c) 2026 John Paul E. Balandan, CPA <paulbalandan@gmail.com>
9: *
10: * For the full copyright and license information, please view
11: * the LICENSE file that was distributed with this source code.
12: */
13:
14: namespace Nexus\Mcp\Server\Handler\Request;
15:
16: use Nexus\Mcp\Core\Exception\AbstractJsonRpcProtocolException;
17: use Nexus\Mcp\Core\Handler\AbstractContext;
18: use Nexus\Mcp\Core\Handler\RequestHandlerInterface;
19: use Nexus\Mcp\Core\Schema\ContentBlock\TextContent;
20: use Nexus\Mcp\Core\Schema\JsonRpc\JsonRpcRequest;
21: use Nexus\Mcp\Core\Schema\Request\CallToolRequest;
22: use Nexus\Mcp\Core\Schema\Result\CallToolResult;
23: use Nexus\Mcp\Core\Schema\Result\InputRequiredResult;
24: use Nexus\Mcp\Server\Exception\ToolOutputValidationException;
25: use Nexus\Mcp\Server\ServerContext;
26: use Nexus\Mcp\Server\Tool\ToolStoreInterface;
27: use Psr\Log\LoggerInterface;
28: use Psr\Log\NullLogger;
29:
30: /**
31: * Handles the `tools/call` request by delegating to a `ToolStoreInterface`.
32: *
33: * @implements RequestHandlerInterface<'tools/call', CallToolResult|InputRequiredResult, ServerContext>
34: */
35: final readonly class CallToolRequestHandler implements RequestHandlerInterface
36: {
37: public function __construct(
38: private ToolStoreInterface $store,
39: private LoggerInterface $logger = new NullLogger(),
40: ) {
41: }
42:
43: #[\Override]
44: public function handle(JsonRpcRequest $request, AbstractContext $context): CallToolResult|InputRequiredResult
45: {
46: \assert($request instanceof CallToolRequest);
47: \assert($context instanceof ServerContext);
48:
49: try {
50: $result = $this->store->call($request->params->name, $request->params->arguments, $context);
51:
52: if ($result instanceof InputRequiredResult) {
53: return $result;
54: }
55:
56: // Spec, "Structured Content": a tool returning structured content SHOULD also return the serialized JSON in a TextContent block.
57: if (null !== $result->structuredContent && [] === $result->content) {
58: return new CallToolResult(
59: content: [new TextContent(text: json_encode(
60: $result->structuredContent,
61: \JSON_THROW_ON_ERROR | \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE,
62: ))],
63: structuredContent: $result->structuredContent,
64: isError: $result->isError,
65: meta: $result->meta,
66: );
67: }
68:
69: return $result;
70: } catch (AbstractJsonRpcProtocolException $e) {
71: throw $e;
72: } catch (ToolOutputValidationException $e) {
73: $this->logger->error(
74: 'Tool result violated the declared outputSchema.',
75: ['tool' => $request->params->name, 'exception' => $e],
76: );
77: } catch (\Throwable $e) {
78: $this->logger->error(
79: 'Uncaught tool executor exception. Returning generic error to peer.',
80: ['tool' => $request->params->name, 'exception' => $e],
81: );
82: }
83:
84: return new CallToolResult(
85: content: [new TextContent(text: 'Tool execution failed.')],
86: isError: true,
87: );
88: }
89: }
90: