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