[Server] Simplify server extensibility with pluggable method handlers (#100)
* refactor: use a custom method handler registration system * refactor: remove redundant Completer and CompleterInterface * docs: update server-builder documentation to reflect custom method handler changes
This commit is contained in:
parent
e94fd82061
commit
fcf605d5d7
6 changed files with 896 additions and 2865 deletions
|
|
@ -20,27 +20,24 @@ use Mcp\Capability\Discovery\Discoverer;
|
|||
use Mcp\Capability\Discovery\DocBlockParser;
|
||||
use Mcp\Capability\Discovery\HandlerResolver;
|
||||
use Mcp\Capability\Discovery\SchemaGenerator;
|
||||
use Mcp\Capability\Prompt\PromptGetter;
|
||||
use Mcp\Capability\Prompt\PromptGetterInterface;
|
||||
use Mcp\Capability\Registry;
|
||||
use Mcp\Capability\Registry\Container;
|
||||
use Mcp\Capability\Registry\ElementReference;
|
||||
use Mcp\Capability\Registry\ReferenceHandler;
|
||||
use Mcp\Capability\Resource\ResourceReader;
|
||||
use Mcp\Capability\Resource\ResourceReaderInterface;
|
||||
use Mcp\Capability\Tool\ToolCaller;
|
||||
use Mcp\Capability\Tool\ToolCallerInterface;
|
||||
use Mcp\Exception\ConfigurationException;
|
||||
use Mcp\JsonRpc\MessageFactory;
|
||||
use Mcp\Schema\Annotations;
|
||||
use Mcp\Schema\Implementation;
|
||||
use Mcp\Schema\Prompt;
|
||||
use Mcp\Schema\PromptArgument;
|
||||
use Mcp\Schema\Resource;
|
||||
use Mcp\Schema\ResourceTemplate;
|
||||
use Mcp\Schema\ServerCapabilities;
|
||||
use Mcp\Schema\Tool;
|
||||
use Mcp\Schema\ToolAnnotations;
|
||||
use Mcp\Server;
|
||||
use Mcp\Server\Handler\JsonRpcHandler;
|
||||
use Mcp\Server\Handler\MethodHandlerInterface;
|
||||
use Mcp\Server\Session\InMemorySessionStore;
|
||||
use Mcp\Server\Session\SessionFactory;
|
||||
use Mcp\Server\Session\SessionFactoryInterface;
|
||||
|
|
@ -64,12 +61,6 @@ final class Builder
|
|||
|
||||
private ?CacheInterface $discoveryCache = null;
|
||||
|
||||
private ?ToolCallerInterface $toolCaller = null;
|
||||
|
||||
private ?ResourceReaderInterface $resourceReader = null;
|
||||
|
||||
private ?PromptGetterInterface $promptGetter = null;
|
||||
|
||||
private ?EventDispatcherInterface $eventDispatcher = null;
|
||||
|
||||
private ?ContainerInterface $container = null;
|
||||
|
|
@ -84,6 +75,13 @@ final class Builder
|
|||
|
||||
private ?string $instructions = null;
|
||||
|
||||
private ?ServerCapabilities $explicitCapabilities = null;
|
||||
|
||||
/**
|
||||
* @var array<int, MethodHandlerInterface>
|
||||
*/
|
||||
private array $customMethodHandlers = [];
|
||||
|
||||
/**
|
||||
* @var array{
|
||||
* handler: Handler,
|
||||
|
|
@ -174,6 +172,40 @@ final class Builder
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Explicitly set server capabilities. If set, this overrides automatic detection.
|
||||
*/
|
||||
public function setCapabilities(ServerCapabilities $capabilities): self
|
||||
{
|
||||
$this->explicitCapabilities = $capabilities;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a single custom method handler.
|
||||
*/
|
||||
public function addMethodHandler(MethodHandlerInterface $handler): self
|
||||
{
|
||||
$this->customMethodHandlers[] = $handler;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register multiple custom method handlers.
|
||||
*
|
||||
* @param iterable<int, MethodHandlerInterface> $handlers
|
||||
*/
|
||||
public function addMethodHandlers(iterable $handlers): self
|
||||
{
|
||||
foreach ($handlers as $handler) {
|
||||
$this->customMethodHandlers[] = $handler;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a PSR-3 logger instance. Defaults to NullLogger.
|
||||
*/
|
||||
|
|
@ -191,27 +223,6 @@ final class Builder
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function setToolCaller(ToolCallerInterface $toolCaller): self
|
||||
{
|
||||
$this->toolCaller = $toolCaller;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setResourceReader(ResourceReaderInterface $resourceReader): self
|
||||
{
|
||||
$this->resourceReader = $resourceReader;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setPromptGetter(PromptGetterInterface $promptGetter): self
|
||||
{
|
||||
$this->promptGetter = $promptGetter;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a PSR-11 DI container, primarily for resolving user-defined handler classes.
|
||||
* Defaults to a basic internal container.
|
||||
|
|
@ -333,49 +344,60 @@ final class Builder
|
|||
public function build(): Server
|
||||
{
|
||||
$logger = $this->logger ?? new NullLogger();
|
||||
|
||||
$container = $this->container ?? new Container();
|
||||
$registry = new Registry($this->eventDispatcher, $logger);
|
||||
|
||||
$referenceHandler = new ReferenceHandler($container);
|
||||
$toolCaller = $this->toolCaller ??= new ToolCaller($registry, $referenceHandler, $logger);
|
||||
$resourceReader = $this->resourceReader ??= new ResourceReader($registry, $referenceHandler, $logger);
|
||||
$promptGetter = $this->promptGetter ??= new PromptGetter($registry, $referenceHandler, $logger);
|
||||
|
||||
$this->registerCapabilities($registry, $logger);
|
||||
|
||||
if (null !== $this->discoveryBasePath) {
|
||||
$discovery = new Discoverer($registry, $logger);
|
||||
|
||||
if (null !== $this->discoveryCache) {
|
||||
$discovery = new CachedDiscoverer($discovery, $this->discoveryCache, $logger);
|
||||
}
|
||||
|
||||
$discovery->discover($this->discoveryBasePath, $this->discoveryScanDirs, $this->discoveryExcludeDirs);
|
||||
$this->performDiscovery($registry, $logger);
|
||||
}
|
||||
|
||||
$sessionTtl = $this->sessionTtl ?? 3600;
|
||||
$sessionFactory = $this->sessionFactory ?? new SessionFactory();
|
||||
$sessionStore = $this->sessionStore ?? new InMemorySessionStore($sessionTtl);
|
||||
$messageFactory = MessageFactory::make();
|
||||
|
||||
return new Server(
|
||||
jsonRpcHandler: JsonRpcHandler::make(
|
||||
registry: $registry,
|
||||
referenceProvider: $registry,
|
||||
configuration: new Configuration(
|
||||
$this->serverInfo,
|
||||
$registry->getCapabilities(),
|
||||
$this->paginationLimit, $this->instructions,
|
||||
),
|
||||
toolCaller: $toolCaller,
|
||||
resourceReader: $resourceReader,
|
||||
promptGetter: $promptGetter,
|
||||
sessionStore: $sessionStore,
|
||||
sessionFactory: $sessionFactory,
|
||||
logger: $logger,
|
||||
),
|
||||
$capabilities = $this->explicitCapabilities ?? $registry->getCapabilities();
|
||||
$configuration = new Configuration($this->serverInfo, $capabilities, $this->paginationLimit, $this->instructions);
|
||||
$referenceHandler = new ReferenceHandler($container);
|
||||
|
||||
$methodHandlers = array_merge($this->customMethodHandlers, [
|
||||
new Handler\Request\PingHandler(),
|
||||
new Handler\Request\InitializeHandler($configuration),
|
||||
new Handler\Request\ListToolsHandler($registry, $this->paginationLimit),
|
||||
new Handler\Request\CallToolHandler($registry, $referenceHandler, $logger),
|
||||
new Handler\Request\ListResourcesHandler($registry, $this->paginationLimit),
|
||||
new Handler\Request\ListResourceTemplatesHandler($registry, $this->paginationLimit),
|
||||
new Handler\Request\ReadResourceHandler($registry, $referenceHandler, $logger),
|
||||
new Handler\Request\ListPromptsHandler($registry, $this->paginationLimit),
|
||||
new Handler\Request\GetPromptHandler($registry, $referenceHandler, $logger),
|
||||
|
||||
new Handler\Notification\InitializedHandler(),
|
||||
]);
|
||||
|
||||
$jsonRpcHandler = new JsonRpcHandler(
|
||||
methodHandlers: $methodHandlers,
|
||||
messageFactory: $messageFactory,
|
||||
sessionFactory: $sessionFactory,
|
||||
sessionStore: $sessionStore,
|
||||
logger: $logger,
|
||||
);
|
||||
|
||||
return new Server($jsonRpcHandler, $logger);
|
||||
}
|
||||
|
||||
private function performDiscovery(
|
||||
Registry\ReferenceRegistryInterface $registry,
|
||||
LoggerInterface $logger,
|
||||
): void {
|
||||
$discovery = new Discoverer($registry, $logger);
|
||||
|
||||
if (null !== $this->discoveryCache) {
|
||||
$discovery = new CachedDiscoverer($discovery, $this->discoveryCache, $logger);
|
||||
}
|
||||
|
||||
$discovery->discover($this->discoveryBasePath, $this->discoveryScanDirs, $this->discoveryExcludeDirs);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -11,12 +11,6 @@
|
|||
|
||||
namespace Mcp\Server\Handler;
|
||||
|
||||
use Mcp\Capability\Completion\Completer;
|
||||
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;
|
||||
|
|
@ -27,8 +21,6 @@ use Mcp\Schema\JsonRpc\HasMethodInterface;
|
|||
use Mcp\Schema\JsonRpc\Request;
|
||||
use Mcp\Schema\JsonRpc\Response;
|
||||
use Mcp\Schema\Request\InitializeRequest;
|
||||
use Mcp\Server\Configuration;
|
||||
use Mcp\Server\Handler;
|
||||
use Mcp\Server\Session\SessionFactoryInterface;
|
||||
use Mcp\Server\Session\SessionInterface;
|
||||
use Mcp\Server\Session\SessionStoreInterface;
|
||||
|
|
@ -44,56 +36,15 @@ use Symfony\Component\Uid\Uuid;
|
|||
class JsonRpcHandler
|
||||
{
|
||||
/**
|
||||
* @var array<int, MethodHandlerInterface>
|
||||
*/
|
||||
private readonly array $methodHandlers;
|
||||
|
||||
/**
|
||||
* @param iterable<int, MethodHandlerInterface> $methodHandlers
|
||||
* @param array<int, MethodHandlerInterface> $methodHandlers
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly array $methodHandlers,
|
||||
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,
|
||||
Configuration $configuration,
|
||||
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($configuration),
|
||||
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\ListResourceTemplatesHandler($referenceProvider, $paginationLimit),
|
||||
new Handler\Request\CallToolHandler($toolCaller, $logger),
|
||||
new Handler\Request\ListToolsHandler($referenceProvider, $paginationLimit),
|
||||
new Handler\Request\CompletionCompleteHandler(new Completer($referenceProvider)),
|
||||
],
|
||||
logger: $logger,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -11,12 +11,16 @@
|
|||
|
||||
namespace Mcp\Server\Handler\Request;
|
||||
|
||||
use Mcp\Capability\Tool\ToolCallerInterface;
|
||||
use Mcp\Capability\Registry\ReferenceHandlerInterface;
|
||||
use Mcp\Capability\Registry\ReferenceProviderInterface;
|
||||
use Mcp\Exception\ExceptionInterface;
|
||||
use Mcp\Exception\ToolCallException;
|
||||
use Mcp\Exception\ToolNotFoundException;
|
||||
use Mcp\Schema\JsonRpc\Error;
|
||||
use Mcp\Schema\JsonRpc\HasMethodInterface;
|
||||
use Mcp\Schema\JsonRpc\Response;
|
||||
use Mcp\Schema\Request\CallToolRequest;
|
||||
use Mcp\Schema\Result\CallToolResult;
|
||||
use Mcp\Server\Handler\MethodHandlerInterface;
|
||||
use Mcp\Server\Session\SessionInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
|
@ -29,7 +33,8 @@ use Psr\Log\NullLogger;
|
|||
final class CallToolHandler implements MethodHandlerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ToolCallerInterface $toolCaller,
|
||||
private readonly ReferenceProviderInterface $referenceProvider,
|
||||
private readonly ReferenceHandlerInterface $referenceHandler,
|
||||
private readonly LoggerInterface $logger = new NullLogger(),
|
||||
) {
|
||||
}
|
||||
|
|
@ -43,20 +48,44 @@ final class CallToolHandler implements MethodHandlerInterface
|
|||
{
|
||||
\assert($message instanceof CallToolRequest);
|
||||
|
||||
$toolName = $message->name;
|
||||
$arguments = $message->arguments ?? [];
|
||||
|
||||
$this->logger->debug('Executing tool', ['name' => $toolName, 'arguments' => $arguments]);
|
||||
|
||||
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,
|
||||
],
|
||||
);
|
||||
$reference = $this->referenceProvider->getTool($toolName);
|
||||
if (null === $reference) {
|
||||
throw new ToolNotFoundException($message);
|
||||
}
|
||||
|
||||
$result = $this->referenceHandler->handle($reference, $arguments);
|
||||
$formatted = $reference->formatResult($result);
|
||||
|
||||
$this->logger->debug('Tool executed successfully', [
|
||||
'name' => $toolName,
|
||||
'result_type' => \gettype($result),
|
||||
]);
|
||||
|
||||
return new Response($message->getId(), new CallToolResult($formatted));
|
||||
} catch (ToolNotFoundException $e) {
|
||||
$this->logger->error('Tool not found', ['name' => $toolName]);
|
||||
|
||||
return new Error($message->getId(), Error::METHOD_NOT_FOUND, $e->getMessage());
|
||||
} catch (ToolCallException|ExceptionInterface $e) {
|
||||
$this->logger->error(\sprintf('Error while executing tool "%s": "%s".', $toolName, $e->getMessage()), [
|
||||
'tool' => $toolName,
|
||||
'arguments' => $arguments,
|
||||
]);
|
||||
|
||||
return Error::forInternalError('Error while executing tool', $message->getId());
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->error('Unhandled error during tool execution', [
|
||||
'name' => $toolName,
|
||||
'exception' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
return Error::forInternalError('Error while executing tool', $message->getId());
|
||||
}
|
||||
|
||||
return new Response($message->getId(), $content);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,14 +11,16 @@
|
|||
|
||||
namespace Mcp\Server\Handler\Request;
|
||||
|
||||
use Mcp\Capability\Completion\CompleterInterface;
|
||||
use Mcp\Exception\ExceptionInterface;
|
||||
use Mcp\Capability\Completion\ProviderInterface;
|
||||
use Mcp\Capability\Registry\ReferenceProviderInterface;
|
||||
use Mcp\Schema\JsonRpc\Error;
|
||||
use Mcp\Schema\JsonRpc\HasMethodInterface;
|
||||
use Mcp\Schema\JsonRpc\Response;
|
||||
use Mcp\Schema\Request\CompletionCompleteRequest;
|
||||
use Mcp\Schema\Result\CompletionCompleteResult;
|
||||
use Mcp\Server\Handler\MethodHandlerInterface;
|
||||
use Mcp\Server\Session\SessionInterface;
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Handles completion/complete requests.
|
||||
|
|
@ -28,7 +30,8 @@ use Mcp\Server\Session\SessionInterface;
|
|||
final class CompletionCompleteHandler implements MethodHandlerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly CompleterInterface $completer,
|
||||
private readonly ReferenceProviderInterface $referenceProvider,
|
||||
private readonly ?ContainerInterface $container = null,
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
@ -41,12 +44,45 @@ final class CompletionCompleteHandler implements MethodHandlerInterface
|
|||
{
|
||||
\assert($message instanceof CompletionCompleteRequest);
|
||||
|
||||
try {
|
||||
$result = $this->completer->complete($message);
|
||||
} catch (ExceptionInterface) {
|
||||
return Error::forInternalError('Error while handling completion request', $message->getId());
|
||||
$name = $message->argument['name'] ?? '';
|
||||
$value = $message->argument['value'] ?? '';
|
||||
|
||||
$reference = match ($message->ref->type) {
|
||||
'ref/prompt' => $this->referenceProvider->getPrompt($message->ref->name),
|
||||
'ref/resource' => $this->referenceProvider->getResourceTemplate($message->ref->uri),
|
||||
default => null,
|
||||
};
|
||||
|
||||
if (null === $reference) {
|
||||
return new Response($message->getId(), new CompletionCompleteResult([]));
|
||||
}
|
||||
|
||||
return new Response($message->getId(), $result);
|
||||
$providers = $reference->completionProviders;
|
||||
$provider = $providers[$name] ?? null;
|
||||
if (null === $provider) {
|
||||
return new Response($message->getId(), new CompletionCompleteResult([]));
|
||||
}
|
||||
|
||||
if (\is_string($provider)) {
|
||||
if (!class_exists($provider)) {
|
||||
return Error::forInternalError('Invalid completion provider', $message->getId());
|
||||
}
|
||||
$provider = $this->container?->has($provider) ? $this->container->get($provider) : new $provider();
|
||||
}
|
||||
|
||||
if (!$provider instanceof ProviderInterface) {
|
||||
return Error::forInternalError('Invalid completion provider type', $message->getId());
|
||||
}
|
||||
|
||||
try {
|
||||
$completions = $provider->getCompletions($value);
|
||||
$total = \count($completions);
|
||||
$hasMore = $total > 100;
|
||||
$paged = \array_slice($completions, 0, 100);
|
||||
|
||||
return new Response($message->getId(), new CompletionCompleteResult($paged, $total, $hasMore));
|
||||
} catch (\Throwable) {
|
||||
return Error::forInternalError('Error while handling completion request', $message->getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,14 +11,20 @@
|
|||
|
||||
namespace Mcp\Server\Handler\Request;
|
||||
|
||||
use Mcp\Capability\Prompt\PromptGetterInterface;
|
||||
use Mcp\Capability\Registry\ReferenceHandlerInterface;
|
||||
use Mcp\Capability\Registry\ReferenceProviderInterface;
|
||||
use Mcp\Exception\ExceptionInterface;
|
||||
use Mcp\Exception\PromptGetException;
|
||||
use Mcp\Exception\PromptNotFoundException;
|
||||
use Mcp\Schema\JsonRpc\Error;
|
||||
use Mcp\Schema\JsonRpc\HasMethodInterface;
|
||||
use Mcp\Schema\JsonRpc\Response;
|
||||
use Mcp\Schema\Request\GetPromptRequest;
|
||||
use Mcp\Schema\Result\GetPromptResult;
|
||||
use Mcp\Server\Handler\MethodHandlerInterface;
|
||||
use Mcp\Server\Session\SessionInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\NullLogger;
|
||||
|
||||
/**
|
||||
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
|
||||
|
|
@ -26,7 +32,9 @@ use Mcp\Server\Session\SessionInterface;
|
|||
final class GetPromptHandler implements MethodHandlerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly PromptGetterInterface $promptGetter,
|
||||
private readonly ReferenceProviderInterface $referenceProvider,
|
||||
private readonly ReferenceHandlerInterface $referenceHandler,
|
||||
private readonly LoggerInterface $logger = new NullLogger(),
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
@ -39,12 +47,32 @@ final class GetPromptHandler implements MethodHandlerInterface
|
|||
{
|
||||
\assert($message instanceof GetPromptRequest);
|
||||
|
||||
$promptName = $message->name;
|
||||
$arguments = $message->arguments ?? [];
|
||||
|
||||
try {
|
||||
$messages = $this->promptGetter->get($message);
|
||||
} catch (ExceptionInterface) {
|
||||
$reference = $this->referenceProvider->getPrompt($promptName);
|
||||
if (null === $reference) {
|
||||
throw new PromptNotFoundException($message);
|
||||
}
|
||||
|
||||
$result = $this->referenceHandler->handle($reference, $arguments);
|
||||
|
||||
$formatted = $reference->formatResult($result);
|
||||
|
||||
return new Response($message->getId(), new GetPromptResult($formatted));
|
||||
} catch (PromptNotFoundException $e) {
|
||||
$this->logger->error('Prompt not found', ['prompt_name' => $promptName]);
|
||||
|
||||
return new Error($message->getId(), Error::METHOD_NOT_FOUND, $e->getMessage());
|
||||
} catch (PromptGetException|ExceptionInterface $e) {
|
||||
$this->logger->error('Error while handling prompt', ['prompt_name' => $promptName]);
|
||||
|
||||
return Error::forInternalError('Error while handling prompt', $message->getId());
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->error('Error while handling prompt', ['prompt_name' => $promptName]);
|
||||
|
||||
return Error::forInternalError('Error while handling prompt', $message->getId());
|
||||
}
|
||||
|
||||
return new Response($message->getId(), $messages);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,15 +11,19 @@
|
|||
|
||||
namespace Mcp\Server\Handler\Request;
|
||||
|
||||
use Mcp\Capability\Resource\ResourceReaderInterface;
|
||||
use Mcp\Exception\ExceptionInterface;
|
||||
use Mcp\Capability\Registry\ReferenceHandlerInterface;
|
||||
use Mcp\Capability\Registry\ReferenceProviderInterface;
|
||||
use Mcp\Capability\Registry\ResourceTemplateReference;
|
||||
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\Schema\Result\ReadResourceResult;
|
||||
use Mcp\Server\Handler\MethodHandlerInterface;
|
||||
use Mcp\Server\Session\SessionInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\NullLogger;
|
||||
|
||||
/**
|
||||
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
|
||||
|
|
@ -27,7 +31,9 @@ use Mcp\Server\Session\SessionInterface;
|
|||
final class ReadResourceHandler implements MethodHandlerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ResourceReaderInterface $resourceReader,
|
||||
private readonly ReferenceProviderInterface $referenceProvider,
|
||||
private readonly ReferenceHandlerInterface $referenceHandler,
|
||||
private readonly LoggerInterface $logger = new NullLogger(),
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
@ -40,14 +46,33 @@ final class ReadResourceHandler implements MethodHandlerInterface
|
|||
{
|
||||
\assert($message instanceof ReadResourceRequest);
|
||||
|
||||
$uri = $message->uri;
|
||||
|
||||
$this->logger->debug('Reading resource', ['uri' => $uri]);
|
||||
|
||||
try {
|
||||
$contents = $this->resourceReader->read($message);
|
||||
$reference = $this->referenceProvider->getResource($uri);
|
||||
if (null === $reference) {
|
||||
throw new ResourceNotFoundException($message);
|
||||
}
|
||||
|
||||
$result = $this->referenceHandler->handle($reference, ['uri' => $uri]);
|
||||
|
||||
if ($reference instanceof ResourceTemplateReference) {
|
||||
$formatted = $reference->formatResult($result, $uri, $reference->resourceTemplate->mimeType);
|
||||
} else {
|
||||
$formatted = $reference->formatResult($result, $uri, $reference->schema->mimeType);
|
||||
}
|
||||
|
||||
return new Response($message->getId(), new ReadResourceResult($formatted));
|
||||
} catch (ResourceNotFoundException $e) {
|
||||
$this->logger->error('Resource not found', ['uri' => $uri]);
|
||||
|
||||
return new Error($message->getId(), Error::RESOURCE_NOT_FOUND, $e->getMessage());
|
||||
} catch (ExceptionInterface) {
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->error(\sprintf('Error while reading resource "%s": "%s".', $uri, $e->getMessage()));
|
||||
|
||||
return Error::forInternalError('Error while reading resource', $message->getId());
|
||||
}
|
||||
|
||||
return new Response($message->getId(), $contents);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue