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\Schema\Elicitation\ElicitResult;
18: use Nexus\Mcp\Core\Schema\MetaObject;
19: use Nexus\Mcp\Core\Schema\MetaObject\RequestMetaObject;
20: use Nexus\Mcp\Core\Schema\Result\InputResponse;
21:
22: /**
23: * Parameters for a `tools/call` request.
24: *
25: * @extends InputResponseRequestParams<array{
26: * _meta: template-type<RequestMetaObject, MetaObject, 'T'>,
27: * name: non-empty-string,
28: * arguments?: array<array-key, mixed>,
29: * inputResponses?: array<int|non-empty-string, array<string, mixed>>,
30: * requestState?: string,
31: * }>
32: *
33: * @see https://modelcontextprotocol.io/specification/2026-07-28/schema#calltoolrequestparams
34: */
35: final readonly class CallToolRequestParams extends InputResponseRequestParams
36: {
37: /**
38: * @param non-empty-string $name
39: * @param null|array<array-key, mixed> $arguments
40: * @param null|array<int|non-empty-string, InputResponse> $inputResponses
41: */
42: public function __construct(
43: public string $name,
44: RequestMetaObject $meta,
45: public ?array $arguments = null,
46: ?array $inputResponses = null,
47: ?string $requestState = null,
48: ) {
49: Assert::that($name)->isNonEmptyString('"params.name" must be a non-empty string, {type} given.');
50:
51: parent::__construct(inputResponses: $inputResponses, requestState: $requestState, meta: $meta);
52: }
53:
54: #[\Override]
55: public static function fromArray(array $data): static
56: {
57: Assert::that($data)->hasOffset('name', '"params" is missing the required "name" key.');
58: $name = $data['name'];
59: Assert::that($name)->isNonEmptyString('"params.name" must be a non-empty string, {type} given.');
60:
61: $arguments = null;
62:
63: if (\array_key_exists('arguments', $data)) {
64: Assert::that($data['arguments'])
65: ->isArray('"params.arguments" must be an object, {type} given.')
66: ;
67: $arguments = $data['arguments'];
68: }
69:
70: $inputResponses = null;
71:
72: if (\array_key_exists('inputResponses', $data)) {
73: Assert::that($data['inputResponses'])
74: ->isArray('"params.inputResponses" must be an object, {type} given.')
75: ->keys()->isIntOrNonEmptyString('each "params.inputResponses" key must be an int or non-empty string.')
76: ;
77: Assert::that($data['inputResponses'])
78: ->values()
79: ->isArray('each "params.inputResponses" entry must be an object, {type} given.')
80: ->isMap('each "params.inputResponses" entry must be a string-keyed object.')
81: ;
82: $inputResponses = array_map(ElicitResult::fromArray(...), $data['inputResponses']);
83: }
84:
85: $requestState = null;
86:
87: if (\array_key_exists('requestState', $data)) {
88: Assert::that($data['requestState'])->isString('"params.requestState" must be a string, {type} given.');
89: $requestState = $data['requestState'];
90: }
91:
92: Assert::that($data)->hasOffset('_meta', '"params" is missing the required "_meta" key.');
93: Assert::that($data['_meta'])
94: ->isArray('"params._meta" must be an object, {type} given.')
95: ->not()->isNonEmptyList('"params._meta" must be a string-keyed object.')
96: ;
97: $meta = RequestMetaObject::fromArray($data['_meta']);
98:
99: return new self(
100: name: $name,
101: meta: $meta,
102: arguments: $arguments,
103: inputResponses: $inputResponses,
104: requestState: $requestState,
105: );
106: }
107:
108: #[\Override]
109: public function toArray(): array
110: {
111: $data = [
112: '_meta' => $this->meta->toArray(),
113: 'name' => $this->name,
114: ];
115:
116: if (null !== $this->arguments && [] !== $this->arguments) {
117: $data['arguments'] = $this->arguments;
118: }
119:
120: if (null !== $this->inputResponses) {
121: $data['inputResponses'] = array_map(
122: static fn(InputResponse $response): array => $response->toArray(),
123: $this->inputResponses,
124: );
125: }
126:
127: if (null !== $this->requestState) {
128: $data['requestState'] = $this->requestState;
129: }
130:
131: return $data;
132: }
133:
134: #[\Override]
135: public function jsonSerialize(): array
136: {
137: $data = parent::jsonSerialize();
138:
139: if (null !== $this->inputResponses) {
140: $data['inputResponses'] = array_map(
141: static fn(InputResponse $response): array|\stdClass => $response->jsonSerialize(),
142: $this->inputResponses,
143: );
144:
145: if (array_is_list($this->inputResponses)) {
146: $data['inputResponses'] = (object) $data['inputResponses'];
147: }
148: }
149:
150: if (null !== $this->arguments && array_is_list($this->arguments) && [] !== $this->arguments) {
151: $data['arguments'] = (object) $this->arguments;
152: }
153:
154: return $data;
155: }
156: }
157: