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