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\Resource;
15:
16: use Nexus\Assert\Assert;
17: use Nexus\Mcp\Core\Schema\Cursor;
18: use Nexus\Mcp\Core\Schema\Resource\ResourceTemplate;
19: use Nexus\Mcp\Core\Schema\Result\ListResourceTemplatesResult;
20: use Nexus\Mcp\Core\Schema\Result\ReadResourceResult;
21: use Nexus\Mcp\Core\UriTemplate\Matcher;
22: use Nexus\Mcp\Core\UriTemplate\Validator;
23: use Nexus\Mcp\Server\AbstractPaginatedStore;
24: use Nexus\Mcp\Server\Exception\ResourceNotFoundException;
25: use Nexus\Mcp\Server\ServerContext;
26:
27: /**
28: * In-memory implementation of `ResourceTemplateStoreInterface`.
29: *
30: * @extends AbstractPaginatedStore<ResourceTemplateEntry>
31: */
32: final readonly class ResourceTemplateStore extends AbstractPaginatedStore implements ResourceTemplateStoreInterface
33: {
34: protected const string STORE_LABEL = 'Resource template store';
35:
36: /**
37: * @var list<array{pattern: non-empty-string, entry: ResourceTemplateEntry}>
38: */
39: private array $compiled;
40:
41: /**
42: * @param array<non-empty-string, ResourceTemplateEntry> $entries
43: */
44: public function __construct(array $entries = [], int $pageSize = self::DEFAULT_PAGE_SIZE)
45: {
46: parent::__construct($entries, $pageSize);
47:
48: $compiled = [];
49:
50: foreach ($this->entries as $key => $entry) {
51: Validator::validate($key, 'ResourceTemplate');
52: Assert::that($entry->template->uriTemplate)
53: ->isIdentical($key, 'Resource template store entry key "{other}" must match its template URI "{value}".')
54: ;
55: $compiled[] = ['pattern' => Matcher::compile($key), 'entry' => $entry];
56: }
57:
58: $this->compiled = $compiled;
59: }
60:
61: #[\Override]
62: public function list(?Cursor $cursor): ListResourceTemplatesResult
63: {
64: return $this->paginate(
65: $cursor,
66: static fn(ResourceTemplateEntry $entry): ResourceTemplate => $entry->template,
67: static fn(array $templates, ?Cursor $nextCursor): ListResourceTemplatesResult => new ListResourceTemplatesResult($templates, $nextCursor),
68: );
69: }
70:
71: #[\Override]
72: public function read(string $uri, ServerContext $context): ReadResourceResult
73: {
74: foreach ($this->compiled as ['pattern' => $pattern, 'entry' => $entry]) {
75: $bindings = Matcher::matchCompiled($pattern, $uri);
76:
77: if (null !== $bindings) {
78: return $entry->reader->read($uri, $bindings, $context);
79: }
80: }
81:
82: throw new ResourceNotFoundException($uri, $context->requestId);
83: }
84: }
85: