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
1 changed files with 4656 additions and 334 deletions
|
|
@ -18,9 +18,15 @@ use Mcp\Capability\Discovery\HandlerResolver;
|
|||
use Mcp\Capability\Discovery\SchemaGenerator;
|
||||
use Mcp\Capability\Prompt\Completion\EnumCompletionProvider;
|
||||
use Mcp\Capability\Prompt\Completion\ListCompletionProvider;
|
||||
use Mcp\Capability\Prompt\PromptGetter;
|
||||
use Mcp\Capability\Prompt\PromptGetterInterface;
|
||||
use Mcp\Capability\Registry;
|
||||
use Mcp\Capability\Registry\Container;
|
||||
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\Handler;
|
||||
use Mcp\Schema\Annotations;
|
||||
|
|
@ -49,6 +55,12 @@ final class ServerBuilder
|
|||
|
||||
private ?CacheInterface $cache = null;
|
||||
|
||||
private ?ToolCallerInterface $toolCaller = null;
|
||||
|
||||
private ?ResourceReaderInterface $resourceReader = null;
|
||||
|
||||
private ?PromptGetterInterface $promptGetter = null;
|
||||
|
||||
private ?EventDispatcherInterface $eventDispatcher = null;
|
||||
|
||||
private ?ContainerInterface $container = null;
|
||||
|
|
@ -149,6 +161,27 @@ final class ServerBuilder
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function withToolCaller(ToolCallerInterface $toolCaller): self
|
||||
{
|
||||
$this->toolCaller = $toolCaller;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withResourceReader(ResourceReaderInterface $resourceReader): self
|
||||
{
|
||||
$this->resourceReader = $resourceReader;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withPromptGetter(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.
|
||||
|
|
@ -175,8 +208,13 @@ final class ServerBuilder
|
|||
/**
|
||||
* Manually registers a tool handler.
|
||||
*/
|
||||
public function withTool(callable|array|string $handler, ?string $name = null, ?string $description = null, ?ToolAnnotations $annotations = null, ?array $inputSchema = null): self
|
||||
{
|
||||
public function withTool(
|
||||
callable|array|string $handler,
|
||||
?string $name = null,
|
||||
?string $description = null,
|
||||
?ToolAnnotations $annotations = null,
|
||||
?array $inputSchema = null,
|
||||
): self {
|
||||
$this->manualTools[] = compact('handler', 'name', 'description', 'annotations', 'inputSchema');
|
||||
|
||||
return $this;
|
||||
|
|
@ -185,8 +223,15 @@ final class ServerBuilder
|
|||
/**
|
||||
* Manually registers a resource handler.
|
||||
*/
|
||||
public function withResource(callable|array|string $handler, string $uri, ?string $name = null, ?string $description = null, ?string $mimeType = null, ?int $size = null, ?Annotations $annotations = null): self
|
||||
{
|
||||
public function withResource(
|
||||
callable|array|string $handler,
|
||||
string $uri,
|
||||
?string $name = null,
|
||||
?string $description = null,
|
||||
?string $mimeType = null,
|
||||
?int $size = null,
|
||||
?Annotations $annotations = null,
|
||||
): self {
|
||||
$this->manualResources[] = compact('handler', 'uri', 'name', 'description', 'mimeType', 'size', 'annotations');
|
||||
|
||||
return $this;
|
||||
|
|
@ -195,9 +240,22 @@ final class ServerBuilder
|
|||
/**
|
||||
* Manually registers a resource template handler.
|
||||
*/
|
||||
public function withResourceTemplate(callable|array|string $handler, string $uriTemplate, ?string $name = null, ?string $description = null, ?string $mimeType = null, ?Annotations $annotations = null): self
|
||||
{
|
||||
$this->manualResourceTemplates[] = compact('handler', 'uriTemplate', 'name', 'description', 'mimeType', 'annotations');
|
||||
public function withResourceTemplate(
|
||||
callable|array|string $handler,
|
||||
string $uriTemplate,
|
||||
?string $name = null,
|
||||
?string $description = null,
|
||||
?string $mimeType = null,
|
||||
?Annotations $annotations = null,
|
||||
): self {
|
||||
$this->manualResourceTemplates[] = compact(
|
||||
'handler',
|
||||
'uriTemplate',
|
||||
'name',
|
||||
'description',
|
||||
'mimeType',
|
||||
'annotations',
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
|
@ -220,7 +278,12 @@ final class ServerBuilder
|
|||
$logger = $this->logger ?? new NullLogger();
|
||||
|
||||
$container = $this->container ?? new Container();
|
||||
$registry = new Registry(new ReferenceHandler($container), $this->eventDispatcher, $logger);
|
||||
$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->registerManualElements($registry, $logger);
|
||||
|
||||
|
|
@ -230,8 +293,16 @@ final class ServerBuilder
|
|||
}
|
||||
|
||||
return new Server(
|
||||
Handler::make($registry, $this->serverInfo, $logger),
|
||||
$logger,
|
||||
jsonRpcHandler: Handler::make(
|
||||
registry: $registry,
|
||||
referenceProvider: $registry,
|
||||
implementation: $this->serverInfo,
|
||||
toolCaller: $toolCaller,
|
||||
resourceReader: $resourceReader,
|
||||
promptGetter: $promptGetter,
|
||||
logger: $logger,
|
||||
),
|
||||
logger: $logger,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -239,8 +310,10 @@ final class ServerBuilder
|
|||
* Helper to perform the actual registration based on stored data.
|
||||
* Moved into the builder.
|
||||
*/
|
||||
private function registerManualElements(Registry $registry, LoggerInterface $logger = new NullLogger()): void
|
||||
{
|
||||
private function registerManualElements(
|
||||
Registry\ReferenceRegistryInterface $registry,
|
||||
LoggerInterface $logger = new NullLogger(),
|
||||
): void {
|
||||
if (empty($this->manualTools) && empty($this->manualResources) && empty($this->manualResourceTemplates) && empty($this->manualPrompts)) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -270,10 +343,15 @@ final class ServerBuilder
|
|||
$tool = new Tool($name, $inputSchema, $description, $data['annotations']);
|
||||
$registry->registerTool($tool, $data['handler'], true);
|
||||
|
||||
$handlerDesc = $data['handler'] instanceof \Closure ? 'Closure' : (\is_array($data['handler']) ? implode('::', $data['handler']) : $data['handler']);
|
||||
$handlerDesc = $data['handler'] instanceof \Closure ? 'Closure' : (\is_array(
|
||||
$data['handler'],
|
||||
) ? implode('::', $data['handler']) : $data['handler']);
|
||||
$logger->debug("Registered manual tool {$name} from handler {$handlerDesc}");
|
||||
} catch (\Throwable $e) {
|
||||
$logger->error('Failed to register manual tool', ['handler' => $data['handler'], 'name' => $data['name'], 'exception' => $e]);
|
||||
$logger->error(
|
||||
'Failed to register manual tool',
|
||||
['handler' => $data['handler'], 'name' => $data['name'], 'exception' => $e],
|
||||
);
|
||||
throw new ConfigurationException("Error registering manual tool '{$data['name']}': {$e->getMessage()}", 0, $e);
|
||||
}
|
||||
}
|
||||
|
|
@ -303,10 +381,15 @@ final class ServerBuilder
|
|||
$resource = new Resource($uri, $name, $description, $mimeType, $annotations, $size);
|
||||
$registry->registerResource($resource, $data['handler'], true);
|
||||
|
||||
$handlerDesc = $data['handler'] instanceof \Closure ? 'Closure' : (\is_array($data['handler']) ? implode('::', $data['handler']) : $data['handler']);
|
||||
$handlerDesc = $data['handler'] instanceof \Closure ? 'Closure' : (\is_array(
|
||||
$data['handler'],
|
||||
) ? implode('::', $data['handler']) : $data['handler']);
|
||||
$logger->debug("Registered manual resource {$name} from handler {$handlerDesc}");
|
||||
} catch (\Throwable $e) {
|
||||
$logger->error('Failed to register manual resource', ['handler' => $data['handler'], 'uri' => $data['uri'], 'exception' => $e]);
|
||||
$logger->error(
|
||||
'Failed to register manual resource',
|
||||
['handler' => $data['handler'], 'uri' => $data['uri'], 'exception' => $e],
|
||||
);
|
||||
throw new ConfigurationException("Error registering manual resource '{$data['uri']}': {$e->getMessage()}", 0, $e);
|
||||
}
|
||||
}
|
||||
|
|
@ -336,10 +419,15 @@ final class ServerBuilder
|
|||
$completionProviders = $this->getCompletionProviders($reflection);
|
||||
$registry->registerResourceTemplate($template, $data['handler'], $completionProviders, true);
|
||||
|
||||
$handlerDesc = $data['handler'] instanceof \Closure ? 'Closure' : (\is_array($data['handler']) ? implode('::', $data['handler']) : $data['handler']);
|
||||
$handlerDesc = $data['handler'] instanceof \Closure ? 'Closure' : (\is_array(
|
||||
$data['handler'],
|
||||
) ? implode('::', $data['handler']) : $data['handler']);
|
||||
$logger->debug("Registered manual template {$name} from handler {$handlerDesc}");
|
||||
} catch (\Throwable $e) {
|
||||
$logger->error('Failed to register manual template', ['handler' => $data['handler'], 'uriTemplate' => $data['uriTemplate'], 'exception' => $e]);
|
||||
$logger->error(
|
||||
'Failed to register manual template',
|
||||
['handler' => $data['handler'], 'uriTemplate' => $data['uriTemplate'], 'exception' => $e],
|
||||
);
|
||||
throw new ConfigurationException("Error registering manual resource template '{$data['uriTemplate']}': {$e->getMessage()}", 0, $e);
|
||||
}
|
||||
}
|
||||
|
|
@ -362,7 +450,9 @@ final class ServerBuilder
|
|||
}
|
||||
|
||||
$arguments = [];
|
||||
$paramTags = $reflection instanceof \ReflectionMethod ? $docBlockParser->getParamTags($docBlockParser->parseDocBlock($reflection->getDocComment() ?? null)) : [];
|
||||
$paramTags = $reflection instanceof \ReflectionMethod ? $docBlockParser->getParamTags(
|
||||
$docBlockParser->parseDocBlock($reflection->getDocComment() ?? null),
|
||||
) : [];
|
||||
foreach ($reflection->getParameters() as $param) {
|
||||
$reflectionType = $param->getType();
|
||||
|
||||
|
|
@ -375,7 +465,7 @@ final class ServerBuilder
|
|||
$arguments[] = new PromptArgument(
|
||||
$param->getName(),
|
||||
$paramTag ? trim((string) $paramTag->getDescription()) : null,
|
||||
!$param->isOptional() && !$param->isDefaultValueAvailable()
|
||||
!$param->isOptional() && !$param->isDefaultValueAvailable(),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -383,10 +473,15 @@ final class ServerBuilder
|
|||
$completionProviders = $this->getCompletionProviders($reflection);
|
||||
$registry->registerPrompt($prompt, $data['handler'], $completionProviders, true);
|
||||
|
||||
$handlerDesc = $data['handler'] instanceof \Closure ? 'Closure' : (\is_array($data['handler']) ? implode('::', $data['handler']) : $data['handler']);
|
||||
$handlerDesc = $data['handler'] instanceof \Closure ? 'Closure' : (\is_array(
|
||||
$data['handler'],
|
||||
) ? implode('::', $data['handler']) : $data['handler']);
|
||||
$logger->debug("Registered manual prompt {$name} from handler {$handlerDesc}");
|
||||
} catch (\Throwable $e) {
|
||||
$logger->error('Failed to register manual prompt', ['handler' => $data['handler'], 'name' => $data['name'], 'exception' => $e]);
|
||||
$logger->error(
|
||||
'Failed to register manual prompt',
|
||||
['handler' => $data['handler'], 'name' => $data['name'], 'exception' => $e],
|
||||
);
|
||||
throw new ConfigurationException("Error registering manual prompt '{$data['name']}': {$e->getMessage()}", 0, $e);
|
||||
}
|
||||
}
|
||||
|
|
@ -403,7 +498,10 @@ final class ServerBuilder
|
|||
continue;
|
||||
}
|
||||
|
||||
$completionAttributes = $param->getAttributes(CompletionProvider::class, \ReflectionAttribute::IS_INSTANCEOF);
|
||||
$completionAttributes = $param->getAttributes(
|
||||
CompletionProvider::class,
|
||||
\ReflectionAttribute::IS_INSTANCEOF,
|
||||
);
|
||||
if (!empty($completionAttributes)) {
|
||||
$attributeInstance = $completionAttributes[0]->newInstance();
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue