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