diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 7c7caee..4953437 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -12,92 +12,15 @@ parameters: count: 2 path: examples/http-discovery-userprofile/McpElements.php - - - message: '#^PHPDoc tag @return with type array is incompatible with native type object\.$#' - identifier: return.phpDocType - count: 1 - path: src/Schema/Result/EmptyResult.php - - message: '#^Method Mcp\\Schema\\Result\\ReadResourceResult\:\:jsonSerialize\(\) should return array\{contents\: array\\} but returns array\{contents\: array\\}\.$#' identifier: return.type count: 1 path: src/Schema/Result/ReadResourceResult.php - - - 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/Builder.php - - - - 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/Builder.php - - - - 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/Builder.php - - - - 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/Builder.php - - - - 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/Builder.php - - - - 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/Builder.php - - - - 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/Builder.php - - - - 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/Builder.php - - message: '#^Property Mcp\\Server\\Builder\:\:\$instructions is never read, only written\.$#' identifier: property.onlyWritten count: 1 path: src/Server/Builder.php - - - message: '#^Property Mcp\\Server\\Builder\:\:\$prompts type has no value type specified in iterable type array\.$#' - identifier: missingType.iterableValue - count: 1 - path: src/Server/Builder.php - - - - message: '#^Property Mcp\\Server\\Builder\:\:\$resourceTemplates type has no value type specified in iterable type array\.$#' - identifier: missingType.iterableValue - count: 1 - path: src/Server/Builder.php - - - - message: '#^Property Mcp\\Server\\Builder\:\:\$resources type has no value type specified in iterable type array\.$#' - identifier: missingType.iterableValue - count: 1 - path: src/Server/Builder.php - - - - message: '#^Property Mcp\\Server\\Builder\:\:\$tools type has no value type specified in iterable type array\.$#' - identifier: missingType.iterableValue - count: 1 - path: src/Server/Builder.php diff --git a/src/Schema/Result/EmptyResult.php b/src/Schema/Result/EmptyResult.php index 0570b21..2641698 100644 --- a/src/Schema/Result/EmptyResult.php +++ b/src/Schema/Result/EmptyResult.php @@ -32,9 +32,6 @@ public static function fromArray(): self return new self(); } - /** - * @return array{} - */ public function jsonSerialize(): object { return new \stdClass(); diff --git a/src/Server/Builder.php b/src/Server/Builder.php index 73bb53e..f63003d 100644 --- a/src/Server/Builder.php +++ b/src/Server/Builder.php @@ -14,6 +14,7 @@ use Mcp\Capability\Attribute\CompletionProvider; use Mcp\Capability\Completion\EnumCompletionProvider; use Mcp\Capability\Completion\ListCompletionProvider; +use Mcp\Capability\Completion\ProviderInterface; use Mcp\Capability\Discovery\CachedDiscoverer; use Mcp\Capability\Discovery\Discoverer; use Mcp\Capability\Discovery\DocBlockParser; @@ -23,6 +24,7 @@ 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; @@ -50,6 +52,8 @@ use Psr\SimpleCache\CacheInterface; /** + * @phpstan-import-type Handler from ElementReference + * * @author Kyrian Obikwelu */ final class Builder @@ -80,40 +84,48 @@ final class Builder private ?string $instructions = null; - /** @var array< - * array{handler: array|string|\Closure, - * name: string|null, - * description: string|null, - * annotations: ToolAnnotations|null} - * > */ + /** + * @var array{ + * handler: Handler, + * name: ?string, + * description: ?string, + * annotations: ?ToolAnnotations, + * }[] + */ private array $tools = []; - /** @var array< - * array{handler: array|string|\Closure, + /** + * @var array{ + * handler: Handler, * uri: string, - * name: string|null, - * description: string|null, - * mimeType: string|null, + * name: ?string, + * description: ?string, + * mimeType: ?string, * size: int|null, - * annotations: Annotations|null} - * > */ + * annotations: ?Annotations, + * }[] + */ private array $resources = []; - /** @var array< - * array{handler: array|string|\Closure, + /** + * @var array{ + * handler: Handler, * uriTemplate: string, - * name: string|null, - * description: string|null, - * mimeType: string|null, - * annotations: Annotations|null} - * > */ + * name: ?string, + * description: ?string, + * mimeType: ?string, + * annotations: ?Annotations, + * }[] + */ private array $resourceTemplates = []; - /** @var array< - * array{handler: array|string|\Closure, - * name: string|null, - * description: string|null} - * > */ + /** + * @var array{ + * handler: Handler, + * name: ?string, + * description: ?string, + * }[] + */ private array $prompts = []; private ?string $discoveryBasePath = null; @@ -223,6 +235,10 @@ public function setSession( return $this; } + /** + * @param string[] $scanDirs + * @param string[] $excludeDirs + */ public function setDiscovery( string $basePath, array $scanDirs = ['.', 'src'], @@ -239,6 +255,9 @@ public function setDiscovery( /** * Manually registers a tool handler. + * + * @param Handler $handler + * @param array|null $inputSchema */ public function addTool( callable|array|string $handler, @@ -254,9 +273,11 @@ public function addTool( /** * Manually registers a resource handler. + * + * @param Handler $handler */ public function addResource( - callable|array|string $handler, + \Closure|array|string $handler, string $uri, ?string $name = null, ?string $description = null, @@ -271,9 +292,11 @@ public function addResource( /** * Manually registers a resource template handler. + * + * @param Handler $handler */ public function addResourceTemplate( - callable|array|string $handler, + \Closure|array|string $handler, string $uriTemplate, ?string $name = null, ?string $description = null, @@ -294,8 +317,10 @@ public function addResourceTemplate( /** * Manually registers a prompt handler. + * + * @param Handler $handler */ - public function addPrompt(callable|array|string $handler, ?string $name = null, ?string $description = null): self + public function addPrompt(\Closure|array|string $handler, ?string $name = null, ?string $description = null): self { $this->prompts[] = compact('handler', 'name', 'description'); @@ -387,9 +412,7 @@ private function registerCapabilities( $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 = $this->getHandlerDescription($data['handler']); $logger->debug("Registered manual tool {$name} from handler {$handlerDesc}"); } catch (\Throwable $e) { $logger->error( @@ -425,9 +448,7 @@ private function registerCapabilities( $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 = $this->getHandlerDescription($data['handler']); $logger->debug("Registered manual resource {$name} from handler {$handlerDesc}"); } catch (\Throwable $e) { $logger->error( @@ -463,9 +484,7 @@ private function registerCapabilities( $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 = $this->getHandlerDescription($data['handler']); $logger->debug("Registered manual template {$name} from handler {$handlerDesc}"); } catch (\Throwable $e) { $logger->error( @@ -517,9 +536,7 @@ private function registerCapabilities( $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 = $this->getHandlerDescription($data['handler']); $logger->debug("Registered manual prompt {$name} from handler {$handlerDesc}"); } catch (\Throwable $e) { $logger->error( @@ -533,6 +550,28 @@ private function registerCapabilities( $logger->debug('Manual element registration complete.'); } + /** + * @param Handler $handler + */ + private function getHandlerDescription(\Closure|array|string $handler): string + { + if ($handler instanceof \Closure) { + return 'Closure'; + } + + if (\is_array($handler)) { + return \sprintf('%s::%s', + \is_object($handler[0]) ? $handler[0]::class : $handler[0], + $handler[1], + ); + } + + return (string) $handler; + } + + /** + * @return array + */ private function getCompletionProviders(\ReflectionMethod|\ReflectionFunction $reflection): array { $completionProviders = [];