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