[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:
Kyrian Obikwelu 2025-09-28 23:58:04 +01:00 • committed by GitHub
commit 41486bfaa4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 65 additions and 67 deletions

View file

@ -23,12 +23,12 @@ use Mcp\Event\ResourceListChangedEvent;
use Mcp\Event\ResourceTemplateListChangedEvent;
use Mcp\Event\ToolListChangedEvent;
use Mcp\Exception\InvalidCursorException;
use Mcp\Schema\Page;
use Mcp\Schema\Prompt;
use Mcp\Schema\Resource;
use Mcp\Schema\ResourceTemplate;
use Mcp\Schema\ServerCapabilities;
use Mcp\Schema\Tool;
use Mcp\Server\RequestHandler\Reference\Page;
use Psr\EventDispatcher\EventDispatcherInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;

View file

@ -11,7 +11,7 @@
namespace Mcp\Capability\Registry;
use Mcp\Server\RequestHandler\Reference\Page;
use Mcp\Schema\Page;
/**
* Interface for providing access to registered MCP elements.

35
src/Schema/Page.php Normal file
View file

@ -0,0 +1,35 @@
<?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\Schema;
/**
* @phpstan-type PageItem Tool|Prompt|ResourceTemplate|Resource
*
* @extends \ArrayObject<int|string, PageItem>
*/
final class Page extends \ArrayObject
{
/**
* @param array<int|string, PageItem> $references Items can be Tool, Prompt, ResourceTemplate, or Resource
*/
public function __construct(
public readonly array $references,
public readonly ?string $nextCursor,
) {
parent::__construct($references, \ArrayObject::ARRAY_AS_PROPS);
}
public function count(): int
{
return \count($this->references);
}
}

View file

@ -11,9 +11,9 @@
namespace Mcp;
use Mcp\JsonRpc\Handler;
use Mcp\Server\Builder;
use Mcp\Server\TransportInterface;
use Mcp\Server\Handler\JsonRpcHandler;
use Mcp\Server\Transport\TransportInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use Symfony\Component\Uid\Uuid;
@ -25,7 +25,7 @@ use Symfony\Component\Uid\Uuid;
final class Server
{
public function __construct(
private readonly Handler $jsonRpcHandler,
private readonly JsonRpcHandler $jsonRpcHandler,
private readonly LoggerInterface $logger = new NullLogger(),
) {
}

View file

@ -29,7 +29,6 @@ use Mcp\Capability\Resource\ResourceReaderInterface;
use Mcp\Capability\Tool\ToolCaller;
use Mcp\Capability\Tool\ToolCallerInterface;
use Mcp\Exception\ConfigurationException;
use Mcp\JsonRpc\Handler;
use Mcp\Schema\Annotations;
use Mcp\Schema\Implementation;
use Mcp\Schema\Prompt;
@ -39,6 +38,7 @@ use Mcp\Schema\ResourceTemplate;
use Mcp\Schema\Tool;
use Mcp\Schema\ToolAnnotations;
use Mcp\Server;
use Mcp\Server\Handler\JsonRpcHandler;
use Mcp\Server\Session\InMemorySessionStore;
use Mcp\Server\Session\SessionFactory;
use Mcp\Server\Session\SessionFactoryInterface;
@ -334,7 +334,7 @@ final class Builder
$sessionStore = $this->sessionStore ?? new InMemorySessionStore($sessionTtl);
return new Server(
jsonRpcHandler: Handler::make(
jsonRpcHandler: JsonRpcHandler::make(
registry: $registry,
referenceProvider: $registry,
implementation: $this->serverInfo,

View 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()]);
}
}

View 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;
}

View 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;
}
}

View 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);
}
}

View 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);
}
}

View 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),
);
}
}

View 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),
);
}
}

View 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),
);
}
}

View 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),
);
}
}

View 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());
}
}

View 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);
}
}

View file

@ -11,7 +11,6 @@
namespace Mcp\Server\Transport;
use Mcp\Server\TransportInterface;
use Symfony\Component\Uid\Uuid;
/**

View file

@ -11,7 +11,6 @@
namespace Mcp\Server\Transport;
use Mcp\Server\TransportInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use Symfony\Component\Uid\Uuid;

View file

@ -12,7 +12,6 @@
namespace Mcp\Server\Transport;
use Mcp\Schema\JsonRpc\Error;
use Mcp\Server\TransportInterface;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;

View file

@ -0,0 +1,68 @@
<?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\Transport;
use Symfony\Component\Uid\Uuid;
/**
* @author Christopher Hertel <mail@christopher-hertel.de>
* @author Kyrian Obikwelu <koshnawaza@gmail.com>
*/
interface TransportInterface
{
/**
* Initializes the transport.
*/
public function initialize(): void;
/**
* Registers a callback that will be invoked whenever the transport receives an incoming message.
*
* @param callable(string $message, ?Uuid $sessionId): void $listener The callback function to execute when the message occurs
*/
public function onMessage(callable $listener): void;
/**
* Starts the transport's execution process.
*
* - For a blocking transport like STDIO, this method will run a continuous loop.
* - For a single-request transport like HTTP, this will process the request
* and return a result (e.g., a PSR-7 Response) to be sent to the client.
*
* @return mixed the result of the transport's execution, if any
*/
public function listen(): mixed;
/**
* Sends a raw JSON-RPC message string back to the client.
*
* @param string $data The JSON-RPC message string to send
* @param array<string, mixed> $context The context of the message
*/
public function send(string $data, array $context): void;
/**
* Registers a callback that will be invoked when a session needs to be destroyed.
* This can happen when a client disconnects or explicitly ends their session.
*
* @param callable(Uuid $sessionId): void $listener The callback function to execute when destroying a session
*/
public function onSessionEnd(callable $listener): void;
/**
* Closes the transport and cleans up any resources.
*
* This method should be called when the transport is no longer needed.
* It should clean up any resources and close any connections.
*/
public function close(): void;
}

Some files were not shown because too many files have changed in this diff Show more