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\Core\Schema\Resource;
15:
16: use Nexus\Assert\Assert;
17: use Nexus\Mcp\Core\Schema\Annotations;
18: use Nexus\Mcp\Core\Schema\Arrayable;
19: use Nexus\Mcp\Core\Schema\BaseMetadata;
20: use Nexus\Mcp\Core\Schema\Icon;
21: use Nexus\Mcp\Core\Schema\Icons;
22: use Nexus\Mcp\Core\Schema\MetaObject;
23: use Nexus\Mcp\Core\Validation\IdentifierNameValidator;
24: use Nexus\Mcp\Core\Validation\Rfc6570UriTemplateValidator;
25:
26: /**
27: * A template description for resources available on the server.
28: *
29: * @implements Arrayable<array{
30: * name: non-empty-string,
31: * uriTemplate: non-empty-string,
32: * title?: non-empty-string,
33: * description?: non-empty-string,
34: * mimeType?: non-empty-string,
35: * annotations?: template-type<Annotations, Arrayable, 'T'>,
36: * icons?: list<template-type<Icon, Arrayable, 'T'>>,
37: * _meta?: template-type<MetaObject, Arrayable, 'T'>,
38: * }>
39: *
40: * @see https://modelcontextprotocol.io/specification/2025-11-25/schema#resourcetemplate
41: */
42: final readonly class ResourceTemplate extends BaseMetadata implements Arrayable, Icons
43: {
44: /**
45: * @var non-empty-string
46: */
47: public string $uriTemplate;
48:
49: /**
50: * @var null|non-empty-string
51: */
52: public ?string $description;
53:
54: /**
55: * @var null|non-empty-string
56: */
57: public ?string $mimeType;
58:
59: /**
60: * @var null|list<Icon>
61: */
62: public ?array $icons;
63:
64: /**
65: * @param null|list<Icon> $icons
66: */
67: public function __construct(
68: string $name,
69: string $uriTemplate,
70: ?string $title = null,
71: ?string $description = null,
72: ?string $mimeType = null,
73: public Annotations $annotations = new Annotations(),
74: ?array $icons = null,
75: public MetaObject $meta = new MetaObject(),
76: ) {
77: parent::__construct($name, $title);
78:
79: IdentifierNameValidator::validate($name, 'resource template "name"');
80: Rfc6570UriTemplateValidator::validate($uriTemplate, 'resource template "uriTemplate"');
81:
82: Assert::that($description)->nullOr()->isNonEmptyString('resource template "description" must be a non-empty string or null.');
83: Assert::that($mimeType)->nullOr()->isNonEmptyString('resource template "mimeType" must be a non-empty string or null.');
84:
85: if (null !== $icons) {
86: Assert::that($icons)->values()->isInstanceOf(Icon::class);
87: }
88:
89: $this->uriTemplate = $uriTemplate;
90: $this->description = $description;
91: $this->mimeType = $mimeType;
92: $this->icons = $icons;
93: }
94:
95: /**
96: * @param array<string, mixed> $data
97: */
98: #[\Override]
99: public static function fromArray(array $data): static
100: {
101: Assert::that($data)->hasOffset('name', 'resource template missing the required "name" key.');
102: $name = $data['name'];
103: Assert::that($name)->isString('resource template "name" must be a string, {type} given.');
104:
105: Assert::that($data)->hasOffset('uriTemplate', 'resource template missing the required "uriTemplate" key.');
106: $uriTemplate = $data['uriTemplate'];
107: Assert::that($uriTemplate)->isString('resource template "uriTemplate" must be a string, {type} given.');
108:
109: $title = $data['title'] ?? null;
110: Assert::that($title)->nullOr()->isString('resource template "title" must be a string or null, {type} given.');
111:
112: $description = $data['description'] ?? null;
113: Assert::that($description)->nullOr()->isString('resource template "description" must be a string or null, {type} given.');
114:
115: $mimeType = $data['mimeType'] ?? null;
116: Assert::that($mimeType)->nullOr()->isString('resource template "mimeType" must be a string or null, {type} given.');
117:
118: $annotations = new Annotations();
119:
120: if (\array_key_exists('annotations', $data)) {
121: Assert::that($data['annotations'])
122: ->isArray('resource template "annotations" must be an object, {type} given.')
123: ->isMap('resource template "annotations" must be a string-keyed object.')
124: ;
125: $annotations = Annotations::fromArray($data['annotations']);
126: }
127:
128: $icons = null;
129:
130: if (isset($data['icons'])) {
131: Assert::that($data['icons'])
132: ->isList('resource template "icons" must be a list, {type} given.')
133: ->values()
134: ->isArray('each resource template "icons" must be an object, {type} given.')
135: ->isMap('each resource template "icons" must be a string-keyed object.')
136: ;
137: $icons = array_map(Icon::fromArray(...), $data['icons']);
138: }
139:
140: $meta = new MetaObject();
141:
142: if (\array_key_exists('_meta', $data)) {
143: Assert::that($data['_meta'])
144: ->isArray('resource template "_meta" must be an object, {type} given.')
145: ->isMap('resource template "_meta" must be a string-keyed object.')
146: ;
147: $meta = MetaObject::fromArray($data['_meta']);
148: }
149:
150: return new self($name, $uriTemplate, $title, $description, $mimeType, $annotations, $icons, $meta);
151: }
152:
153: #[\Override]
154: public function toArray(): array
155: {
156: $data = [
157: 'name' => $this->name,
158: 'uriTemplate' => $this->uriTemplate,
159: ];
160:
161: if (null !== $this->title) {
162: $data['title'] = $this->title;
163: }
164:
165: if (null !== $this->description) {
166: $data['description'] = $this->description;
167: }
168:
169: if (null !== $this->mimeType) {
170: $data['mimeType'] = $this->mimeType;
171: }
172:
173: $annotations = $this->annotations->toArray();
174:
175: if ([] !== $annotations) {
176: $data['annotations'] = $annotations;
177: }
178:
179: if (null !== $this->icons) {
180: $data['icons'] = array_map(static fn(Icon $icon): array => $icon->toArray(), $this->icons);
181: }
182:
183: $meta = $this->meta->toArray();
184:
185: if ([] !== $meta) {
186: $data['_meta'] = $meta;
187: }
188:
189: return $data;
190: }
191:
192: #[\Override]
193: public function jsonSerialize(): array
194: {
195: return $this->toArray();
196: }
197: }
198: