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/2025-11-25/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: /**
64: * @param array<string, mixed> $data
65: */
66: #[\Override]
67: public static function fromArray(array $data): static
68: {
69: $audience = null;
70:
71: if (isset($data['audience'])) {
72: Assert::that($data['audience'])->isList('"annotations.audience" must be a list, {type} given.');
73: $audience = array_map(
74: static fn(mixed $role): Role => EnumValueValidator::parse(Role::class, $role, 'each "annotations.audience"'),
75: $data['audience'],
76: );
77: }
78:
79: $priority = $data['priority'] ?? null;
80:
81: if (null !== $priority) {
82: $priority = self::parseNumber($priority, '"annotations.priority" must be a number or null, {type} given.');
83: }
84:
85: $lastModified = $data['lastModified'] ?? null;
86: Assert::that($lastModified)->nullOr()->isString('"annotations.lastModified" must be a string or null, {type} given.');
87:
88: return new self($audience, $priority, $lastModified);
89: }
90:
91: #[\Override]
92: public function toArray(): array
93: {
94: $data = [];
95:
96: if (null !== $this->audience) {
97: $data['audience'] = array_map(static fn(Role $role): string => $role->value, $this->audience);
98: }
99:
100: if (null !== $this->priority) {
101: $data['priority'] = $this->priority;
102: }
103:
104: if (null !== $this->lastModified) {
105: $data['lastModified'] = Iso8601DateTimeValidator::format($this->lastModified);
106: }
107:
108: return $data;
109: }
110:
111: #[\Override]
112: public function jsonSerialize(): array|\stdClass
113: {
114: $data = $this->toArray();
115:
116: return [] === $data ? new \stdClass() : $data;
117: }
118: }
119: