Registry Architecture Refactoring - Enhanced Separation of Concerns (#46)
* refactor: Separate Registry concerns following SOLID principles [WIP] - Extract ReferenceProvider and ReferenceRegistryInterface interfaces - Create DefaultToolExecutor with ReferenceHandlerInterface - Remove execution responsibility from Registry class - Enable custom handler and executor implementations * refactor: implement SOLID principles with separated execution concerns * Create DefaultResourceReader, DefaultPromptGetter * Refactor JsonRpc Handler and RequestHandlers to use dedicated executors * Update ServerBuilder to support custom executors via dependency injection * chore: remove extra comments * feat: introduce DispatchableRegistry for enhanced tool and resource management * refactor: use proper interfaces * refactor: implement HandlerInterface for improved abstraction and flexibility * test: add unit tests for DispatchableRegistry and Registry classes * refactor: cover with unit tests Resource reader, Prompt getter and Tool executor * cs fix * phpstan fix * test: add unit tests for CallToolHandler, GetPromptHandler, PingHandler, and ReadResourceHandler * refactor: revert Handler * chore: remove style guide for tests * refactor: remove "Default" prefix from classes * refactor: remove DispatchableRegistry * refactor: use package specific exception classes * refactor: remove DispatchableRegistry * refactor: add logger support to PromptGetter and ResourceReader classes * Update src/Capability/Prompt/PromptGetter.php Co-authored-by: Christopher Hertel <mail@christopher-hertel.de> * refactor: Use FQN for Resource class to avoid cs-fixer misinterpreting it as `resource` * refactor: rename ToolExecutor to ToolCaller and related classes for clarity and consistency * refactor: add missed docblock * refactor: rename ToolExecutor references to ToolCaller * refactor: rename ToolCallerTest property and variable references * cs fix * ignore some phpstan errors --------- Co-authored-by: Christopher Hertel <mail@christopher-hertel.de>
This commit is contained in:
parent
bfc3b82d19
commit
99bf56eef8
13 changed files with 4656 additions and 334 deletions
|
|
@ -19,7 +19,7 @@ use Mcp\Capability\Attribute\McpTool;
|
|||
use Mcp\Capability\Prompt\Completion\EnumCompletionProvider;
|
||||
use Mcp\Capability\Prompt\Completion\ListCompletionProvider;
|
||||
use Mcp\Capability\Prompt\Completion\ProviderInterface;
|
||||
use Mcp\Capability\Registry;
|
||||
use Mcp\Capability\Registry\ReferenceRegistryInterface;
|
||||
use Mcp\Exception\ExceptionInterface;
|
||||
use Mcp\Schema\Prompt;
|
||||
use Mcp\Schema\PromptArgument;
|
||||
|
|
@ -44,7 +44,7 @@ use Symfony\Component\Finder\SplFileInfo;
|
|||
class Discoverer
|
||||
{
|
||||
public function __construct(
|
||||
private readonly Registry $registry,
|
||||
private readonly ReferenceRegistryInterface $registry,
|
||||
private readonly LoggerInterface $logger = new NullLogger(),
|
||||
private ?DocBlockParser $docBlockParser = null,
|
||||
private ?SchemaGenerator $schemaGenerator = null,
|
||||
|
|
|
|||
69
src/Capability/Prompt/PromptGetter.php
Normal file
69
src/Capability/Prompt/PromptGetter.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\Capability\Prompt;
|
||||
|
||||
use Mcp\Capability\Registry\ReferenceHandlerInterface;
|
||||
use Mcp\Capability\Registry\ReferenceProviderInterface;
|
||||
use Mcp\Exception\PromptGetException;
|
||||
use Mcp\Exception\PromptNotFoundException;
|
||||
use Mcp\Schema\Request\GetPromptRequest;
|
||||
use Mcp\Schema\Result\GetPromptResult;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\NullLogger;
|
||||
|
||||
/**
|
||||
* @author Pavel Buchnev <butschster@gmail.com>
|
||||
*/
|
||||
final class PromptGetter implements PromptGetterInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ReferenceProviderInterface $referenceProvider,
|
||||
private readonly ReferenceHandlerInterface $referenceHandler,
|
||||
private readonly LoggerInterface $logger = new NullLogger(),
|
||||
) {
|
||||
}
|
||||
|
||||
public function get(GetPromptRequest $request): GetPromptResult
|
||||
{
|
||||
$promptName = $request->name;
|
||||
$arguments = $request->arguments ?? [];
|
||||
|
||||
$this->logger->debug('Getting prompt', ['name' => $promptName, 'arguments' => $arguments]);
|
||||
|
||||
$reference = $this->referenceProvider->getPrompt($promptName);
|
||||
|
||||
if (null === $reference) {
|
||||
$this->logger->warning('Prompt not found', ['name' => $promptName]);
|
||||
throw new PromptNotFoundException($request);
|
||||
}
|
||||
|
||||
try {
|
||||
$result = $this->referenceHandler->handle($reference, $arguments);
|
||||
$formattedResult = $reference->formatResult($result);
|
||||
|
||||
$this->logger->debug('Prompt retrieved successfully', [
|
||||
'name' => $promptName,
|
||||
'result_type' => \gettype($result),
|
||||
]);
|
||||
|
||||
return new GetPromptResult($formattedResult);
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->error('Prompt retrieval failed', [
|
||||
'name' => $promptName,
|
||||
'exception' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
throw new PromptGetException($request, $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -11,9 +11,9 @@
|
|||
|
||||
namespace Mcp\Capability;
|
||||
|
||||
use Mcp\Capability\Registry\ElementReference;
|
||||
use Mcp\Capability\Registry\PromptReference;
|
||||
use Mcp\Capability\Registry\ReferenceHandler;
|
||||
use Mcp\Capability\Registry\ReferenceProviderInterface;
|
||||
use Mcp\Capability\Registry\ReferenceRegistryInterface;
|
||||
use Mcp\Capability\Registry\ResourceReference;
|
||||
use Mcp\Capability\Registry\ResourceTemplateReference;
|
||||
use Mcp\Capability\Registry\ToolReference;
|
||||
|
|
@ -21,9 +21,6 @@ use Mcp\Event\PromptListChangedEvent;
|
|||
use Mcp\Event\ResourceListChangedEvent;
|
||||
use Mcp\Event\ResourceTemplateListChangedEvent;
|
||||
use Mcp\Event\ToolListChangedEvent;
|
||||
use Mcp\Exception\InvalidArgumentException;
|
||||
use Mcp\Schema\Content\PromptMessage;
|
||||
use Mcp\Schema\Content\ResourceContents;
|
||||
use Mcp\Schema\Prompt;
|
||||
use Mcp\Schema\Resource;
|
||||
use Mcp\Schema\ResourceTemplate;
|
||||
|
|
@ -34,11 +31,14 @@ use Psr\Log\LoggerInterface;
|
|||
use Psr\Log\NullLogger;
|
||||
|
||||
/**
|
||||
* @phpstan-import-type CallableArray from ElementReference
|
||||
* Registry implementation that manages MCP element registration and access.
|
||||
* Implements both ReferenceProvider (for access) and ReferenceRegistry (for registration)
|
||||
* following the Interface Segregation Principle.
|
||||
*
|
||||
* @author Kyrian Obikwelu <koshnawaza@gmail.com>
|
||||
* @author Pavel Buchnev <butschster@gmail.com>
|
||||
*/
|
||||
class Registry
|
||||
final class Registry implements ReferenceProviderInterface, ReferenceRegistryInterface
|
||||
{
|
||||
/**
|
||||
* @var array<string, ToolReference>
|
||||
|
|
@ -61,7 +61,6 @@ class Registry
|
|||
private array $resourceTemplates = [];
|
||||
|
||||
public function __construct(
|
||||
private readonly ReferenceHandler $referenceHandler = new ReferenceHandler(),
|
||||
private readonly ?EventDispatcherInterface $eventDispatcher = null,
|
||||
private readonly LoggerInterface $logger = new NullLogger(),
|
||||
) {
|
||||
|
|
@ -74,28 +73,27 @@ class Registry
|
|||
}
|
||||
|
||||
return new ServerCapabilities(
|
||||
tools: true, // [] !== $this->tools,
|
||||
tools: [] !== $this->tools,
|
||||
toolsListChanged: $this->eventDispatcher instanceof EventDispatcherInterface,
|
||||
resources: [] !== $this->resources || [] !== $this->resourceTemplates,
|
||||
resourcesSubscribe: false,
|
||||
resourcesListChanged: $this->eventDispatcher instanceof EventDispatcherInterface,
|
||||
prompts: [] !== $this->prompts,
|
||||
promptsListChanged: $this->eventDispatcher instanceof EventDispatcherInterface,
|
||||
logging: false, // true,
|
||||
logging: false,
|
||||
completions: true,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable|CallableArray|string $handler
|
||||
*/
|
||||
public function registerTool(Tool $tool, callable|array|string $handler, bool $isManual = false): void
|
||||
{
|
||||
$toolName = $tool->name;
|
||||
$existing = $this->tools[$toolName] ?? null;
|
||||
|
||||
if ($existing && !$isManual && $existing->isManual) {
|
||||
$this->logger->debug("Ignoring discovered tool '{$toolName}' as it conflicts with a manually registered one.");
|
||||
$this->logger->debug(
|
||||
"Ignoring discovered tool '{$toolName}' as it conflicts with a manually registered one.",
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
|
@ -105,16 +103,15 @@ class Registry
|
|||
$this->eventDispatcher?->dispatch(new ToolListChangedEvent());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable|CallableArray|string $handler
|
||||
*/
|
||||
public function registerResource(Resource $resource, callable|array|string $handler, bool $isManual = false): void
|
||||
{
|
||||
$uri = $resource->uri;
|
||||
$existing = $this->resources[$uri] ?? null;
|
||||
|
||||
if ($existing && !$isManual && $existing->isManual) {
|
||||
$this->logger->debug("Ignoring discovered resource '{$uri}' as it conflicts with a manually registered one.");
|
||||
$this->logger->debug(
|
||||
"Ignoring discovered resource '{$uri}' as it conflicts with a manually registered one.",
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
|
@ -124,10 +121,6 @@ class Registry
|
|||
$this->eventDispatcher?->dispatch(new ResourceListChangedEvent());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable|CallableArray|string $handler
|
||||
* @param array<string, class-string|object> $completionProviders
|
||||
*/
|
||||
public function registerResourceTemplate(
|
||||
ResourceTemplate $template,
|
||||
callable|array|string $handler,
|
||||
|
|
@ -138,20 +131,23 @@ class Registry
|
|||
$existing = $this->resourceTemplates[$uriTemplate] ?? null;
|
||||
|
||||
if ($existing && !$isManual && $existing->isManual) {
|
||||
$this->logger->debug("Ignoring discovered template '{$uriTemplate}' as it conflicts with a manually registered one.");
|
||||
$this->logger->debug(
|
||||
"Ignoring discovered template '{$uriTemplate}' as it conflicts with a manually registered one.",
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->resourceTemplates[$uriTemplate] = new ResourceTemplateReference($template, $handler, $isManual, $completionProviders);
|
||||
$this->resourceTemplates[$uriTemplate] = new ResourceTemplateReference(
|
||||
$template,
|
||||
$handler,
|
||||
$isManual,
|
||||
$completionProviders,
|
||||
);
|
||||
|
||||
$this->eventDispatcher?->dispatch(new ResourceTemplateListChangedEvent());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable|CallableArray|string $handler
|
||||
* @param array<string, class-string|object> $completionProviders
|
||||
*/
|
||||
public function registerPrompt(
|
||||
Prompt $prompt,
|
||||
callable|array|string $handler,
|
||||
|
|
@ -162,7 +158,9 @@ class Registry
|
|||
$existing = $this->prompts[$promptName] ?? null;
|
||||
|
||||
if ($existing && !$isManual && $existing->isManual) {
|
||||
$this->logger->debug("Ignoring discovered prompt '{$promptName}' as it conflicts with a manually registered one.");
|
||||
$this->logger->debug(
|
||||
"Ignoring discovered prompt '{$promptName}' as it conflicts with a manually registered one.",
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
|
@ -172,20 +170,6 @@ class Registry
|
|||
$this->eventDispatcher?->dispatch(new PromptListChangedEvent());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if any elements (manual or discovered) are currently registered.
|
||||
*/
|
||||
public function hasElements(): bool
|
||||
{
|
||||
return !empty($this->tools)
|
||||
|| !empty($this->resources)
|
||||
|| !empty($this->prompts)
|
||||
|| !empty($this->resourceTemplates);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear discovered elements from registry.
|
||||
*/
|
||||
public function clear(): void
|
||||
{
|
||||
$clearCount = 0;
|
||||
|
|
@ -220,43 +204,15 @@ class Registry
|
|||
}
|
||||
}
|
||||
|
||||
public function handleCallTool(string $name, array $arguments): array
|
||||
{
|
||||
$reference = $this->getTool($name);
|
||||
|
||||
if (null === $reference) {
|
||||
throw new InvalidArgumentException(\sprintf('Tool "%s" is not registered.', $name));
|
||||
}
|
||||
|
||||
return $reference->formatResult(
|
||||
$this->referenceHandler->handle($reference, $arguments)
|
||||
);
|
||||
}
|
||||
|
||||
public function getTool(string $name): ?ToolReference
|
||||
{
|
||||
return $this->tools[$name] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ResourceContents[]
|
||||
*/
|
||||
public function handleReadResource(string $uri): array
|
||||
{
|
||||
$reference = $this->getResource($uri);
|
||||
|
||||
if (null === $reference) {
|
||||
throw new InvalidArgumentException(\sprintf('Resource "%s" is not registered.', $uri));
|
||||
}
|
||||
|
||||
return $reference->formatResult(
|
||||
$this->referenceHandler->handle($reference, ['uri' => $uri]),
|
||||
$uri,
|
||||
);
|
||||
}
|
||||
|
||||
public function getResource(string $uri, bool $includeTemplates = true): ResourceReference|ResourceTemplateReference|null
|
||||
{
|
||||
public function getResource(
|
||||
string $uri,
|
||||
bool $includeTemplates = true,
|
||||
): ResourceReference|ResourceTemplateReference|null {
|
||||
$registration = $this->resources[$uri] ?? null;
|
||||
if ($registration) {
|
||||
return $registration;
|
||||
|
|
@ -282,54 +238,37 @@ class Registry
|
|||
return $this->resourceTemplates[$uriTemplate] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return PromptMessage[]
|
||||
*/
|
||||
public function handleGetPrompt(string $name, ?array $arguments): array
|
||||
{
|
||||
$reference = $this->getPrompt($name);
|
||||
|
||||
if (null === $reference) {
|
||||
throw new InvalidArgumentException(\sprintf('Prompt "%s" is not registered.', $name));
|
||||
}
|
||||
|
||||
return $reference->formatResult(
|
||||
$this->referenceHandler->handle($reference, $arguments)
|
||||
);
|
||||
}
|
||||
|
||||
public function getPrompt(string $name): ?PromptReference
|
||||
{
|
||||
return $this->prompts[$name] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, Tool>
|
||||
*/
|
||||
public function getTools(): array
|
||||
{
|
||||
return array_map(fn (ToolReference $tool) => $tool->tool, $this->tools);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, resource>
|
||||
*/
|
||||
public function getResources(): array
|
||||
{
|
||||
return array_map(fn (ResourceReference $resource) => $resource->schema, $this->resources);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, Prompt>
|
||||
*/
|
||||
public function getPrompts(): array
|
||||
{
|
||||
return array_map(fn (PromptReference $prompt) => $prompt->prompt, $this->prompts);
|
||||
}
|
||||
|
||||
/** @return array<string, ResourceTemplate> */
|
||||
public function getResourceTemplates(): array
|
||||
{
|
||||
return array_map(fn ($template) => $template->resourceTemplate, $this->resourceTemplates);
|
||||
return array_map(fn (ResourceTemplateReference $template) => $template->resourceTemplate,
|
||||
$this->resourceTemplates);
|
||||
}
|
||||
|
||||
public function hasElements(): bool
|
||||
{
|
||||
return !empty($this->tools)
|
||||
|| !empty($this->resources)
|
||||
|| !empty($this->prompts)
|
||||
|| !empty($this->resourceTemplates);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ use Psr\Container\ContainerInterface;
|
|||
/**
|
||||
* @author Kyrian Obikwelu <koshnawaza@gmail.com>
|
||||
*/
|
||||
class ReferenceHandler
|
||||
final class ReferenceHandler implements ReferenceHandlerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ?ContainerInterface $container = null,
|
||||
|
|
|
|||
34
src/Capability/Registry/ReferenceHandlerInterface.php
Normal file
34
src/Capability/Registry/ReferenceHandlerInterface.php
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<?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\Capability\Registry;
|
||||
|
||||
/**
|
||||
* Interface for handling execution of MCP elements.
|
||||
* Allows custom implementations of element execution logic.
|
||||
*
|
||||
* @author Pavel Buchnev <butschster@gmail.com>
|
||||
*/
|
||||
interface ReferenceHandlerInterface
|
||||
{
|
||||
/**
|
||||
* Handles execution of an MCP element reference.
|
||||
*
|
||||
* @param ElementReference $reference the element reference to execute
|
||||
* @param array<string, mixed> $arguments arguments to pass to the handler
|
||||
*
|
||||
* @return mixed the result of the element execution
|
||||
*
|
||||
* @throws \Mcp\Exception\InvalidArgumentException if the handler is invalid
|
||||
* @throws \Mcp\Exception\RegistryException if execution fails
|
||||
*/
|
||||
public function handle(ElementReference $reference, array $arguments): mixed;
|
||||
}
|
||||
78
src/Capability/Registry/ReferenceProviderInterface.php
Normal file
78
src/Capability/Registry/ReferenceProviderInterface.php
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
<?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\Capability\Registry;
|
||||
|
||||
use Mcp\Schema\Prompt;
|
||||
use Mcp\Schema\ResourceTemplate;
|
||||
use Mcp\Schema\Tool;
|
||||
|
||||
/**
|
||||
* Interface for providing access to registered MCP elements.
|
||||
* Separates the concern of accessing elements from registering them.
|
||||
*
|
||||
* @author Pavel Buchnev <butschster@gmail.com>
|
||||
*/
|
||||
interface ReferenceProviderInterface
|
||||
{
|
||||
/**
|
||||
* Gets a tool reference by name.
|
||||
*/
|
||||
public function getTool(string $name): ?ToolReference;
|
||||
|
||||
/**
|
||||
* Gets a resource reference by URI (includes template matching if enabled).
|
||||
*/
|
||||
public function getResource(string $uri, bool $includeTemplates = true): ResourceReference|ResourceTemplateReference|null;
|
||||
|
||||
/**
|
||||
* Gets a resource template reference by URI template.
|
||||
*/
|
||||
public function getResourceTemplate(string $uriTemplate): ?ResourceTemplateReference;
|
||||
|
||||
/**
|
||||
* Gets a prompt reference by name.
|
||||
*/
|
||||
public function getPrompt(string $name): ?PromptReference;
|
||||
|
||||
/**
|
||||
* Gets all registered tools.
|
||||
*
|
||||
* @return array<string, Tool>
|
||||
*/
|
||||
public function getTools(): array;
|
||||
|
||||
/**
|
||||
* Gets all registered resources.
|
||||
*
|
||||
* @return array<string, \Mcp\Schema\Resource>
|
||||
*/
|
||||
public function getResources(): array;
|
||||
|
||||
/**
|
||||
* Gets all registered prompts.
|
||||
*
|
||||
* @return array<string, Prompt>
|
||||
*/
|
||||
public function getPrompts(): array;
|
||||
|
||||
/**
|
||||
* Gets all registered resource templates.
|
||||
*
|
||||
* @return array<string, ResourceTemplate>
|
||||
*/
|
||||
public function getResourceTemplates(): array;
|
||||
|
||||
/**
|
||||
* Checks if any elements (manual or discovered) are currently registered.
|
||||
*/
|
||||
public function hasElements(): bool;
|
||||
}
|
||||
79
src/Capability/Registry/ReferenceRegistryInterface.php
Normal file
79
src/Capability/Registry/ReferenceRegistryInterface.php
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
<?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\Capability\Registry;
|
||||
|
||||
use Mcp\Schema\Prompt;
|
||||
use Mcp\Schema\Resource;
|
||||
use Mcp\Schema\ResourceTemplate;
|
||||
use Mcp\Schema\ServerCapabilities;
|
||||
use Mcp\Schema\Tool;
|
||||
|
||||
/**
|
||||
* @phpstan-import-type Handler from ElementReference
|
||||
*
|
||||
* Interface for registering MCP elements.
|
||||
* Separates the concern of registering elements from accessing them.
|
||||
*
|
||||
* @author Pavel Buchnev <butschster@gmail.com>
|
||||
*/
|
||||
interface ReferenceRegistryInterface
|
||||
{
|
||||
/**
|
||||
* Gets server capabilities based on registered elements.
|
||||
*/
|
||||
public function getCapabilities(): ServerCapabilities;
|
||||
|
||||
/**
|
||||
* Registers a tool with its handler.
|
||||
*
|
||||
* @param Handler $handler
|
||||
*/
|
||||
public function registerTool(Tool $tool, callable|array|string $handler, bool $isManual = false): void;
|
||||
|
||||
/**
|
||||
* Registers a resource with its handler.
|
||||
*
|
||||
* @param Handler $handler
|
||||
*/
|
||||
public function registerResource(Resource $resource, callable|array|string $handler, bool $isManual = false): void;
|
||||
|
||||
/**
|
||||
* Registers a resource template with its handler and completion providers.
|
||||
*
|
||||
* @param Handler $handler
|
||||
* @param array<string, class-string|object> $completionProviders
|
||||
*/
|
||||
public function registerResourceTemplate(
|
||||
ResourceTemplate $template,
|
||||
callable|array|string $handler,
|
||||
array $completionProviders = [],
|
||||
bool $isManual = false,
|
||||
): void;
|
||||
|
||||
/**
|
||||
* Registers a prompt with its handler and completion providers.
|
||||
*
|
||||
* @param Handler $handler
|
||||
* @param array<string, class-string|object> $completionProviders
|
||||
*/
|
||||
public function registerPrompt(
|
||||
Prompt $prompt,
|
||||
callable|array|string $handler,
|
||||
array $completionProviders = [],
|
||||
bool $isManual = false,
|
||||
): void;
|
||||
|
||||
/**
|
||||
* Clear discovered elements from registry.
|
||||
*/
|
||||
public function clear(): void;
|
||||
}
|
||||
|
|
@ -55,7 +55,8 @@ class ResourceTemplateReference extends ElementReference
|
|||
}
|
||||
|
||||
/**
|
||||
* Gets the resource template.
|
||||
* @deprecated
|
||||
* Gets the resource template
|
||||
*
|
||||
* @return array<int, ResourceContents> array of ResourceContents objects
|
||||
*/
|
||||
|
|
@ -162,7 +163,7 @@ class ResourceTemplateReference extends ElementReference
|
|||
* - array: Converted to JSON if MIME type is application/json or contains 'json'
|
||||
* For other MIME types, will try to convert to JSON with a warning
|
||||
*/
|
||||
protected function formatResult(mixed $readResult, string $uri, ?string $mimeType = null): array
|
||||
public function formatResult(mixed $readResult, string $uri, ?string $mimeType = null): array
|
||||
{
|
||||
if ($readResult instanceof ResourceContents) {
|
||||
return [$readResult];
|
||||
|
|
|
|||
|
|
@ -1,30 +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\Capability\Resource;
|
||||
|
||||
/**
|
||||
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
|
||||
*/
|
||||
final class ResourceReadResult
|
||||
{
|
||||
public function __construct(
|
||||
public readonly string $result,
|
||||
public readonly string $uri,
|
||||
|
||||
/**
|
||||
* @var "text"|"blob"
|
||||
*/
|
||||
public readonly string $type = 'text',
|
||||
public readonly string $mimeType = 'text/plain',
|
||||
) {
|
||||
}
|
||||
}
|
||||
68
src/Capability/Resource/ResourceReader.php
Normal file
68
src/Capability/Resource/ResourceReader.php
Normal 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\Capability\Resource;
|
||||
|
||||
use Mcp\Capability\Registry\ReferenceHandlerInterface;
|
||||
use Mcp\Capability\Registry\ReferenceProviderInterface;
|
||||
use Mcp\Exception\ResourceNotFoundException;
|
||||
use Mcp\Exception\ResourceReadException;
|
||||
use Mcp\Schema\Request\ReadResourceRequest;
|
||||
use Mcp\Schema\Result\ReadResourceResult;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\NullLogger;
|
||||
|
||||
/**
|
||||
* @author Pavel Buchnev <butschster@gmail.com>
|
||||
*/
|
||||
final class ResourceReader implements ResourceReaderInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ReferenceProviderInterface $referenceProvider,
|
||||
private readonly ReferenceHandlerInterface $referenceHandler,
|
||||
private readonly LoggerInterface $logger = new NullLogger(),
|
||||
) {
|
||||
}
|
||||
|
||||
public function read(ReadResourceRequest $request): ReadResourceResult
|
||||
{
|
||||
$uri = $request->uri;
|
||||
|
||||
$this->logger->debug('Reading resource', ['uri' => $uri]);
|
||||
|
||||
$reference = $this->referenceProvider->getResource($uri);
|
||||
|
||||
if (null === $reference) {
|
||||
$this->logger->warning('Resource not found', ['uri' => $uri]);
|
||||
throw new ResourceNotFoundException($request);
|
||||
}
|
||||
|
||||
try {
|
||||
$result = $this->referenceHandler->handle($reference, ['uri' => $uri]);
|
||||
$formattedResult = $reference->formatResult($result, $uri);
|
||||
|
||||
$this->logger->debug('Resource read successfully', [
|
||||
'uri' => $uri,
|
||||
'result_type' => \gettype($result),
|
||||
]);
|
||||
|
||||
return new ReadResourceResult($formattedResult);
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->error('Resource read failed', [
|
||||
'uri' => $uri,
|
||||
'exception' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
throw new ResourceReadException($request, $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
81
src/Capability/Tool/ToolCaller.php
Normal file
81
src/Capability/Tool/ToolCaller.php
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
<?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\Capability\Tool;
|
||||
|
||||
use Mcp\Capability\Registry\ReferenceHandlerInterface;
|
||||
use Mcp\Capability\Registry\ReferenceProviderInterface;
|
||||
use Mcp\Exception\ToolCallException;
|
||||
use Mcp\Exception\ToolNotFoundException;
|
||||
use Mcp\Schema\Content\AudioContent;
|
||||
use Mcp\Schema\Content\EmbeddedResource;
|
||||
use Mcp\Schema\Content\ImageContent;
|
||||
use Mcp\Schema\Content\TextContent;
|
||||
use Mcp\Schema\Request\CallToolRequest;
|
||||
use Mcp\Schema\Result\CallToolResult;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\NullLogger;
|
||||
|
||||
/**
|
||||
* Default implementation of ToolCallerInterface that uses ReferenceProvider
|
||||
* and ReferenceHandlerInterface to execute tools.
|
||||
*
|
||||
* @author Pavel Buchnev <butschster@gmail.com>
|
||||
*/
|
||||
final class ToolCaller implements ToolCallerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ReferenceProviderInterface $referenceProvider,
|
||||
private readonly ReferenceHandlerInterface $referenceHandler,
|
||||
private readonly LoggerInterface $logger = new NullLogger(),
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ToolCallException if the tool execution fails
|
||||
* @throws ToolNotFoundException if the tool is not found
|
||||
*/
|
||||
public function call(CallToolRequest $request): CallToolResult
|
||||
{
|
||||
$toolName = $request->name;
|
||||
$arguments = $request->arguments ?? [];
|
||||
|
||||
$this->logger->debug('Executing tool', ['name' => $toolName, 'arguments' => $arguments]);
|
||||
|
||||
$toolReference = $this->referenceProvider->getTool($toolName);
|
||||
|
||||
if (null === $toolReference) {
|
||||
$this->logger->warning('Tool not found', ['name' => $toolName]);
|
||||
throw new ToolNotFoundException($request);
|
||||
}
|
||||
|
||||
try {
|
||||
$result = $this->referenceHandler->handle($toolReference, $arguments);
|
||||
/** @var TextContent[]|ImageContent[]|EmbeddedResource[]|AudioContent[] $formattedResult */
|
||||
$formattedResult = $toolReference->formatResult($result);
|
||||
|
||||
$this->logger->debug('Tool executed successfully', [
|
||||
'name' => $toolName,
|
||||
'result_type' => \gettype($result),
|
||||
]);
|
||||
|
||||
return new CallToolResult($formattedResult);
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->error('Tool execution failed', [
|
||||
'name' => $toolName,
|
||||
'exception' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
throw new ToolCallException($request, $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
29
src/Capability/Tool/ToolCallerInterface.php
Normal file
29
src/Capability/Tool/ToolCallerInterface.php
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<?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\Capability\Tool;
|
||||
|
||||
use Mcp\Exception\ToolCallException;
|
||||
use Mcp\Exception\ToolNotFoundException;
|
||||
use Mcp\Schema\Request\CallToolRequest;
|
||||
use Mcp\Schema\Result\CallToolResult;
|
||||
|
||||
/**
|
||||
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
|
||||
*/
|
||||
interface ToolCallerInterface
|
||||
{
|
||||
/**
|
||||
* @throws ToolCallException if the tool execution fails
|
||||
* @throws ToolNotFoundException if the tool is not found
|
||||
*/
|
||||
public function call(CallToolRequest $request): CallToolResult;
|
||||
}
|
||||
|
|
@ -14,9 +14,9 @@ namespace Mcp\Capability;
|
|||
use Mcp\Capability\Tool\CollectionInterface;
|
||||
use Mcp\Capability\Tool\IdentifierInterface;
|
||||
use Mcp\Capability\Tool\MetadataInterface;
|
||||
use Mcp\Capability\Tool\ToolExecutorInterface;
|
||||
use Mcp\Capability\Tool\ToolCallerInterface;
|
||||
use Mcp\Exception\InvalidCursorException;
|
||||
use Mcp\Exception\ToolExecutionException;
|
||||
use Mcp\Exception\ToolCallException;
|
||||
use Mcp\Exception\ToolNotFoundException;
|
||||
use Mcp\Schema\Request\CallToolRequest;
|
||||
use Mcp\Schema\Result\CallToolResult;
|
||||
|
|
@ -26,7 +26,7 @@ use Mcp\Schema\Result\CallToolResult;
|
|||
*
|
||||
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
|
||||
*/
|
||||
class ToolChain implements ToolExecutorInterface, CollectionInterface
|
||||
class ToolChain implements ToolCallerInterface, CollectionInterface
|
||||
{
|
||||
public function __construct(
|
||||
/**
|
||||
|
|
@ -63,11 +63,11 @@ class ToolChain implements ToolExecutorInterface, CollectionInterface
|
|||
public function call(CallToolRequest $request): CallToolResult
|
||||
{
|
||||
foreach ($this->items as $item) {
|
||||
if ($item instanceof ToolExecutorInterface && $request->name === $item->getName()) {
|
||||
if ($item instanceof ToolCallerInterface && $request->name === $item->getName()) {
|
||||
try {
|
||||
return $item->call($request);
|
||||
} catch (\Throwable $e) {
|
||||
throw new ToolExecutionException($request, $e);
|
||||
throw new ToolCallException($request, $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue