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\JsonRpc;
15:
16: use Nexus\Assert\Assert;
17: use Nexus\Mcp\Core\Schema\Arrayable;
18: use Nexus\Mcp\Core\Schema\Enum\ResultType;
19: use Nexus\Mcp\Core\Schema\RequestId;
20: use Nexus\Mcp\Core\Schema\Result;
21:
22: /**
23: * A successful (non-error) response to a request.
24: *
25: * @template-covariant TEnvelope of array<string, mixed> = array<string, mixed>
26: *
27: * @implements Arrayable<TEnvelope>
28: *
29: * @see https://modelcontextprotocol.io/specification/2026-07-28/schema#jsonrpcresultresponse
30: */
31: abstract readonly class JsonRpcResultResponse implements Arrayable, JsonRpcResponse
32: {
33: public function __construct(
34: public RequestId $id,
35: public Result $result,
36: ) {
37: }
38:
39: #[\Override]
40: public function jsonSerialize(): array
41: {
42: return [
43: 'jsonrpc' => self::JSONRPC_VERSION,
44: 'id' => $this->id->id,
45: 'result' => $this->result->jsonSerialize(),
46: ];
47: }
48:
49: /**
50: * @param array<string, mixed> $data
51: *
52: * @throws \InvalidArgumentException
53: */
54: protected static function parseId(array $data): RequestId
55: {
56: Assert::that($data)->hasOffset('id', 'missing the required "id" key.');
57: $id = $data['id'];
58: Assert::that($id)->isIntOrNonEmptyString('"id" must be an int or non-empty string, {type} given.');
59:
60: return new RequestId(id: $id);
61: }
62:
63: /**
64: * @param array<string, mixed> $data
65: *
66: * @return array<string, mixed>
67: *
68: * @throws \InvalidArgumentException
69: */
70: protected static function parseResult(array $data): array
71: {
72: Assert::that($data)->hasOffset('result', 'missing the required "result" key.');
73: $result = $data['result'];
74: Assert::that($result)
75: ->isArray('"result" must be an object, {type} given.')
76: ->isMap('"result" must be a string-keyed object.')
77: ;
78:
79: return $result;
80: }
81:
82: /**
83: * @param array<string, mixed> $payload
84: */
85: protected static function isInputRequired(array $payload): bool
86: {
87: return ($payload['resultType'] ?? null) === ResultType::InputRequired->value;
88: }
89:
90: /**
91: * @param array<string, mixed> $payload
92: *
93: * @throws \InvalidArgumentException
94: */
95: protected static function rejectInputRequired(array $payload): void
96: {
97: if (self::isInputRequired($payload)) {
98: throw new \InvalidArgumentException('"result" returned "input_required" for a method that does not support it.');
99: }
100: }
101: }
102: