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;
15:
16: use Nexus\Assert\Assert;
17: use Nexus\Mcp\Core\Schema\Enum\Role;
18: use Nexus\Mcp\Core\Validation\EnumValueValidator;
19: use Nexus\Mcp\Core\Validation\Iso8601DateTimeValidator;
20:
21: /**
22: * Optional annotations for the client. The client can use annotations to inform how objects are
23: * used or displayed.
24: *
25: * @implements Arrayable<array{
26: * audience?: list<'assistant'|'user'>,
27: * priority?: float,
28: * lastModified?: string,
29: * }>
30: *
31: * @see https://modelcontextprotocol.io/specification/draft/schema#annotations
32: */
33: final readonly class Annotations implements Arrayable
34: {
35: use ParsesNumber;
36:
37: public ?\DateTimeImmutable $lastModified;
38:
39: /**
40: * @param null|list<Role> $audience
41: */
42: public function __construct(
43: public ?array $audience = null,
44: public ?float $priority = null,
45: ?string $lastModified = null,
46: ) {
47: if (null !== $this->audience) {
48: Assert::that($this->audience)
49: ->values()
50: ->isInstanceOf(Role::class, 'each "annotations.audience" must be a valid role, {type} given.')
51: ;
52: }
53:
54: Assert::that($this->priority)->nullOr()->isBetween(0.0, 1.0, message: '"annotations.priority" must be between 0.0 and 1.0.');
55:
56: if (null !== $lastModified) {
57: $lastModified = Iso8601DateTimeValidator::parse($lastModified, '"annotations.lastModified"');
58: }
59:
60: $this->lastModified = $lastModified;
61: }
62:
63: #[\Override]
64: public static function fromArray(array $data): static
65: {
66: $audience = null;
67:
68: if (isset($data['audience'])) {
69: Assert::that($data['audience'])->isList('"annotations.audience" must be a list, {type} given.');
70: $audience = array_map(
71: static fn(mixed $role): Role => EnumValueValidator::parse(Role::class, $role, 'each "annotations.audience"'),
72: $data['audience'],
73: );
74: }
75:
76: $priority = $data['priority'] ?? null;
77:
78: if (null !== $priority) {
79: $priority = self::parseNumber($priority, '"annotations.priority" must be a number or null, {type} given.');
80: }
81:
82: $lastModified = $data['lastModified'] ?? null;
83: Assert::that($lastModified)->nullOr()->isString('"annotations.lastModified" must be a string or null, {type} given.');
84:
85: return new self(audience: $audience, priority: $priority, lastModified: $lastModified);
86: }
87:
88: #[\Override]
89: public function toArray(): array
90: {
91: $data = [];
92:
93: if (null !== $this->audience) {
94: $data['audience'] = array_map(static fn(Role $role): string => $role->value, $this->audience);
95: }
96:
97: if (null !== $this->priority) {
98: $data['priority'] = $this->priority;
99: }
100:
101: if (null !== $this->lastModified) {
102: $data['lastModified'] = Iso8601DateTimeValidator::format($this->lastModified);
103: }
104:
105: return $data;
106: }
107:
108: #[\Override]
109: public function jsonSerialize(): array|\stdClass
110: {
111: $data = $this->toArray();
112:
113: return [] === $data ? new \stdClass() : $data;
114: }
115: }
116: