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/2025-11-25/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, $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: /**
86: * @param array<string, mixed> $data
87: */
88: #[\Override]
89: public static function fromArray(array $data): static
90: {
91: Assert::that($data)->hasOffset('name', 'prompt missing the required "name" key.');
92: $name = $data['name'];
93: Assert::that($name)->isString('prompt "name" must be a string, {type} given.');
94:
95: $title = $data['title'] ?? null;
96: Assert::that($title)->nullOr()->isString('prompt "title" must be a string or null, {type} given.');
97:
98: $description = $data['description'] ?? null;
99: Assert::that($description)->nullOr()->isString('prompt "description" must be a string or null, {type} given.');
100:
101: $arguments = null;
102:
103: if (isset($data['arguments'])) {
104: Assert::that($data['arguments'])
105: ->isList('prompt "arguments" must be a list, {type} given.')
106: ->values()
107: ->isArray('each prompt "arguments" must be an object, {type} given.')
108: ->isMap('each prompt "arguments" must be a string-keyed object.')
109: ;
110: $arguments = array_map(PromptArgument::fromArray(...), $data['arguments']);
111: }
112:
113: $icons = null;
114:
115: if (isset($data['icons'])) {
116: Assert::that($data['icons'])
117: ->isList('prompt "icons" must be a list, {type} given.')
118: ->values()
119: ->isArray('each prompt "icons" must be an object, {type} given.')
120: ->isMap('each prompt "icons" must be a string-keyed object.')
121: ;
122: $icons = array_map(Icon::fromArray(...), $data['icons']);
123: }
124:
125: $meta = new MetaObject();
126:
127: if (\array_key_exists('_meta', $data)) {
128: Assert::that($data['_meta'])
129: ->isArray('prompt "_meta" must be an object, {type} given.')
130: ->isMap('prompt "_meta" must be a string-keyed object.')
131: ;
132: $meta = MetaObject::fromArray($data['_meta']);
133: }
134:
135: return new self($name, $title, $description, $arguments, $icons, $meta);
136: }
137:
138: #[\Override]
139: public function toArray(): array
140: {
141: $data = ['name' => $this->name];
142:
143: if (null !== $this->title) {
144: $data['title'] = $this->title;
145: }
146:
147: if (null !== $this->description) {
148: $data['description'] = $this->description;
149: }
150:
151: if (null !== $this->arguments) {
152: $data['arguments'] = array_map(
153: static fn(PromptArgument $argument): array => $argument->toArray(),
154: $this->arguments,
155: );
156: }
157:
158: if (null !== $this->icons) {
159: $data['icons'] = array_map(
160: static fn(Icon $icon): array => $icon->toArray(),
161: $this->icons,
162: );
163: }
164:
165: $meta = $this->meta->toArray();
166:
167: if ([] !== $meta) {
168: $data['_meta'] = $meta;
169: }
170:
171: return $data;
172: }
173:
174: #[\Override]
175: public function jsonSerialize(): array
176: {
177: return $this->toArray();
178: }
179: }
180: