Adopt Schema component in server handlers
This commit is contained in:
parent
6025f890f9
commit
09707b4798
21 changed files with 1516 additions and 2424 deletions
|
|
@ -1,163 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the official PHP MCP SDK.
|
||||
*
|
||||
* A collaboration between Symfony and the PHP Foundation.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Mcp\Server;
|
||||
|
||||
use Mcp\Exception\ExceptionInterface;
|
||||
use Mcp\Exception\HandlerNotFoundException;
|
||||
use Mcp\Exception\InvalidInputMessageException;
|
||||
use Mcp\Exception\NotFoundExceptionInterface;
|
||||
use Mcp\Message\Error;
|
||||
use Mcp\Message\Factory;
|
||||
use Mcp\Message\Notification;
|
||||
use Mcp\Message\Request;
|
||||
use Mcp\Message\Response;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* @final
|
||||
*
|
||||
* @author Christopher Hertel <mail@christopher-hertel.de>
|
||||
*/
|
||||
class JsonRpcHandler
|
||||
{
|
||||
/**
|
||||
* @var array<int, RequestHandlerInterface>
|
||||
*/
|
||||
private readonly array $requestHandlers;
|
||||
|
||||
/**
|
||||
* @var array<int, NotificationHandlerInterface>
|
||||
*/
|
||||
private readonly array $notificationHandlers;
|
||||
|
||||
/**
|
||||
* @param iterable<RequestHandlerInterface> $requestHandlers
|
||||
* @param iterable<NotificationHandlerInterface> $notificationHandlers
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly Factory $messageFactory,
|
||||
iterable $requestHandlers,
|
||||
iterable $notificationHandlers,
|
||||
private readonly LoggerInterface $logger,
|
||||
) {
|
||||
$this->requestHandlers = $requestHandlers instanceof \Traversable ? iterator_to_array($requestHandlers) : $requestHandlers;
|
||||
$this->notificationHandlers = $notificationHandlers instanceof \Traversable ? iterator_to_array($notificationHandlers) : $notificationHandlers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return iterable<string|null>
|
||||
*
|
||||
* @throws ExceptionInterface When a handler throws an exception during message processing
|
||||
* @throws \JsonException When JSON encoding of the response fails
|
||||
*/
|
||||
public function process(string $input): iterable
|
||||
{
|
||||
$this->logger->info('Received message to process', ['message' => $input]);
|
||||
|
||||
try {
|
||||
$messages = $this->messageFactory->create($input);
|
||||
} catch (\JsonException $e) {
|
||||
$this->logger->warning('Failed to decode json message', ['exception' => $e]);
|
||||
|
||||
yield $this->encodeResponse(Error::parseError($e->getMessage()));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($messages as $message) {
|
||||
if ($message instanceof InvalidInputMessageException) {
|
||||
$this->logger->warning('Failed to create message', ['exception' => $message]);
|
||||
yield $this->encodeResponse(Error::invalidRequest(0, $message->getMessage()));
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->logger->info('Decoded incoming message', ['message' => $message]);
|
||||
|
||||
try {
|
||||
yield $message instanceof Notification
|
||||
? $this->handleNotification($message)
|
||||
: $this->encodeResponse($this->handleRequest($message));
|
||||
} catch (\DomainException) {
|
||||
yield null;
|
||||
} catch (NotFoundExceptionInterface $e) {
|
||||
$this->logger->warning(\sprintf('Failed to create response: %s', $e->getMessage()), ['exception' => $e]);
|
||||
|
||||
yield $this->encodeResponse(Error::methodNotFound($message->id, $e->getMessage()));
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
$this->logger->warning(\sprintf('Invalid argument: %s', $e->getMessage()), ['exception' => $e]);
|
||||
|
||||
yield $this->encodeResponse(Error::invalidParams($message->id, $e->getMessage()));
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->critical(\sprintf('Uncaught exception: %s', $e->getMessage()), ['exception' => $e]);
|
||||
|
||||
yield $this->encodeResponse(Error::internalError($message->id, $e->getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \JsonException When JSON encoding fails
|
||||
*/
|
||||
private function encodeResponse(Response|Error|null $response): ?string
|
||||
{
|
||||
if (null === $response) {
|
||||
$this->logger->warning('Response is null');
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->logger->info('Encoding response', ['response' => $response]);
|
||||
|
||||
if ($response instanceof Response && [] === $response->result) {
|
||||
return json_encode($response, \JSON_THROW_ON_ERROR | \JSON_FORCE_OBJECT);
|
||||
}
|
||||
|
||||
return json_encode($response, \JSON_THROW_ON_ERROR);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null
|
||||
*
|
||||
* @throws ExceptionInterface When a notification handler throws an exception
|
||||
*/
|
||||
private function handleNotification(Notification $notification)
|
||||
{
|
||||
$handled = false;
|
||||
foreach ($this->notificationHandlers as $handler) {
|
||||
if ($handler->supports($notification)) {
|
||||
$handler->handle($notification);
|
||||
$handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$handled) {
|
||||
$this->logger->warning(\sprintf('No handler found for "%s".', $notification->method), ['notification' => $notification]);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws NotFoundExceptionInterface When no handler is found for the request method
|
||||
* @throws ExceptionInterface When a request handler throws an exception
|
||||
*/
|
||||
private function handleRequest(Request $request): Response|Error
|
||||
{
|
||||
foreach ($this->requestHandlers as $handler) {
|
||||
if ($handler->supports($request)) {
|
||||
return $handler->createResponse($request);
|
||||
}
|
||||
}
|
||||
|
||||
throw new HandlerNotFoundException(\sprintf('No handler found for method "%s".', $request->method));
|
||||
}
|
||||
}
|
||||
|
|
@ -12,17 +12,20 @@
|
|||
namespace Mcp\Server;
|
||||
|
||||
use Mcp\Exception\ExceptionInterface;
|
||||
use Mcp\Message\Notification;
|
||||
use Mcp\Schema\JsonRpc\Error;
|
||||
use Mcp\Schema\JsonRpc\HasMethodInterface;
|
||||
use Mcp\Schema\JsonRpc\Request;
|
||||
use Mcp\Schema\JsonRpc\Response;
|
||||
|
||||
/**
|
||||
* @author Christopher Hertel <mail@christopher-hertel.de>
|
||||
*/
|
||||
interface NotificationHandlerInterface
|
||||
interface MethodHandlerInterface
|
||||
{
|
||||
public function supports(Notification $message): bool;
|
||||
public function supports(HasMethodInterface $message): bool;
|
||||
|
||||
/**
|
||||
* @throws ExceptionInterface When the handler encounters an error processing the notification
|
||||
* @throws ExceptionInterface When the handler encounters an error processing the request
|
||||
*/
|
||||
public function handle(Notification $notification): void;
|
||||
public function handle(HasMethodInterface $message): Response|Error|null;
|
||||
}
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the official PHP MCP SDK.
|
||||
*
|
||||
* A collaboration between Symfony and the PHP Foundation.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Mcp\Server\NotificationHandler;
|
||||
|
||||
use Mcp\Message\Notification;
|
||||
use Mcp\Server\NotificationHandlerInterface;
|
||||
|
||||
/**
|
||||
* @author Christopher Hertel <mail@christopher-hertel.de>
|
||||
*/
|
||||
abstract class BaseNotificationHandler implements NotificationHandlerInterface
|
||||
{
|
||||
public function supports(Notification $message): bool
|
||||
{
|
||||
return $message->method === \sprintf('notifications/%s', $this->supportedNotification());
|
||||
}
|
||||
|
||||
abstract protected function supportedNotification(): string;
|
||||
}
|
||||
|
|
@ -11,19 +11,24 @@
|
|||
|
||||
namespace Mcp\Server\NotificationHandler;
|
||||
|
||||
use Mcp\Message\Notification;
|
||||
use Mcp\Schema\JsonRpc\Error;
|
||||
use Mcp\Schema\JsonRpc\HasMethodInterface;
|
||||
use Mcp\Schema\JsonRpc\Response;
|
||||
use Mcp\Schema\Notification\InitializedNotification;
|
||||
use Mcp\Server\MethodHandlerInterface;
|
||||
|
||||
/**
|
||||
* @author Christopher Hertel <mail@christopher-hertel.de>
|
||||
*/
|
||||
final class InitializedHandler extends BaseNotificationHandler
|
||||
final class InitializedHandler implements MethodHandlerInterface
|
||||
{
|
||||
protected function supportedNotification(): string
|
||||
public function supports(HasMethodInterface $message): bool
|
||||
{
|
||||
return 'initialized';
|
||||
return $message instanceof InitializedNotification;
|
||||
}
|
||||
|
||||
public function handle(Notification $notification): void
|
||||
public function handle(InitializedNotification|HasMethodInterface $message): Response|Error|null
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,28 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the official PHP MCP SDK.
|
||||
*
|
||||
* A collaboration between Symfony and the PHP Foundation.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Mcp\Server\RequestHandler;
|
||||
|
||||
use Mcp\Message\Request;
|
||||
use Mcp\Server\RequestHandlerInterface;
|
||||
|
||||
/**
|
||||
* @author Christopher Hertel <mail@christopher-hertel.de>
|
||||
*/
|
||||
abstract class BaseRequestHandler implements RequestHandlerInterface
|
||||
{
|
||||
public function supports(Request $message): bool
|
||||
{
|
||||
return $message->method === $this->supportedMethod();
|
||||
}
|
||||
|
||||
abstract protected function supportedMethod(): string;
|
||||
}
|
||||
50
src/Server/RequestHandler/CallToolHandler.php
Normal file
50
src/Server/RequestHandler/CallToolHandler.php
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the official PHP MCP SDK.
|
||||
*
|
||||
* A collaboration between Symfony and the PHP Foundation.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Mcp\Server\RequestHandler;
|
||||
|
||||
use Mcp\Capability\Tool\ToolExecutorInterface;
|
||||
use Mcp\Exception\ExceptionInterface;
|
||||
use Mcp\Schema\JsonRpc\Error;
|
||||
use Mcp\Schema\JsonRpc\HasMethodInterface;
|
||||
use Mcp\Schema\JsonRpc\Response;
|
||||
use Mcp\Schema\Request\CallToolRequest;
|
||||
use Mcp\Server\MethodHandlerInterface;
|
||||
|
||||
/**
|
||||
* @author Christopher Hertel <mail@christopher-hertel.de>
|
||||
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
|
||||
*/
|
||||
final class CallToolHandler implements MethodHandlerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ToolExecutorInterface $toolExecutor,
|
||||
) {
|
||||
}
|
||||
|
||||
public function supports(HasMethodInterface $message): bool
|
||||
{
|
||||
return $message instanceof CallToolRequest;
|
||||
}
|
||||
|
||||
public function handle(CallToolRequest|HasMethodInterface $message): Response|Error
|
||||
{
|
||||
\assert($message instanceof CallToolRequest);
|
||||
|
||||
try {
|
||||
$result = $this->toolExecutor->call($message);
|
||||
} catch (ExceptionInterface) {
|
||||
return Error::forInternalError('Error while executing tool', $message->getId());
|
||||
}
|
||||
|
||||
return new Response($message->getId(), $result);
|
||||
}
|
||||
}
|
||||
49
src/Server/RequestHandler/GetPromptHandler.php
Normal file
49
src/Server/RequestHandler/GetPromptHandler.php
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the official PHP MCP SDK.
|
||||
*
|
||||
* A collaboration between Symfony and the PHP Foundation.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Mcp\Server\RequestHandler;
|
||||
|
||||
use Mcp\Capability\Prompt\PromptGetterInterface;
|
||||
use Mcp\Exception\ExceptionInterface;
|
||||
use Mcp\Schema\JsonRpc\Error;
|
||||
use Mcp\Schema\JsonRpc\HasMethodInterface;
|
||||
use Mcp\Schema\JsonRpc\Response;
|
||||
use Mcp\Schema\Request\GetPromptRequest;
|
||||
use Mcp\Server\MethodHandlerInterface;
|
||||
|
||||
/**
|
||||
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
|
||||
*/
|
||||
final class GetPromptHandler implements MethodHandlerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly PromptGetterInterface $getter,
|
||||
) {
|
||||
}
|
||||
|
||||
public function supports(HasMethodInterface $message): bool
|
||||
{
|
||||
return $message instanceof GetPromptRequest;
|
||||
}
|
||||
|
||||
public function handle(GetPromptRequest|HasMethodInterface $message): Response|Error
|
||||
{
|
||||
\assert($message instanceof GetPromptRequest);
|
||||
|
||||
try {
|
||||
$result = $this->getter->get($message);
|
||||
} catch (ExceptionInterface) {
|
||||
return Error::forInternalError('Error while handling prompt', $message->getId());
|
||||
}
|
||||
|
||||
return new Response($message->getId(), $result);
|
||||
}
|
||||
}
|
||||
|
|
@ -11,35 +11,36 @@
|
|||
|
||||
namespace Mcp\Server\RequestHandler;
|
||||
|
||||
use Mcp\Message\Request;
|
||||
use Mcp\Message\Response;
|
||||
use Mcp\Schema\Implementation;
|
||||
use Mcp\Schema\JsonRpc\HasMethodInterface;
|
||||
use Mcp\Schema\JsonRpc\Response;
|
||||
use Mcp\Schema\Request\InitializeRequest;
|
||||
use Mcp\Schema\Result\InitializeResult;
|
||||
use Mcp\Schema\ServerCapabilities;
|
||||
use Mcp\Server\MethodHandlerInterface;
|
||||
|
||||
/**
|
||||
* @author Christopher Hertel <mail@christopher-hertel.de>
|
||||
*/
|
||||
final class InitializeHandler extends BaseRequestHandler
|
||||
final class InitializeHandler implements MethodHandlerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly string $name = 'app',
|
||||
private readonly string $version = 'dev',
|
||||
public readonly ?ServerCapabilities $capabilities = new ServerCapabilities(),
|
||||
public readonly ?Implementation $serverInfo = new Implementation(),
|
||||
) {
|
||||
}
|
||||
|
||||
public function createResponse(Request $message): Response
|
||||
public function supports(HasMethodInterface $message): bool
|
||||
{
|
||||
return new Response($message->id, [
|
||||
'protocolVersion' => '2025-03-26',
|
||||
'capabilities' => [
|
||||
'prompts' => ['listChanged' => false],
|
||||
'tools' => ['listChanged' => false],
|
||||
'resources' => ['listChanged' => false, 'subscribe' => false],
|
||||
],
|
||||
'serverInfo' => ['name' => $this->name, 'version' => $this->version],
|
||||
]);
|
||||
return $message instanceof InitializeRequest;
|
||||
}
|
||||
|
||||
protected function supportedMethod(): string
|
||||
public function handle(InitializeRequest|HasMethodInterface $message): Response
|
||||
{
|
||||
return 'initialize';
|
||||
\assert($message instanceof InitializeRequest);
|
||||
|
||||
return new Response($message->getId(),
|
||||
new InitializeResult($this->capabilities, $this->serverInfo),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
64
src/Server/RequestHandler/ListPromptsHandler.php
Normal file
64
src/Server/RequestHandler/ListPromptsHandler.php
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the official PHP MCP SDK.
|
||||
*
|
||||
* A collaboration between Symfony and the PHP Foundation.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Mcp\Server\RequestHandler;
|
||||
|
||||
use Mcp\Capability\Prompt\CollectionInterface;
|
||||
use Mcp\Schema\JsonRpc\HasMethodInterface;
|
||||
use Mcp\Schema\JsonRpc\Response;
|
||||
use Mcp\Schema\Prompt;
|
||||
use Mcp\Schema\PromptArgument;
|
||||
use Mcp\Schema\Request\ListPromptsRequest;
|
||||
use Mcp\Schema\Result\ListPromptsResult;
|
||||
use Mcp\Server\MethodHandlerInterface;
|
||||
|
||||
/**
|
||||
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
|
||||
*/
|
||||
final class ListPromptsHandler implements MethodHandlerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly CollectionInterface $collection,
|
||||
private readonly int $pageSize = 20,
|
||||
) {
|
||||
}
|
||||
|
||||
public function supports(HasMethodInterface $message): bool
|
||||
{
|
||||
return $message instanceof ListPromptsRequest;
|
||||
}
|
||||
|
||||
public function handle(ListPromptsRequest|HasMethodInterface $message): Response
|
||||
{
|
||||
\assert($message instanceof ListPromptsRequest);
|
||||
|
||||
$cursor = null;
|
||||
$prompts = [];
|
||||
|
||||
$metadataList = $this->collection->getMetadata($this->pageSize, $message->cursor);
|
||||
|
||||
foreach ($metadataList as $metadata) {
|
||||
$cursor = $metadata->getName();
|
||||
$prompts[] = new Prompt(
|
||||
$metadata->getName(),
|
||||
$metadata->getDescription(),
|
||||
array_map(fn (array $data) => PromptArgument::fromArray($data), $metadata->getArguments()),
|
||||
);
|
||||
}
|
||||
|
||||
$nextCursor = (null !== $cursor && \count($prompts) === $this->pageSize) ? $cursor : null;
|
||||
|
||||
return new Response(
|
||||
$message->getId(),
|
||||
new ListPromptsResult($prompts, $nextCursor),
|
||||
);
|
||||
}
|
||||
}
|
||||
66
src/Server/RequestHandler/ListResourcesHandler.php
Normal file
66
src/Server/RequestHandler/ListResourcesHandler.php
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the official PHP MCP SDK.
|
||||
*
|
||||
* A collaboration between Symfony and the PHP Foundation.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Mcp\Server\RequestHandler;
|
||||
|
||||
use Mcp\Capability\Resource\CollectionInterface;
|
||||
use Mcp\Schema\JsonRpc\HasMethodInterface;
|
||||
use Mcp\Schema\JsonRpc\Response;
|
||||
use Mcp\Schema\Request\ListResourcesRequest;
|
||||
use Mcp\Schema\Resource;
|
||||
use Mcp\Schema\Result\ListResourcesResult;
|
||||
use Mcp\Server\MethodHandlerInterface;
|
||||
|
||||
/**
|
||||
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
|
||||
*/
|
||||
final class ListResourcesHandler implements MethodHandlerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly CollectionInterface $collection,
|
||||
private readonly int $pageSize = 20,
|
||||
) {
|
||||
}
|
||||
|
||||
public function supports(HasMethodInterface $message): bool
|
||||
{
|
||||
return $message instanceof ListResourcesRequest;
|
||||
}
|
||||
|
||||
public function handle(ListResourcesRequest|HasMethodInterface $message): Response
|
||||
{
|
||||
\assert($message instanceof ListResourcesRequest);
|
||||
|
||||
$cursor = null;
|
||||
$resources = [];
|
||||
|
||||
$metadataList = $this->collection->getMetadata($this->pageSize, $message->cursor);
|
||||
|
||||
foreach ($metadataList as $metadata) {
|
||||
$cursor = $metadata->getUri();
|
||||
$resources[] = new Resource(
|
||||
$metadata->getUri(),
|
||||
$metadata->getName(),
|
||||
$metadata->getDescription(),
|
||||
$metadata->getMimeType(),
|
||||
null,
|
||||
$metadata->getSize(),
|
||||
);
|
||||
}
|
||||
|
||||
$nextCursor = (null !== $cursor && \count($resources) === $this->pageSize) ? $cursor : null;
|
||||
|
||||
return new Response(
|
||||
$message->getId(),
|
||||
new ListResourcesResult($resources, $nextCursor),
|
||||
);
|
||||
}
|
||||
}
|
||||
69
src/Server/RequestHandler/ListToolsHandler.php
Normal file
69
src/Server/RequestHandler/ListToolsHandler.php
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the official PHP MCP SDK.
|
||||
*
|
||||
* A collaboration between Symfony and the PHP Foundation.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Mcp\Server\RequestHandler;
|
||||
|
||||
use Mcp\Capability\Tool\CollectionInterface;
|
||||
use Mcp\Schema\JsonRpc\HasMethodInterface;
|
||||
use Mcp\Schema\JsonRpc\Response;
|
||||
use Mcp\Schema\Request\ListToolsRequest;
|
||||
use Mcp\Schema\Result\ListToolsResult;
|
||||
use Mcp\Schema\Tool;
|
||||
use Mcp\Server\MethodHandlerInterface;
|
||||
|
||||
/**
|
||||
* @author Christopher Hertel <mail@christopher-hertel.de>
|
||||
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
|
||||
*/
|
||||
final class ListToolsHandler implements MethodHandlerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly CollectionInterface $collection,
|
||||
private readonly int $pageSize = 20,
|
||||
) {
|
||||
}
|
||||
|
||||
public function supports(HasMethodInterface $message): bool
|
||||
{
|
||||
return $message instanceof ListToolsRequest;
|
||||
}
|
||||
|
||||
public function handle(ListToolsRequest|HasMethodInterface $message): Response
|
||||
{
|
||||
\assert($message instanceof ListToolsRequest);
|
||||
|
||||
$cursor = null;
|
||||
$tools = [];
|
||||
|
||||
$metadataList = $this->collection->getMetadata($this->pageSize, $message->cursor);
|
||||
|
||||
foreach ($metadataList as $tool) {
|
||||
$cursor = $tool->getName();
|
||||
$inputSchema = $tool->getInputSchema();
|
||||
$tools[] = new Tool(
|
||||
$tool->getName(),
|
||||
[] === $inputSchema ? [
|
||||
'type' => 'object',
|
||||
'$schema' => 'http://json-schema.org/draft-07/schema#',
|
||||
] : $inputSchema,
|
||||
$tool->getDescription(),
|
||||
null,
|
||||
);
|
||||
}
|
||||
|
||||
$nextCursor = (null !== $cursor && \count($tools) === $this->pageSize) ? $cursor : null;
|
||||
|
||||
return new Response(
|
||||
$message->getId(),
|
||||
new ListToolsResult($tools, $nextCursor),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -11,21 +11,26 @@
|
|||
|
||||
namespace Mcp\Server\RequestHandler;
|
||||
|
||||
use Mcp\Message\Request;
|
||||
use Mcp\Message\Response;
|
||||
use Mcp\Schema\JsonRpc\HasMethodInterface;
|
||||
use Mcp\Schema\JsonRpc\Response;
|
||||
use Mcp\Schema\Request\PingRequest;
|
||||
use Mcp\Schema\Result\EmptyResult;
|
||||
use Mcp\Server\MethodHandlerInterface;
|
||||
|
||||
/**
|
||||
* @author Christopher Hertel <mail@christopher-hertel.de>
|
||||
*/
|
||||
final class PingHandler extends BaseRequestHandler
|
||||
final class PingHandler implements MethodHandlerInterface
|
||||
{
|
||||
public function createResponse(Request $message): Response
|
||||
public function supports(HasMethodInterface $message): bool
|
||||
{
|
||||
return new Response($message->id, []);
|
||||
return $message instanceof PingRequest;
|
||||
}
|
||||
|
||||
protected function supportedMethod(): string
|
||||
public function handle(PingRequest|HasMethodInterface $message): Response
|
||||
{
|
||||
return 'ping';
|
||||
\assert($message instanceof PingRequest);
|
||||
|
||||
return new Response($message->getId(), new EmptyResult());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,83 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the official PHP MCP SDK.
|
||||
*
|
||||
* A collaboration between Symfony and the PHP Foundation.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Mcp\Server\RequestHandler;
|
||||
|
||||
use Mcp\Capability\Prompt\PromptGet;
|
||||
use Mcp\Capability\Prompt\PromptGetterInterface;
|
||||
use Mcp\Exception\ExceptionInterface;
|
||||
use Mcp\Exception\InvalidArgumentException;
|
||||
use Mcp\Message\Error;
|
||||
use Mcp\Message\Request;
|
||||
use Mcp\Message\Response;
|
||||
|
||||
/**
|
||||
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
|
||||
*/
|
||||
final class PromptGetHandler extends BaseRequestHandler
|
||||
{
|
||||
public function __construct(
|
||||
private readonly PromptGetterInterface $getter,
|
||||
) {
|
||||
}
|
||||
|
||||
public function createResponse(Request $message): Response|Error
|
||||
{
|
||||
$name = $message->params['name'];
|
||||
$arguments = $message->params['arguments'] ?? [];
|
||||
|
||||
try {
|
||||
$result = $this->getter->get(new PromptGet(uniqid('', true), $name, $arguments));
|
||||
} catch (ExceptionInterface) {
|
||||
return Error::internalError($message->id, 'Error while handling prompt');
|
||||
}
|
||||
|
||||
$messages = [];
|
||||
foreach ($result->messages as $resultMessage) {
|
||||
$content = match ($resultMessage->type) {
|
||||
'text' => [
|
||||
'type' => 'text',
|
||||
'text' => $resultMessage->result,
|
||||
],
|
||||
'image', 'audio' => [
|
||||
'type' => $resultMessage->type,
|
||||
'data' => $resultMessage->result,
|
||||
'mimeType' => $resultMessage->mimeType,
|
||||
],
|
||||
'resource' => [
|
||||
'type' => 'resource',
|
||||
'resource' => [
|
||||
'uri' => $resultMessage->uri,
|
||||
'mimeType' => $resultMessage->mimeType,
|
||||
'text' => $resultMessage->result,
|
||||
],
|
||||
],
|
||||
// TODO better exception
|
||||
default => throw new InvalidArgumentException('Unsupported PromptGet result type: '.$resultMessage->type),
|
||||
};
|
||||
|
||||
$messages[] = [
|
||||
'role' => $resultMessage->role,
|
||||
'content' => $content,
|
||||
];
|
||||
}
|
||||
|
||||
return new Response($message->id, [
|
||||
'description' => $result->description,
|
||||
'messages' => $messages,
|
||||
]);
|
||||
}
|
||||
|
||||
protected function supportedMethod(): string
|
||||
{
|
||||
return 'prompts/get';
|
||||
}
|
||||
}
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the official PHP MCP SDK.
|
||||
*
|
||||
* A collaboration between Symfony and the PHP Foundation.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Mcp\Server\RequestHandler;
|
||||
|
||||
use Mcp\Capability\Prompt\CollectionInterface;
|
||||
use Mcp\Message\Request;
|
||||
use Mcp\Message\Response;
|
||||
|
||||
/**
|
||||
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
|
||||
*/
|
||||
final class PromptListHandler extends BaseRequestHandler
|
||||
{
|
||||
public function __construct(
|
||||
private readonly CollectionInterface $collection,
|
||||
private readonly int $pageSize = 20,
|
||||
) {
|
||||
}
|
||||
|
||||
public function createResponse(Request $message): Response
|
||||
{
|
||||
$nextCursor = null;
|
||||
$prompts = [];
|
||||
|
||||
$metadataList = $this->collection->getMetadata(
|
||||
$this->pageSize,
|
||||
$message->params['cursor'] ?? null
|
||||
);
|
||||
|
||||
foreach ($metadataList as $metadata) {
|
||||
$nextCursor = $metadata->getName();
|
||||
$result = [
|
||||
'name' => $metadata->getName(),
|
||||
];
|
||||
|
||||
$description = $metadata->getDescription();
|
||||
if (null !== $description) {
|
||||
$result['description'] = $description;
|
||||
}
|
||||
|
||||
$arguments = [];
|
||||
foreach ($metadata->getArguments() as $data) {
|
||||
$argument = [
|
||||
'name' => $data['name'],
|
||||
'required' => $data['required'] ?? false,
|
||||
];
|
||||
|
||||
if (isset($data['description'])) {
|
||||
$argument['description'] = $data['description'];
|
||||
}
|
||||
$arguments[] = $argument;
|
||||
}
|
||||
|
||||
if ([] !== $arguments) {
|
||||
$result['arguments'] = $arguments;
|
||||
}
|
||||
|
||||
$prompts[] = $result;
|
||||
}
|
||||
|
||||
$result = [
|
||||
'prompts' => $prompts,
|
||||
];
|
||||
|
||||
if (null !== $nextCursor && \count($prompts) === $this->pageSize) {
|
||||
$result['nextCursor'] = $nextCursor;
|
||||
}
|
||||
|
||||
return new Response($message->id, $result);
|
||||
}
|
||||
|
||||
protected function supportedMethod(): string
|
||||
{
|
||||
return 'prompts/list';
|
||||
}
|
||||
}
|
||||
52
src/Server/RequestHandler/ReadResourceHandler.php
Normal file
52
src/Server/RequestHandler/ReadResourceHandler.php
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the official PHP MCP SDK.
|
||||
*
|
||||
* A collaboration between Symfony and the PHP Foundation.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Mcp\Server\RequestHandler;
|
||||
|
||||
use Mcp\Capability\Resource\ResourceReaderInterface;
|
||||
use Mcp\Exception\ExceptionInterface;
|
||||
use Mcp\Exception\ResourceNotFoundException;
|
||||
use Mcp\Schema\JsonRpc\Error;
|
||||
use Mcp\Schema\JsonRpc\HasMethodInterface;
|
||||
use Mcp\Schema\JsonRpc\Response;
|
||||
use Mcp\Schema\Request\ReadResourceRequest;
|
||||
use Mcp\Server\MethodHandlerInterface;
|
||||
|
||||
/**
|
||||
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
|
||||
*/
|
||||
final class ReadResourceHandler implements MethodHandlerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ResourceReaderInterface $reader,
|
||||
) {
|
||||
}
|
||||
|
||||
public function supports(HasMethodInterface $message): bool
|
||||
{
|
||||
return $message instanceof ReadResourceRequest;
|
||||
}
|
||||
|
||||
public function handle(ReadResourceRequest|HasMethodInterface $message): Response|Error
|
||||
{
|
||||
\assert($message instanceof ReadResourceRequest);
|
||||
|
||||
try {
|
||||
$result = $this->reader->read($message);
|
||||
} catch (ResourceNotFoundException $e) {
|
||||
return new Error($message->getId(), Error::RESOURCE_NOT_FOUND, $e->getMessage());
|
||||
} catch (ExceptionInterface) {
|
||||
return Error::forInternalError('Error while reading resource', $message->getId());
|
||||
}
|
||||
|
||||
return new Response($message->getId(), $result);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the official PHP MCP SDK.
|
||||
*
|
||||
* A collaboration between Symfony and the PHP Foundation.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Mcp\Server\RequestHandler;
|
||||
|
||||
use Mcp\Capability\Resource\CollectionInterface;
|
||||
use Mcp\Message\Request;
|
||||
use Mcp\Message\Response;
|
||||
|
||||
/**
|
||||
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
|
||||
*/
|
||||
final class ResourceListHandler extends BaseRequestHandler
|
||||
{
|
||||
public function __construct(
|
||||
private readonly CollectionInterface $collection,
|
||||
private readonly int $pageSize = 20,
|
||||
) {
|
||||
}
|
||||
|
||||
public function createResponse(Request $message): Response
|
||||
{
|
||||
$nextCursor = null;
|
||||
$resources = [];
|
||||
|
||||
$metadataList = $this->collection->getMetadata(
|
||||
$this->pageSize,
|
||||
$message->params['cursor'] ?? null
|
||||
);
|
||||
|
||||
foreach ($metadataList as $metadata) {
|
||||
$nextCursor = $metadata->getUri();
|
||||
$result = [
|
||||
'uri' => $metadata->getUri(),
|
||||
'name' => $metadata->getName(),
|
||||
];
|
||||
|
||||
$description = $metadata->getDescription();
|
||||
if (null !== $description) {
|
||||
$result['description'] = $description;
|
||||
}
|
||||
|
||||
$mimeType = $metadata->getMimeType();
|
||||
if (null !== $mimeType) {
|
||||
$result['mimeType'] = $mimeType;
|
||||
}
|
||||
|
||||
$size = $metadata->getSize();
|
||||
if (null !== $size) {
|
||||
$result['size'] = $size;
|
||||
}
|
||||
|
||||
$resources[] = $result;
|
||||
}
|
||||
|
||||
$result = [
|
||||
'resources' => $resources,
|
||||
];
|
||||
|
||||
if (null !== $nextCursor && \count($resources) === $this->pageSize) {
|
||||
$result['nextCursor'] = $nextCursor;
|
||||
}
|
||||
|
||||
return new Response($message->id, $result);
|
||||
}
|
||||
|
||||
protected function supportedMethod(): string
|
||||
{
|
||||
return 'resources/list';
|
||||
}
|
||||
}
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the official PHP MCP SDK.
|
||||
*
|
||||
* A collaboration between Symfony and the PHP Foundation.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Mcp\Server\RequestHandler;
|
||||
|
||||
use Mcp\Capability\Resource\ResourceRead;
|
||||
use Mcp\Capability\Resource\ResourceReaderInterface;
|
||||
use Mcp\Exception\ExceptionInterface;
|
||||
use Mcp\Exception\ResourceNotFoundException;
|
||||
use Mcp\Message\Error;
|
||||
use Mcp\Message\Request;
|
||||
use Mcp\Message\Response;
|
||||
|
||||
/**
|
||||
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
|
||||
*/
|
||||
final class ResourceReadHandler extends BaseRequestHandler
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ResourceReaderInterface $reader,
|
||||
) {
|
||||
}
|
||||
|
||||
public function createResponse(Request $message): Response|Error
|
||||
{
|
||||
$uri = $message->params['uri'];
|
||||
|
||||
try {
|
||||
$result = $this->reader->read(new ResourceRead(uniqid('', true), $uri));
|
||||
} catch (ResourceNotFoundException $e) {
|
||||
return new Error($message->id, Error::RESOURCE_NOT_FOUND, $e->getMessage());
|
||||
} catch (ExceptionInterface) {
|
||||
return Error::internalError($message->id, 'Error while reading resource');
|
||||
}
|
||||
|
||||
return new Response($message->id, [
|
||||
'contents' => [
|
||||
[
|
||||
'uri' => $result->uri,
|
||||
'mimeType' => $result->mimeType,
|
||||
$result->type => $result->result,
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
protected function supportedMethod(): string
|
||||
{
|
||||
return 'resources/read';
|
||||
}
|
||||
}
|
||||
|
|
@ -1,76 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the official PHP MCP SDK.
|
||||
*
|
||||
* A collaboration between Symfony and the PHP Foundation.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Mcp\Server\RequestHandler;
|
||||
|
||||
use Mcp\Capability\Tool\ToolCall;
|
||||
use Mcp\Capability\Tool\ToolExecutorInterface;
|
||||
use Mcp\Exception\ExceptionInterface;
|
||||
use Mcp\Exception\InvalidArgumentException;
|
||||
use Mcp\Message\Error;
|
||||
use Mcp\Message\Request;
|
||||
use Mcp\Message\Response;
|
||||
|
||||
/**
|
||||
* @author Christopher Hertel <mail@christopher-hertel.de>
|
||||
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
|
||||
*/
|
||||
final class ToolCallHandler extends BaseRequestHandler
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ToolExecutorInterface $toolExecutor,
|
||||
) {
|
||||
}
|
||||
|
||||
public function createResponse(Request $message): Response|Error
|
||||
{
|
||||
$name = $message->params['name'];
|
||||
$arguments = $message->params['arguments'] ?? [];
|
||||
|
||||
try {
|
||||
$result = $this->toolExecutor->call(new ToolCall(uniqid('', true), $name, $arguments));
|
||||
} catch (ExceptionInterface) {
|
||||
return Error::internalError($message->id, 'Error while executing tool');
|
||||
}
|
||||
|
||||
$content = match ($result->type) {
|
||||
'text' => [
|
||||
'type' => 'text',
|
||||
'text' => $result->result,
|
||||
],
|
||||
'image', 'audio' => [
|
||||
'type' => $result->type,
|
||||
'data' => $result->result,
|
||||
'mimeType' => $result->mimeType,
|
||||
],
|
||||
'resource' => [
|
||||
'type' => 'resource',
|
||||
'resource' => [
|
||||
'uri' => $result->uri,
|
||||
'mimeType' => $result->mimeType,
|
||||
'text' => $result->result,
|
||||
],
|
||||
],
|
||||
// TODO better exception
|
||||
default => throw new InvalidArgumentException('Unsupported tool result type: '.$result->type),
|
||||
};
|
||||
|
||||
return new Response($message->id, [
|
||||
'content' => [$content], // TODO: allow multiple `ToolCallResult`s in the future
|
||||
'isError' => $result->isError,
|
||||
]);
|
||||
}
|
||||
|
||||
protected function supportedMethod(): string
|
||||
{
|
||||
return 'tools/call';
|
||||
}
|
||||
}
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the official PHP MCP SDK.
|
||||
*
|
||||
* A collaboration between Symfony and the PHP Foundation.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Mcp\Server\RequestHandler;
|
||||
|
||||
use Mcp\Capability\Tool\CollectionInterface;
|
||||
use Mcp\Message\Request;
|
||||
use Mcp\Message\Response;
|
||||
|
||||
/**
|
||||
* @author Christopher Hertel <mail@christopher-hertel.de>
|
||||
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
|
||||
*/
|
||||
final class ToolListHandler extends BaseRequestHandler
|
||||
{
|
||||
public function __construct(
|
||||
private readonly CollectionInterface $collection,
|
||||
private readonly int $pageSize = 20,
|
||||
) {
|
||||
}
|
||||
|
||||
public function createResponse(Request $message): Response
|
||||
{
|
||||
$nextCursor = null;
|
||||
$tools = [];
|
||||
|
||||
$metadataList = $this->collection->getMetadata(
|
||||
$this->pageSize,
|
||||
$message->params['cursor'] ?? null
|
||||
);
|
||||
|
||||
foreach ($metadataList as $tool) {
|
||||
$nextCursor = $tool->getName();
|
||||
$inputSchema = $tool->getInputSchema();
|
||||
$tools[] = [
|
||||
'name' => $tool->getName(),
|
||||
'description' => $tool->getDescription(),
|
||||
'inputSchema' => [] === $inputSchema ? [
|
||||
'type' => 'object',
|
||||
'$schema' => 'http://json-schema.org/draft-07/schema#',
|
||||
] : $inputSchema,
|
||||
];
|
||||
}
|
||||
|
||||
$result = [
|
||||
'tools' => $tools,
|
||||
];
|
||||
|
||||
if (null !== $nextCursor && \count($tools) === $this->pageSize) {
|
||||
$result['nextCursor'] = $nextCursor;
|
||||
}
|
||||
|
||||
return new Response($message->id, $result);
|
||||
}
|
||||
|
||||
protected function supportedMethod(): string
|
||||
{
|
||||
return 'tools/list';
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue