*/ final class Builder { private ?Implementation $serverInfo = null; private RegistryInterface $registry; private ?SubscriptionManagerInterface $subscriptionManager = null; private ?LoggerInterface $logger = null; private ?CacheInterface $discoveryCache = null; private ?EventDispatcherInterface $eventDispatcher = null; private ?ContainerInterface $container = null; private ?SchemaGeneratorInterface $schemaGenerator = null; private ?ReferenceHandlerInterface $referenceHandler = null; private ?DiscovererInterface $discoverer = null; private ?SessionManagerInterface $sessionManager = null; private ?SessionStoreInterface $sessionStore = null; private int $paginationLimit = 50; private ?string $instructions = null; private ?ProtocolVersion $protocolVersion = null; /** * @var array> */ private array $requestHandlers = []; /** * @var array */ private array $notificationHandlers = []; /** * @var array{ * handler: Handler, * name: ?string, * title: ?string, * description: ?string, * annotations: ?ToolAnnotations, * icons: ?Icon[], * meta: ?array, * outputSchema: ?array, * }[] */ private array $tools = []; /** * @var array{ * handler: Handler, * uri: string, * name: ?string, * description: ?string, * mimeType: ?string, * size: int|null, * annotations: ?Annotations, * icons: ?Icon[], * meta: ?array * }[] */ private array $resources = []; /** * @var array{ * handler: Handler, * uriTemplate: string, * name: ?string, * description: ?string, * mimeType: ?string, * annotations: ?Annotations, * meta: ?array * }[] */ private array $resourceTemplates = []; /** * @var array{ * handler: Handler, * name: ?string, * description: ?string, * icons: ?Icon[], * meta: ?array * }[] */ private array $prompts = []; private ?string $discoveryBasePath = null; /** * @var string[] */ private array $discoveryScanDirs = []; /** * @var array|string[] */ private array $discoveryExcludeDirs = []; private ?ServerCapabilities $serverCapabilities = null; /** * @var LoaderInterface[] */ private array $loaders = []; /** * Sets the server's identity. Required. * * @param ?Icon[] $icons */ public function setServerInfo( string $name, string $version, ?string $description = null, ?array $icons = null, ?string $websiteUrl = null, ): self { $this->serverInfo = new Implementation(trim($name), trim($version), $description, $icons, $websiteUrl); 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; } /** * Explicitly set server capabilities. If set, this overrides automatic detection. */ public function setCapabilities(ServerCapabilities $serverCapabilities): self { $this->serverCapabilities = $serverCapabilities; return $this; } /** * Register a single custom method handler. * * @param RequestHandlerInterface $handler */ public function addRequestHandler(RequestHandlerInterface $handler): self { $this->requestHandlers[] = $handler; return $this; } /** * Register multiple custom method handlers. * * @param iterable> $handlers */ public function addRequestHandlers(iterable $handlers): self { foreach ($handlers as $handler) { $this->requestHandlers[] = $handler; } return $this; } /** * Register a single custom notification handler. */ public function addNotificationHandler(NotificationHandlerInterface $handler): self { $this->notificationHandlers[] = $handler; return $this; } /** * Register multiple custom notification handlers. * * @param iterable $handlers */ public function addNotificationHandlers(iterable $handlers): self { foreach ($handlers as $handler) { $this->notificationHandlers[] = $handler; } return $this; } public function setRegistry(RegistryInterface $registry): self { $this->registry = $registry; 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; } /** * 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 setSchemaGenerator(SchemaGeneratorInterface $schemaGenerator): self { $this->schemaGenerator = $schemaGenerator; return $this; } public function setReferenceHandler(ReferenceHandlerInterface $referenceHandler): self { $this->referenceHandler = $referenceHandler; return $this; } public function setDiscoverer(DiscovererInterface $discoverer): self { $this->discoverer = $discoverer; return $this; } public function setResourceSubscriptionManager(SubscriptionManagerInterface $subscriptionManager): self { $this->subscriptionManager = $subscriptionManager; return $this; } public function setSession( ?SessionStoreInterface $sessionStore = null, ?SessionManagerInterface $sessionManager = null, ): self { $this->sessionStore = $sessionStore; $this->sessionManager = $sessionManager; if (null !== $sessionManager && null !== $sessionStore) { throw new InvalidArgumentException('Cannot set both SessionStore and SessionManager. Set only one or the other.'); } return $this; } /** * @param string[] $scanDirs * @param string[] $excludeDirs */ 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; } public function setProtocolVersion(ProtocolVersion $protocolVersion): self { $this->protocolVersion = $protocolVersion; return $this; } /** * Manually registers a tool handler. * * @param Handler $handler * @param ?string $title Optional human-readable title for display in UI * @param array|null $inputSchema * @param ?Icon[] $icons * @param array|null $meta * @param array|null $outputSchema */ public function addTool( callable|array|string $handler, ?string $name = null, ?string $title = null, ?string $description = null, ?ToolAnnotations $annotations = null, ?array $inputSchema = null, ?array $icons = null, ?array $meta = null, ?array $outputSchema = null, ): self { $this->tools[] = compact( 'handler', 'name', 'title', 'description', 'annotations', 'inputSchema', 'icons', 'meta', 'outputSchema', ); return $this; } /** * Manually registers a resource handler. * * @param Handler $handler * @param ?Icon[] $icons * @param array|null $meta */ public function addResource( \Closure|array|string $handler, string $uri, ?string $name = null, ?string $description = null, ?string $mimeType = null, ?int $size = null, ?Annotations $annotations = null, ?array $icons = null, ?array $meta = null, ): self { $this->resources[] = compact( 'handler', 'uri', 'name', 'description', 'mimeType', 'size', 'annotations', 'icons', 'meta', ); return $this; } /** * Manually registers a resource template handler. * * @param Handler $handler * @param array|null $meta */ public function addResourceTemplate( \Closure|array|string $handler, string $uriTemplate, ?string $name = null, ?string $description = null, ?string $mimeType = null, ?Annotations $annotations = null, ?array $meta = null, ): self { $this->resourceTemplates[] = compact( 'handler', 'uriTemplate', 'name', 'description', 'mimeType', 'annotations', 'meta', ); return $this; } /** * Manually registers a prompt handler. * * @param Handler $handler * @param ?Icon[] $icons * @param array|null $meta */ public function addPrompt( \Closure|array|string $handler, ?string $name = null, ?string $title = null, ?string $description = null, ?array $icons = null, ?array $meta = null, ): self { $this->prompts[] = compact('handler', 'name', 'title', 'description', 'icons', 'meta'); return $this; } /** * Register a single custom loader. */ public function addLoader(LoaderInterface $loader): self { $this->loaders[] = $loader; return $this; } /** * @param iterable $loaders */ public function addLoaders(iterable $loaders): self { foreach ($loaders as $loader) { $this->loaders[] = $loader; } return $this; } /** * Builds the fully configured Server instance. */ public function build(): Server { $logger = $this->logger ?? new NullLogger(); $container = $this->container ?? new Container(); $registry = $this->registry ?? new Registry($this->eventDispatcher, $logger); $subscriptionManager = $this->subscriptionManager ?? new SessionSubscriptionManager($logger); $loaders = [ ...$this->loaders, new ArrayLoader($this->tools, $this->resources, $this->resourceTemplates, $this->prompts, $logger, $this->schemaGenerator), ]; $sessionManager = $this->sessionManager ?? new SessionManager( $this->sessionStore ?? new InMemorySessionStore(), $logger, ); if (null !== $this->discoveryBasePath) { if (null !== $this->discoverer || class_exists(Finder::class)) { $discoverer = $this->discoverer ?? $this->createDiscoverer($logger); $loaders[] = new DiscoveryLoader($this->discoveryBasePath, $this->discoveryScanDirs, $this->discoveryExcludeDirs, $discoverer); } else { $logger->warning('File-based discovery requires symfony/finder. Skipping automatic discovery. Run: composer require symfony/finder'); } } foreach ($loaders as $loader) { $loader->load($registry); } $messageFactory = MessageFactory::make(); $capabilities = $this->serverCapabilities ?? new ServerCapabilities( tools: $registry->hasTools(), toolsListChanged: $this->eventDispatcher instanceof EventDispatcherInterface, resources: $registry->hasResources() || $registry->hasResourceTemplates(), resourcesSubscribe: $registry->hasResources() || $registry->hasResourceTemplates(), resourcesListChanged: $this->eventDispatcher instanceof EventDispatcherInterface, prompts: $registry->hasPrompts(), promptsListChanged: $this->eventDispatcher instanceof EventDispatcherInterface, logging: true, completions: true, ); $serverInfo = $this->serverInfo ?? new Implementation(); $configuration = new Configuration($serverInfo, $capabilities, $this->paginationLimit, $this->instructions, $this->protocolVersion); $referenceHandler = $this->referenceHandler ?? new ReferenceHandler($container); $requestHandlers = array_merge($this->requestHandlers, [ new Handler\Request\CallToolHandler($registry, $referenceHandler, $logger), new Handler\Request\CompletionCompleteHandler($registry, $container), new Handler\Request\GetPromptHandler($registry, $referenceHandler, $logger), new Handler\Request\InitializeHandler($configuration), new Handler\Request\ListPromptsHandler($registry, $this->paginationLimit), new Handler\Request\ListResourcesHandler($registry, $this->paginationLimit), new Handler\Request\ListResourceTemplatesHandler($registry, $this->paginationLimit), new Handler\Request\ListToolsHandler($registry, $this->paginationLimit), new Handler\Request\PingHandler(), new Handler\Request\ReadResourceHandler($registry, $referenceHandler, $logger), new Handler\Request\ResourceSubscribeHandler($registry, $subscriptionManager, $logger), new Handler\Request\ResourceUnsubscribeHandler($registry, $subscriptionManager, $logger), new Handler\Request\SetLogLevelHandler(), ]); $notificationHandlers = array_merge($this->notificationHandlers, [ new Handler\Notification\InitializedHandler(), ]); $protocol = new Protocol( requestHandlers: $requestHandlers, notificationHandlers: $notificationHandlers, messageFactory: $messageFactory, sessionManager: $sessionManager, logger: $logger, eventDispatcher: $this->eventDispatcher, ); return new Server($protocol, $logger); } private function createDiscoverer(LoggerInterface $logger): DiscovererInterface { $discoverer = new Discoverer($logger, null, $this->schemaGenerator); if (null !== $this->discoveryCache) { return new CachedDiscoverer($discoverer, $this->discoveryCache, $logger); } return $discoverer; } }