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