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\Extension\Skills\Schema\Result;
15:
16: use Nexus\Assert\Assert;
17: use Nexus\Mcp\Core\Schema\Arrayable;
18: use Nexus\Mcp\Core\Schema\Cursor;
19: use Nexus\Mcp\Core\Schema\Enum\ResultType;
20: use Nexus\Mcp\Core\Schema\MetaObject;
21: use Nexus\Mcp\Core\Schema\MetaObject\GenericResultMetaObject;
22: use Nexus\Mcp\Core\Schema\MetaObject\ResultMetaObject;
23: use Nexus\Mcp\Core\Schema\Resource\Resource;
24: use Nexus\Mcp\Core\Schema\Result;
25: use Nexus\Mcp\Core\Schema\Result\ServerResult;
26:
27: /**
28: * The result returned by the server for a `resources/directory/read` request.
29: *
30: * @extends Result<array{
31: * _meta?: template-type<ResultMetaObject, MetaObject, 'T'>,
32: * resultType: non-empty-string,
33: * resources: list<template-type<Resource, Arrayable, 'T'>>,
34: * nextCursor?: non-empty-string,
35: * }>
36: *
37: * @see https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/seps/2640-skills-extension.md
38: */
39: final readonly class ReadResourceDirectoryResult extends Result implements ServerResult
40: {
41: /**
42: * @param list<Resource> $resources The directory's direct children
43: */
44: public function __construct(
45: public array $resources,
46: public ?Cursor $nextCursor = null,
47: ResultMetaObject $meta = new GenericResultMetaObject(),
48: ) {
49: Assert::that($resources)
50: ->isList('"result.resources" must be a list, non-list array given.')
51: ->values()->isInstanceOf(Resource::class)
52: ;
53:
54: parent::__construct(meta: $meta);
55: }
56:
57: #[\Override]
58: public static function fromArray(array $data): static
59: {
60: Assert::that($data)->hasOffset('resources', '"result" is missing the required "resources" key.');
61: Assert::that($data['resources'])
62: ->isList('"result.resources" must be a list, {type} given.')
63: ->values()
64: ->isArray('each "result.resources" entry must be an object, {type} given.')
65: ->isMap('each "result.resources" entry must be a string-keyed object.')
66: ;
67: $resources = array_map(Resource::fromArray(...), $data['resources']);
68:
69: $nextCursor = null;
70:
71: if (\array_key_exists('nextCursor', $data)) {
72: $raw = $data['nextCursor'];
73: Assert::that($raw)->isNonEmptyString('"result.nextCursor" must be a non-empty string, {type} given.');
74: $nextCursor = new Cursor(cursor: $raw);
75: }
76:
77: $meta = new GenericResultMetaObject();
78:
79: if (\array_key_exists('_meta', $data)) {
80: Assert::that($data['_meta'])
81: ->isArray('"result._meta" must be an object, {type} given.')
82: ->not()->isNonEmptyList('"result._meta" must be a string-keyed object.')
83: ;
84: $meta = GenericResultMetaObject::fromArray($data['_meta']);
85: }
86:
87: return new self(resources: $resources, nextCursor: $nextCursor, meta: $meta);
88: }
89:
90: #[\Override]
91: public function toArray(): array
92: {
93: $data = [];
94: $meta = $this->meta->toArray();
95:
96: if ([] !== $meta) {
97: $data['_meta'] = $meta;
98: }
99:
100: $data['resultType'] = self::getResultType();
101: $data['resources'] = array_map(static fn(Resource $resource): array => $resource->toArray(), $this->resources);
102:
103: if (null !== $this->nextCursor) {
104: $data['nextCursor'] = $this->nextCursor->cursor;
105: }
106:
107: return $data;
108: }
109:
110: #[\Override]
111: public function rebuildWithMeta(ResultMetaObject $meta): static
112: {
113: return new self(resources: $this->resources, nextCursor: $this->nextCursor, meta: $meta);
114: }
115:
116: #[\Override]
117: protected function getResultType(): string
118: {
119: return ResultType::Complete->value;
120: }
121: }
122: