Skip to content
Merged
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
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@
"pint --test",
"rector --dry-run"
],
"test:unit": "pest --ci --coverage --min=91.0",
"test:unit": "pest --ci --coverage --min=91.3",
"test:types": "phpstan",
"test": [
"@test:lint",
Expand Down
13 changes: 13 additions & 0 deletions src/Request.php
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,17 @@ public function sessionId(): ?string
{
return $this->sessionId;
}

/**
* @param array<string, mixed> $arguments
*/
public function setArguments(array $arguments): void
{
$this->arguments = $arguments;
}

public function setSessionId(?string $sessionId): void
{
$this->sessionId = $sessionId;
}
}
32 changes: 26 additions & 6 deletions src/Server.php
Original file line number Diff line number Diff line change
Expand Up @@ -228,12 +228,7 @@ public function createContext(): ServerContext
*/
protected function handleMessage(JsonRpcRequest $request, ServerContext $context): void
{
/** @var Method $methodClass */
$methodClass = Container::getInstance()->make(
$this->methods[$request->method],
);

$response = $methodClass->handle($request, $context);
$response = $this->runMethodHandle($request, $context);

if (! is_iterable($response)) {
$this->transport->send($response->toJson());
Expand All @@ -248,6 +243,31 @@ protected function handleMessage(JsonRpcRequest $request, ServerContext $context
});
}

/**
* @return iterable<JsonRpcResponse>|JsonRpcResponse
*
* @throws JsonRpcException
*/
protected function runMethodHandle(JsonRpcRequest $request, ServerContext $context): iterable|JsonRpcResponse
{
$container = Container::getInstance();

/** @var Method $methodClass */
$methodClass = $container->make(
$this->methods[$request->method],
);

$container->instance('mcp.request', $request->toRequest());

try {
$response = $methodClass->handle($request, $context);
} finally {
$container->forgetInstance('mcp.request');
}

return $response;
}

