1: <?php
2:
3: declare(strict_types=1);
4:
5: /**
6: * This file is part of the Nexus MCP SDK package.
7: *
8: * (c) 2025 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\Schema\Tool;
15:
16: use Nexus\Mcp\Schema\Arrayable;
17:
18: /**
19: * Additional properties describing a Tool to clients.
20: *
21: * NOTE: all properties in `ToolAnnotations` are **hints**. They are not guaranteed to provide a faithful description
22: * of tool behavior (including descriptive properties like title).
23: *
24: * Clients should never make tool use decisions based on `ToolAnnotations` received from untrusted servers.
25: *
26: * @implements Arrayable<array{
27: * title?: non-empty-string,
28: * readonlyHint?: bool,
29: * destructiveHint?: bool,
30: * idempotentHint?: bool,
31: * openWorldHint?: bool,
32: * }>
33: */
34: final readonly class ToolAnnotations implements \JsonSerializable, Arrayable
35: {
36: /**
37: * @param null|non-empty-string $title A human-readable title for the tool.
38: * @param null|bool $readOnlyHint If true, the tool does not modify its environment.
39: * @param null|bool $destructiveHint If true, the tool may perform destructive updates to its environment.
40: * If false, the tool performs only additive updates.
41: * @param null|bool $idempotentHint If true, calling the tool repeatedly with the same arguments will have no additional
42: * effect on the its environment.
43: * @param null|bool $openWorldHint If true, this tool may interact with an “open world” of external entities. If false,
44: * the tool’s domain of interaction is closed. For example, the world of a web search
45: * tool is open, whereas that of a memory tool is not.
46: */
47: public function __construct(
48: public ?string $title = null,
49: public ?bool $readOnlyHint = null,
50: public ?bool $destructiveHint = null,
51: public ?bool $idempotentHint = null,
52: public ?bool $openWorldHint = null,
53: ) {
54: if (false !== $this->readOnlyHint && (\is_bool($this->destructiveHint) || \is_bool($this->idempotentHint))) {
55: throw new \LogicException('If readOnlyHint is set and is not false, setting destructiveHint or idempotentHint is useless.');
56: }
57: }
58:
59: #[\Override]
60: public function toArray(): array
61: {
62: return array_filter([
63: 'title' => $this->title,
64: 'readOnlyHint' => $this->readOnlyHint,
65: 'destructiveHint' => $this->destructiveHint,
66: 'idempotentHint' => $this->idempotentHint,
67: 'openWorldHint' => $this->openWorldHint,
68: ], static fn(mixed $value): bool => null !== $value);
69: }
70:
71: /**
72: * @return template-type<self, Arrayable, 'T'>
73: */
74: #[\Override]
75: public function jsonSerialize(): array
76: {
77: return $this->toArray();
78: }
79: }
80: