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\RequestParams;
15:
16: use Nexus\Assert\Assert;
17: use Nexus\Mcp\Core\JsonRpc\MessageDiscriminator;
18: use Nexus\Mcp\Core\Schema\MetaObject;
19: use Nexus\Mcp\Core\Schema\MetaObject\RequestMetaObject;
20: use Nexus\Mcp\Core\Schema\Prompt\PromptReference;
21: use Nexus\Mcp\Core\Schema\RequestParams;
22: use Nexus\Mcp\Core\Schema\Resource\ResourceTemplateReference;
23:
24: /**
25: * Parameters for a `completion/complete` request.
26: *
27: * @extends RequestParams<array{
28: * _meta: template-type<RequestMetaObject, MetaObject, 'T'>,
29: * ref: array<string, mixed>,
30: * argument: array{name: string, value: string},
31: * context?: array{arguments?: array<array-key, string>},
32: * }>
33: *
34: * @see https://modelcontextprotocol.io/specification/2026-07-28/schema#completerequestparams
35: */
36: final readonly class CompleteRequestParams extends RequestParams
37: {
38: /**
39: * @var array{name: string, value: string}
40: */
41: public array $argument;
42:
43: /**
44: * @var null|array{arguments: non-empty-array<array-key, string>}
45: */
46: public ?array $context;
47:
48: /**
49: * @param array{name: string, value: string} $argument
50: * @param null|array{arguments?: array<array-key, string>} $context
51: */
52: public function __construct(
53: public PromptReference|ResourceTemplateReference $ref,
54: array $argument,
55: RequestMetaObject $meta,
56: ?array $context = null,
57: ) {
58: Assert::that($argument['name'])->isString('"params.argument.name" must be a string, {type} given.');
59: Assert::that($argument['value'])->isString('"params.argument.value" must be a string, {type} given.');
60:
61: if (null !== $context && \array_key_exists('arguments', $context)) {
62: Assert::that($context['arguments'])
63: ->isArray('"params.context.arguments" must be an object, {type} given.')
64: ->values()->isString('each "params.context.arguments" must be a string, {type} given.')
65: ;
66: }
67:
68: $this->argument = ['name' => $argument['name'], 'value' => $argument['value']];
69: $arguments = $context['arguments'] ?? [];
70: $this->context = [] === $arguments ? null : ['arguments' => $arguments];
71:
72: parent::__construct(meta: $meta);
73: }
74:
75: #[\Override]
76: public static function fromArray(array $data): static
77: {
78: Assert::that($data)->hasOffset('ref', '"params" is missing the required "ref" key.');
79: Assert::that($data['ref'])
80: ->isArray('"params.ref" must be an object, {type} given.')
81: ->isMap('"params.ref" must be a string-keyed object.')
82: ;
83:
84: Assert::that($data)->hasOffset('argument', '"params" is missing the required "argument" key.');
85: Assert::that($data['argument'])
86: ->isArray('"params.argument" must be an object, {type} given.')
87: ->isMap('"params.argument" must be a string-keyed object.')
88: ;
89: Assert::that($data['argument'])->hasOffset('name', '"params.argument" is missing the required "name" key.');
90: Assert::that($data['argument']['name'])->isString('"params.argument.name" must be a string, {type} given.');
91: Assert::that($data['argument'])->hasOffset('value', '"params.argument" is missing the required "value" key.');
92: Assert::that($data['argument']['value'])->isString('"params.argument.value" must be a string, {type} given.');
93:
94: $context = null;
95:
96: if (\array_key_exists('context', $data)) {
97: Assert::that($data['context'])
98: ->isArray('"params.context" must be an object, {type} given.')
99: ->isMap('"params.context" must be a string-keyed object.')
100: ;
101: $context = [];
102:
103: if (\array_key_exists('arguments', $data['context'])) {
104: Assert::that($data['context']['arguments'])
105: ->isArray('"params.context.arguments" must be an object, {type} given.')
106: ->values()->isString('each "params.context.arguments" must be a string, {type} given.')
107: ;
108: $context['arguments'] = $data['context']['arguments'];
109: }
110: }
111:
112: Assert::that($data)->hasOffset('_meta', '"params" is missing the required "_meta" key.');
113: Assert::that($data['_meta'])
114: ->isArray('"params._meta" must be an object, {type} given.')
115: ->not()->isNonEmptyList('"params._meta" must be a string-keyed object.')
116: ;
117: $meta = RequestMetaObject::fromArray($data['_meta']);
118:
119: return new self(
120: ref: self::dispatchRef($data['ref']),
121: argument: ['name' => $data['argument']['name'], 'value' => $data['argument']['value']],
122: meta: $meta,
123: context: $context,
124: );
125: }
126:
127: #[\Override]
128: public function toArray(): array
129: {
130: $data = [
131: '_meta' => $this->meta->toArray(),
132: 'ref' => $this->ref->toArray(),
133: 'argument' => $this->argument,
134: ];
135:
136: if (null !== $this->context) {
137: $data['context'] = $this->context;
138: }
139:
140: return $data;
141: }
142:
143: #[\Override]
144: public function jsonSerialize(): array
145: {
146: $data = parent::jsonSerialize();
147: $arguments = $this->context['arguments'] ?? [];
148:
149: if ([] !== $arguments && array_is_list($arguments)) {
150: $data['context'] = ['arguments' => (object) $arguments];
151: }
152:
153: return $data;
154: }
155:
156: /**
157: * @param array<string, mixed> $data
158: *
159: * @throws \InvalidArgumentException
160: */
161: private static function dispatchRef(array $data): PromptReference|ResourceTemplateReference
162: {
163: $type = MessageDiscriminator::readType($data, '"params.ref"');
164:
165: return match ($type) {
166: PromptReference::TYPE => PromptReference::fromArray($data),
167: ResourceTemplateReference::TYPE => ResourceTemplateReference::fromArray($data),
168: default => throw MessageDiscriminator::buildUnknownTypeError(
169: '"params.ref"',
170: [PromptReference::TYPE, ResourceTemplateReference::TYPE],
171: $type,
172: ),
173: };
174: }
175: }
176: