diff --git a/src/Response.php b/src/Response.php index a64457e8..ae95d10e 100644 --- a/src/Response.php +++ b/src/Response.php @@ -19,43 +19,56 @@ class Response use Conditionable; use Macroable; + /** + * @param array|null $meta + */ protected function __construct( protected Content $content, protected Role $role = Role::USER, protected bool $isError = false, + protected ?array $meta = null, ) { // } /** * @param array $params + * @param array|null $meta */ - public static function notification(string $method, array $params = []): static + public static function notification(string $method, array $params = [], ?array $meta = null): static { - return new static(new Notification($method, $params)); + return new static(new Notification($method, $params, $meta)); } - public static function text(string $text): static + /** + * @param array|null $meta + */ + public static function text(string $text, ?array $meta = null): static { - return new static(new Text($text)); + return new static(new Text($text, $meta)); } /** * @internal * + * @param array|null $meta + * * @throws JsonException */ - public static function json(mixed $content): static + public static function json(mixed $content, ?array $meta = null): static { return static::text(json_encode( $content, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT, - )); + ), $meta); } - public static function blob(string $content): static + /** + * @param array|null $meta + */ + public static function blob(string $content, ?array $meta = null): static { - return new static(new Blob($content)); + return new static(new Blob($content, $meta)); } public static function error(string $text): static @@ -86,7 +99,7 @@ public static function image(): Content public function asAssistant(): static { - return new static($this->content, Role::ASSISTANT, $this->isError); + return new static($this->content, Role::ASSISTANT, $this->isError, $this->meta); } public function isNotification(): bool diff --git a/src/Server/Content/Blob.php b/src/Server/Content/Blob.php index 5f04a1d4..0c6f35f2 100644 --- a/src/Server/Content/Blob.php +++ b/src/Server/Content/Blob.php @@ -5,6 +5,7 @@ namespace Laravel\Mcp\Server\Content; use InvalidArgumentException; +use Laravel\Mcp\Server\Content\Concerns\HasMeta; use Laravel\Mcp\Server\Contracts\Content; use Laravel\Mcp\Server\Prompt; use Laravel\Mcp\Server\Resource; @@ -12,9 +13,16 @@ class Blob implements Content { - public function __construct(protected string $content) - { - // + use HasMeta; + + /** + * @param array|null $meta + */ + public function __construct( + protected string $content, + ?array $meta = null + ) { + $this->meta = $meta; } /** @@ -42,13 +50,13 @@ public function toPrompt(Prompt $prompt): array */ public function toResource(Resource $resource): array { - return [ + return $this->withMeta([ 'blob' => base64_encode($this->content), 'uri' => $resource->uri(), 'name' => $resource->name(), 'title' => $resource->title(), 'mimeType' => $resource->mimeType(), - ]; + ]); } public function __toString(): string @@ -61,9 +69,9 @@ public function __toString(): string */ public function toArray(): array { - return [ + return $this->withMeta([ 'type' => 'blob', 'blob' => $this->content, - ]; + ]); } } diff --git a/src/Server/Content/Concerns/HasMeta.php b/src/Server/Content/Concerns/HasMeta.php new file mode 100644 index 00000000..e7439833 --- /dev/null +++ b/src/Server/Content/Concerns/HasMeta.php @@ -0,0 +1,24 @@ +|null + */ + protected ?array $meta = null; + + /** + * @param array $baseArray + * @return array + */ + protected function withMeta(array $baseArray): array + { + return ($meta = $this->meta) + ? [...$baseArray, '_meta' => $meta] + : $baseArray; + } +} diff --git a/src/Server/Content/Notification.php b/src/Server/Content/Notification.php index 53006291..6d72e4ef 100644 --- a/src/Server/Content/Notification.php +++ b/src/Server/Content/Notification.php @@ -4,6 +4,7 @@ namespace Laravel\Mcp\Server\Content; +use Laravel\Mcp\Server\Content\Concerns\HasMeta; use Laravel\Mcp\Server\Contracts\Content; use Laravel\Mcp\Server\Prompt; use Laravel\Mcp\Server\Resource; @@ -11,12 +12,15 @@ class Notification implements Content { + use HasMeta; + /** * @param array $params + * @param array|null $meta */ - public function __construct(protected string $method, protected array $params) + public function __construct(protected string $method, protected array $params, ?array $meta = null) { - // + $this->meta = $meta; } /** @@ -53,9 +57,15 @@ public function __toString(): string */ public function toArray(): array { + $params = $this->params; + + if ($this->meta !== null && $this->meta !== [] && ! isset($params['_meta'])) { + $params['_meta'] = $this->meta; + } + return [ 'method' => $this->method, - 'params' => $this->params, + 'params' => $params, ]; } } diff --git a/src/Server/Content/Text.php b/src/Server/Content/Text.php index c800a6f5..549cf8b4 100644 --- a/src/Server/Content/Text.php +++ b/src/Server/Content/Text.php @@ -4,6 +4,7 @@ namespace Laravel\Mcp\Server\Content; +use Laravel\Mcp\Server\Content\Concerns\HasMeta; use Laravel\Mcp\Server\Contracts\Content; use Laravel\Mcp\Server\Prompt; use Laravel\Mcp\Server\Resource; @@ -11,9 +12,16 @@ class Text implements Content { - public function __construct(protected string $text) - { - // + use HasMeta; + + /** + * @param array|null $meta + */ + public function __construct( + protected string $text, + ?array $meta = null + ) { + $this->meta = $meta; } /** @@ -37,13 +45,13 @@ public function toPrompt(Prompt $prompt): array */ public function toResource(Resource $resource): array { - return [ + return $this->withMeta([ 'text' => $this->text, 'uri' => $resource->uri(), 'name' => $resource->name(), 'title' => $resource->title(), 'mimeType' => $resource->mimeType(), - ]; + ]); } public function __toString(): string @@ -56,9 +64,9 @@ public function __toString(): string */ public function toArray(): array { - return [ + return $this->withMeta([ 'type' => 'text', 'text' => $this->text, - ]; + ]); } } diff --git a/src/Server/Primitive.php b/src/Server/Primitive.php index 7a3adb12..86007d28 100644 --- a/src/Server/Primitive.php +++ b/src/Server/Primitive.php @@ -19,6 +19,11 @@ abstract class Primitive implements Arrayable protected string $description = ''; + /** + * @var array|null + */ + protected ?array $meta = null; + public function name(): string { return $this->name === '' @@ -40,6 +45,14 @@ public function description(): string : $this->description; } + /** + * @return array|null + */ + public function meta(): ?array + { + return $this->meta; + } + public function eligibleForRegistration(): bool { if (method_exists($this, 'shouldRegister')) { @@ -49,6 +62,19 @@ public function eligibleForRegistration(): bool return true; } + /** + * @template T of array + * + * @param T $baseArray + * @return T&array{_meta?: array} + */ + protected function withMeta(array $baseArray): array + { + return ($meta = $this->meta()) + ? [...$baseArray, '_meta' => $meta] + : $baseArray; + } + /** * @return array */ diff --git a/src/Server/Prompt.php b/src/Server/Prompt.php index 1e5dbd9e..276c1808 100644 --- a/src/Server/Prompt.php +++ b/src/Server/Prompt.php @@ -28,11 +28,12 @@ public function toMethodCall(): array } /** - * @return array{name: string, title: string, description: string, arguments: array} + * @return array{name: string, title: string, description: string, arguments: array}>} */ public function toArray(): array { - return [ + // @phpstan-ignore return.type + return $this->withMeta([ 'name' => $this->name(), 'title' => $this->title(), 'description' => $this->description(), @@ -40,6 +41,6 @@ public function toArray(): array fn (Argument $argument): array => $argument->toArray(), $this->arguments(), ), - ]; + ]); } } diff --git a/src/Server/Resource.php b/src/Server/Resource.php index 26b2eac8..212049b0 100644 --- a/src/Server/Resource.php +++ b/src/Server/Resource.php @@ -34,14 +34,25 @@ public function toMethodCall(): array return ['uri' => $this->uri()]; } + /** + * @return array{ + * name: string, + * title: string, + * description: string, + * uri: string, + * mimeType: string, + * _meta?: array + * } + */ public function toArray(): array { - return [ + // @phpstan-ignore return.type + return $this->withMeta([ 'name' => $this->name(), 'title' => $this->title(), 'description' => $this->description(), 'uri' => $this->uri(), 'mimeType' => $this->mimeType(), - ]; + ]); } } diff --git a/src/Server/Tool.php b/src/Server/Tool.php index a9568aff..65eef974 100644 --- a/src/Server/Tool.php +++ b/src/Server/Tool.php @@ -51,24 +51,28 @@ public function toMethodCall(): array * title?: string|null, * description?: string|null, * inputSchema?: array, - * annotations?: array|object + * annotations?: array|object, + * _meta?: array * } */ public function toArray(): array { $annotations = $this->annotations(); + $schema = JsonSchema::object( $this->schema(...), )->toArray(); $schema['properties'] ??= (object) []; - return [ + // @phpstan-ignore return.type + return $this->withMeta([ 'name' => $this->name(), 'title' => $this->title(), 'description' => $this->description(), 'inputSchema' => $schema, 'annotations' => $annotations === [] ? (object) [] : $annotations, - ]; + ]); + } } diff --git a/src/Server/Transport/JsonRpcRequest.php b/src/Server/Transport/JsonRpcRequest.php index 1ee32878..7f8db407 100644 --- a/src/Server/Transport/JsonRpcRequest.php +++ b/src/Server/Transport/JsonRpcRequest.php @@ -60,6 +60,14 @@ public function get(string $key, mixed $default = null): mixed return $this->params[$key] ?? $default; } + /** + * @retrun array|null + */ + public function meta(): ?array + { + return is_array($this->params['_meta']) ? $this->params['_meta'] : null; + } + public function toRequest(): Request { return new Request($this->params['arguments'] ?? [], $this->sessionId); diff --git a/tests/Unit/Content/BlobTest.php b/tests/Unit/Content/BlobTest.php index 7d7823e2..d334ff6d 100644 --- a/tests/Unit/Content/BlobTest.php +++ b/tests/Unit/Content/BlobTest.php @@ -29,6 +29,31 @@ ]); }); +it('preserves meta when converting to a resource payload', function (): void { + $blob = new Blob('raw-bytes', ['encoding' => 'base64']); + $resource = new class extends Resource + { + protected string $uri = 'file://avatar.png'; + + protected string $name = 'avatar'; + + protected string $title = 'User Avatar'; + + protected string $mimeType = 'image/png'; + }; + + $payload = $blob->toResource($resource); + + expect($payload)->toEqual([ + 'blob' => base64_encode('raw-bytes'), + 'uri' => 'file://avatar.png', + 'name' => 'avatar', + 'title' => 'User Avatar', + 'mimeType' => 'image/png', + '_meta' => ['encoding' => 'base64'], + ]); +}); + it('throws when used in tools', function (): void { $blob = new Blob('anything'); @@ -55,3 +80,22 @@ 'blob' => 'bytes', ]); }); + +it('supports meta in constructor', function (): void { + $blob = new Blob('binary-data', ['encoding' => 'base64']); + + expect($blob->toArray())->toEqual([ + 'type' => 'blob', + 'blob' => 'binary-data', + '_meta' => ['encoding' => 'base64'], + ]); +}); + +it('does not include meta if null', function (): void { + $blob = new Blob('data'); + + expect($blob->toArray())->toEqual([ + 'type' => 'blob', + 'blob' => 'data', + ]); +}); diff --git a/tests/Unit/Content/NotificationTest.php b/tests/Unit/Content/NotificationTest.php index 0a2ad155..2f3db15d 100644 --- a/tests/Unit/Content/NotificationTest.php +++ b/tests/Unit/Content/NotificationTest.php @@ -62,3 +62,68 @@ 'params' => ['x' => 1, 'y' => 2], ]); }); + +it('supports constructor meta', function (): void { + $notification = new Notification('test/event', ['data' => 'value'], ['author' => 'system']); + + expect($notification->toArray())->toEqual([ + 'method' => 'test/event', + 'params' => [ + 'data' => 'value', + '_meta' => ['author' => 'system'], + ], + ]); +}); + +it('supports params _meta', function (): void { + $notification = new Notification('test/event', [ + 'data' => 'value', + '_meta' => ['source' => 'params'], + ]); + + expect($notification->toArray())->toEqual([ + 'method' => 'test/event', + 'params' => [ + 'data' => 'value', + '_meta' => ['source' => 'params'], + ], + ]); +}); + +it('keeps params _meta when both params and constructor have meta', function (): void { + $notification = new Notification('test/event', [ + 'data' => 'value', + '_meta' => ['source' => 'params', 'keep' => 'this'], + ], ['source' => 'constructor', 'author' => 'system']); + + expect($notification->toArray())->toEqual([ + 'method' => 'test/event', + 'params' => [ + 'data' => 'value', + '_meta' => [ + 'source' => 'params', // Params _meta is kept + 'keep' => 'this', + ], + ], + ]); +}); + +it('does not include _meta if null', function (): void { + $notification = new Notification('test/event', ['data' => 'value']); + + expect($notification->toArray())->toEqual([ + 'method' => 'test/event', + 'params' => ['data' => 'value'], + ]) + ->and($notification->toArray())->not->toHaveKey('_meta'); +}); + +it('does not include _meta if empty', function (): void { + $notification = new Notification('test/event', ['data' => 'value'], []); + + expect($notification->toArray())->toEqual([ + 'method' => 'test/event', + 'params' => ['data' => 'value'], + ]) + ->and($notification->toArray()['params'])->not->toHaveKey('_meta'); +}); diff --git a/tests/Unit/Content/TextTest.php b/tests/Unit/Content/TextTest.php index ade98a93..89536eb4 100644 --- a/tests/Unit/Content/TextTest.php +++ b/tests/Unit/Content/TextTest.php @@ -29,6 +29,31 @@ ]); }); +it('preserves meta when converting to a resource payload', function (): void { + $text = new Text('Hello world', ['author' => 'John']); + $resource = new class extends Resource + { + protected string $uri = 'file://readme.txt'; + + protected string $name = 'readme'; + + protected string $title = 'Readme File'; + + protected string $mimeType = 'text/plain'; + }; + + $payload = $text->toResource($resource); + + expect($payload)->toEqual([ + 'text' => 'Hello world', + 'uri' => 'file://readme.txt', + 'name' => 'readme', + 'title' => 'Readme File', + 'mimeType' => 'text/plain', + '_meta' => ['author' => 'John'], + ]); +}); + it('may be used in tools', function (): void { $text = new Text('Run me'); @@ -65,3 +90,22 @@ 'text' => 'abc', ]); }); + +it('supports meta in constructor', function (): void { + $text = new Text('Hello', ['author' => 'John']); + + expect($text->toArray())->toEqual([ + 'type' => 'text', + 'text' => 'Hello', + '_meta' => ['author' => 'John'], + ]); +}); + +it('does not include meta if null', function (): void { + $text = new Text('Hello'); + + expect($text->toArray())->toEqual([ + 'type' => 'text', + 'text' => 'Hello', + ]); +}); diff --git a/tests/Unit/Methods/CallToolTest.php b/tests/Unit/Methods/CallToolTest.php index d8c72c47..77334b7c 100644 --- a/tests/Unit/Methods/CallToolTest.php +++ b/tests/Unit/Methods/CallToolTest.php @@ -7,6 +7,7 @@ use Tests\Fixtures\CurrentTimeTool; use Tests\Fixtures\SayHiTool; use Tests\Fixtures\SayHiTwiceTool; +use Tests\Fixtures\SayHiWithMetaTool; it('returns a valid call tool response', function (): void { $request = JsonRpcRequest::from([ @@ -145,6 +146,53 @@ ]); }); +it('includes result meta when responses provide it', function (): void { + $request = JsonRpcRequest::from([ + 'jsonrpc' => '2.0', + 'id' => 1, + 'method' => 'tools/call', + 'params' => [ + 'name' => 'say-hi-with-meta-tool', + 'arguments' => ['name' => 'John Doe'], + ], + ]); + + $context = new ServerContext( + supportedProtocolVersions: ['2025-03-26'], + serverCapabilities: [], + serverName: 'Test Server', + serverVersion: '1.0.0', + instructions: 'Test instructions', + maxPaginationLength: 50, + defaultPaginationLength: 10, + tools: [SayHiWithMetaTool::class], + resources: [], + prompts: [], + ); + + $method = new CallTool; + + $this->instance('mcp.request', $request->toRequest()); + $response = $method->handle($request, $context); + + $payload = $response->toArray(); + + expect($payload['id'])->toEqual(1) + ->and($payload['result'])->toEqual([ + 'content' => [ + [ + 'type' => 'text', + 'text' => 'Hello, John Doe!', + ], + ], + 'isError' => false, + '_meta' => [ + 'requestId' => 'abc-123', + 'source' => 'tests/fixtures', + ], + ]); +}); + it('may resolve dependencies out of the container', function (): void { $request = JsonRpcRequest::from([ 'jsonrpc' => '2.0', diff --git a/tests/Unit/Prompts/PromptTest.php b/tests/Unit/Prompts/PromptTest.php new file mode 100644 index 00000000..ccab64d7 --- /dev/null +++ b/tests/Unit/Prompts/PromptTest.php @@ -0,0 +1,87 @@ +meta())->toBeNull() + ->and($prompt->toArray())->not->toHaveKey('_meta'); +}); + +it('can have custom meta', function (): void { + $prompt = new class extends Prompt + { + protected ?array $meta = [ + 'category' => 'greeting', + 'tags' => ['hello', 'welcome'], + ]; + + public function description(): string + { + return 'Test prompt'; + } + + public function handle(): Response + { + return Response::text('Hello'); + } + }; + + expect($prompt->toArray()['_meta'])->toEqual([ + 'category' => 'greeting', + 'tags' => ['hello', 'welcome'], + ]); +}); + +it('includes meta in array representation with other fields', function (): void { + $prompt = new class extends Prompt + { + protected string $name = 'greet'; + + protected string $title = 'Greeting Prompt'; + + protected string $description = 'A friendly greeting'; + + protected ?array $meta = [ + 'version' => '1.0', + ]; + + public function handle(): Response + { + return Response::text('Hello'); + } + + public function arguments(): array + { + return [ + new Argument('name', 'User name', true), + ]; + } + }; + + $array = $prompt->toArray(); + + expect($array) + ->toHaveKey('name', 'greet') + ->toHaveKey('title', 'Greeting Prompt') + ->toHaveKey('description', 'A friendly greeting') + ->toHaveKey('arguments') + ->toHaveKey('_meta') + ->and($array['_meta'])->toEqual(['version' => '1.0']) + ->and($array['arguments'])->toHaveCount(1); + +}); diff --git a/tests/Unit/Resources/ResourceTest.php b/tests/Unit/Resources/ResourceTest.php index 9f812743..35888430 100644 --- a/tests/Unit/Resources/ResourceTest.php +++ b/tests/Unit/Resources/ResourceTest.php @@ -141,3 +141,46 @@ public function handle(): string }; expect($resource->description())->toBe('A test resource.'); }); + +it('returns no meta by default', function (): void { + $resource = new class extends Resource + { + public function description(): string + { + return 'Test resource'; + } + + public function handle(): string + { + return 'Content'; + } + }; + + expect($resource->meta())->toBeNull() + ->and($resource->toArray())->not->toHaveKey('_meta'); +}); + +it('can have custom meta', function (): void { + $resource = new class extends Resource + { + protected ?array $meta = [ + 'author' => 'John Doe', + 'version' => '1.0', + ]; + + public function description(): string + { + return 'Test resource'; + } + + public function handle(): string + { + return 'Content'; + } + }; + + expect($resource->toArray()['_meta'])->toEqual([ + 'author' => 'John Doe', + 'version' => '1.0', + ]); +}); diff --git a/tests/Unit/ResponseTest.php b/tests/Unit/ResponseTest.php index d9484d7a..ca4c8838 100644 --- a/tests/Unit/ResponseTest.php +++ b/tests/Unit/ResponseTest.php @@ -122,3 +122,33 @@ $content = $response->content(); expect((string) $content)->toBe(json_encode($data, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT)); }); + +it('creates text response with content meta', function (): void { + $response = Response::text('Hello', ['author' => 'John']); + + expect($response->content())->toBeInstanceOf(Text::class) + ->and($response->content()->toArray())->toHaveKey('_meta') + ->and($response->content()->toArray()['_meta'])->toEqual(['author' => 'John']); +}); + +it('creates blob response with content meta', function (): void { + $response = Response::blob('binary', ['encoding' => 'utf-8']); + + expect($response->content())->toBeInstanceOf(Blob::class) + ->and($response->content()->toArray())->toHaveKey('_meta') + ->and($response->content()->toArray()['_meta'])->toEqual(['encoding' => 'utf-8']); +}); + +it('creates notification response with content meta', function (): void { + $response = Response::notification('test/event', ['data' => 'value'], ['author' => 'system']); + + expect($response->content())->toBeInstanceOf(Notification::class) + ->and($response->content()->toArray()['params'])->toHaveKey('_meta') + ->and($response->content()->toArray()['params']['_meta'])->toEqual(['author' => 'system']); +}); + +it('has no result meta by default', function (): void { + $response = Response::text('Hello'); + + expect($response->meta())->toBeNull(); +}); diff --git a/tests/Unit/Tools/ToolTest.php b/tests/Unit/Tools/ToolTest.php index c30c158d..ae29aa71 100644 --- a/tests/Unit/Tools/ToolTest.php +++ b/tests/Unit/Tools/ToolTest.php @@ -94,6 +94,16 @@ ->and($array['inputSchema']['required'])->toEqual(['message']); }); +it('returns no meta by default', function (): void { + $tool = new TestTool; + expect($tool->meta())->toBeNull(); +}); + +it('can have custom meta', function (): void { + $tool = new CustomMetaTool; + expect($tool->toArray()['_meta'])->toEqual(['key' => 'value']); +}); + class TestTool extends Tool { public function description(): string @@ -155,3 +165,10 @@ public function schema(\Illuminate\JsonSchema\JsonSchema $schema): array ]; } } + +class CustomMetaTool extends TestTool +{ + protected ?array $meta = [ + 'key' => 'value', + ]; +} diff --git a/tests/Unit/Transport/JsonRpcRequestTest.php b/tests/Unit/Transport/JsonRpcRequestTest.php index e234f4b7..cf036d82 100644 --- a/tests/Unit/Transport/JsonRpcRequestTest.php +++ b/tests/Unit/Transport/JsonRpcRequestTest.php @@ -113,3 +113,56 @@ expect($requestWithCursor->cursor())->toEqual('CUR123') ->and($requestWithCursor->get('foo'))->toEqual('bar'); }); + +it('extracts _meta from params', function (): void { + $request = JsonRpcRequest::from([ + 'jsonrpc' => '2.0', + 'id' => 1, + 'method' => 'tools/call', + 'params' => [ + 'name' => 'echo', + '_meta' => [ + 'progressToken' => 'token-123', + 'customKey' => 'customValue', + ], + ], + ]); + + expect($request->params['_meta'] ?? null)->toEqual([ + 'progressToken' => 'token-123', + 'customKey' => 'customValue', + ]) + ->and($request->params)->toHaveKey('_meta') + ->and($request->params)->toHaveKey('name', 'echo'); + + // _meta should remain in params (matches official SDK) +}); + +it('has null meta when not provided', function (): void { + $request = JsonRpcRequest::from([ + 'jsonrpc' => '2.0', + 'id' => 1, + 'method' => 'tools/call', + 'params' => [ + 'name' => 'echo', + ], + ]); + + expect($request->params['_meta'] ?? null)->toBeNull(); +}); + +it('passes meta to Request object', function (): void { + $jsonRpcRequest = JsonRpcRequest::from([ + 'jsonrpc' => '2.0', + 'id' => 1, + 'method' => 'tools/call', + 'params' => [ + 'arguments' => ['message' => 'Hello'], + '_meta' => ['requestId' => '456'], + ], + ]); + + $request = $jsonRpcRequest->toRequest(); + + expect($request->meta())->toEqual(['requestId' => '456']); +}); diff --git a/tests/Unit/Transport/JsonRpcResponseTest.php b/tests/Unit/Transport/JsonRpcResponseTest.php index 1deb19e0..e8489189 100644 --- a/tests/Unit/Transport/JsonRpcResponseTest.php +++ b/tests/Unit/Transport/JsonRpcResponseTest.php @@ -37,3 +37,36 @@ expect($response->toJson())->toEqual($expectedJson); }); + +it('includes _meta in result when provided in result array', function (): void { + $response = JsonRpcResponse::result( + 1, + [ + 'content' => 'Hello', + '_meta' => [ + 'requestId' => '123', + 'timestamp' => 1234567890, + ], + ] + ); + + $expectedArray = [ + 'jsonrpc' => '2.0', + 'id' => 1, + 'result' => [ + 'content' => 'Hello', + '_meta' => [ + 'requestId' => '123', + 'timestamp' => 1234567890, + ], + ], + ]; + + expect($response->toArray())->toEqual($expectedArray); +}); + +it('does not include _meta when not in result', function (): void { + $response = JsonRpcResponse::result(1, ['content' => 'Hello']); + + expect($response->toArray()['result'])->not->toHaveKey('_meta'); +});