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