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