|
3 | 3 | namespace OPGG\LaravelMcpServer\Services\ToolService; |
4 | 4 |
|
5 | 5 | use InvalidArgumentException; |
6 | | -use JsonException; |
7 | 6 |
|
8 | 7 | /** |
9 | 8 | * Value object describing a structured tool response. |
10 | 9 | */ |
11 | 10 | final class ToolResponse |
12 | 11 | { |
| 12 | + /** |
| 13 | + * @var array<int, array{type: string, text: string, source?: string}> |
| 14 | + */ |
| 15 | + private array $content; |
| 16 | + |
| 17 | + /** |
| 18 | + * @var array<string, mixed> |
| 19 | + */ |
| 20 | + private array $metadata; |
| 21 | + |
13 | 22 | /** |
14 | 23 | * @param array<int, array{type: string, text: string, source?: string}> $content |
15 | 24 | * @param array<string, mixed> $metadata |
16 | 25 | */ |
17 | | - private function __construct(private array $content, private array $metadata = []) |
| 26 | + private bool $includeContent; |
| 27 | + |
| 28 | + private function __construct(array $content, array $metadata = [], bool $includeContent = true) |
18 | 29 | { |
19 | 30 | if (array_key_exists('content', $metadata)) { |
20 | 31 | throw new InvalidArgumentException('Metadata must not contain a content key.'); |
21 | 32 | } |
22 | 33 |
|
23 | 34 | $this->content = array_values($content); |
| 35 | + $this->metadata = $metadata; |
| 36 | + $this->includeContent = $includeContent && $this->content !== []; |
24 | 37 |
|
25 | 38 | foreach ($this->content as $index => $item) { |
26 | 39 | if (! is_array($item) || ! isset($item['type'], $item['text'])) { |
@@ -60,41 +73,26 @@ public static function text(string $text, string $type = 'text', array $metadata |
60 | 73 | } |
61 | 74 |
|
62 | 75 | /** |
63 | | - * Create a ToolResponse that includes structured content alongside serialised text. |
| 76 | + * Create a ToolResponse that includes structured content alongside optional serialised text. |
64 | 77 | * |
65 | 78 | * @param array<int, array{type: string, text: string, source?: string}>|null $content |
66 | 79 | * @param array<string, mixed> $metadata |
67 | | - * |
68 | | - * @throws JsonException |
69 | 80 | */ |
70 | 81 | public static function structured(array $structuredContent, ?array $content = null, array $metadata = []): self |
71 | 82 | { |
72 | | - $json = json_encode($structuredContent, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE); |
73 | | - |
74 | 83 | $contentItems = $content !== null ? array_values($content) : []; |
75 | 84 |
|
76 | | - $hasSerialisedText = false; |
77 | | - foreach ($contentItems as $item) { |
78 | | - if (isset($item['type'], $item['text']) && $item['type'] === 'text' && $item['text'] === $json) { |
79 | | - $hasSerialisedText = true; |
80 | | - break; |
81 | | - } |
82 | | - } |
83 | | - |
84 | | - if (! $hasSerialisedText) { |
85 | | - $contentItems[] = [ |
86 | | - 'type' => 'text', |
87 | | - 'text' => $json, |
88 | | - ]; |
89 | | - } |
90 | | - |
91 | | - return new self($contentItems, [ |
92 | | - ...$metadata, |
93 | | - // The MCP 2025-06-18 spec encourages servers to mirror structured payloads in the |
94 | | - // `structuredContent` field for reliable client parsing. |
95 | | - // @see https://modelcontextprotocol.io/specification/2025-06-18#structured-content |
96 | | - 'structuredContent' => $structuredContent, |
97 | | - ]); |
| 85 | + return new self( |
| 86 | + $contentItems, |
| 87 | + [ |
| 88 | + ...$metadata, |
| 89 | + // The MCP 2025-06-18 spec encourages servers to mirror structured payloads in the |
| 90 | + // `structuredContent` field for reliable client parsing. |
| 91 | + // @see https://modelcontextprotocol.io/specification/2025-06-18#structured-content |
| 92 | + 'structuredContent' => $structuredContent, |
| 93 | + ], |
| 94 | + $contentItems !== [] |
| 95 | + ); |
98 | 96 | } |
99 | 97 |
|
100 | 98 | /** |
@@ -124,9 +122,14 @@ public function metadata(): array |
124 | 122 | */ |
125 | 123 | public function toArray(): array |
126 | 124 | { |
127 | | - return [ |
| 125 | + $payload = [ |
128 | 126 | ...$this->metadata, |
129 | | - 'content' => $this->content, |
130 | 127 | ]; |
| 128 | + |
| 129 | + if ($this->includeContent) { |
| 130 | + $payload['content'] = $this->content; |
| 131 | + } |
| 132 | + |
| 133 | + return $payload; |
131 | 134 | } |
132 | 135 | } |
0 commit comments