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