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:
Pavel Buchnev 2025-09-14 22:44:18 +04:00 • committed by GitHub
commit 99bf56eef8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 4656 additions and 334 deletions

View file

@ -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,

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

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

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

View file

@ -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];