[Capability] Remove CompletionProvider's dead providerClass argument (#499)
* [Capability] Remove CompletionProvider's dead providerClass argument It always threw on construction, so it never worked in any release. `provider:` takes the same class-string and is now the first positional argument. * Reword Changelog
This commit is contained in:
parent
749c0353ef
commit
f39625b257
11 changed files with 197 additions and 10 deletions
|
|
@ -5,6 +5,7 @@ All notable changes to `mcp/sdk` will be documented in this file.
|
|||
0.9.0
|
||||
-----
|
||||
|
||||
* [BC Break] Remove the `providerClass` argument of `#[CompletionProvider]`. Use `provider:`, which takes the same class-string and is now the first positional argument.
|
||||
* Add `HttpTransport::getSessionId()` to read the server-minted `Mcp-Session-Id`: a request-scoped caller can persist it and pass it back through the constructor's `$headers` on a later transport. Always `null` on `2026-07-28`, which removed protocol-level sessions.
|
||||
|
||||
0.8.0
|
||||
|
|
|
|||
|
|
@ -24,9 +24,9 @@ class CompletionProvider
|
|||
* @param class-string<ProviderInterface>|ProviderInterface|null $provider if a class-string, it will be resolved
|
||||
* from the container at the point of use
|
||||
* @param ?array<int, int|float|string> $values a list of values to use for completion
|
||||
* @param class-string|null $enum an enum whose cases are the completions
|
||||
*/
|
||||
public function __construct(
|
||||
public ?string $providerClass = null,
|
||||
public string|ProviderInterface|null $provider = null,
|
||||
public ?array $values = null,
|
||||
public ?string $enum = null,
|
||||
|
|
|
|||
|
|
@ -325,8 +325,6 @@ final class Discoverer implements DiscovererInterface
|
|||
|
||||
if ($attributeInstance->provider) {
|
||||
$completionProviders[$param->getName()] = $attributeInstance->provider;
|
||||
} elseif ($attributeInstance->providerClass) {
|
||||
$completionProviders[$param->getName()] = $attributeInstance->provider;
|
||||
} elseif ($attributeInstance->values) {
|
||||
$completionProviders[$param->getName()] = new ListCompletionProvider($attributeInstance->values);
|
||||
} elseif ($attributeInstance->enum) {
|
||||
|
|
|
|||
|
|
@ -287,7 +287,7 @@ final class ReflectedElementLoader implements LoaderInterface
|
|||
}
|
||||
|
||||
/**
|
||||
* @return array<string, ProviderInterface>
|
||||
* @return array<string, class-string<ProviderInterface>|ProviderInterface>
|
||||
*/
|
||||
private function getCompletionProviders(\ReflectionMethod|\ReflectionFunction $reflection): array
|
||||
{
|
||||
|
|
@ -307,8 +307,6 @@ final class ReflectedElementLoader implements LoaderInterface
|
|||
|
||||
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) {
|
||||
|
|
|
|||
57
tests/Integration/CompletionTest.php
Normal file
57
tests/Integration/CompletionTest.php
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
<?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\Tests\Integration;
|
||||
|
||||
use Mcp\Schema\PromptReference;
|
||||
use PHPUnit\Framework\Attributes\TestDox;
|
||||
|
||||
/**
|
||||
* Argument completion driven by a `#[CompletionProvider(provider: …)]` class-string.
|
||||
*
|
||||
* The fixture's provider only exists in the container, so completions coming
|
||||
* back at all is what proves the attribute reached the registry and the
|
||||
* container was asked to build it.
|
||||
*
|
||||
* @see Fixture/completion.php for the server under test
|
||||
*/
|
||||
final class CompletionTest extends IntegrationTestCase
|
||||
{
|
||||
#[TestDox('a class-string provider completes from the container-built instance')]
|
||||
public function testClassStringProviderCompletesFromTheContainer(): void
|
||||
{
|
||||
$client = $this->connect('completion');
|
||||
|
||||
$result = $client->complete(new PromptReference('book_seat'), ['name' => 'seat', 'value' => '12']);
|
||||
|
||||
$this->assertSame(['12A', '12B'], $result->values);
|
||||
}
|
||||
|
||||
#[TestDox('an empty value offers every completion the provider knows')]
|
||||
public function testEmptyValueOffersEveryCompletion(): void
|
||||
{
|
||||
$client = $this->connect('completion');
|
||||
|
||||
$result = $client->complete(new PromptReference('book_seat'), ['name' => 'seat', 'value' => '']);
|
||||
|
||||
$this->assertSame(['12A', '12B', '14C'], $result->values);
|
||||
}
|
||||
|
||||
#[TestDox('an argument the prompt does not declare completes to nothing')]
|
||||
public function testUnknownArgumentCompletesToNothing(): void
|
||||
{
|
||||
$client = $this->connect('completion');
|
||||
|
||||
$result = $client->complete(new PromptReference('book_seat'), ['name' => 'unknown', 'value' => '1']);
|
||||
|
||||
$this->assertSame([], $result->values);
|
||||
}
|
||||
}
|
||||
38
tests/Integration/Fixture/Completion/BookingElements.php
Normal file
38
tests/Integration/Fixture/Completion/BookingElements.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\Tests\Integration\Fixture\Completion;
|
||||
|
||||
use Mcp\Capability\Attribute\CompletionProvider;
|
||||
use Mcp\Capability\Attribute\McpPrompt;
|
||||
|
||||
/**
|
||||
* @author Christopher Hertel <mail@christopher-hertel.de>
|
||||
*/
|
||||
final class BookingElements
|
||||
{
|
||||
/**
|
||||
* Confirms a seat booking.
|
||||
*
|
||||
* @param string $seat the seat to book
|
||||
*
|
||||
* @return array the prompt messages
|
||||
*/
|
||||
#[McpPrompt(name: 'book_seat')]
|
||||
public function bookSeat(
|
||||
#[CompletionProvider(provider: SeatCompletionProvider::class)]
|
||||
string $seat,
|
||||
): array {
|
||||
return [
|
||||
['role' => 'user', 'content' => \sprintf('Book seat %s for me.', $seat)],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
<?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\Tests\Integration\Fixture\Completion;
|
||||
|
||||
use Mcp\Capability\Completion\ProviderInterface;
|
||||
|
||||
/**
|
||||
* A provider that cannot be built without its seat map.
|
||||
*
|
||||
* The constructor takes a scalar the auto-wiring container cannot supply, so a
|
||||
* completion that comes back with seats in it proves the provider was taken
|
||||
* from the container rather than instantiated on the spot.
|
||||
*
|
||||
* @author Christopher Hertel <mail@christopher-hertel.de>
|
||||
*/
|
||||
final class SeatCompletionProvider implements ProviderInterface
|
||||
{
|
||||
/**
|
||||
* @param list<string> $seats
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly array $seats,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getCompletions(string $currentValue): array
|
||||
{
|
||||
return array_values(array_filter(
|
||||
$this->seats,
|
||||
static fn (string $seat): bool => str_starts_with($seat, $currentValue),
|
||||
));
|
||||
}
|
||||
}
|
||||
31
tests/Integration/Fixture/completion.php
Normal file
31
tests/Integration/Fixture/completion.php
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
<?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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Server for {@see \Mcp\Tests\Integration\CompletionTest}.
|
||||
*/
|
||||
|
||||
use Mcp\Capability\Registry\Container;
|
||||
use Mcp\Server;
|
||||
use Mcp\Server\Transport\StdioTransport;
|
||||
use Mcp\Tests\Integration\Fixture\Completion\SeatCompletionProvider;
|
||||
|
||||
require_once dirname(__DIR__, 3).'/vendor/autoload.php';
|
||||
|
||||
$container = new Container();
|
||||
$container->set(SeatCompletionProvider::class, new SeatCompletionProvider(['12A', '12B', '14C']));
|
||||
|
||||
Server::builder()
|
||||
->setServerInfo('integration-server', '1.0.0')
|
||||
->setContainer($container)
|
||||
->setDiscovery(__DIR__, ['Completion'])
|
||||
->build()
|
||||
->run(new StdioTransport());
|
||||
|
|
@ -26,6 +26,15 @@ class CompletionProviderTest extends TestCase
|
|||
$this->assertNull($attribute->enum);
|
||||
}
|
||||
|
||||
public function testCanBeConstructedWithAPositionalProviderClass(): void
|
||||
{
|
||||
$attribute = new CompletionProvider(CompletionProviderFixture::class);
|
||||
|
||||
$this->assertSame(CompletionProviderFixture::class, $attribute->provider);
|
||||
$this->assertNull($attribute->values);
|
||||
$this->assertNull($attribute->enum);
|
||||
}
|
||||
|
||||
public function testCanBeConstructedWithProviderInstance(): void
|
||||
{
|
||||
$instance = new CompletionProviderFixture();
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ class DiscoveryTest extends TestCase
|
|||
$this->assertEquals([InvocablePromptFixture::class, '__invoke'], $prompts['InvokableGreeterPrompt']->handler);
|
||||
|
||||
$this->assertArrayHasKey('content_creator', $prompts);
|
||||
$this->assertCount(3, $prompts['content_creator']->completionProviders);
|
||||
$this->assertCount(4, $prompts['content_creator']->completionProviders);
|
||||
|
||||
$templates = $discovery->getResourceTemplates();
|
||||
$this->assertCount(4, $templates);
|
||||
|
|
@ -165,7 +165,7 @@ class DiscoveryTest extends TestCase
|
|||
$discovery = $this->discoverer->discover(__DIR__, ['Fixtures']);
|
||||
|
||||
$this->assertArrayHasKey('content_creator', $prompts = $discovery->getPrompts());
|
||||
$this->assertCount(3, $prompts['content_creator']->completionProviders);
|
||||
$this->assertCount(4, $prompts['content_creator']->completionProviders);
|
||||
|
||||
$typeProvider = $prompts['content_creator']->completionProviders['type'];
|
||||
$this->assertInstanceOf(ListCompletionProvider::class, $typeProvider);
|
||||
|
|
@ -182,4 +182,14 @@ class DiscoveryTest extends TestCase
|
|||
$categoryProvider = $templates['content://{category}/{slug}']->completionProviders['category'];
|
||||
$this->assertInstanceOf(ListCompletionProvider::class, $categoryProvider);
|
||||
}
|
||||
|
||||
public function testDiscoversPositionalCompletionProviderAsClassString(): void
|
||||
{
|
||||
$discovery = $this->discoverer->discover(__DIR__, ['Fixtures']);
|
||||
|
||||
$this->assertArrayHasKey('content_creator', $prompts = $discovery->getPrompts());
|
||||
|
||||
// Kept as a class-string so the container resolves it at the point of use.
|
||||
$this->assertEquals(CompletionProviderFixture::class, $prompts['content_creator']->completionProviders['author']);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,13 +14,14 @@ namespace Mcp\Tests\Unit\Capability\Discovery\Fixtures;
|
|||
use Mcp\Capability\Attribute\CompletionProvider;
|
||||
use Mcp\Capability\Attribute\McpPrompt;
|
||||
use Mcp\Capability\Attribute\McpResourceTemplate;
|
||||
use Mcp\Tests\Unit\Capability\Attribute\CompletionProviderFixture;
|
||||
use Mcp\Tests\Unit\Fixtures\Enum\PriorityEnum;
|
||||
use Mcp\Tests\Unit\Fixtures\Enum\StatusEnum;
|
||||
|
||||
class EnhancedCompletionHandler
|
||||
{
|
||||
/**
|
||||
* Create content with list and enum completion providers.
|
||||
* Create content with list, enum and positional provider completion providers.
|
||||
*/
|
||||
#[McpPrompt(name: 'content_creator')]
|
||||
public function createContent(
|
||||
|
|
@ -30,9 +31,11 @@ class EnhancedCompletionHandler
|
|||
string $status,
|
||||
#[CompletionProvider(enum: PriorityEnum::class)]
|
||||
string $priority,
|
||||
#[CompletionProvider(CompletionProviderFixture::class)]
|
||||
string $author,
|
||||
): array {
|
||||
return [
|
||||
['role' => 'user', 'content' => "Create a {$type} with status {$status} and priority {$priority}"],
|
||||
['role' => 'user', 'content' => "Create a {$type} with status {$status} and priority {$priority} for {$author}"],
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue