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