Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 66 additions & 86 deletions src/Capability/Registry.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@

use Mcp\Capability\Discovery\DiscoveryState;
use Mcp\Capability\Registry\PromptReference;
use Mcp\Capability\Registry\ReferenceProviderInterface;
use Mcp\Capability\Registry\ReferenceRegistryInterface;
use Mcp\Capability\Registry\ResourceReference;
use Mcp\Capability\Registry\ResourceTemplateReference;
use Mcp\Capability\Registry\ToolReference;
Expand All @@ -31,21 +29,17 @@
use Mcp\Schema\Prompt;
use Mcp\Schema\Resource;
use Mcp\Schema\ResourceTemplate;
use Mcp\Schema\ServerCapabilities;
use Mcp\Schema\Tool;
use Psr\EventDispatcher\EventDispatcherInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;

/**
* Registry implementation that manages MCP element registration and access.
* Implements both ReferenceProvider (for access) and ReferenceRegistry (for registration)
* following the Interface Segregation Principle.
*
* @author Kyrian Obikwelu <koshnawaza@gmail.com>
* @author Pavel Buchnev <butschster@gmail.com>
*/
final class Registry implements ReferenceProviderInterface, ReferenceRegistryInterface
final class Registry implements RegistryInterface
{
/**
* @var array<string, ToolReference>
Expand All @@ -67,34 +61,13 @@ final class Registry implements ReferenceProviderInterface, ReferenceRegistryInt
*/
private array $resourceTemplates = [];

private ServerCapabilities $serverCapabilities;

public function __construct(
private readonly ?EventDispatcherInterface $eventDispatcher = null,
private readonly LoggerInterface $logger = new NullLogger(),
private readonly NameValidator $nameValidator = new NameValidator(),
) {
}

public function getCapabilities(): ServerCapabilities
{
if (!$this->hasElements()) {
$this->logger->info('No capabilities registered on server.');
}

return $this->serverCapabilities ?? new ServerCapabilities(
tools: [] !== $this->tools,
toolsListChanged: $this->eventDispatcher instanceof EventDispatcherInterface,
resources: [] !== $this->resources || [] !== $this->resourceTemplates,
resourcesSubscribe: false,
resourcesListChanged: $this->eventDispatcher instanceof EventDispatcherInterface,
prompts: [] !== $this->prompts,
promptsListChanged: $this->eventDispatcher instanceof EventDispatcherInterface,
logging: false,
completions: true,
);
}

public function registerTool(Tool $tool, callable|array|string $handler, bool $isManual = false): void
{
$toolName = $tool->name;
Expand Down Expand Up @@ -220,41 +193,9 @@ public function clear(): void
}
}

public function getTool(string $name): ToolReference
{
return $this->tools[$name] ?? throw new ToolNotFoundException($name);
}

public function getResource(
string $uri,
bool $includeTemplates = true,
): ResourceReference|ResourceTemplateReference {
$registration = $this->resources[$uri] ?? null;
if ($registration) {
return $registration;
}

if ($includeTemplates) {
foreach ($this->resourceTemplates as $template) {
if ($template->matches($uri)) {
return $template;
}
}
}

$this->logger->debug('No resource matched URI.', ['uri' => $uri]);

throw new ResourceNotFoundException($uri);
}

public function getResourceTemplate(string $uriTemplate): ResourceTemplateReference
public function hasTools(): bool
{
return $this->resourceTemplates[$uriTemplate] ?? throw new ResourceNotFoundException($uriTemplate);
}

public function getPrompt(string $name): PromptReference
{
return $this->prompts[$name] ?? throw new PromptNotFoundException($name);
return [] !== $this->tools;
}

public function getTools(?int $limit = null, ?string $cursor = null): Page
Expand All @@ -279,6 +220,16 @@ public function getTools(?int $limit = null, ?string $cursor = null): Page
return new Page($paginatedTools, $nextCursor);
}

public function getTool(string $name): ToolReference
{
return $this->tools[$name] ?? throw new ToolNotFoundException($name);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it'd be nice for reviewing to keep the previous order of methods :D.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yea fair, i think we're currently a bit rough with PR scopes 😬


public function hasResources(): bool
{
return [] !== $this->resources;
}

public function getResources(?int $limit = null, ?string $cursor = null): Page
{
$resources = [];
Expand All @@ -301,26 +252,31 @@ public function getResources(?int $limit = null, ?string $cursor = null): Page
return new Page($paginatedResources, $nextCursor);
}

public function getPrompts(?int $limit = null, ?string $cursor = null): Page
{
$prompts = [];
foreach ($this->prompts as $promptReference) {
$prompts[$promptReference->prompt->name] = $promptReference->prompt;
public function getResource(
string $uri,
bool $includeTemplates = true,
): ResourceReference|ResourceTemplateReference {
$registration = $this->resources[$uri] ?? null;
if ($registration) {
return $registration;
}

if (null === $limit) {
return new Page($prompts, null);
if ($includeTemplates) {
foreach ($this->resourceTemplates as $template) {
if ($template->matches($uri)) {
return $template;
}
}
Comment on lines +264 to +269
Copy link
Contributor

@soyuka soyuka Nov 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if ($includeTemplates) {
foreach ($this->resourceTemplates as $template) {
if ($template->matches($uri)) {
return $template;
}
}
if (!$includeTemplates) {
throw new ResourceNotFoundException($uri);
}
foreach ($this->resourceTemplates as $template) {
if ($template->matches($uri)) {
return $template;
}
}

}

$paginatedPrompts = $this->paginateResults($prompts, $limit, $cursor);
$this->logger->debug('No resource matched URI.', ['uri' => $uri]);

$nextCursor = $this->calculateNextCursor(
\count($prompts),
$cursor,
$limit
);
throw new ResourceNotFoundException($uri);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

weird to log and to throw ?

}

return new Page($paginatedPrompts, $nextCursor);
public function hasResourceTemplates(): bool
{
return [] !== $this->resourceTemplates;
}

public function getResourceTemplates(?int $limit = null, ?string $cursor = null): Page
Expand All @@ -345,12 +301,41 @@ public function getResourceTemplates(?int $limit = null, ?string $cursor = null)
return new Page($paginatedTemplates, $nextCursor);
}

public function hasElements(): bool
public function getResourceTemplate(string $uriTemplate): ResourceTemplateReference
{
return !empty($this->tools)
|| !empty($this->resources)
|| !empty($this->prompts)
|| !empty($this->resourceTemplates);
return $this->resourceTemplates[$uriTemplate] ?? throw new ResourceNotFoundException($uriTemplate);
}

public function hasPrompts(): bool
{
return [] !== $this->prompts;
}

public function getPrompts(?int $limit = null, ?string $cursor = null): Page
{
$prompts = [];
foreach ($this->prompts as $promptReference) {
$prompts[$promptReference->prompt->name] = $promptReference->prompt;
}

if (null === $limit) {
return new Page($prompts, null);
}

$paginatedPrompts = $this->paginateResults($prompts, $limit, $cursor);

$nextCursor = $this->calculateNextCursor(
\count($prompts),
$cursor,
$limit
);

return new Page($paginatedPrompts, $nextCursor);
}

public function getPrompt(string $name): PromptReference
{
return $this->prompts[$name] ?? throw new PromptNotFoundException($name);
}

/**
Expand Down Expand Up @@ -464,9 +449,4 @@ private function paginateResults(array $items, int $limit, ?string $cursor = nul

return array_values(\array_slice($items, $offset, $limit));
}

public function setServerCapabilities(ServerCapabilities $serverCapabilities): void
{
$this->serverCapabilities = $serverCapabilities;
}
}
4 changes: 2 additions & 2 deletions src/Capability/Registry/Loader/ArrayLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
use Mcp\Capability\Discovery\HandlerResolver;
use Mcp\Capability\Discovery\SchemaGenerator;
use Mcp\Capability\Registry\ElementReference;
use Mcp\Capability\Registry\ReferenceRegistryInterface;
use Mcp\Capability\RegistryInterface;
use Mcp\Exception\ConfigurationException;
use Mcp\Schema\Annotations;
use Mcp\Schema\Icon;
Expand Down Expand Up @@ -86,7 +86,7 @@ public function __construct(
) {
}

public function load(ReferenceRegistryInterface $registry): void
public function load(RegistryInterface $registry): void
{
$docBlockParser = new DocBlockParser(logger: $this->logger);
$schemaGenerator = new SchemaGenerator($docBlockParser);
Expand Down
4 changes: 2 additions & 2 deletions src/Capability/Registry/Loader/DiscoveryLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

use Mcp\Capability\Discovery\CachedDiscoverer;
use Mcp\Capability\Discovery\Discoverer;
use Mcp\Capability\Registry\ReferenceRegistryInterface;
use Mcp\Capability\RegistryInterface;
use Psr\Log\LoggerInterface;
use Psr\SimpleCache\CacheInterface;

Expand All @@ -35,7 +35,7 @@ public function __construct(
) {
}

public function load(ReferenceRegistryInterface $registry): void
public function load(RegistryInterface $registry): void
{
// This now encapsulates the discovery process
$discoverer = new Discoverer($this->logger);
Expand Down
4 changes: 2 additions & 2 deletions src/Capability/Registry/Loader/LoaderInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@

namespace Mcp\Capability\Registry\Loader;

use Mcp\Capability\Registry\ReferenceRegistryInterface;
use Mcp\Capability\RegistryInterface;

/**
* @author Antoine Bluchet <soyuka@gmail.com>
*/
interface LoaderInterface
{
public function load(ReferenceRegistryInterface $registry): void;
public function load(RegistryInterface $registry): void;
}
7 changes: 5 additions & 2 deletions src/Capability/Registry/ReferenceHandlerInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@

namespace Mcp\Capability\Registry;

use Mcp\Exception\InvalidArgumentException;
use Mcp\Exception\RegistryException;

/**
* Interface for handling execution of MCP elements.
* Allows custom implementations of element execution logic.
Expand All @@ -27,8 +30,8 @@ interface ReferenceHandlerInterface
*
* @return mixed the result of the element execution
*
* @throws \Mcp\Exception\InvalidArgumentException if the handler is invalid
* @throws \Mcp\Exception\RegistryException if execution fails
* @throws InvalidArgumentException if the handler is invalid
* @throws RegistryException if execution fails
*/
public function handle(ElementReference $reference, array $arguments): mixed;
}
79 changes: 0 additions & 79 deletions src/Capability/Registry/ReferenceProviderInterface.php

This file was deleted.

Loading
Loading