Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion src/Request.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,12 @@ class Request implements Arrayable

/**
* @param array<string, mixed> $arguments
* @param array<string, mixed>|null $meta
*/
public function __construct(
protected array $arguments = [],
protected ?string $sessionId = null
protected ?string $sessionId = null,
protected ?array $meta = null,
) {
//
}
Expand Down Expand Up @@ -92,6 +94,14 @@ public function sessionId(): ?string
return $this->sessionId;
}

/**
* @return array<string, mixed>|null
*/
public function meta(): ?array
{
return $this->meta;
}

/**
* @param array<string, mixed> $arguments
*/
Expand All @@ -104,4 +114,12 @@ public function setSessionId(?string $sessionId): void
{
$this->sessionId = $sessionId;
}

/**
* @param array<string, mixed>|null $meta
*/
public function setMeta(?array $meta): void
{
$this->meta = $meta;
}
}
30 changes: 30 additions & 0 deletions src/Response.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Illuminate\Support\Traits\Conditionable;
use Illuminate\Support\Traits\Macroable;
use InvalidArgumentException;
use JsonException;
use Laravel\Mcp\Enums\Role;
use Laravel\Mcp\Exceptions\NotImplementedException;
Expand Down Expand Up @@ -68,6 +69,35 @@ public function content(): Content
return $this->content;
}

/**
* @param Response|array<int, Response> $responses
*/
public static function make(Response|array $responses): ResponseFactory
{
if (is_array($responses)) {
foreach ($responses as $index => $response) {
// @phpstan-ignore instanceof.alwaysTrue
if (! $response instanceof Response) {
throw new InvalidArgumentException(
"Invalid response type at index {$index}: Expected ".Response::class.', but received '.get_debug_type($response).'.'
);
}
}
}

return new ResponseFactory($responses);
}

/**
* @param string|array<string, mixed> $meta
*/
public function withMeta(string|array $meta, mixed $value = null): static
{
$this->content->setMeta($meta, $value);

return $this;
}

/**
* @throws NotImplementedException
*/
Expand Down
57 changes: 57 additions & 0 deletions src/ResponseFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

declare(strict_types=1);

namespace Laravel\Mcp;

use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Traits\Conditionable;
use Illuminate\Support\Traits\Macroable;
use Laravel\Mcp\Server\Concerns\HasMeta;

class ResponseFactory
{
use Conditionable;
use HasMeta;
use Macroable;

/**
* @var Collection<int, Response>
*/
public Collection $responses;

/**
* @param Response|array<int, Response> $responses
*/
public function __construct(Response|array $responses)
{
$this->responses = collect(Arr::wrap($responses));
}

/**
* @param string|array<string, mixed> $meta
*/
public function withMeta(string|array $meta, mixed $value = null): static
{
$this->setMeta($meta, $value);

return $this;
}

/**
* @return Collection<int, Response>
*/
public function responses(): Collection
{
return $this->responses;
}

/**
* @return array<string, mixed>|null
*/
public function getMeta(): ?array
{
return $this->meta;
}
}
48 changes: 48 additions & 0 deletions src/Server/Concerns/HasMeta.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

declare(strict_types=1);

namespace Laravel\Mcp\Server\Concerns;

use InvalidArgumentException;

trait HasMeta
{
/**
* @var array<string, mixed>|null
*/
protected ?array $meta = null;

/**
* @param string|array<string, mixed> $meta
*/
public function setMeta(string|array $meta, mixed $value = null): void
{
$this->meta ??= [];

if (! is_array($meta)) {
if (is_null($value)) {
throw new InvalidArgumentException('Value is required when using key-value signature.');
}

$this->meta[$meta] = $value;

return;
}

$this->meta = array_merge($this->meta, $meta);
}

/**
* @template T of array<string, mixed>
*
* @param T $baseArray
* @return T&array{_meta?: array<string, mixed>}
*/
public function mergeMeta(array $baseArray): array
{
return ($meta = $this->meta)
? [...$baseArray, '_meta' => $meta]
: $baseArray;
}
}
11 changes: 7 additions & 4 deletions src/Server/Content/Blob.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@
namespace Laravel\Mcp\Server\Content;

use InvalidArgumentException;
use Laravel\Mcp\Server\Concerns\HasMeta;
use Laravel\Mcp\Server\Contracts\Content;
use Laravel\Mcp\Server\Prompt;
use Laravel\Mcp\Server\Resource;
use Laravel\Mcp\Server\Tool;

