refactor: rename ServerBuilder to Builder (#85)
Remove redundant "Server" prefix since class is already in Server namespace.
This commit is contained in:
parent
38441b8dc7
commit
3dcd2f717a
4 changed files with 36 additions and 32 deletions
|
|
@ -31,79 +31,79 @@ parameters:
|
|||
path: src/Schema/Result/ReadResourceResult.php
|
||||
|
||||
-
|
||||
message: '#^Method Mcp\\Server\\ServerBuilder\:\:addPrompt\(\) has parameter \$handler with no value type specified in iterable type array\.$#'
|
||||
message: '#^Method Mcp\\Server\\Builder\:\:addPrompt\(\) has parameter \$handler with no value type specified in iterable type array\.$#'
|
||||
identifier: missingType.iterableValue
|
||||
count: 1
|
||||
path: src/Server/ServerBuilder.php
|
||||
path: src/Server/Builder.php
|
||||
|
||||
-
|
||||
message: '#^Method Mcp\\Server\\ServerBuilder\:\:addResource\(\) has parameter \$handler with no value type specified in iterable type array\.$#'
|
||||
message: '#^Method Mcp\\Server\\Builder\:\:addResource\(\) has parameter \$handler with no value type specified in iterable type array\.$#'
|
||||
identifier: missingType.iterableValue
|
||||
count: 1
|
||||
path: src/Server/ServerBuilder.php
|
||||
path: src/Server/Builder.php
|
||||
|
||||
-
|
||||
message: '#^Method Mcp\\Server\\ServerBuilder\:\:addResourceTemplate\(\) has parameter \$handler with no value type specified in iterable type array\.$#'
|
||||
message: '#^Method Mcp\\Server\\Builder\:\:addResourceTemplate\(\) has parameter \$handler with no value type specified in iterable type array\.$#'
|
||||
identifier: missingType.iterableValue
|
||||
count: 1
|
||||
path: src/Server/ServerBuilder.php
|
||||
path: src/Server/Builder.php
|
||||
|
||||
-
|
||||
message: '#^Method Mcp\\Server\\ServerBuilder\:\:addTool\(\) has parameter \$handler with no value type specified in iterable type array\.$#'
|
||||
message: '#^Method Mcp\\Server\\Builder\:\:addTool\(\) has parameter \$handler with no value type specified in iterable type array\.$#'
|
||||
identifier: missingType.iterableValue
|
||||
count: 1
|
||||
path: src/Server/ServerBuilder.php
|
||||
path: src/Server/Builder.php
|
||||
|
||||
-
|
||||
message: '#^Method Mcp\\Server\\ServerBuilder\:\:addTool\(\) has parameter \$inputSchema with no value type specified in iterable type array\.$#'
|
||||
message: '#^Method Mcp\\Server\\Builder\:\:addTool\(\) has parameter \$inputSchema with no value type specified in iterable type array\.$#'
|
||||
identifier: missingType.iterableValue
|
||||
count: 1
|
||||
path: src/Server/ServerBuilder.php
|
||||
path: src/Server/Builder.php
|
||||
|
||||
-
|
||||
message: '#^Method Mcp\\Server\\ServerBuilder\:\:getCompletionProviders\(\) return type has no value type specified in iterable type array\.$#'
|
||||
message: '#^Method Mcp\\Server\\Builder\:\:getCompletionProviders\(\) return type has no value type specified in iterable type array\.$#'
|
||||
identifier: missingType.iterableValue
|
||||
count: 1
|
||||
path: src/Server/ServerBuilder.php
|
||||
path: src/Server/Builder.php
|
||||
|
||||
-
|
||||
message: '#^Method Mcp\\Server\\ServerBuilder\:\:setDiscovery\(\) has parameter \$excludeDirs with no value type specified in iterable type array\.$#'
|
||||
message: '#^Method Mcp\\Server\\Builder\:\:setDiscovery\(\) has parameter \$excludeDirs with no value type specified in iterable type array\.$#'
|
||||
identifier: missingType.iterableValue
|
||||
count: 1
|
||||
path: src/Server/ServerBuilder.php
|
||||
path: src/Server/Builder.php
|
||||
|
||||
-
|
||||
message: '#^Method Mcp\\Server\\ServerBuilder\:\:setDiscovery\(\) has parameter \$scanDirs with no value type specified in iterable type array\.$#'
|
||||
message: '#^Method Mcp\\Server\\Builder\:\:setDiscovery\(\) has parameter \$scanDirs with no value type specified in iterable type array\.$#'
|
||||
identifier: missingType.iterableValue
|
||||
count: 1
|
||||
path: src/Server/ServerBuilder.php
|
||||
path: src/Server/Builder.php
|
||||
|
||||
-
|
||||
message: '#^Property Mcp\\Server\\ServerBuilder\:\:\$instructions is never read, only written\.$#'
|
||||
message: '#^Property Mcp\\Server\\Builder\:\:\$instructions is never read, only written\.$#'
|
||||
identifier: property.onlyWritten
|
||||
count: 1
|
||||
path: src/Server/ServerBuilder.php
|
||||
path: src/Server/Builder.php
|
||||
|
||||
-
|
||||
message: '#^Property Mcp\\Server\\ServerBuilder\:\:\$prompts type has no value type specified in iterable type array\.$#'
|
||||
message: '#^Property Mcp\\Server\\Builder\:\:\$prompts type has no value type specified in iterable type array\.$#'
|
||||
identifier: missingType.iterableValue
|
||||
count: 1
|
||||
path: src/Server/ServerBuilder.php
|
||||
path: src/Server/Builder.php
|
||||
|
||||
-
|
||||
message: '#^Property Mcp\\Server\\ServerBuilder\:\:\$resourceTemplates type has no value type specified in iterable type array\.$#'
|
||||
message: '#^Property Mcp\\Server\\Builder\:\:\$resourceTemplates type has no value type specified in iterable type array\.$#'
|
||||
identifier: missingType.iterableValue
|
||||
count: 1
|
||||
path: src/Server/ServerBuilder.php
|
||||
path: src/Server/Builder.php
|
||||
|
||||
-
|
||||
message: '#^Property Mcp\\Server\\ServerBuilder\:\:\$resources type has no value type specified in iterable type array\.$#'
|
||||
message: '#^Property Mcp\\Server\\Builder\:\:\$resources type has no value type specified in iterable type array\.$#'
|
||||
identifier: missingType.iterableValue
|
||||
count: 1
|
||||
path: src/Server/ServerBuilder.php
|
||||
path: src/Server/Builder.php
|
||||
|
||||
-
|
||||
message: '#^Property Mcp\\Server\\ServerBuilder\:\:\$tools type has no value type specified in iterable type array\.$#'
|
||||
message: '#^Property Mcp\\Server\\Builder\:\:\$tools type has no value type specified in iterable type array\.$#'
|
||||
identifier: missingType.iterableValue
|
||||
count: 1
|
||||
path: src/Server/ServerBuilder.php
|
||||
path: src/Server/Builder.php
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
namespace Mcp;
|
||||
|
||||
use Mcp\JsonRpc\Handler;
|
||||
use Mcp\Server\ServerBuilder;
|
||||
use Mcp\Server\Builder;
|
||||
use Mcp\Server\TransportInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\NullLogger;
|
||||
|
|
@ -30,9 +30,9 @@ final class Server
|
|||
) {
|
||||
}
|
||||
|
||||
public static function builder(): ServerBuilder
|
||||
public static function builder(): Builder
|
||||
{
|
||||
return new ServerBuilder();
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public function connect(TransportInterface $transport): void
|
||||
|
|
|
|||
566
src/Server/Builder.php
Normal file
566
src/Server/Builder.php
Normal file
|
|
@ -0,0 +1,566 @@
|
|||
<?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;
|
||||
|
||||
use Mcp\Capability\Attribute\CompletionProvider;
|
||||
use Mcp\Capability\Discovery\CachedDiscoverer;
|
||||
use Mcp\Capability\Discovery\Discoverer;
|
||||
use Mcp\Capability\Discovery\DocBlockParser;
|
||||
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;
|
||||
use Mcp\Schema\Implementation;
|
||||
use Mcp\Schema\Prompt;
|
||||
use Mcp\Schema\PromptArgument;
|
||||
use Mcp\Schema\Resource;
|
||||
use Mcp\Schema\ResourceTemplate;
|
||||
use Mcp\Schema\Tool;
|
||||
use Mcp\Schema\ToolAnnotations;
|
||||
use Mcp\Server;
|
||||
use Mcp\Server\Session\InMemorySessionStore;
|
||||
use Mcp\Server\Session\SessionFactory;
|
||||
use Mcp\Server\Session\SessionFactoryInterface;
|
||||
use Mcp\Server\Session\SessionStoreInterface;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\NullLogger;
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
|
||||
/**
|
||||
* @author Kyrian Obikwelu <koshnawaza@gmail.com>
|
||||
*/
|
||||
final class Builder
|
||||
{
|
||||
private ?Implementation $serverInfo = null;
|
||||
|
||||
private ?LoggerInterface $logger = null;
|
||||
|
||||
private ?CacheInterface $discoveryCache = null;
|
||||
|
||||
private ?ToolCallerInterface $toolCaller = null;
|
||||
|
||||
private ?ResourceReaderInterface $resourceReader = null;
|
||||
|
||||
private ?PromptGetterInterface $promptGetter = null;
|
||||
|
||||
private ?EventDispatcherInterface $eventDispatcher = null;
|
||||
|
||||
private ?ContainerInterface $container = null;
|
||||
|
||||
private ?SessionFactoryInterface $sessionFactory = null;
|
||||
|
||||
private ?SessionStoreInterface $sessionStore = null;
|
||||
|
||||
private int $sessionTtl = 3600;
|
||||
|
||||
private int $paginationLimit = 50;
|
||||
|
||||
private ?string $instructions = null;
|
||||
|
||||
/** @var array<
|
||||
* array{handler: array|string|\Closure,
|
||||
* name: string|null,
|
||||
* description: string|null,
|
||||
* annotations: ToolAnnotations|null}
|
||||
* > */
|
||||
private array $tools = [];
|
||||
|
||||
/** @var array<
|
||||
* array{handler: array|string|\Closure,
|
||||
* uri: string,
|
||||
* name: string|null,
|
||||
* description: string|null,
|
||||
* mimeType: string|null,
|
||||
* size: int|null,
|
||||
* annotations: Annotations|null}
|
||||
* > */
|
||||
private array $resources = [];
|
||||
|
||||
/** @var array<
|
||||
* array{handler: array|string|\Closure,
|
||||
* uriTemplate: string,
|
||||
* name: string|null,
|
||||
* description: string|null,
|
||||
* mimeType: string|null,
|
||||
* annotations: Annotations|null}
|
||||
* > */
|
||||
private array $resourceTemplates = [];
|
||||
|
||||
/** @var array<
|
||||
* array{handler: array|string|\Closure,
|
||||
* name: string|null,
|
||||
* description: string|null}
|
||||
* > */
|
||||
private array $prompts = [];
|
||||
|
||||
private ?string $discoveryBasePath = null;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private array $discoveryScanDirs = [];
|
||||
|
||||
/**
|
||||
* @var array|string[]
|
||||
*/
|
||||
private array $discoveryExcludeDirs = [];
|
||||
|
||||
/**
|
||||
* Sets the server's identity. Required.
|
||||
*/
|
||||
public function setServerInfo(string $name, string $version, ?string $description = null): self
|
||||
{
|
||||
$this->serverInfo = new Implementation(trim($name), trim($version), $description);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the server's pagination limit.
|
||||
*/
|
||||
public function setPaginationLimit(int $paginationLimit): self
|
||||
{
|
||||
$this->paginationLimit = $paginationLimit;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the instructions describing how to use the server and its features.
|
||||
*
|
||||
* This can be used by clients to improve the LLM's understanding of available tools, resources,
|
||||
* etc. It can be thought of like a "hint" to the model. For example, this information MAY
|
||||
* be added to the system prompt.
|
||||
*/
|
||||
public function setInstructions(?string $instructions): self
|
||||
{
|
||||
$this->instructions = $instructions;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a PSR-3 logger instance. Defaults to NullLogger.
|
||||
*/
|
||||
public function setLogger(LoggerInterface $logger): self
|
||||
{
|
||||
$this->logger = $logger;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setEventDispatcher(EventDispatcherInterface $eventDispatcher): self
|
||||
{
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
|
||||
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.
|
||||
*/
|
||||
public function setContainer(ContainerInterface $container): self
|
||||
{
|
||||
$this->container = $container;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setSession(
|
||||
SessionStoreInterface $sessionStore,
|
||||
SessionFactoryInterface $sessionFactory = new SessionFactory(),
|
||||
int $ttl = 3600,
|
||||
): self {
|
||||
$this->sessionFactory = $sessionFactory;
|
||||
$this->sessionStore = $sessionStore;
|
||||
$this->sessionTtl = $ttl;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setDiscovery(
|
||||
string $basePath,
|
||||
array $scanDirs = ['.', 'src'],
|
||||
array $excludeDirs = [],
|
||||
?CacheInterface $cache = null,
|
||||
): self {
|
||||
$this->discoveryBasePath = $basePath;
|
||||
$this->discoveryScanDirs = $scanDirs;
|
||||
$this->discoveryExcludeDirs = $excludeDirs;
|
||||
$this->discoveryCache = $cache;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually registers a tool handler.
|
||||
*/
|
||||
public function addTool(
|
||||
callable|array|string $handler,
|
||||
?string $name = null,
|
||||
?string $description = null,
|
||||
?ToolAnnotations $annotations = null,
|
||||
?array $inputSchema = null,
|
||||
): self {
|
||||
$this->tools[] = compact('handler', 'name', 'description', 'annotations', 'inputSchema');
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually registers a resource handler.
|
||||
*/
|
||||
public function addResource(
|
||||
callable|array|string $handler,
|
||||
string $uri,
|
||||
?string $name = null,
|
||||
?string $description = null,
|
||||
?string $mimeType = null,
|
||||
?int $size = null,
|
||||
?Annotations $annotations = null,
|
||||
): self {
|
||||
$this->resources[] = compact('handler', 'uri', 'name', 'description', 'mimeType', 'size', 'annotations');
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually registers a resource template handler.
|
||||
*/
|
||||
public function addResourceTemplate(
|
||||
callable|array|string $handler,
|
||||
string $uriTemplate,
|
||||
?string $name = null,
|
||||
?string $description = null,
|
||||
?string $mimeType = null,
|
||||
?Annotations $annotations = null,
|
||||
): self {
|
||||
$this->resourceTemplates[] = compact(
|
||||
'handler',
|
||||
'uriTemplate',
|
||||
'name',
|
||||
'description',
|
||||
'mimeType',
|
||||
'annotations',
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Manually registers a prompt handler.
|
||||
*/
|
||||
public function addPrompt(callable|array|string $handler, ?string $name = null, ?string $description = null): self
|
||||
{
|
||||
$this->prompts[] = compact('handler', 'name', 'description');
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the fully configured Server instance.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
$sessionTtl = $this->sessionTtl ?? 3600;
|
||||
$sessionFactory = $this->sessionFactory ?? new SessionFactory();
|
||||
$sessionStore = $this->sessionStore ?? new InMemorySessionStore($sessionTtl);
|
||||
|
||||
return new Server(
|
||||
jsonRpcHandler: Handler::make(
|
||||
registry: $registry,
|
||||
referenceProvider: $registry,
|
||||
implementation: $this->serverInfo,
|
||||
toolCaller: $toolCaller,
|
||||
resourceReader: $resourceReader,
|
||||
promptGetter: $promptGetter,
|
||||
sessionStore: $sessionStore,
|
||||
sessionFactory: $sessionFactory,
|
||||
logger: $logger,
|
||||
paginationLimit: $this->paginationLimit,
|
||||
),
|
||||
logger: $logger,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to perform the actual registration based on stored data.
|
||||
* Moved into the builder.
|
||||
*/
|
||||
private function registerCapabilities(
|
||||
Registry\ReferenceRegistryInterface $registry,
|
||||
LoggerInterface $logger = new NullLogger(),
|
||||
): void {
|
||||
if (empty($this->tools) && empty($this->resources) && empty($this->resourceTemplates) && empty($this->prompts)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$docBlockParser = new DocBlockParser(logger: $logger);
|
||||
$schemaGenerator = new SchemaGenerator($docBlockParser);
|
||||
|
||||
// Register Tools
|
||||
foreach ($this->tools as $data) {
|
||||
try {
|
||||
$reflection = HandlerResolver::resolve($data['handler']);
|
||||
|
||||
if ($reflection instanceof \ReflectionFunction) {
|
||||
$name = $data['name'] ?? 'closure_tool_'.spl_object_id($data['handler']);
|
||||
$description = $data['description'] ?? null;
|
||||
} else {
|
||||
$classShortName = $reflection->getDeclaringClass()->getShortName();
|
||||
$methodName = $reflection->getName();
|
||||
$docBlock = $docBlockParser->parseDocBlock($reflection->getDocComment() ?? null);
|
||||
|
||||
$name = $data['name'] ?? ('__invoke' === $methodName ? $classShortName : $methodName);
|
||||
$description = $data['description'] ?? $docBlockParser->getSummary($docBlock) ?? null;
|
||||
}
|
||||
|
||||
$inputSchema = $data['inputSchema'] ?? $schemaGenerator->generate($reflection);
|
||||
|
||||
$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']);
|
||||
$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],
|
||||
);
|
||||
throw new ConfigurationException("Error registering manual tool '{$data['name']}': {$e->getMessage()}", 0, $e);
|
||||
}
|
||||
}
|
||||
|
||||
// Register Resources
|
||||
foreach ($this->resources as $data) {
|
||||
try {
|
||||
$reflection = HandlerResolver::resolve($data['handler']);
|
||||
|
||||
if ($reflection instanceof \ReflectionFunction) {
|
||||
$name = $data['name'] ?? 'closure_resource_'.spl_object_id($data['handler']);
|
||||
$description = $data['description'] ?? null;
|
||||
} else {
|
||||
$classShortName = $reflection->getDeclaringClass()->getShortName();
|
||||
$methodName = $reflection->getName();
|
||||
$docBlock = $docBlockParser->parseDocBlock($reflection->getDocComment() ?? null);
|
||||
|
||||
$name = $data['name'] ?? ('__invoke' === $methodName ? $classShortName : $methodName);
|
||||
$description = $data['description'] ?? $docBlockParser->getSummary($docBlock) ?? null;
|
||||
}
|
||||
|
||||
$uri = $data['uri'];
|
||||
$mimeType = $data['mimeType'];
|
||||
$size = $data['size'];
|
||||
$annotations = $data['annotations'];
|
||||
|
||||
$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']);
|
||||
$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],
|
||||
);
|
||||
throw new ConfigurationException("Error registering manual resource '{$data['uri']}': {$e->getMessage()}", 0, $e);
|
||||
}
|
||||
}
|
||||
|
||||
// Register Templates
|
||||
foreach ($this->resourceTemplates as $data) {
|
||||
try {
|
||||
$reflection = HandlerResolver::resolve($data['handler']);
|
||||
|
||||
if ($reflection instanceof \ReflectionFunction) {
|
||||
$name = $data['name'] ?? 'closure_template_'.spl_object_id($data['handler']);
|
||||
$description = $data['description'] ?? null;
|
||||
} else {
|
||||
$classShortName = $reflection->getDeclaringClass()->getShortName();
|
||||
$methodName = $reflection->getName();
|
||||
$docBlock = $docBlockParser->parseDocBlock($reflection->getDocComment() ?? null);
|
||||
|
||||
$name = $data['name'] ?? ('__invoke' === $methodName ? $classShortName : $methodName);
|
||||
$description = $data['description'] ?? $docBlockParser->getSummary($docBlock) ?? null;
|
||||
}
|
||||
|
||||
$uriTemplate = $data['uriTemplate'];
|
||||
$mimeType = $data['mimeType'];
|
||||
$annotations = $data['annotations'];
|
||||
|
||||
$template = new ResourceTemplate($uriTemplate, $name, $description, $mimeType, $annotations);
|
||||
$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']);
|
||||
$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],
|
||||
);
|
||||
throw new ConfigurationException("Error registering manual resource template '{$data['uriTemplate']}': {$e->getMessage()}", 0, $e);
|
||||
}
|
||||
}
|
||||
|
||||
// Register Prompts
|
||||
foreach ($this->prompts as $data) {
|
||||
try {
|
||||
$reflection = HandlerResolver::resolve($data['handler']);
|
||||
|
||||
if ($reflection instanceof \ReflectionFunction) {
|
||||
$name = $data['name'] ?? 'closure_prompt_'.spl_object_id($data['handler']);
|
||||
$description = $data['description'] ?? null;
|
||||
} else {
|
||||
$classShortName = $reflection->getDeclaringClass()->getShortName();
|
||||
$methodName = $reflection->getName();
|
||||
$docBlock = $docBlockParser->parseDocBlock($reflection->getDocComment() ?? null);
|
||||
|
||||
$name = $data['name'] ?? ('__invoke' === $methodName ? $classShortName : $methodName);
|
||||
$description = $data['description'] ?? $docBlockParser->getSummary($docBlock) ?? null;
|
||||
}
|
||||
|
||||
$arguments = [];
|
||||
$paramTags = $reflection instanceof \ReflectionMethod ? $docBlockParser->getParamTags(
|
||||
$docBlockParser->parseDocBlock($reflection->getDocComment() ?? null),
|
||||
) : [];
|
||||
foreach ($reflection->getParameters() as $param) {
|
||||
$reflectionType = $param->getType();
|
||||
|
||||
// Basic DI check (heuristic)
|
||||
if ($reflectionType instanceof \ReflectionNamedType && !$reflectionType->isBuiltin()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$paramTag = $paramTags['$'.$param->getName()] ?? null;
|
||||
$arguments[] = new PromptArgument(
|
||||
$param->getName(),
|
||||
$paramTag ? trim((string) $paramTag->getDescription()) : null,
|
||||
!$param->isOptional() && !$param->isDefaultValueAvailable(),
|
||||
);
|
||||
}
|
||||
|
||||
$prompt = new Prompt($name, $description, $arguments);
|
||||
$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']);
|
||||
$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],
|
||||
);
|
||||
throw new ConfigurationException("Error registering manual prompt '{$data['name']}': {$e->getMessage()}", 0, $e);
|
||||
}
|
||||
}
|
||||
|
||||
$logger->debug('Manual element registration complete.');
|
||||
}
|
||||
|
||||
private function getCompletionProviders(\ReflectionMethod|\ReflectionFunction $reflection): array
|
||||
{
|
||||
$completionProviders = [];
|
||||
foreach ($reflection->getParameters() as $param) {
|
||||
$reflectionType = $param->getType();
|
||||
if ($reflectionType instanceof \ReflectionNamedType && !$reflectionType->isBuiltin()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$completionAttributes = $param->getAttributes(
|
||||
CompletionProvider::class,
|
||||
\ReflectionAttribute::IS_INSTANCEOF,
|
||||
);
|
||||
if (!empty($completionAttributes)) {
|
||||
$attributeInstance = $completionAttributes[0]->newInstance();
|
||||
|
||||
if ($attributeInstance->provider) {
|
||||
$completionProviders[$param->getName()] = $attributeInstance->provider;
|
||||
} elseif ($attributeInstance->providerClass) {
|
||||
$completionProviders[$param->getName()] = $attributeInstance->providerClass;
|
||||
} elseif ($attributeInstance->values) {
|
||||
$completionProviders[$param->getName()] = new ListCompletionProvider($attributeInstance->values);
|
||||
} elseif ($attributeInstance->enum) {
|
||||
$completionProviders[$param->getName()] = new EnumCompletionProvider($attributeInstance->enum);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $completionProviders;
|
||||
}
|
||||
}
|
||||
|
|
@ -17,7 +17,7 @@ use Mcp\Schema\ServerCapabilities;
|
|||
/**
|
||||
* Value Object holding core configuration and shared dependencies for the MCP Server instance.
|
||||
*
|
||||
* This object is typically assembled by the ServerBuilder and passed to the Server constructor.
|
||||
* This object is typically assembled by the Builder and passed to the Server constructor.
|
||||
*
|
||||
* @author Kyrian Obikwelu <koshnawaza@gmail.com>
|
||||
*/
|
||||
|
|
|
|||
Loading…
Reference in a new issue