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\Error;
15:
16: use Nexus\Assert\Assert;
17: use Nexus\Mcp\Core\Schema\Arrayable;
18: use Nexus\Mcp\Core\Schema\ClientCapabilities;
19: use Nexus\Mcp\Core\Schema\Enum\ProtocolErrorCode;
20: use Nexus\Mcp\Core\Schema\Error;
21:
22: /**
23: * Returned when processing a request requires a capability the client did not declare in
24: * `clientCapabilities`. For HTTP, the response status code MUST be `400 Bad Request`.
25: *
26: * @extends Error<array{
27: * code: -32021,
28: * message: non-empty-string,
29: * data: array{requiredCapabilities: template-type<ClientCapabilities, Arrayable, 'T'>},
30: * }>
31: *
32: * @see https://modelcontextprotocol.io/specification/2026-07-28/schema#missingrequiredclientcapabilityerror
33: */
34: final readonly class MissingRequiredClientCapabilityError extends Error
35: {
36: public const string DEFAULT_MESSAGE = 'Missing required client capability';
37:
38: /**
39: * @param non-empty-string $message
40: */
41: public function __construct(
42: public ClientCapabilities $requiredCapabilities,
43: string $message = self::DEFAULT_MESSAGE,
44: ) {
45: parent::__construct(
46: code: ProtocolErrorCode::MissingRequiredClientCapability,
47: message: $message,
48: data: ['requiredCapabilities' => $requiredCapabilities->toArray()],
49: );
50: }
51:
52: #[\Override]
53: public static function fromArray(array $data): static
54: {
55: $message = $data['message'] ?? self::DEFAULT_MESSAGE;
56: Assert::that($message)->isNonEmptyString('error "message" must be a non-empty string, {type} given.');
57:
58: $payload = $data['data'] ?? null;
59: Assert::that($payload)
60: ->isArray('error "data" must be an object, {type} given.')
61: ->isMap('error "data" must be a string-keyed object.')
62: ;
63:
64: Assert::that($payload)->hasOffset('requiredCapabilities', 'error "data" is missing the required "requiredCapabilities" key.');
65: Assert::that($payload['requiredCapabilities'])
66: ->isArray('error "data.requiredCapabilities" must be an object, {type} given.')
67: ->isMap('error "data.requiredCapabilities" must be a string-keyed object.')
68: ;
69:
70: return new self(
71: requiredCapabilities: ClientCapabilities::fromArray($payload['requiredCapabilities']),
72: message: $message,
73: );
74: }
75:
76: #[\Override]
77: public function toArray(): array
78: {
79: return [
80: 'code' => ProtocolErrorCode::MissingRequiredClientCapability->value,
81: 'message' => $this->message,
82: 'data' => ['requiredCapabilities' => $this->requiredCapabilities->toArray()],
83: ];
84: }
85:
86: #[\Override]
87: public function jsonSerialize(): array
88: {
89: return [
90: 'code' => ProtocolErrorCode::MissingRequiredClientCapability->value,
91: 'message' => $this->message,
92: 'data' => ['requiredCapabilities' => $this->requiredCapabilities->jsonSerialize()],
93: ];
94: }
95: }
96: