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