protected function handleInitializeMessage(JsonRpcRequest $request, ServerContext $context): void
{
$response = (new Initialize)->handle($request, $context);
Expand Down
15 changes: 15 additions & 0 deletions src/Server/McpServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use Laravel\Mcp\Console\Commands\MakeServerCommand;
use Laravel\Mcp\Console\Commands\MakeToolCommand;
use Laravel\Mcp\Console\Commands\StartCommand;
use Laravel\Mcp\Request;

class McpServiceProvider extends ServiceProvider
{
Expand All @@ -23,6 +24,7 @@ public function register(): void
public function boot(): void
{
$this->registerRoutes();
$this->registerContainerCallbacks();

if ($this->app->runningInConsole()) {
$this->registerCommands();
Expand Down Expand Up @@ -63,6 +65,19 @@ protected function registerRoutes(): void
Route::group([], $path);
}

protected function registerContainerCallbacks(): void
{
$this->app->resolving(Request::class, function (Request $request, $app): void {
if ($app->bound('mcp.request')) {
/** @var Request $currentRequest */
$currentRequest = $app->make('mcp.request');

$request->setArguments($currentRequest->all());
$request->setSessionId($currentRequest->sessionId());
}
});
}

protected function registerCommands(): void
{
$this->commands([
Expand Down
24 changes: 10 additions & 14 deletions src/Server/Methods/CallTool.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,40 +28,36 @@ class CallTool implements Errable, Method
*
* @throws JsonRpcException
*/
public function handle(JsonRpcRequest $jsonRpcRequest, ServerContext $context): Generator|JsonRpcResponse
public function handle(JsonRpcRequest $request, ServerContext $context): Generator|JsonRpcResponse
{
if (is_null($jsonRpcRequest->get('name'))) {
if (is_null($request->get('name'))) {
throw new JsonRpcException(
'Missing [name] parameter.',
-32602,
$jsonRpcRequest->id,
$request->id,
);
}

$request = $jsonRpcRequest->toRequest();

$tool = $context
->tools($request)
->tools()
->first(
fn ($tool): bool => $tool->name() === $jsonRpcRequest->params['name'],
fn ($tool): bool => $tool->name() === $request->params['name'],
fn () => throw new JsonRpcException(
"Tool [{$jsonRpcRequest->params['name']}] not found.",
"Tool [{$request->params['name']}] not found.",
-32602,
$jsonRpcRequest->id,
$request->id,
));

try {
// @phpstan-ignore-next-line
$response = Container::getInstance()->call([$tool, 'handle'], [
'request' => $request,
]);
$response = Container::getInstance()->call([$tool, 'handle']);
} catch (ValidationException $validationException) {
$response = Response::error(ValidationMessages::from($validationException));
}

return is_iterable($response)
? $this->toJsonRpcStreamedResponse($jsonRpcRequest, $response, $this->serializable($tool))
: $this->toJsonRpcResponse($jsonRpcRequest, $response, $this->serializable($tool));
? $this->toJsonRpcStreamedResponse($request, $response, $this->serializable($tool))
: $this->toJsonRpcResponse($request, $response, $this->serializable($tool));
}

/**
Expand Down
24 changes: 10 additions & 14 deletions src/Server/Methods/GetPrompt.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,39 +27,35 @@ class GetPrompt implements Method
*
* @throws JsonRpcException
*/
public function handle(JsonRpcRequest $jsonRpcRequest, ServerContext $context): Generator|JsonRpcResponse
public function handle(JsonRpcRequest $request, ServerContext $context): Generator|JsonRpcResponse
{
if (is_null($jsonRpcRequest->get('name'))) {
if (is_null($request->get('name'))) {
throw new JsonRpcException(
'Missing [name] parameter.',
-32602,
$jsonRpcRequest->id,
$request->id,
);
}

$request = $jsonRpcRequest->toRequest();

$prompt = $context->prompts($request)
$prompt = $context->prompts()
->first(
fn ($prompt): bool => $prompt->name() === $jsonRpcRequest->get('name'),
fn ($prompt): bool => $prompt->name() === $request->get('name'),
fn () => throw new JsonRpcException(
"Prompt [{$jsonRpcRequest->get('name')}] not found.",
"Prompt [{$request->get('name')}] not found.",
-32602,
$jsonRpcRequest->id,
$request->id,
));

try {
// @phpstan-ignore-next-line
$response = Container::getInstance()->call([$prompt, 'handle'], [
'request' => $request,
]);
$response = Container::getInstance()->call([$prompt, 'handle']);
} catch (ValidationException $validationException) {
$response = Response::error('Invalid params: '.ValidationMessages::from($validationException));
}

return is_iterable($response)
? $this->toJsonRpcStreamedResponse($jsonRpcRequest, $response, $this->serializable($prompt))
: $this->toJsonRpcResponse($jsonRpcRequest, $response, $this->serializable($prompt));
? $this->toJsonRpcStreamedResponse($request, $response, $this->serializable($prompt))
: $this->toJsonRpcResponse($request, $response, $this->serializable($prompt));
}

/**
Expand Down
10 changes: 5 additions & 5 deletions src/Server/Methods/ListPrompts.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@

class ListPrompts implements Method
{
public function handle(JsonRpcRequest $jsonRpcRequest, ServerContext $context): JsonRpcResponse
public function handle(JsonRpcRequest $request, ServerContext $context): JsonRpcResponse
{
$paginator = new CursorPaginator(
items: $context->prompts($jsonRpcRequest->toRequest()),
perPage: $context->perPage($jsonRpcRequest->get('per_page')),
cursor: $jsonRpcRequest->cursor(),
items: $context->prompts(),
perPage: $context->perPage($request->get('per_page')),
cursor: $request->cursor(),
);

return JsonRpcResponse::result($jsonRpcRequest->id, $paginator->paginate('prompts'));
return JsonRpcResponse::result($request->id, $paginator->paginate('prompts'));
}
}
10 changes: 5 additions & 5 deletions src/Server/Methods/ListResources.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@

class ListResources implements Method
{
public function handle(JsonRpcRequest $jsonRpcRequest, ServerContext $context): JsonRpcResponse
public function handle(JsonRpcRequest $request, ServerContext $context): JsonRpcResponse
{
$paginator = new CursorPaginator(
items: $context->resources($jsonRpcRequest->toRequest()),
perPage: $context->perPage($jsonRpcRequest->get('per_page')),
cursor: $jsonRpcRequest->cursor(),
items: $context->resources(),
perPage: $context->perPage($request->get('per_page')),
cursor: $request->cursor(),
);

return JsonRpcResponse::result($jsonRpcRequest->id, $paginator->paginate('resources'));
return JsonRpcResponse::result($request->id, $paginator->paginate('resources'));
}
}
12 changes: 5 additions & 7 deletions src/Server/Methods/ListTools.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,14 @@

class ListTools implements Method
{
public function handle(JsonRpcRequest $jsonRpcRequest, ServerContext $context): JsonRpcResponse
public function handle(JsonRpcRequest $request, ServerContext $context): JsonRpcResponse
{
$request = $jsonRpcRequest->toRequest();

$paginator = new CursorPaginator(
items: $context->tools($request),
perPage: $context->perPage($jsonRpcRequest->get('per_page')),
cursor: $jsonRpcRequest->cursor(),
items: $context->tools(),
perPage: $context->perPage($request->get('per_page')),
cursor: $request->cursor(),
);

return JsonRpcResponse::result($jsonRpcRequest->id, $paginator->paginate('tools'));
return JsonRpcResponse::result($request->id, $paginator->paginate('tools'));
}
}
24 changes: 10 additions & 14 deletions src/Server/Methods/ReadResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,39 +27,35 @@ class ReadResource implements Method
*
* @throws JsonRpcException
*/
public function handle(JsonRpcRequest $jsonRpcRequest, ServerContext $context): Generator|JsonRpcResponse
public function handle(JsonRpcRequest $request, ServerContext $context): Generator|JsonRpcResponse
{
if (is_null($jsonRpcRequest->get('uri'))) {
if (is_null($request->get('uri'))) {
throw new JsonRpcException(
'Missing [uri] parameter.',
-32002,
$jsonRpcRequest->id,
$request->id,
);
}

$request = $jsonRpcRequest->toRequest();

$resource = $context->resources($request)
$resource = $context->resources()
->first(
fn (Resource $resource): bool => $resource->uri() === $jsonRpcRequest->get('uri'),
fn (Resource $resource): bool => $resource->uri() === $request->get('uri'),
fn () => throw new JsonRpcException(
"Resource [{$jsonRpcRequest->get('uri')}] not found.",
"Resource [{$request->get('uri')}] not found.",
-32002,
$jsonRpcRequest->id,
$request->id,
));

try {
// @phpstan-ignore-next-line
$response = Container::getInstance()->call([$resource, 'handle'], [
'request' => $request,
]);
$response = Container::getInstance()->call([$resource, 'handle']);
} catch (ValidationException $validationException) {
$response = Response::error('Invalid params: '.ValidationMessages::from($validationException));
}

return is_iterable($response)
? $this->toJsonRpcStreamedResponse($jsonRpcRequest, $response, $this->serializable($resource))
: $this->toJsonRpcResponse($jsonRpcRequest, $response, $this->serializable($resource));
? $this->toJsonRpcStreamedResponse($request, $response, $this->serializable($resource))
: $this->toJsonRpcResponse($request, $response, $this->serializable($resource));
}

protected function serializable(Resource $resource): callable
Expand Down
7 changes: 2 additions & 5 deletions src/Server/Primitive.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
use Illuminate\Container\Container;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Support\Str;
use Laravel\Mcp\Request;

/**
* @implements Arrayable<string, mixed>
Expand Down Expand Up @@ -41,12 +40,10 @@ public function description(): string
: $this->description;
}

public function eligibleForRegistration(Request $request): bool
public function eligibleForRegistration(): bool
{
if (method_exists($this, 'shouldRegister')) {
return Container::getInstance()->call([$this, 'shouldRegister'], [
'request' => $request,
]);
return Container::getInstance()->call([$this, 'shouldRegister']);
}

return true;
Expand Down
13 changes: 6 additions & 7 deletions src/Server/ServerContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

use Illuminate\Container\Container;
use Illuminate\Support\Collection;
use Laravel\Mcp\Request;

class ServerContext
{
Expand Down Expand Up @@ -35,36 +34,36 @@ public function __construct(
/**
* @return Collection<int, Tool>
*/
public function tools(Request $request): Collection
public function tools(): Collection
{
return collect($this->tools)->map(fn (Tool|string $toolClass) => is_string($toolClass)
? Container::getInstance()->make($toolClass)
: $toolClass
)->filter(fn (Tool $tool): bool => $tool->eligibleForRegistration($request));
)->filter(fn (Tool $tool): bool => $tool->eligibleForRegistration());
}

/**
* @return Collection<int, Resource>
*/
public function resources(Request $request): Collection
public function resources(): Collection
{
return collect($this->resources)->map(
fn (Resource|string $resourceClass) => is_string($resourceClass)
? Container::getInstance()->make($resourceClass)
: $resourceClass
)->filter(fn (Resource $tool): bool => $tool->eligibleForRegistration($request));
)->filter(fn (Resource $resource): bool => $resource->eligibleForRegistration());
}

/**
* @return Collection<int, Prompt>
*/
public function prompts(Request $request): Collection
public function prompts(): Collection
{
return collect($this->prompts)->map(
fn ($promptClass) => is_string($promptClass)
? Container::getInstance()->make($promptClass)
: $promptClass
)->filter(fn (Prompt $prompt): bool => $prompt->eligibleForRegistration($request));
)->filter(fn (Prompt $prompt): bool => $prompt->eligibleForRegistration());
}

public function perPage(?int $requestedPerPage = null): int
Expand Down
Loading
Loading