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:
28: /**
29: * The result returned by the server for a `resources/read` request.
30: *
31: * @extends CacheableResult<array{
32: * _meta?: template-type<ResultMetaObject, MetaObject, 'T'>,
33: * resultType: non-empty-string,
34: * contents: list<template-type<BlobResourceContents|TextResourceContents, Arrayable, 'T'>>,
35: * ttlMs: int,
36: * cacheScope: value-of<CacheScope>,
37: * }>
38: *
39: * @see https://modelcontextprotocol.io/specification/2026-07-28/schema#readresourceresult
40: */
41: final readonly class ReadResourceResult extends CacheableResult implements ServerResult
42: {
43: /**
44: * @param list<BlobResourceContents|TextResourceContents> $contents
45: */
46: public function __construct(
47: public array $contents,
48: int $ttlMs,
49: CacheScope $cacheScope,
50: ResultMetaObject $meta = new GenericResultMetaObject(),
51: ) {
52: Assert::that($contents)
53: ->isList('"result.contents" must be a list, non-list array given.')
54: ->values()->isInstanceOf(ResourceContents::class)
55: ;
56:
57: parent::__construct(ttlMs: $ttlMs, cacheScope: $cacheScope, meta: $meta);
58: }
59:
60: #[\Override]
61: public static function fromArray(array $data): static
62: {
63: Assert::that($data)->hasOffset('contents', '"result" is missing the required "contents" key.');
64: Assert::that($data['contents'])
65: ->isList('"result.contents" must be a list, {type} given.')
66: ->values()
67: ->isArray('each "result.contents" must be an object, {type} given.')
68: ->isMap('each "result.contents" must be a string-keyed object.')
69: ;
70: $contents = array_map(
71: static fn(array $entry): BlobResourceContents|TextResourceContents => ResourceContentsDispatcher::fromArray($entry, 'ReadResourceResult contents'),
72: $data['contents'],
73: );
74:
75: Assert::that($data)->hasOffset('ttlMs', '"result" is missing the required "ttlMs" key.');
76: $ttlMs = $data['ttlMs'];
77: Assert::that($ttlMs)->isInt('"result.ttlMs" must be an integer, {type} given.');
78:
79: Assert::that($data)->hasOffset('cacheScope', '"result" is missing the required "cacheScope" key.');
80: Assert::that($data['cacheScope'])->isOneOf(array_column(CacheScope::cases(), 'value'), '"result.cacheScope" must be one of {choices}, {value} given.');
81: $cacheScope = CacheScope::from($data['cacheScope']);
82:
83: $meta = new GenericResultMetaObject();
84:
85: if (\array_key_exists('_meta', $data)) {
86: Assert::that($data['_meta'])
87: ->isArray('"result._meta" must be an object, {type} given.')
88: ->not()->isNonEmptyList('"result._meta" must be a string-keyed object.')
89: ;
90: $meta = GenericResultMetaObject::fromArray($data['_meta']);
91: }
92:
93: return new self(contents: $contents, ttlMs: $ttlMs, cacheScope: $cacheScope, meta: $meta);
94: }
95:
96: #[\Override]
97: public function toArray(): array
98: {
99: $data = [];
100: $meta = $this->meta->toArray();
101:
102: if ([] !== $meta) {
103: $data['_meta'] = $meta;
104: }
105:
106: $data['resultType'] = self::getResultType();
107: $data['contents'] = array_map(static fn(ResourceContents $entry): array => $entry->toArray(), $this->contents);
108: $data['ttlMs'] = $this->ttlMs;
109: $data['cacheScope'] = $this->cacheScope->value;
110:
111: return $data;
112: }
113:
114: #[\Override]
115: public function rebuildWithMeta(ResultMetaObject $meta): static
116: {
117: return new self(
118: contents: $this->contents,
119: ttlMs: $this->ttlMs,
120: cacheScope: $this->cacheScope,
121: meta: $meta,
122: );
123: }
124:
125: #[\Override]
126: protected function getResultType(): string
127: {
128: return ResultType::Complete->value;
129: }
130: }
131: