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\Tool;
15:
16: use Nexus\Mcp\Core\Exception\InvalidParamsException;
17: use Nexus\Mcp\Core\Schema\Cursor;
18: use Nexus\Mcp\Core\Schema\Result\CallToolResult;
19: use Nexus\Mcp\Core\Schema\Result\ListToolsResult;
20: use Nexus\Mcp\Core\Schema\Tool\Tool;
21: use Nexus\Mcp\Server\AbstractPaginatedStore;
22: use Nexus\Mcp\Server\Exception\ToolNotFoundException;
23: use Nexus\Mcp\Server\Exception\ToolOutputValidationException;
24: use Nexus\Mcp\Server\ServerContext;
25: use Nexus\Mcp\Server\Validation\OpisSchemaValidator;
26: use Nexus\Mcp\Server\Validation\SchemaValidatorInterface;
27:
28: /**
29: * In-memory implementation of `ToolStoreInterface`.
30: *
31: * @extends AbstractPaginatedStore<ToolEntry>
32: */
33: final readonly class ToolStore extends AbstractPaginatedStore implements ToolStoreInterface
34: {
35: protected const string STORE_LABEL = 'Tool store';
36:
37: /**
38: * @param array<non-empty-string, ToolEntry> $entries
39: */
40: public function __construct(
41: array $entries = [],
42: int $pageSize = self::DEFAULT_PAGE_SIZE,
43: private SchemaValidatorInterface $validator = new OpisSchemaValidator(),
44: ) {
45: parent::__construct($entries, $pageSize);
46: }
47:
48: #[\Override]
49: public function list(?Cursor $cursor): ListToolsResult
50: {
51: return $this->paginate(
52: $cursor,
53: static fn(ToolEntry $entry): Tool => $entry->tool,
54: static fn(array $tools, ?Cursor $nextCursor): ListToolsResult => new ListToolsResult($tools, $nextCursor),
55: );
56: }
57:
58: #[\Override]
59: public function call(string $name, ?array $arguments, ServerContext $context): CallToolResult
60: {
61: $entry = $this->entries[$name] ?? throw new ToolNotFoundException($name, $context->requestId);
62:
63: $tool = $entry->tool;
64:
65: $inputData = null === $arguments || [] === $arguments ? new \stdClass() : $arguments;
66: $inputErrors = $this->validator->validate($inputData, $tool->inputSchema);
67:
68: if ([] !== $inputErrors) {
69: throw new InvalidParamsException(
70: $context->requestId,
71: \sprintf('Invalid arguments for tool "%s": %s', $name, implode('; ', $inputErrors)),
72: );
73: }
74:
75: $result = $entry->executor->execute($arguments, $context);
76:
77: if (null !== $tool->outputSchema && true !== $result->isError && null !== $result->structuredContent) {
78: $outputData = [] === $result->structuredContent ? new \stdClass() : $result->structuredContent;
79: $outputErrors = $this->validator->validate($outputData, $tool->outputSchema);
80:
81: if ([] !== $outputErrors) {
82: throw new ToolOutputValidationException($name, $outputErrors);
83: }
84: }
85:
86: return $result;
87: }
88: }
89: