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\Result;
15:
16: use Nexus\Assert\Assert;
17: use Nexus\Mcp\Core\JsonRpc\ResourceContentsDispatcher;
18: use Nexus\Mcp\Core\Schema\Arrayable;
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\Resource\BlobResourceContents;
25: use Nexus\Mcp\Core\Schema\Resource\ResourceContents;
26: use Nexus\Mcp\Core\Schema\Resource\TextResourceContents;
27: use Nexus\Mcp\Core\Validation\EnumValueValidator;
28:
29: /**
30: * The result returned by the server for a `resources/read` request.
31: *
32: * @extends CacheableResult<array{
33: * _meta?: template-type<ResultMetaObject, MetaObject, 'T'>,
34: * resultType: non-empty-string,
35: * contents: list<template-type<BlobResourceContents|TextResourceContents, Arrayable, 'T'>>,
36: * ttlMs: int,
37: * cacheScope: value-of<CacheScope>,
38: * }>
39: *
40: * @see https://modelcontextprotocol.io/specification/2026-07-28/schema#readresourceresult
41: */
42: final readonly class ReadResourceResult extends CacheableResult implements ServerResult
43: {
44: /**
45: * @var list<BlobResourceContents|TextResourceContents>
46: */
47: public array $contents;
48:
49: /**
50: * @param list<BlobResourceContents|TextResourceContents> $contents
51: */
52: public function __construct(
53: array $contents,
54: int $ttlMs,
55: CacheScope $cacheScope,
56: ResultMetaObject $meta = new GenericResultMetaObject(),
57: ) {
58: Assert::that($contents)
59: ->isList('"result.contents" must be a list, non-list array given.')
60: ->values()->isInstanceOf(ResourceContents::class)
61: ;
62:
63: $this->contents = $contents;
64:
65: parent::__construct(ttlMs: $ttlMs, cacheScope: $cacheScope, meta: $meta);
66: }
67:
68: #[\Override]
69: public static function fromArray(array $data): static
70: {
71: Assert::that($data)->hasOffset('contents', '"result" is missing the required "contents" key.');
72: Assert::that($data['contents'])
73: ->isList('"result.contents" must be a list, {type} given.')
74: ->values()
75: ->isArray('each "result.contents" must be an object, {type} given.')
76: ->isMap('each "result.contents" must be a string-keyed object.')
77: ;
78: $contents = array_map(
79: static fn(array $entry): BlobResourceContents|TextResourceContents => ResourceContentsDispatcher::fromArray($entry, 'ReadResourceResult contents'),
80: $data['contents'],
81: );
82:
83: Assert::that($data)->hasOffset('ttlMs', '"result" is missing the required "ttlMs" key.');
84: $ttlMs = $data['ttlMs'];
85: Assert::that($ttlMs)->isInt('"result.ttlMs" must be an integer, {type} given.');
86:
87: Assert::that($data)->hasOffset('cacheScope', '"result" is missing the required "cacheScope" key.');
88: $cacheScope = EnumValueValidator::parse(CacheScope::class, $data['cacheScope'], '"result.cacheScope"');
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: ->isMap('"result._meta" must be a string-keyed object.')
96: ;
97: $meta = GenericResultMetaObject::fromArray($data['_meta']);
98: }
99:
100: return new self(contents: $contents, ttlMs: $ttlMs, cacheScope: $cacheScope, 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['contents'] = array_map(static fn(ResourceContents $entry): array => $entry->toArray(), $this->contents);
115: $data['ttlMs'] = $this->ttlMs;
116: $data['cacheScope'] = $this->cacheScope->value;
117:
118: return $data;
119: }
120:
121: #[\Override]
122: public function rebuildWithMeta(ResultMetaObject $meta): static
123: {
124: return new self(
125: contents: $this->contents,
126: ttlMs: $this->ttlMs,
127: cacheScope: $this->cacheScope,
128: meta: $meta,
129: );
130: }
131:
132: #[\Override]
133: protected function getResultType(): string
134: {
135: return ResultType::Complete->value;
136: }
137: }
138: