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