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\ContentBlockDispatcher;
18: use Nexus\Mcp\Core\Schema\ContentBlock;
19: use Nexus\Mcp\Core\Schema\ContentBlock\AudioContent;
20: use Nexus\Mcp\Core\Schema\ContentBlock\EmbeddedResource;
21: use Nexus\Mcp\Core\Schema\ContentBlock\ImageContent;
22: use Nexus\Mcp\Core\Schema\ContentBlock\ResourceLink;
23: use Nexus\Mcp\Core\Schema\ContentBlock\TextContent;
24: use Nexus\Mcp\Core\Schema\MetaObject;
25: use Nexus\Mcp\Core\Schema\Result;
26:
27: /**
28: * The server's response to a tool call.
29: *
30: * @see https://modelcontextprotocol.io/specification/2025-11-25/schema#calltoolresult
31: */
32: final readonly class CallToolResult extends Result implements ServerResult
33: {
34: /**
35: * @var list<AudioContent|EmbeddedResource|ImageContent|ResourceLink|TextContent>
36: */
37: public array $content;
38:
39: /**
40: * @var null|array<string, mixed>
41: */
42: public ?array $structuredContent;
43:
44: /**
45: * @param list<AudioContent|EmbeddedResource|ImageContent|ResourceLink|TextContent> $content
46: * @param null|array<string, mixed> $structuredContent
47: */
48: public function __construct(
49: array $content,
50: ?array $structuredContent = null,
51: public ?bool $isError = null,
52: MetaObject $meta = new MetaObject(),
53: ) {
54: Assert::that($content)
55: ->isList('"result.content" must be a list, non-list array given.')
56: ->values()->isInstanceOf(ContentBlock::class)
57: ;
58:
59: if (null !== $structuredContent) {
60: Assert::that($structuredContent)->isMap('"result.structuredContent" must be a string-keyed map.');
61: }
62:
63: $this->content = $content;
64: $this->structuredContent = $structuredContent;
65:
66: parent::__construct($meta);
67: }
68:
69: #[\Override]
70: public static function fromArray(array $data): static
71: {
72: Assert::that($data)->hasOffset('content', '"result" missing the required "content" key.');
73: Assert::that($data['content'])
74: ->isList('"result.content" must be a list, {type} given.')
75: ->values()
76: ->isArray('each "result.content" must be an object, {type} given.')
77: ->isMap('each "result.content" must be a string-keyed object.')
78: ;
79: $content = array_map(
80: static fn(array $block): AudioContent|EmbeddedResource|ImageContent|ResourceLink|TextContent => ContentBlockDispatcher::fromArray($block, 'CallToolResult content'),
81: $data['content'],
82: );
83:
84: $structuredContent = null;
85:
86: if (\array_key_exists('structuredContent', $data)) {
87: Assert::that($data['structuredContent'])
88: ->isArray('"result.structuredContent" must be an object, {type} given.')
89: ->isMap('"result.structuredContent" must be a string-keyed object.')
90: ;
91: $structuredContent = $data['structuredContent'];
92: }
93:
94: $isError = $data['isError'] ?? null;
95: Assert::that($isError)->nullOr()->isBool('"result.isError" must be a bool or null, {type} given.');
96:
97: $meta = new MetaObject();
98:
99: if (\array_key_exists('_meta', $data)) {
100: Assert::that($data['_meta'])
101: ->isArray('"result._meta" must be an object, {type} given.')
102: ->isMap('"result._meta" must be a string-keyed object.')
103: ;
104: $meta = MetaObject::fromArray($data['_meta']);
105: }
106:
107: return new self($content, $structuredContent, $isError, $meta);
108: }
109:
110: #[\Override]
111: public function toArray(): array
112: {
113: $data = [
114: ...parent::toArray(),
115: 'content' => array_map(
116: static fn(AudioContent|EmbeddedResource|ImageContent|ResourceLink|TextContent $block): array => $block->toArray(),
117: $this->content,
118: ),
119: ];
120:
121: if (null !== $this->structuredContent) {
122: $data['structuredContent'] = $this->structuredContent;
123: }
124:
125: if (null !== $this->isError) {
126: $data['isError'] = $this->isError;
127: }
128:
129: return $data;
130: }
131:
132: #[\Override]
133: public function jsonSerialize(): array
134: {
135: return $this->toArray();
136: }
137: }
138: