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\Tool;
15:
16: use Nexus\Assert\Assert;
17: use Nexus\Mcp\Core\Schema\Arrayable;
18:
19: /**
20: * Additional properties describing a `Tool` to clients.
21: *
22: * NOTE: all properties in `ToolAnnotations` are **hints**.
23: * They are not guaranteed to provide a faithful description of
24: * tool behavior (including descriptive properties like `title`).
25: *
26: * Clients should never make tool use decisions based on `ToolAnnotations`
27: * received from untrusted servers.
28: *
29: * @implements Arrayable<array{
30: * title?: non-empty-string,
31: * readOnlyHint?: bool,
32: * destructiveHint?: bool,
33: * idempotentHint?: bool,
34: * openWorldHint?: bool,
35: * }>
36: *
37: * @see https://modelcontextprotocol.io/specification/2026-07-28/schema#toolannotations
38: */
39: final readonly class ToolAnnotations implements Arrayable
40: {
41: /**
42: * @param null|non-empty-string $title
43: */
44: public function __construct(
45: public ?string $title = null,
46: public ?bool $readOnlyHint = null,
47: public ?bool $destructiveHint = null,
48: public ?bool $idempotentHint = null,
49: public ?bool $openWorldHint = null,
50: ) {
51: Assert::that($title)->nullOr()->isNonEmptyString('"annotations.title" must be a non-empty string or null.');
52: }
53:
54: #[\Override]
55: public static function fromArray(array $data): static
56: {
57: $title = $data['title'] ?? null;
58: Assert::that($title)->nullOr()->isNonEmptyString('"annotations.title" must be a non-empty string or null, {type} given.');
59:
60: $readOnlyHint = $data['readOnlyHint'] ?? null;
61: Assert::that($readOnlyHint)->nullOr()->isBool('"annotations.readOnlyHint" must be a bool or null, {type} given.');
62:
63: $destructiveHint = $data['destructiveHint'] ?? null;
64: Assert::that($destructiveHint)->nullOr()->isBool('"annotations.destructiveHint" must be a bool or null, {type} given.');
65:
66: $idempotentHint = $data['idempotentHint'] ?? null;
67: Assert::that($idempotentHint)->nullOr()->isBool('"annotations.idempotentHint" must be a bool or null, {type} given.');
68:
69: $openWorldHint = $data['openWorldHint'] ?? null;
70: Assert::that($openWorldHint)->nullOr()->isBool('"annotations.openWorldHint" must be a bool or null, {type} given.');
71:
72: return new self(
73: title: $title,
74: readOnlyHint: $readOnlyHint,
75: destructiveHint: $destructiveHint,
76: idempotentHint: $idempotentHint,
77: openWorldHint: $openWorldHint,
78: );
79: }
80:
81: #[\Override]
82: public function toArray(): array
83: {
84: $data = [];
85:
86: if (null !== $this->title) {
87: $data['title'] = $this->title;
88: }
89:
90: if (null !== $this->readOnlyHint) {
91: $data['readOnlyHint'] = $this->readOnlyHint;
92: }
93:
94: if (null !== $this->destructiveHint) {
95: $data['destructiveHint'] = $this->destructiveHint;
96: }
97:
98: if (null !== $this->idempotentHint) {
99: $data['idempotentHint'] = $this->idempotentHint;
100: }
101:
102: if (null !== $this->openWorldHint) {
103: $data['openWorldHint'] = $this->openWorldHint;
104: }
105:
106: return $data;
107: }
108:
109: #[\Override]
110: public function jsonSerialize(): array|\stdClass
111: {
112: $data = $this->toArray();
113:
114: return [] === $data ? new \stdClass() : $data;
115: }
116: }
117: