[Server] Reorganize server handlers and transport structure for better organization (#86)
* refactor: reorganize server handlers and transport structure * feat: move the test files to corresponding directories * fix: linter issues * fix: correct PHPDoc formatting and streamline handler imports * refactor: update import statement for ServerBuilder to Builder
This commit is contained in:
parent
3dcd2f717a
commit
41486bfaa4
11 changed files with 65 additions and 67 deletions
304
src/Server/Handler/JsonRpcHandler.php
Normal file
304
src/Server/Handler/JsonRpcHandler.php
Normal file
|
|
@ -0,0 +1,304 @@
|
|||
<?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\Handler;
|
||||
|
||||
use Mcp\Capability\Prompt\PromptGetterInterface;
|
||||
use Mcp\Capability\Registry\ReferenceProviderInterface;
|
||||
use Mcp\Capability\Registry\ReferenceRegistryInterface;
|
||||
use Mcp\Capability\Resource\ResourceReaderInterface;
|
||||
use Mcp\Capability\Tool\ToolCallerInterface;
|
||||
use Mcp\Exception\ExceptionInterface;
|
||||
use Mcp\Exception\HandlerNotFoundException;
|
||||
use Mcp\Exception\InvalidInputMessageException;
|
||||
use Mcp\Exception\NotFoundExceptionInterface;
|
||||
use Mcp\JsonRpc\MessageFactory;
|
||||
use Mcp\Schema\Implementation;
|
||||
use Mcp\Schema\JsonRpc\Error;
|
||||
use Mcp\Schema\JsonRpc\HasMethodInterface;
|
||||
use Mcp\Schema\JsonRpc\Request;
|
||||
use Mcp\Schema\JsonRpc\Response;
|
||||
use Mcp\Schema\Request\InitializeRequest;
|
||||
use Mcp\Server\Handler;
|
||||
use Mcp\Server\Session\SessionFactoryInterface;
|
||||
use Mcp\Server\Session\SessionInterface;
|
||||
use Mcp\Server\Session\SessionStoreInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\NullLogger;
|
||||
use Symfony\Component\Uid\Uuid;
|
||||
|
||||
/**
|
||||
* @final
|
||||
*
|
||||
* @author Christopher Hertel <mail@christopher-hertel.de>
|
||||
*/
|
||||
class JsonRpcHandler
|
||||
{
|
||||
/**
|
||||
* @var array<int, MethodHandlerInterface>
|
||||
*/
|
||||
private readonly array $methodHandlers;
|
||||
|
||||
/**
|
||||
* @param iterable<int, MethodHandlerInterface> $methodHandlers
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly MessageFactory $messageFactory,
|
||||
private readonly SessionFactoryInterface $sessionFactory,
|
||||
private readonly SessionStoreInterface $sessionStore,
|
||||
iterable $methodHandlers,
|
||||
private readonly LoggerInterface $logger = new NullLogger(),
|
||||
) {
|
||||
$this->methodHandlers = $methodHandlers instanceof \Traversable ? iterator_to_array(
|
||||
$methodHandlers,
|
||||
) : $methodHandlers;
|
||||
}
|
||||
|
||||
public static function make(
|
||||
ReferenceRegistryInterface $registry,
|
||||
ReferenceProviderInterface $referenceProvider,
|
||||
Implementation $implementation,
|
||||
ToolCallerInterface $toolCaller,
|
||||
ResourceReaderInterface $resourceReader,
|
||||
PromptGetterInterface $promptGetter,
|
||||
SessionStoreInterface $sessionStore,
|
||||
SessionFactoryInterface $sessionFactory,
|
||||
LoggerInterface $logger = new NullLogger(),
|
||||
int $paginationLimit = 50,
|
||||
): self {
|
||||
return new self(
|
||||
messageFactory: MessageFactory::make(),
|
||||
sessionFactory: $sessionFactory,
|
||||
sessionStore: $sessionStore,
|
||||
methodHandlers: [
|
||||
new Notification\InitializedHandler(),
|
||||
new Handler\Request\InitializeHandler($registry->getCapabilities(), $implementation),
|
||||
new Handler\Request\PingHandler(),
|
||||
new Handler\Request\ListPromptsHandler($referenceProvider, $paginationLimit),
|
||||
new Handler\Request\GetPromptHandler($promptGetter),
|
||||
new Handler\Request\ListResourcesHandler($referenceProvider, $paginationLimit),
|
||||
new Handler\Request\ReadResourceHandler($resourceReader),
|
||||
new Handler\Request\CallToolHandler($toolCaller, $logger),
|
||||
new Handler\Request\ListToolsHandler($referenceProvider, $paginationLimit),
|
||||
],
|
||||
logger: $logger,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return iterable<array{string|null, array<string, mixed>}>
|
||||
*/
|
||||
public function process(string $input, ?Uuid $sessionId): iterable
|
||||
{
|
||||
$this->logger->info('Received message to process.', ['message' => $input]);
|
||||
|
||||
$this->runGarbageCollection();
|
||||
|
||||
try {
|
||||
$messages = iterator_to_array($this->messageFactory->create($input));
|
||||
} catch (\JsonException $e) {
|
||||
$this->logger->warning('Failed to decode json message.', ['exception' => $e]);
|
||||
$error = Error::forParseError($e->getMessage());
|
||||
yield [$this->encodeResponse($error), []];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$hasInitializeRequest = false;
|
||||
foreach ($messages as $message) {
|
||||
if ($message instanceof InitializeRequest) {
|
||||
$hasInitializeRequest = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$session = null;
|
||||
|
||||
if ($hasInitializeRequest) {
|
||||
// Spec: An initialize request must not be part of a batch.
|
||||
if (\count($messages) > 1) {
|
||||
$error = Error::forInvalidRequest('The "initialize" request MUST NOT be part of a batch.');
|
||||
yield [$this->encodeResponse($error), []];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Spec: An initialize request must not have a session ID.
|
||||
if ($sessionId) {
|
||||
$error = Error::forInvalidRequest('A session ID MUST NOT be sent with an "initialize" request.');
|
||||
yield [$this->encodeResponse($error), []];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$session = $this->sessionFactory->create($this->sessionStore);
|
||||
} else {
|
||||
if (!$sessionId) {
|
||||
$error = Error::forInvalidRequest('A valid session id is REQUIRED for non-initialize requests.');
|
||||
yield [$this->encodeResponse($error), ['status_code' => 400]];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$this->sessionStore->exists($sessionId)) {
|
||||
$error = Error::forInvalidRequest('Session not found or has expired.');
|
||||
yield [$this->encodeResponse($error), ['status_code' => 404]];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$session = $this->sessionFactory->createWithId($sessionId, $this->sessionStore);
|
||||
}
|
||||
|
||||
foreach ($messages as $message) {
|
||||
if ($message instanceof InvalidInputMessageException) {
|
||||
$this->logger->warning('Failed to create message.', ['exception' => $message]);
|
||||
$error = Error::forInvalidRequest($message->getMessage());
|
||||
yield [$this->encodeResponse($error), []];
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->logger->debug(\sprintf('Decoded incoming message "%s".', $message::class), [
|
||||
'method' => $message->getMethod(),
|
||||
]);
|
||||
|
||||
$messageId = $message instanceof Request ? $message->getId() : 0;
|
||||
|
||||
try {
|
||||
$response = $this->handle($message, $session);
|
||||
yield [$this->encodeResponse($response), ['session_id' => $session->getId()]];
|
||||
} catch (\DomainException) {
|
||||
yield [null, []];
|
||||
} catch (NotFoundExceptionInterface $e) {
|
||||
$this->logger->warning(
|
||||
\sprintf('Failed to create response: %s', $e->getMessage()),
|
||||
['exception' => $e],
|
||||
);
|
||||
|
||||
$error = Error::forMethodNotFound($e->getMessage(), $messageId);
|
||||
yield [$this->encodeResponse($error), []];
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
$this->logger->warning(\sprintf('Invalid argument: %s', $e->getMessage()), ['exception' => $e]);
|
||||
|
||||
$error = Error::forInvalidParams($e->getMessage(), $messageId);
|
||||
yield [$this->encodeResponse($error), []];
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->critical(\sprintf('Uncaught exception: %s', $e->getMessage()), ['exception' => $e]);
|
||||
|
||||
$error = Error::forInternalError($e->getMessage(), $messageId);
|
||||
yield [$this->encodeResponse($error), []];
|
||||
}
|
||||
}
|
||||
|
||||
$session->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a response to JSON, handling encoding errors gracefully.
|
||||
*/
|
||||
private function encodeResponse(Response|Error|null $response): ?string
|
||||
{
|
||||
if (null === $response) {
|
||||
$this->logger->info('The handler created an empty response.');
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->logger->info('Encoding response.', ['response' => $response]);
|
||||
|
||||
try {
|
||||
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);
|
||||
} catch (\JsonException $e) {
|
||||
$this->logger->error('Failed to encode response to JSON.', [
|
||||
'message_id' => $response->getId(),
|
||||
'exception' => $e,
|
||||
]);
|
||||
|
||||
$fallbackError = new Error(
|
||||
id: $response->getId(),
|
||||
code: Error::INTERNAL_ERROR,
|
||||
message: 'Response could not be encoded to JSON'
|
||||
);
|
||||
|
||||
return json_encode($fallbackError, \JSON_THROW_ON_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the handler does support the message, but does not create a response, other handlers will be tried.
|
||||
*
|
||||
* @throws NotFoundExceptionInterface When no handler is found for the request method
|
||||
* @throws ExceptionInterface When a request handler throws an exception
|
||||
*/
|
||||
private function handle(HasMethodInterface $message, SessionInterface $session): Response|Error|null
|
||||
{
|
||||
$this->logger->info(\sprintf('Handling message for method "%s".', $message::getMethod()), [
|
||||
'message' => $message,
|
||||
]);
|
||||
|
||||
$handled = false;
|
||||
foreach ($this->methodHandlers as $handler) {
|
||||
if (!$handler->supports($message)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$return = $handler->handle($message, $session);
|
||||
$handled = true;
|
||||
|
||||
$this->logger->debug(\sprintf('Message handled by "%s".', $handler::class), [
|
||||
'method' => $message::getMethod(),
|
||||
'response' => $return,
|
||||
]);
|
||||
|
||||
if (null !== $return) {
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
|
||||
if ($handled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
throw new HandlerNotFoundException(\sprintf('No handler found for method "%s".', $message::getMethod()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Run garbage collection on expired sessions.
|
||||
* Uses the session store's internal TTL configuration.
|
||||
*/
|
||||
private function runGarbageCollection(): void
|
||||
{
|
||||
if (random_int(0, 100) > 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
$deletedSessions = $this->sessionStore->gc();
|
||||
if (!empty($deletedSessions)) {
|
||||
$this->logger->debug('Garbage collected expired sessions.', [
|
||||
'count' => \count($deletedSessions),
|
||||
'session_ids' => array_map(fn (Uuid $id) => $id->toRfc4122(), $deletedSessions),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy a specific session.
|
||||
*/
|
||||
public function destroySession(Uuid $sessionId): void
|
||||
{
|
||||
$this->sessionStore->destroy($sessionId);
|
||||
$this->logger->info('Session destroyed.', ['session_id' => $sessionId->toRfc4122()]);
|
||||
}
|
||||
}
|
||||
32
src/Server/Handler/MethodHandlerInterface.php
Normal file
32
src/Server/Handler/MethodHandlerInterface.php
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<?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\Handler;
|
||||
|
||||
use Mcp\Exception\ExceptionInterface;
|
||||
use Mcp\Schema\JsonRpc\Error;
|
||||
use Mcp\Schema\JsonRpc\HasMethodInterface;
|
||||
use Mcp\Schema\JsonRpc\Request;
|
||||
use Mcp\Schema\JsonRpc\Response;
|
||||
use Mcp\Server\Session\SessionInterface;
|
||||
|
||||
/**
|
||||
* @author Christopher Hertel <mail@christopher-hertel.de>
|
||||
*/
|
||||
interface MethodHandlerInterface
|
||||
{
|
||||
public function supports(HasMethodInterface $message): bool;
|
||||
|
||||
/**
|
||||
* @throws ExceptionInterface When the handler encounters an error processing the request
|
||||
*/
|
||||
public function handle(HasMethodInterface $message, SessionInterface $session): Response|Error|null;
|
||||
}
|
||||
37
src/Server/Handler/Notification/InitializedHandler.php
Normal file
37
src/Server/Handler/Notification/InitializedHandler.php
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
<?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\Handler\Notification;
|
||||
|
||||
use Mcp\Schema\JsonRpc\Error;
|
||||
use Mcp\Schema\JsonRpc\HasMethodInterface;
|
||||
use Mcp\Schema\JsonRpc\Response;
|
||||
use Mcp\Schema\Notification\InitializedNotification;
|
||||
use Mcp\Server\Handler\MethodHandlerInterface;
|
||||
use Mcp\Server\Session\SessionInterface;
|
||||
|
||||
/**
|
||||
* @author Christopher Hertel <mail@christopher-hertel.de>
|
||||
*/
|
||||
final class InitializedHandler implements MethodHandlerInterface
|
||||
{
|
||||
public function supports(HasMethodInterface $message): bool
|
||||
{
|
||||
return $message instanceof InitializedNotification;
|
||||
}
|
||||
|
||||
public function handle(InitializedNotification|HasMethodInterface $message, SessionInterface $session): Response|Error|null
|
||||
{
|
||||
$session->set('initialized', true);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
62
src/Server/Handler/Request/CallToolHandler.php
Normal file
62
src/Server/Handler/Request/CallToolHandler.php
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
<?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\Handler\Request;
|
||||
|
||||
use Mcp\Capability\Tool\ToolCallerInterface;
|
||||
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\Handler\MethodHandlerInterface;
|
||||
use Mcp\Server\Session\SessionInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\NullLogger;
|
||||
|
||||
/**
|
||||
* @author Christopher Hertel <mail@christopher-hertel.de>
|
||||
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
|
||||
*/
|
||||
final class CallToolHandler implements MethodHandlerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ToolCallerInterface $toolCaller,
|
||||
private readonly LoggerInterface $logger = new NullLogger(),
|
||||
) {
|
||||
}
|
||||
|
||||
public function supports(HasMethodInterface $message): bool
|
||||
{
|
||||
return $message instanceof CallToolRequest;
|
||||
}
|
||||
|
||||
public function handle(CallToolRequest|HasMethodInterface $message, SessionInterface $session): Response|Error
|
||||
{
|
||||
\assert($message instanceof CallToolRequest);
|
||||
|
||||
try {
|
||||
$content = $this->toolCaller->call($message);
|
||||
} catch (ExceptionInterface $exception) {
|
||||
$this->logger->error(
|
||||
\sprintf('Error while executing tool "%s": "%s".', $message->name, $exception->getMessage()),
|
||||
[
|
||||
'tool' => $message->name,
|
||||
'arguments' => $message->arguments,
|
||||
],
|
||||
);
|
||||
|
||||
return Error::forInternalError('Error while executing tool', $message->getId());
|
||||
}
|
||||
|
||||
return new Response($message->getId(), $content);
|
||||
}
|
||||
}
|
||||
50
src/Server/Handler/Request/GetPromptHandler.php
Normal file
50
src/Server/Handler/Request/GetPromptHandler.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\Handler\Request;
|
||||
|
||||
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\Handler\MethodHandlerInterface;
|
||||
use Mcp\Server\Session\SessionInterface;
|
||||
|
||||
/**
|
||||
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
|
||||
*/
|
||||
final class GetPromptHandler implements MethodHandlerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly PromptGetterInterface $promptGetter,
|
||||
) {
|
||||
}
|
||||
|
||||
public function supports(HasMethodInterface $message): bool
|
||||
{
|
||||
return $message instanceof GetPromptRequest;
|
||||
}
|
||||
|
||||
public function handle(GetPromptRequest|HasMethodInterface $message, SessionInterface $session): Response|Error
|
||||
{
|
||||
\assert($message instanceof GetPromptRequest);
|
||||
|
||||
try {
|
||||
$messages = $this->promptGetter->get($message);
|
||||
} catch (ExceptionInterface) {
|
||||
return Error::forInternalError('Error while handling prompt', $message->getId());
|
||||
}
|
||||
|
||||
return new Response($message->getId(), $messages);
|
||||
}
|
||||
}
|
||||
50
src/Server/Handler/Request/InitializeHandler.php
Normal file
50
src/Server/Handler/Request/InitializeHandler.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\Handler\Request;
|
||||
|
||||
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\Handler\MethodHandlerInterface;
|
||||
use Mcp\Server\Session\SessionInterface;
|
||||
|
||||
/**
|
||||
* @author Christopher Hertel <mail@christopher-hertel.de>
|
||||
*/
|
||||
final class InitializeHandler implements MethodHandlerInterface
|
||||
{
|
||||
public function __construct(
|
||||
public readonly ?ServerCapabilities $capabilities = new ServerCapabilities(),
|
||||
public readonly ?Implementation $serverInfo = new Implementation(),
|
||||
) {
|
||||
}
|
||||
|
||||
public function supports(HasMethodInterface $message): bool
|
||||
{
|
||||
return $message instanceof InitializeRequest;
|
||||
}
|
||||
|
||||
public function handle(InitializeRequest|HasMethodInterface $message, SessionInterface $session): Response
|
||||
{
|
||||
\assert($message instanceof InitializeRequest);
|
||||
|
||||
$session->set('client_info', $message->clientInfo->jsonSerialize());
|
||||
|
||||
return new Response(
|
||||
$message->getId(),
|
||||
new InitializeResult($this->capabilities, $this->serverInfo),
|
||||
);
|
||||
}
|
||||
}
|
||||
53
src/Server/Handler/Request/ListPromptsHandler.php
Normal file
53
src/Server/Handler/Request/ListPromptsHandler.php
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
<?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\Handler\Request;
|
||||
|
||||
use Mcp\Capability\Registry\ReferenceProviderInterface;
|
||||
use Mcp\Exception\InvalidCursorException;
|
||||
use Mcp\Schema\JsonRpc\HasMethodInterface;
|
||||
use Mcp\Schema\JsonRpc\Response;
|
||||
use Mcp\Schema\Request\ListPromptsRequest;
|
||||
use Mcp\Schema\Result\ListPromptsResult;
|
||||
use Mcp\Server\Handler\MethodHandlerInterface;
|
||||
use Mcp\Server\Session\SessionInterface;
|
||||
|
||||
/**
|
||||
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
|
||||
*/
|
||||
final class ListPromptsHandler implements MethodHandlerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ReferenceProviderInterface $registry,
|
||||
private readonly int $pageSize = 20,
|
||||
) {
|
||||
}
|
||||
|
||||
public function supports(HasMethodInterface $message): bool
|
||||
{
|
||||
return $message instanceof ListPromptsRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidCursorException
|
||||
*/
|
||||
public function handle(ListPromptsRequest|HasMethodInterface $message, SessionInterface $session): Response
|
||||
{
|
||||
\assert($message instanceof ListPromptsRequest);
|
||||
|
||||
$page = $this->registry->getPrompts($this->pageSize, $message->cursor);
|
||||
|
||||
return new Response(
|
||||
$message->getId(),
|
||||
new ListPromptsResult($page->references, $page->nextCursor),
|
||||
);
|
||||
}
|
||||
}
|
||||
53
src/Server/Handler/Request/ListResourcesHandler.php
Normal file
53
src/Server/Handler/Request/ListResourcesHandler.php
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
<?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\Handler\Request;
|
||||
|
||||
use Mcp\Capability\Registry\ReferenceProviderInterface;
|
||||
use Mcp\Exception\InvalidCursorException;
|
||||
use Mcp\Schema\JsonRpc\HasMethodInterface;
|
||||
use Mcp\Schema\JsonRpc\Response;
|
||||
use Mcp\Schema\Request\ListResourcesRequest;
|
||||
use Mcp\Schema\Result\ListResourcesResult;
|
||||
use Mcp\Server\Handler\MethodHandlerInterface;
|
||||
use Mcp\Server\Session\SessionInterface;
|
||||
|
||||
/**
|
||||
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
|
||||
*/
|
||||
final class ListResourcesHandler implements MethodHandlerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ReferenceProviderInterface $registry,
|
||||
private readonly int $pageSize = 20,
|
||||
) {
|
||||
}
|
||||
|
||||
public function supports(HasMethodInterface $message): bool
|
||||
{
|
||||
return $message instanceof ListResourcesRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidCursorException
|
||||
*/
|
||||
public function handle(ListResourcesRequest|HasMethodInterface $message, SessionInterface $session): Response
|
||||
{
|
||||
\assert($message instanceof ListResourcesRequest);
|
||||
|
||||
$page = $this->registry->getResources($this->pageSize, $message->cursor);
|
||||
|
||||
return new Response(
|
||||
$message->getId(),
|
||||
new ListResourcesResult($page->references, $page->nextCursor),
|
||||
);
|
||||
}
|
||||
}
|
||||
54
src/Server/Handler/Request/ListToolsHandler.php
Normal file
54
src/Server/Handler/Request/ListToolsHandler.php
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
<?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\Handler\Request;
|
||||
|
||||
use Mcp\Capability\Registry\ReferenceProviderInterface;
|
||||
use Mcp\Exception\InvalidCursorException;
|
||||
use Mcp\Schema\JsonRpc\HasMethodInterface;
|
||||
use Mcp\Schema\JsonRpc\Response;
|
||||
use Mcp\Schema\Request\ListToolsRequest;
|
||||
use Mcp\Schema\Result\ListToolsResult;
|
||||
use Mcp\Server\Handler\MethodHandlerInterface;
|
||||
use Mcp\Server\Session\SessionInterface;
|
||||
|
||||
/**
|
||||
* @author Christopher Hertel <mail@christopher-hertel.de>
|
||||
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
|
||||
*/
|
||||
final class ListToolsHandler implements MethodHandlerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ReferenceProviderInterface $registry,
|
||||
private readonly int $pageSize = 20,
|
||||
) {
|
||||
}
|
||||
|
||||
public function supports(HasMethodInterface $message): bool
|
||||
{
|
||||
return $message instanceof ListToolsRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InvalidCursorException When the cursor is invalid
|
||||
*/
|
||||
public function handle(ListToolsRequest|HasMethodInterface $message, SessionInterface $session): Response
|
||||
{
|
||||
\assert($message instanceof ListToolsRequest);
|
||||
|
||||
$page = $this->registry->getTools($this->pageSize, $message->cursor);
|
||||
|
||||
return new Response(
|
||||
$message->getId(),
|
||||
new ListToolsResult($page->references, $page->nextCursor),
|
||||
);
|
||||
}
|
||||
}
|
||||
37
src/Server/Handler/Request/PingHandler.php
Normal file
37
src/Server/Handler/Request/PingHandler.php
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
<?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\Handler\Request;
|
||||
|
||||
use Mcp\Schema\JsonRpc\HasMethodInterface;
|
||||
use Mcp\Schema\JsonRpc\Response;
|
||||
use Mcp\Schema\Request\PingRequest;
|
||||
use Mcp\Schema\Result\EmptyResult;
|
||||
use Mcp\Server\Handler\MethodHandlerInterface;
|
||||
use Mcp\Server\Session\SessionInterface;
|
||||
|
||||
/**
|
||||
* @author Christopher Hertel <mail@christopher-hertel.de>
|
||||
*/
|
||||
final class PingHandler implements MethodHandlerInterface
|
||||
{
|
||||
public function supports(HasMethodInterface $message): bool
|
||||
{
|
||||
return $message instanceof PingRequest;
|
||||
}
|
||||
|
||||
public function handle(PingRequest|HasMethodInterface $message, SessionInterface $session): Response
|
||||
{
|
||||
\assert($message instanceof PingRequest);
|
||||
|
||||
return new Response($message->getId(), new EmptyResult());
|
||||
}
|
||||
}
|
||||
53
src/Server/Handler/Request/ReadResourceHandler.php
Normal file
53
src/Server/Handler/Request/ReadResourceHandler.php
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
<?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\Handler\Request;
|
||||
|
||||
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\Handler\MethodHandlerInterface;
|
||||
use Mcp\Server\Session\SessionInterface;
|
||||
|
||||
/**
|
||||
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
|
||||
*/
|
||||
final class ReadResourceHandler implements MethodHandlerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ResourceReaderInterface $resourceReader,
|
||||
) {
|
||||
}
|
||||
|
||||
public function supports(HasMethodInterface $message): bool
|
||||
{
|
||||
return $message instanceof ReadResourceRequest;
|
||||
}
|
||||
|
||||
public function handle(ReadResourceRequest|HasMethodInterface $message, SessionInterface $session): Response|Error
|
||||
{
|
||||
\assert($message instanceof ReadResourceRequest);
|
||||
|
||||
try {
|
||||
$contents = $this->resourceReader->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(), $contents);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue