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\Result;
15:
16: use Nexus\Assert\Assert;
17: use Nexus\Mcp\Core\Schema\Enum\ElicitAction;
18: use Nexus\Mcp\Core\Schema\MetaObject;
19: use Nexus\Mcp\Core\Schema\Result;
20: use Nexus\Mcp\Core\Validation\EnumValueValidator;
21:
22: /**
23: * The client's response to an elicitation request.
24: *
25: * @see https://modelcontextprotocol.io/specification/2025-11-25/schema#elicitresult
26: */
27: final readonly class ElicitResult extends Result implements ClientResult
28: {
29: /**
30: * @var null|array<non-empty-string, bool|int|list<string>|string>
31: */
32: public ?array $content;
33:
34: /**
35: * @param null|array<string, bool|int|list<string>|string> $content
36: */
37: public function __construct(
38: public ElicitAction $action,
39: ?array $content = null,
40: MetaObject $meta = new MetaObject(),
41: ) {
42: if (null !== $content) {
43: Assert::that($content)
44: ->isMap('"result.content" must be a string-keyed map.')
45: ->keys()->isNonEmptyString('each "result.content" key must be a non-empty string.')
46: ;
47:
48: foreach ($content as $key => $value) {
49: self::validateValue($key, $value);
50: }
51: }
52:
53: $this->content = $content;
54:
55: parent::__construct($meta);
56: }
57:
58: /**
59: * @param array<string, mixed> $data
60: */
61: #[\Override]
62: public static function fromArray(array $data): static
63: {
64: Assert::that($data)->hasOffset('action', '"result" missing the required "action" key.');
65: $action = EnumValueValidator::parse(ElicitAction::class, $data['action'], '"result.action"');
66:
67: $content = null;
68:
69: if (\array_key_exists('content', $data)) {
70: Assert::that($data['content'])
71: ->isArray('"result.content" must be an object, {type} given.')
72: ->isMap('"result.content" must be a string-keyed object.')
73: ;
74:
75: foreach ($data['content'] as $key => $value) {
76: self::validateValue('content entry '.$key, $value);
77: }
78:
79: /** @var array<string, bool|int|list<string>|string> $content */
80: $content = $data['content'];
81: }
82:
83: $meta = new MetaObject();
84:
85: if (\array_key_exists('_meta', $data)) {
86: Assert::that($data['_meta'])
87: ->isArray('"result._meta" must be an object, {type} given.')
88: ->isMap('"result._meta" must be a string-keyed object.')
89: ;
90: $meta = MetaObject::fromArray($data['_meta']);
91: }
92:
93: return new self($action, $content, $meta);
94: }
95:
96: #[\Override]
97: public function toArray(): array
98: {
99: $data = [
100: ...parent::toArray(),
101: 'action' => $this->action->value,
102: ];
103:
104: if (null !== $this->content) {
105: $data['content'] = $this->content;
106: }
107:
108: return $data;
109: }
110:
111: #[\Override]
112: public function jsonSerialize(): array
113: {
114: return $this->toArray();
115: }
116:
117: private static function validateValue(string $context, mixed $value): void
118: {
119: if (\is_string($value) || \is_int($value) || \is_bool($value)) {
120: return;
121: }
122:
123: Assert::that($value)
124: ->isList(\sprintf('"result" "%s" must be a string, int, bool, or list of strings, non-list array given.', $context))
125: ->values()->isString(\sprintf('each "result" "%s" list entry must be a string, {type} given.', $context))
126: ;
127: }
128: }
129: