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\Prompt;
15:
16: use Nexus\Assert\Assert;
17: use Nexus\Mcp\Core\Schema\Arrayable;
18: use Nexus\Mcp\Core\Schema\BaseMetadata;
19: use Nexus\Mcp\Core\Schema\Icon;
20: use Nexus\Mcp\Core\Schema\Icons;
21: use Nexus\Mcp\Core\Schema\MetaObject;
22: use Nexus\Mcp\Core\Validation\IdentifierNameValidator;
23:
24: /**
25: * A prompt or prompt template that the server offers.
26: *
27: * @implements Arrayable<array{
28: * name: non-empty-string,
29: * title?: non-empty-string,
30: * description?: non-empty-string,
31: * arguments?: list<template-type<PromptArgument, Arrayable, 'T'>>,
32: * icons?: list<template-type<Icon, Arrayable, 'T'>>,
33: * _meta?: template-type<MetaObject, Arrayable, 'T'>,
34: * }>
35: *
36: * @see https://modelcontextprotocol.io/specification/draft/schema#prompt
37: */
38: final readonly class Prompt extends BaseMetadata implements Arrayable, Icons
39: {
40: /**
41: * @var null|non-empty-string
42: */
43: public ?string $description;
44:
45: /**
46: * @var null|list<PromptArgument>
47: */
48: public ?array $arguments;
49:
50: /**
51: * @var null|list<Icon>
52: */
53: public ?array $icons;
54:
55: /**
56: * @param null|list<PromptArgument> $arguments
57: * @param null|list<Icon> $icons
58: */
59: public function __construct(
60: string $name,
61: ?string $title = null,
62: ?string $description = null,
63: ?array $arguments = null,
64: ?array $icons = null,
65: public MetaObject $meta = new MetaObject(),
66: ) {
67: parent::__construct(name: $name, title: $title);
68:
69: IdentifierNameValidator::validate($name, 'prompt "name"');
70: Assert::that($description)->nullOr()->isNonEmptyString('prompt "description" must be a non-empty string or null.');
71:
72: if (null !== $arguments) {
73: Assert::that($arguments)->values()->isInstanceOf(PromptArgument::class);
74: }
75:
76: if (null !== $icons) {
77: Assert::that($icons)->values()->isInstanceOf(Icon::class);
78: }
79:
80: $this->description = $description;
81: $this->arguments = $arguments;
82: $this->icons = $icons;
83: }
84:
85: #[\Override]
86: public static function fromArray(array $data): static
87: {
88: Assert::that($data)->hasOffset('name', 'prompt is missing the required "name" key.');
89: $name = $data['name'];
90: Assert::that($name)->isString('prompt "name" must be a string, {type} given.');
91:
92: $title = $data['title'] ?? null;
93: Assert::that($title)->nullOr()->isString('prompt "title" must be a string or null, {type} given.');
94:
95: $description = $data['description'] ?? null;
96: Assert::that($description)->nullOr()->isString('prompt "description" must be a string or null, {type} given.');
97:
98: $arguments = null;
99:
100: if (isset($data['arguments'])) {
101: Assert::that($data['arguments'])
102: ->isList('prompt "arguments" must be a list, {type} given.')
103: ->values()
104: ->isArray('each prompt "arguments" must be an object, {type} given.')
105: ->isMap('each prompt "arguments" must be a string-keyed object.')
106: ;
107: $arguments = array_map(PromptArgument::fromArray(...), $data['arguments']);
108: }
109:
110: $icons = null;
111:
112: if (isset($data['icons'])) {
113: Assert::that($data['icons'])
114: ->isList('prompt "icons" must be a list, {type} given.')
115: ->values()
116: ->isArray('each prompt "icons" must be an object, {type} given.')
117: ->isMap('each prompt "icons" must be a string-keyed object.')
118: ;
119: $icons = array_map(Icon::fromArray(...), $data['icons']);
120: }
121:
122: $meta = new MetaObject();
123:
124: if (\array_key_exists('_meta', $data)) {
125: Assert::that($data['_meta'])
126: ->isArray('prompt "_meta" must be an object, {type} given.')
127: ->isMap('prompt "_meta" must be a string-keyed object.')
128: ;
129: $meta = MetaObject::fromArray($data['_meta']);
130: }
131:
132: return new self(
133: name: $name,
134: title: $title,
135: description: $description,
136: arguments: $arguments,
137: icons: $icons,
138: meta: $meta,
139: );
140: }
141:
142: #[\Override]
143: public function toArray(): array
144: {
145: $data = ['name' => $this->name];
146:
147: if (null !== $this->title) {
148: $data['title'] = $this->title;
149: }
150:
151: if (null !== $this->description) {
152: $data['description'] = $this->description;
153: }
154:
155: if (null !== $this->arguments) {
156: $data['arguments'] = array_map(
157: static fn(PromptArgument $argument): array => $argument->toArray(),
158: $this->arguments,
159: );
160: }
161:
162: if (null !== $this->icons) {
163: $data['icons'] = array_map(
164: static fn(Icon $icon): array => $icon->toArray(),
165: $this->icons,
166: );
167: }
168:
169: $meta = $this->meta->toArray();
170:
171: if ([] !== $meta) {
172: $data['_meta'] = $meta;
173: }
174:
175: return $data;
176: }
177:
178: #[\Override]
179: public function jsonSerialize(): array
180: {
181: return $this->toArray();
182: }
183: }
184: