feat: add completion/complete handler, centralize provider logic, and move completion capability (#97)
This commit is contained in:
parent
f0807f149e
commit
4b91567dbd
20 changed files with 173 additions and 81 deletions
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
namespace Mcp\Example\HttpDiscoveryUserProfile;
|
||||
|
||||
use Mcp\Capability\Prompt\Completion\ProviderInterface;
|
||||
use Mcp\Capability\Completion\ProviderInterface;
|
||||
|
||||
final class UserIdCompletionProvider implements ProviderInterface
|
||||
{
|
||||
|
|
|
|||
|
|
@ -12,12 +12,6 @@ parameters:
|
|||
count: 2
|
||||
path: examples/http-discovery-userprofile/McpElements.php
|
||||
|
||||
-
|
||||
message: '#^Call to an undefined method Mcp\\Capability\\Registry\\ResourceTemplateReference\:\:handle\(\)\.$#'
|
||||
identifier: method.notFound
|
||||
count: 1
|
||||
path: src/Capability/Registry/ResourceTemplateReference.php
|
||||
|
||||
-
|
||||
message: '#^PHPDoc tag @return with type array is incompatible with native type object\.$#'
|
||||
identifier: return.phpDocType
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
namespace Mcp\Capability\Attribute;
|
||||
|
||||
use Mcp\Capability\Prompt\Completion\ProviderInterface;
|
||||
use Mcp\Capability\Completion\ProviderInterface;
|
||||
use Mcp\Exception\InvalidArgumentException;
|
||||
|
||||
/**
|
||||
|
|
|
|||
75
src/Capability/Completion/Completer.php
Normal file
75
src/Capability/Completion/Completer.php
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
<?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\Capability\Completion;
|
||||
|
||||
use Mcp\Capability\Registry\ReferenceProviderInterface;
|
||||
use Mcp\Exception\RuntimeException;
|
||||
use Mcp\Schema\Request\CompletionCompleteRequest;
|
||||
use Mcp\Schema\Result\CompletionCompleteResult;
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
/**
|
||||
* @author Kyrian Obikwelu <koshnawaza@gmail.com>
|
||||
*/
|
||||
final class Completer implements CompleterInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ReferenceProviderInterface $referenceProvider,
|
||||
private readonly ?ContainerInterface $container = null,
|
||||
) {
|
||||
}
|
||||
|
||||
public function complete(CompletionCompleteRequest $request): CompletionCompleteResult
|
||||
{
|
||||
$argumentName = $request->argument['name'] ?? '';
|
||||
$currentValue = $request->argument['value'] ?? '';
|
||||
|
||||
$reference = match (true) {
|
||||
'ref/prompt' === $request->ref->type => $this->referenceProvider->getPrompt($request->ref->name),
|
||||
'ref/resource' === $request->ref->type => $this->referenceProvider->getResourceTemplate($request->ref->uri),
|
||||
default => null,
|
||||
};
|
||||
|
||||
if (null === $reference) {
|
||||
return new CompletionCompleteResult([]);
|
||||
}
|
||||
|
||||
$providerClassOrInstance = $reference->completionProviders[$argumentName] ?? null;
|
||||
if (null === $providerClassOrInstance) {
|
||||
return new CompletionCompleteResult([]);
|
||||
}
|
||||
|
||||
if (\is_string($providerClassOrInstance)) {
|
||||
if (!class_exists($providerClassOrInstance)) {
|
||||
throw new RuntimeException(\sprintf('Completion provider class "%s" does not exist.', $providerClassOrInstance));
|
||||
}
|
||||
|
||||
$provider = $this->container?->has($providerClassOrInstance)
|
||||
? $this->container->get($providerClassOrInstance)
|
||||
: new $providerClassOrInstance();
|
||||
} else {
|
||||
$provider = $providerClassOrInstance;
|
||||
}
|
||||
|
||||
if (!$provider instanceof ProviderInterface) {
|
||||
throw new RuntimeException('Completion provider must implement ProviderInterface.');
|
||||
}
|
||||
|
||||
$completions = $provider->getCompletions($currentValue);
|
||||
|
||||
$total = \count($completions);
|
||||
$hasMore = $total > 100;
|
||||
$pagedCompletions = \array_slice($completions, 0, 100);
|
||||
|
||||
return new CompletionCompleteResult($pagedCompletions, $total, $hasMore);
|
||||
}
|
||||
}
|
||||
25
src/Capability/Completion/CompleterInterface.php
Normal file
25
src/Capability/Completion/CompleterInterface.php
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<?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\Capability\Completion;
|
||||
|
||||
use Mcp\Schema\Request\CompletionCompleteRequest;
|
||||
use Mcp\Schema\Result\CompletionCompleteResult;
|
||||
|
||||
/**
|
||||
* Provides completion options for prompt arguments and resource template variables.
|
||||
*
|
||||
* @author Kyrian Obikwelu <koshnawaza@gmail.com>
|
||||
*/
|
||||
interface CompleterInterface
|
||||
{
|
||||
public function complete(CompletionCompleteRequest $request): CompletionCompleteResult;
|
||||
}
|
||||
52
src/Capability/Completion/EnumCompletionProvider.php
Normal file
52
src/Capability/Completion/EnumCompletionProvider.php
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
<?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\Capability\Completion;
|
||||
|
||||
use Mcp\Exception\InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* @author Kyrian Obikwelu <koshnawaza@gmail.com>
|
||||
*/
|
||||
class EnumCompletionProvider implements ProviderInterface
|
||||
{
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private array $values;
|
||||
|
||||
/**
|
||||
* @param class-string $enumClass
|
||||
*/
|
||||
public function __construct(string $enumClass)
|
||||
{
|
||||
if (!enum_exists($enumClass)) {
|
||||
throw new InvalidArgumentException(\sprintf('Class "%s" is not an enum.', $enumClass));
|
||||
}
|
||||
|
||||
$this->values = array_map(
|
||||
fn ($case) => isset($case->value) && \is_string($case->value) ? $case->value : $case->name,
|
||||
$enumClass::cases()
|
||||
);
|
||||
}
|
||||
|
||||
public function getCompletions(string $currentValue): array
|
||||
{
|
||||
if (empty($currentValue)) {
|
||||
return $this->values;
|
||||
}
|
||||
|
||||
return array_values(array_filter(
|
||||
$this->values,
|
||||
fn (string $value) => str_starts_with($value, $currentValue)
|
||||
));
|
||||
}
|
||||
}
|
||||
38
src/Capability/Completion/ListCompletionProvider.php
Normal file
38
src/Capability/Completion/ListCompletionProvider.php
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
<?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\Capability\Completion;
|
||||
|
||||
/**
|
||||
* @author Kyrian Obikwelu <koshnawaza@gmail.com>
|
||||
*/
|
||||
class ListCompletionProvider implements ProviderInterface
|
||||
{
|
||||
/**
|
||||
* @param string[] $values
|
||||
*/
|
||||
public function __construct(
|
||||
private array $values,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getCompletions(string $currentValue): array
|
||||
{
|
||||
if (empty($currentValue)) {
|
||||
return $this->values;
|
||||
}
|
||||
|
||||
return array_values(array_filter(
|
||||
$this->values,
|
||||
fn (string $value) => str_starts_with($value, $currentValue)
|
||||
));
|
||||
}
|
||||
}
|
||||
27
src/Capability/Completion/ProviderInterface.php
Normal file
27
src/Capability/Completion/ProviderInterface.php
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
<?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\Capability\Completion;
|
||||
|
||||
/**
|
||||
* @author Kyrian Obikwelu <koshnawaza@gmail.com>
|
||||
*/
|
||||
interface ProviderInterface
|
||||
{
|
||||
/**
|
||||
* Get completions for a given current value.
|
||||
*
|
||||
* @param string $currentValue the current value to get completions for
|
||||
*
|
||||
* @return string[] the completions
|
||||
*/
|
||||
public function getCompletions(string $currentValue): array;
|
||||
}
|
||||
|
|
@ -16,9 +16,9 @@ use Mcp\Capability\Attribute\McpPrompt;
|
|||
use Mcp\Capability\Attribute\McpResource;
|
||||
use Mcp\Capability\Attribute\McpResourceTemplate;
|
||||
use Mcp\Capability\Attribute\McpTool;
|
||||
use Mcp\Capability\Prompt\Completion\EnumCompletionProvider;
|
||||
use Mcp\Capability\Prompt\Completion\ListCompletionProvider;
|
||||
use Mcp\Capability\Prompt\Completion\ProviderInterface;
|
||||
use Mcp\Capability\Completion\EnumCompletionProvider;
|
||||
use Mcp\Capability\Completion\ListCompletionProvider;
|
||||
use Mcp\Capability\Completion\ProviderInterface;
|
||||
use Mcp\Capability\Registry\PromptReference;
|
||||
use Mcp\Capability\Registry\ReferenceRegistryInterface;
|
||||
use Mcp\Capability\Registry\ResourceReference;
|
||||
|
|
|
|||
|
|
@ -22,8 +22,6 @@ use Mcp\Schema\Content\TextContent;
|
|||
use Mcp\Schema\Content\TextResourceContents;
|
||||
use Mcp\Schema\Enum\Role;
|
||||
use Mcp\Schema\Prompt;
|
||||
use Mcp\Schema\Result\CompletionCompleteResult;
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
/**
|
||||
* @phpstan-import-type Handler from ElementReference
|
||||
|
|
@ -45,33 +43,6 @@ class PromptReference extends ElementReference
|
|||
parent::__construct($handler, $isManual);
|
||||
}
|
||||
|
||||
public function complete(ContainerInterface $container, string $argument, string $value): CompletionCompleteResult
|
||||
{
|
||||
$providerClassOrInstance = $this->completionProviders[$argument] ?? null;
|
||||
if (null === $providerClassOrInstance) {
|
||||
return new CompletionCompleteResult([]);
|
||||
}
|
||||
|
||||
if (\is_string($providerClassOrInstance)) {
|
||||
if (!class_exists($providerClassOrInstance)) {
|
||||
throw new RuntimeException("Completion provider class '{$providerClassOrInstance}' does not exist.");
|
||||
}
|
||||
|
||||
$provider = $container->get($providerClassOrInstance);
|
||||
} else {
|
||||
$provider = $providerClassOrInstance;
|
||||
}
|
||||
|
||||
$completions = $provider->getCompletions($value);
|
||||
|
||||
$total = \count($completions);
|
||||
$hasMore = $total > 100;
|
||||
|
||||
$pagedCompletions = \array_slice($completions, 0, 100);
|
||||
|
||||
return new CompletionCompleteResult($pagedCompletions, $total, $hasMore);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the raw result of a prompt generator into an array of MCP PromptMessages.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ use Mcp\Schema\Content\EmbeddedResource;
|
|||
use Mcp\Schema\Content\ResourceContents;
|
||||
use Mcp\Schema\Content\TextResourceContents;
|
||||
use Mcp\Schema\ResourceTemplate;
|
||||
use Mcp\Schema\Result\CompletionCompleteResult;
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
/**
|
||||
|
|
@ -64,38 +63,12 @@ class ResourceTemplateReference extends ElementReference
|
|||
{
|
||||
$arguments = array_merge($this->uriVariables, ['uri' => $uri]);
|
||||
|
||||
$result = $this->handle($container, $arguments);
|
||||
$referenceHandler = new ReferenceHandler($container);
|
||||
$result = $referenceHandler->handle($this, $arguments);
|
||||
|
||||
return $this->formatResult($result, $uri, $this->resourceTemplate->mimeType);
|
||||
}
|
||||
|
||||
public function complete(ContainerInterface $container, string $argument, string $value): CompletionCompleteResult
|
||||
{
|
||||
$providerClassOrInstance = $this->completionProviders[$argument] ?? null;
|
||||
if (null === $providerClassOrInstance) {
|
||||
return new CompletionCompleteResult([]);
|
||||
}
|
||||
|
||||
if (\is_string($providerClassOrInstance)) {
|
||||
if (!class_exists($providerClassOrInstance)) {
|
||||
throw new RuntimeException(\sprintf('Completion provider class "%s" does not exist.', $providerClassOrInstance));
|
||||
}
|
||||
|
||||
$provider = $container->get($providerClassOrInstance);
|
||||
} else {
|
||||
$provider = $providerClassOrInstance;
|
||||
}
|
||||
|
||||
$completions = $provider->getCompletions($value);
|
||||
|
||||
$total = \count($completions);
|
||||
$hasMore = $total > 100;
|
||||
|
||||
$pagedCompletions = \array_slice($completions, 0, 100);
|
||||
|
||||
return new CompletionCompleteResult($pagedCompletions, $total, $hasMore);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -12,13 +12,13 @@
|
|||
namespace Mcp\Server;
|
||||
|
||||
use Mcp\Capability\Attribute\CompletionProvider;
|
||||
use Mcp\Capability\Completion\EnumCompletionProvider;
|
||||
use Mcp\Capability\Completion\ListCompletionProvider;
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
namespace Mcp\Server\Handler;
|
||||
|
||||
use Mcp\Capability\Completion\Completer;
|
||||
use Mcp\Capability\Prompt\PromptGetterInterface;
|
||||
use Mcp\Capability\Registry\ReferenceProviderInterface;
|
||||
use Mcp\Capability\Registry\ReferenceRegistryInterface;
|
||||
|
|
@ -89,6 +90,7 @@ class JsonRpcHandler
|
|||
new Handler\Request\ListResourceTemplatesHandler($referenceProvider, $paginationLimit),
|
||||
new Handler\Request\CallToolHandler($toolCaller, $logger),
|
||||
new Handler\Request\ListToolsHandler($referenceProvider, $paginationLimit),
|
||||
new Handler\Request\CompletionCompleteHandler(new Completer($referenceProvider)),
|
||||
],
|
||||
logger: $logger,
|
||||
);
|
||||
|
|
|
|||
52
src/Server/Handler/Request/CompletionCompleteHandler.php
Normal file
52
src/Server/Handler/Request/CompletionCompleteHandler.php
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
<?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\Handler\Request;
|
||||
|
||||
use Mcp\Capability\Completion\CompleterInterface;
|
||||
use Mcp\Exception\ExceptionInterface;
|
||||
use Mcp\Schema\JsonRpc\Error;
|
||||
use Mcp\Schema\JsonRpc\HasMethodInterface;
|
||||
use Mcp\Schema\JsonRpc\Response;
|
||||
use Mcp\Schema\Request\CompletionCompleteRequest;
|
||||
use Mcp\Server\Handler\MethodHandlerInterface;
|
||||
use Mcp\Server\Session\SessionInterface;
|
||||
|
||||
/**
|
||||
* Handles completion/complete requests.
|
||||
*
|
||||
* @author Kyrian Obikwelu <koshnawaza@gmail.com>
|
||||
*/
|
||||
final class CompletionCompleteHandler implements MethodHandlerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly CompleterInterface $completer,
|
||||
) {
|
||||
}
|
||||
|
||||
public function supports(HasMethodInterface $message): bool
|
||||
{
|
||||
return $message instanceof CompletionCompleteRequest;
|
||||
}
|
||||
|
||||
public function handle(CompletionCompleteRequest|HasMethodInterface $message, SessionInterface $session): Response|Error
|
||||
{
|
||||
\assert($message instanceof CompletionCompleteRequest);
|
||||
|
||||
try {
|
||||
$result = $this->completer->complete($message);
|
||||
} catch (ExceptionInterface) {
|
||||
return Error::forInternalError('Error while handling completion request', $message->getId());
|
||||
}
|
||||
|
||||
return new Response($message->getId(), $result);
|
||||
}
|
||||
}
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
namespace Mcp\Tests\Unit\Capability\Attribute;
|
||||
|
||||
use Mcp\Capability\Prompt\Completion\ProviderInterface;
|
||||
use Mcp\Capability\Completion\ProviderInterface;
|
||||
|
||||
class CompletionProviderFixture implements ProviderInterface
|
||||
{
|
||||
|
|
|
|||
|
|
@ -11,9 +11,9 @@
|
|||
|
||||
namespace Mcp\Tests\Unit\Capability\Discovery;
|
||||
|
||||
use Mcp\Capability\Completion\EnumCompletionProvider;
|
||||
use Mcp\Capability\Completion\ListCompletionProvider;
|
||||
use Mcp\Capability\Discovery\Discoverer;
|
||||
use Mcp\Capability\Prompt\Completion\EnumCompletionProvider;
|
||||
use Mcp\Capability\Prompt\Completion\ListCompletionProvider;
|
||||
use Mcp\Capability\Registry;
|
||||
use Mcp\Capability\Registry\PromptReference;
|
||||
use Mcp\Capability\Registry\ResourceReference;
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
namespace Mcp\Tests\Unit\Capability\Prompt\Completion;
|
||||
|
||||
use Mcp\Capability\Prompt\Completion\EnumCompletionProvider;
|
||||
use Mcp\Capability\Completion\EnumCompletionProvider;
|
||||
use Mcp\Exception\InvalidArgumentException;
|
||||
use Mcp\Tests\Unit\Fixtures\Enum\PriorityEnum;
|
||||
use Mcp\Tests\Unit\Fixtures\Enum\StatusEnum;
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
namespace Mcp\Tests\Unit\Capability\Prompt\Completion;
|
||||
|
||||
use Mcp\Capability\Prompt\Completion\ListCompletionProvider;
|
||||
use Mcp\Capability\Completion\ListCompletionProvider;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class ListCompletionProviderTest extends TestCase
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
namespace Mcp\Tests\Unit\Capability\Prompt;
|
||||
|
||||
use Mcp\Capability\Prompt\Completion\EnumCompletionProvider;
|
||||
use Mcp\Capability\Completion\EnumCompletionProvider;
|
||||
use Mcp\Capability\Prompt\PromptGetter;
|
||||
use Mcp\Capability\Registry\PromptReference;
|
||||
use Mcp\Capability\Registry\ReferenceHandlerInterface;
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
namespace Mcp\Tests\Unit\Capability\Registry;
|
||||
|
||||
use Mcp\Capability\Prompt\Completion\EnumCompletionProvider;
|
||||
use Mcp\Capability\Completion\EnumCompletionProvider;
|
||||
use Mcp\Capability\Registry;
|
||||
use Mcp\Schema\Prompt;
|
||||
use Mcp\Schema\Resource;
|
||||
|
|
|
|||
Loading…
Reference in a new issue