class Blob implements Content
{
use HasMeta;

public function __construct(protected string $content)
{
//
Expand Down Expand Up @@ -42,13 +45,13 @@ public function toPrompt(Prompt $prompt): array
*/
public function toResource(Resource $resource): array
{
return [
return $this->mergeMeta([
'blob' => base64_encode($this->content),
'uri' => $resource->uri(),
'name' => $resource->name(),
'title' => $resource->title(),
'mimeType' => $resource->mimeType(),
];
]);
}

public function __toString(): string
Expand All @@ -61,9 +64,9 @@ public function __toString(): string
*/
public function toArray(): array
{
return [
return $this->mergeMeta([
'type' => 'blob',
'blob' => $this->content,
];
]);
}
}
11 changes: 10 additions & 1 deletion src/Server/Content/Notification.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@

namespace Laravel\Mcp\Server\Content;

use Laravel\Mcp\Server\Concerns\HasMeta;
use Laravel\Mcp\Server\Contracts\Content;
use Laravel\Mcp\Server\Prompt;
use Laravel\Mcp\Server\Resource;
use Laravel\Mcp\Server\Tool;

class Notification implements Content
{
use HasMeta;

/**
* @param array<string, mixed> $params
*/
Expand Down Expand Up @@ -53,9 +56,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,
];
}
}
11 changes: 7 additions & 4 deletions src/Server/Content/Text.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@

namespace Laravel\Mcp\Server\Content;

use Laravel\Mcp\Server\Concerns\HasMeta;
use Laravel\Mcp\Server\Contracts\Content;
use Laravel\Mcp\Server\Prompt;
use Laravel\Mcp\Server\Resource;
use Laravel\Mcp\Server\Tool;

class Text implements Content
{
use HasMeta;

public function __construct(protected string $text)
{
//
Expand All @@ -37,13 +40,13 @@ public function toPrompt(Prompt $prompt): array
*/
public function toResource(Resource $resource): array
{
return [
return $this->mergeMeta([
'text' => $this->text,
'uri' => $resource->uri(),
'name' => $resource->name(),
'title' => $resource->title(),
'mimeType' => $resource->mimeType(),
];
]);
}

public function __toString(): string
Expand All @@ -56,9 +59,9 @@ public function __toString(): string
*/
public function toArray(): array
{
return [
return $this->mergeMeta([
'type' => 'text',
'text' => $this->text,
];
]);
}
}
5 changes: 5 additions & 0 deletions src/Server/Contracts/Content.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,10 @@ public function toPrompt(Prompt $prompt): array;
*/
public function toResource(Resource $resource): array;

/**
* @param string|array<string, mixed> $meta
*/
public function setMeta(string|array $meta, mixed $value = null): void;

public function __toString(): string;
}
1 change: 1 addition & 0 deletions src/Server/McpServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ protected function registerContainerCallbacks(): void

$request->setArguments($currentRequest->all());
$request->setSessionId($currentRequest->sessionId());
$request->setMeta($currentRequest->meta());
}
});
}
Expand Down
12 changes: 6 additions & 6 deletions src/Server/Methods/CallTool.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@

use Generator;
use Illuminate\Container\Container;
use Illuminate\Support\Collection;
use Illuminate\Validation\ValidationException;
use Laravel\Mcp\Response;
use Laravel\Mcp\ResponseFactory;
use Laravel\Mcp\Server\Contracts\Errable;
use Laravel\Mcp\Server\Contracts\Method;
use Laravel\Mcp\Server\Exceptions\JsonRpcException;
Expand Down Expand Up @@ -61,13 +61,13 @@ public function handle(JsonRpcRequest $request, ServerContext $context): Generat
}

/**
* @return callable(Collection<int, Response>): array{content: array<int, array<string, mixed>>, isError: bool}
* @return callable(ResponseFactory): array<string, mixed>
*/
protected function serializable(Tool $tool): callable
{
return fn (Collection $responses): array => [
'content' => $responses->map(fn (Response $response): array => $response->content()->toTool($tool))->all(),
'isError' => $responses->contains(fn (Response $response): bool => $response->isError()),
];
return fn (ResponseFactory $factory): array => $factory->mergeMeta([
'content' => $factory->responses()->map(fn (Response $response): array => $response->content()->toTool($tool))->all(),
'isError' => $factory->responses()->contains(fn (Response $response): bool => $response->isError()),
]);
}
}
Loading