Introduce Capability Registry & Discovery
This commit is contained in:
parent
09707b4798
commit
da0d5a87a0
31 changed files with 7063 additions and 8 deletions
30
tests/Capability/Attribute/CompletionProviderFixture.php
Normal file
30
tests/Capability/Attribute/CompletionProviderFixture.php
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
<?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\Capability\Attribute;
|
||||
|
||||
use Mcp\Capability\Prompt\Completion\ProviderInterface;
|
||||
|
||||
class CompletionProviderFixture implements ProviderInterface
|
||||
{
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
public static array $completions = ['alpha', 'beta', 'gamma'];
|
||||
public static string $lastCurrentValue = '';
|
||||
|
||||
public function getCompletions(string $currentValue): array
|
||||
{
|
||||
self::$lastCurrentValue = $currentValue;
|
||||
|
||||
return array_filter(self::$completions, fn ($item) => str_starts_with($item, $currentValue));
|
||||
}
|
||||
}
|
||||
85
tests/Capability/Attribute/CompletionProviderTest.php
Normal file
85
tests/Capability/Attribute/CompletionProviderTest.php
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
<?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\Capability\Attribute;
|
||||
|
||||
use Mcp\Capability\Attribute\CompletionProvider;
|
||||
use Mcp\Tests\Fixtures\Enum\StatusEnum;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class CompletionProviderTest extends TestCase
|
||||
{
|
||||
public function testCanBeConstructedWithProviderClass(): void
|
||||
{
|
||||
$attribute = new CompletionProvider(provider: CompletionProviderFixture::class);
|
||||
|
||||
$this->assertSame(CompletionProviderFixture::class, $attribute->provider);
|
||||
$this->assertNull($attribute->values);
|
||||
$this->assertNull($attribute->enum);
|
||||
}
|
||||
|
||||
public function testCanBeConstructedWithProviderInstance(): void
|
||||
{
|
||||
$instance = new CompletionProviderFixture();
|
||||
$attribute = new CompletionProvider(provider: $instance);
|
||||
|
||||
$this->assertSame($instance, $attribute->provider);
|
||||
$this->assertNull($attribute->values);
|
||||
$this->assertNull($attribute->enum);
|
||||
}
|
||||
|
||||
public function testCanBeConstructedWithValuesArray(): void
|
||||
{
|
||||
$values = ['draft', 'published', 'archived'];
|
||||
$attribute = new CompletionProvider(values: $values);
|
||||
|
||||
$this->assertNull($attribute->provider);
|
||||
$this->assertSame($values, $attribute->values);
|
||||
$this->assertNull($attribute->enum);
|
||||
}
|
||||
|
||||
public function testCanBeConstructedWithEnumClass(): void
|
||||
{
|
||||
$attribute = new CompletionProvider(enum: StatusEnum::class);
|
||||
|
||||
$this->assertNull($attribute->provider);
|
||||
$this->assertNull($attribute->values);
|
||||
$this->assertSame(StatusEnum::class, $attribute->enum);
|
||||
}
|
||||
|
||||
public function testThrowsExceptionWhenNoParametersProvided(): void
|
||||
{
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
$this->expectExceptionMessage('Only one of provider, values, or enum can be set');
|
||||
new CompletionProvider();
|
||||
}
|
||||
|
||||
public function testThrowsExceptionWhenMultipleParametersProvided(): void
|
||||
{
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
$this->expectExceptionMessage('Only one of provider, values, or enum can be set');
|
||||
new CompletionProvider(
|
||||
provider: CompletionProviderFixture::class,
|
||||
values: ['test']
|
||||
);
|
||||
}
|
||||
|
||||
public function testThrowsExceptionWhenAllParametersProvided(): void
|
||||
{
|
||||
$this->expectException(\InvalidArgumentException::class);
|
||||
$this->expectExceptionMessage('Only one of provider, values, or enum can be set');
|
||||
new CompletionProvider(
|
||||
provider: CompletionProviderFixture::class,
|
||||
values: ['test'],
|
||||
enum: StatusEnum::class
|
||||
);
|
||||
}
|
||||
}
|
||||
52
tests/Capability/Attribute/McpPromptTest.php
Normal file
52
tests/Capability/Attribute/McpPromptTest.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\Tests\Capability\Attribute;
|
||||
|
||||
use Mcp\Capability\Attribute\McpPrompt;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class McpPromptTest extends TestCase
|
||||
{
|
||||
public function testInstantiatesWithNameAndDescription(): void
|
||||
{
|
||||
// Arrange
|
||||
$name = 'test-prompt-name';
|
||||
$description = 'This is a test prompt description.';
|
||||
|
||||
// Act
|
||||
$attribute = new McpPrompt(name: $name, description: $description);
|
||||
|
||||
// Assert
|
||||
$this->assertSame($name, $attribute->name);
|
||||
$this->assertSame($description, $attribute->description);
|
||||
}
|
||||
|
||||
public function testInstantiatesWithNullValuesForNameAndDescription(): void
|
||||
{
|
||||
// Arrange & Act
|
||||
$attribute = new McpPrompt(name: null, description: null);
|
||||
|
||||
// Assert
|
||||
$this->assertNull($attribute->name);
|
||||
$this->assertNull($attribute->description);
|
||||
}
|
||||
|
||||
public function testInstantiatesWithMissingOptionalArguments(): void
|
||||
{
|
||||
// Arrange & Act
|
||||
$attribute = new McpPrompt(); // Use default constructor values
|
||||
|
||||
// Assert
|
||||
$this->assertNull($attribute->name);
|
||||
$this->assertNull($attribute->description);
|
||||
}
|
||||
}
|
||||
71
tests/Capability/Attribute/McpResourceTemplateTest.php
Normal file
71
tests/Capability/Attribute/McpResourceTemplateTest.php
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
<?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\Capability\Attribute;
|
||||
|
||||
use Mcp\Capability\Attribute\McpResourceTemplate;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class McpResourceTemplateTest extends TestCase
|
||||
{
|
||||
public function testInstantiatesWithCorrectProperties(): void
|
||||
{
|
||||
// Arrange
|
||||
$uriTemplate = 'file:///{path}/data';
|
||||
$name = 'test-template-name';
|
||||
$description = 'This is a test template description.';
|
||||
$mimeType = 'application/json';
|
||||
|
||||
// Act
|
||||
$attribute = new McpResourceTemplate(
|
||||
uriTemplate: $uriTemplate,
|
||||
name: $name,
|
||||
description: $description,
|
||||
mimeType: $mimeType,
|
||||
);
|
||||
|
||||
// Assert
|
||||
$this->assertSame($uriTemplate, $attribute->uriTemplate);
|
||||
$this->assertSame($name, $attribute->name);
|
||||
$this->assertSame($description, $attribute->description);
|
||||
$this->assertSame($mimeType, $attribute->mimeType);
|
||||
}
|
||||
|
||||
public function testInstantiatesWithNullValuesForNameAndDescription(): void
|
||||
{
|
||||
// Arrange & Act
|
||||
$attribute = new McpResourceTemplate(
|
||||
uriTemplate: 'test://{id}', // uriTemplate is required
|
||||
name: null,
|
||||
description: null,
|
||||
mimeType: null,
|
||||
);
|
||||
|
||||
// Assert
|
||||
$this->assertSame('test://{id}', $attribute->uriTemplate);
|
||||
$this->assertNull($attribute->name);
|
||||
$this->assertNull($attribute->description);
|
||||
$this->assertNull($attribute->mimeType);
|
||||
}
|
||||
|
||||
public function testInstantiatesWithMissingOptionalArguments(): void
|
||||
{
|
||||
// Arrange & Act
|
||||
$uriTemplate = 'tmpl://{key}';
|
||||
$attribute = new McpResourceTemplate(uriTemplate: $uriTemplate);
|
||||
|
||||
// Assert
|
||||
$this->assertSame($uriTemplate, $attribute->uriTemplate);
|
||||
$this->assertNull($attribute->name);
|
||||
$this->assertNull($attribute->description);
|
||||
$this->assertNull($attribute->mimeType);
|
||||
}
|
||||
}
|
||||
77
tests/Capability/Attribute/McpResourceTest.php
Normal file
77
tests/Capability/Attribute/McpResourceTest.php
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
<?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\Capability\Attribute;
|
||||
|
||||
use Mcp\Capability\Attribute\McpResource;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class McpResourceTest extends TestCase
|
||||
{
|
||||
public function testInstantiatesWithCorrectProperties(): void
|
||||
{
|
||||
// Arrange
|
||||
$uri = 'file:///test/resource';
|
||||
$name = 'test-resource-name';
|
||||
$description = 'This is a test resource description.';
|
||||
$mimeType = 'text/plain';
|
||||
$size = 1024;
|
||||
|
||||
// Act
|
||||
$attribute = new McpResource(
|
||||
uri: $uri,
|
||||
name: $name,
|
||||
description: $description,
|
||||
mimeType: $mimeType,
|
||||
size: $size,
|
||||
);
|
||||
|
||||
// Assert
|
||||
$this->assertSame($uri, $attribute->uri);
|
||||
$this->assertSame($name, $attribute->name);
|
||||
$this->assertSame($description, $attribute->description);
|
||||
$this->assertSame($mimeType, $attribute->mimeType);
|
||||
$this->assertSame($size, $attribute->size);
|
||||
}
|
||||
|
||||
public function testInstantiatesWithNullValuesForNameAndDescription(): void
|
||||
{
|
||||
// Arrange & Act
|
||||
$attribute = new McpResource(
|
||||
uri: 'file:///test', // URI is required
|
||||
name: null,
|
||||
description: null,
|
||||
mimeType: null,
|
||||
size: null,
|
||||
);
|
||||
|
||||
// Assert
|
||||
$this->assertSame('file:///test', $attribute->uri);
|
||||
$this->assertNull($attribute->name);
|
||||
$this->assertNull($attribute->description);
|
||||
$this->assertNull($attribute->mimeType);
|
||||
$this->assertNull($attribute->size);
|
||||
}
|
||||
|
||||
public function testInstantiatesWithMissingOptionalArguments(): void
|
||||
{
|
||||
// Arrange & Act
|
||||
$uri = 'file:///only-uri';
|
||||
$attribute = new McpResource(uri: $uri);
|
||||
|
||||
// Assert
|
||||
$this->assertSame($uri, $attribute->uri);
|
||||
$this->assertNull($attribute->name);
|
||||
$this->assertNull($attribute->description);
|
||||
$this->assertNull($attribute->mimeType);
|
||||
$this->assertNull($attribute->size);
|
||||
}
|
||||
}
|
||||
52
tests/Capability/Attribute/McpToolTest.php
Normal file
52
tests/Capability/Attribute/McpToolTest.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\Tests\Capability\Attribute;
|
||||
|
||||
use Mcp\Capability\Attribute\McpTool;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class McpToolTest extends TestCase
|
||||
{
|
||||
public function testInstantiatesWithCorrectProperties(): void
|
||||
{
|
||||
// Arrange
|
||||
$name = 'test-tool-name';
|
||||
$description = 'This is a test description.';
|
||||
|
||||
// Act
|
||||
$attribute = new McpTool(name: $name, description: $description);
|
||||
|
||||
// Assert
|
||||
$this->assertSame($name, $attribute->name);
|
||||
$this->assertSame($description, $attribute->description);
|
||||
}
|
||||
|
||||
public function testInstantiatesWithNullValuesForNameAndDescription(): void
|
||||
{
|
||||
// Arrange & Act
|
||||
$attribute = new McpTool(name: null, description: null);
|
||||
|
||||
// Assert
|
||||
$this->assertNull($attribute->name);
|
||||
$this->assertNull($attribute->description);
|
||||
}
|
||||
|
||||
public function testInstantiatesWithMissingOptionalArguments(): void
|
||||
{
|
||||
// Arrange & Act
|
||||
$attribute = new McpTool(); // Use default constructor values
|
||||
|
||||
// Assert
|
||||
$this->assertNull($attribute->name);
|
||||
$this->assertNull($attribute->description);
|
||||
}
|
||||
}
|
||||
182
tests/Capability/Discovery/DiscoveryTest.php
Normal file
182
tests/Capability/Discovery/DiscoveryTest.php
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
<?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\Capability\Discovery;
|
||||
|
||||
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\RegisteredPrompt;
|
||||
use Mcp\Capability\Registry\RegisteredResource;
|
||||
use Mcp\Capability\Registry\RegisteredResourceTemplate;
|
||||
use Mcp\Capability\Registry\RegisteredTool;
|
||||
use Mcp\Tests\Capability\Attribute\CompletionProviderFixture;
|
||||
use Mcp\Tests\Capability\Discovery\Fixtures\DiscoverableToolHandler;
|
||||
use Mcp\Tests\Capability\Discovery\Fixtures\InvocablePromptFixture;
|
||||
use Mcp\Tests\Capability\Discovery\Fixtures\InvocableResourceFixture;
|
||||
use Mcp\Tests\Capability\Discovery\Fixtures\InvocableResourceTemplateFixture;
|
||||
use Mcp\Tests\Capability\Discovery\Fixtures\InvocableToolFixture;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class DiscoveryTest extends TestCase
|
||||
{
|
||||
private Registry $registry;
|
||||
private Discoverer $discoverer;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->registry = new Registry();
|
||||
$this->discoverer = new Discoverer($this->registry);
|
||||
}
|
||||
|
||||
public function testDiscoversAllElementTypesCorrectlyFromFixtureFiles()
|
||||
{
|
||||
$this->discoverer->discover(__DIR__, ['Fixtures']);
|
||||
|
||||
$tools = $this->registry->getTools();
|
||||
$this->assertCount(4, $tools);
|
||||
|
||||
$greetUserTool = $this->registry->getTool('greet_user');
|
||||
$this->assertInstanceOf(RegisteredTool::class, $greetUserTool);
|
||||
$this->assertFalse($greetUserTool->isManual);
|
||||
$this->assertEquals('greet_user', $greetUserTool->tool->name);
|
||||
$this->assertEquals('Greets a user by name.', $greetUserTool->tool->description);
|
||||
$this->assertEquals([DiscoverableToolHandler::class, 'greet'], $greetUserTool->handler);
|
||||
$this->assertArrayHasKey('name', $greetUserTool->tool->inputSchema['properties'] ?? []);
|
||||
|
||||
$repeatActionTool = $this->registry->getTool('repeatAction');
|
||||
$this->assertInstanceOf(RegisteredTool::class, $repeatActionTool);
|
||||
$this->assertEquals('A tool with more complex parameters and inferred name/description.', $repeatActionTool->tool->description);
|
||||
$this->assertTrue($repeatActionTool->tool->annotations->readOnlyHint);
|
||||
$this->assertEquals(['count', 'loudly', 'mode'], array_keys($repeatActionTool->tool->inputSchema['properties'] ?? []));
|
||||
|
||||
$invokableCalcTool = $this->registry->getTool('InvokableCalculator');
|
||||
$this->assertInstanceOf(RegisteredTool::class, $invokableCalcTool);
|
||||
$this->assertFalse($invokableCalcTool->isManual);
|
||||
$this->assertEquals([InvocableToolFixture::class, '__invoke'], $invokableCalcTool->handler);
|
||||
|
||||
$this->assertNull($this->registry->getTool('private_tool_should_be_ignored'));
|
||||
$this->assertNull($this->registry->getTool('protected_tool_should_be_ignored'));
|
||||
$this->assertNull($this->registry->getTool('static_tool_should_be_ignored'));
|
||||
|
||||
$resources = $this->registry->getResources();
|
||||
$this->assertCount(3, $resources);
|
||||
|
||||
$appVersionRes = $this->registry->getResource('app://info/version');
|
||||
$this->assertInstanceOf(RegisteredResource::class, $appVersionRes);
|
||||
$this->assertFalse($appVersionRes->isManual);
|
||||
$this->assertEquals('app_version', $appVersionRes->schema->name);
|
||||
$this->assertEquals('text/plain', $appVersionRes->schema->mimeType);
|
||||
|
||||
$invokableStatusRes = $this->registry->getResource('invokable://config/status');
|
||||
$this->assertInstanceOf(RegisteredResource::class, $invokableStatusRes);
|
||||
$this->assertFalse($invokableStatusRes->isManual);
|
||||
$this->assertEquals([InvocableResourceFixture::class, '__invoke'], $invokableStatusRes->handler);
|
||||
|
||||
$prompts = $this->registry->getPrompts();
|
||||
$this->assertCount(4, $prompts);
|
||||
|
||||
$storyPrompt = $this->registry->getPrompt('creative_story_prompt');
|
||||
$this->assertInstanceOf(RegisteredPrompt::class, $storyPrompt);
|
||||
$this->assertFalse($storyPrompt->isManual);
|
||||
$this->assertCount(2, $storyPrompt->prompt->arguments);
|
||||
$this->assertEquals(CompletionProviderFixture::class, $storyPrompt->completionProviders['genre']);
|
||||
|
||||
$simplePrompt = $this->registry->getPrompt('simpleQuestionPrompt');
|
||||
$this->assertInstanceOf(RegisteredPrompt::class, $simplePrompt);
|
||||
$this->assertFalse($simplePrompt->isManual);
|
||||
|
||||
$invokableGreeter = $this->registry->getPrompt('InvokableGreeterPrompt');
|
||||
$this->assertInstanceOf(RegisteredPrompt::class, $invokableGreeter);
|
||||
$this->assertFalse($invokableGreeter->isManual);
|
||||
$this->assertEquals([InvocablePromptFixture::class, '__invoke'], $invokableGreeter->handler);
|
||||
|
||||
$contentCreatorPrompt = $this->registry->getPrompt('content_creator');
|
||||
$this->assertInstanceOf(RegisteredPrompt::class, $contentCreatorPrompt);
|
||||
$this->assertFalse($contentCreatorPrompt->isManual);
|
||||
$this->assertCount(3, $contentCreatorPrompt->completionProviders);
|
||||
|
||||
$templates = $this->registry->getResourceTemplates();
|
||||
$this->assertCount(4, $templates);
|
||||
|
||||
$productTemplate = $this->registry->getResourceTemplate('product://{region}/details/{productId}');
|
||||
$this->assertInstanceOf(RegisteredResourceTemplate::class, $productTemplate);
|
||||
$this->assertFalse($productTemplate->isManual);
|
||||
$this->assertEquals('product_details_template', $productTemplate->resourceTemplate->name);
|
||||
$this->assertEquals(CompletionProviderFixture::class, $productTemplate->completionProviders['region']);
|
||||
$this->assertEqualsCanonicalizing(['region', 'productId'], $productTemplate->getVariableNames());
|
||||
|
||||
$invokableUserTemplate = $this->registry->getResourceTemplate('invokable://user-profile/{userId}');
|
||||
$this->assertInstanceOf(RegisteredResourceTemplate::class, $invokableUserTemplate);
|
||||
$this->assertFalse($invokableUserTemplate->isManual);
|
||||
$this->assertEquals([InvocableResourceTemplateFixture::class, '__invoke'], $invokableUserTemplate->handler);
|
||||
}
|
||||
|
||||
public function testDoesNotDiscoverElementsFromExcludedDirectories()
|
||||
{
|
||||
$this->discoverer->discover(__DIR__, ['Fixtures']);
|
||||
$this->assertInstanceOf(RegisteredTool::class, $this->registry->getTool('hidden_subdir_tool'));
|
||||
|
||||
$this->registry->clear();
|
||||
|
||||
$this->discoverer->discover(__DIR__, ['Fixtures'], ['SubDir']);
|
||||
$this->assertNull($this->registry->getTool('hidden_subdir_tool'));
|
||||
}
|
||||
|
||||
public function testHandlesEmptyDirectoriesOrDirectoriesWithNoPhpFiles()
|
||||
{
|
||||
$this->discoverer->discover(__DIR__, ['EmptyDir']);
|
||||
$this->assertEmpty($this->registry->getTools());
|
||||
}
|
||||
|
||||
public function testCorrectlyInfersNamesAndDescriptionsFromMethodsOrClassesIfNotSetInAttribute()
|
||||
{
|
||||
$this->discoverer->discover(__DIR__, ['Fixtures']);
|
||||
|
||||
$repeatActionTool = $this->registry->getTool('repeatAction');
|
||||
$this->assertEquals('repeatAction', $repeatActionTool->tool->name);
|
||||
$this->assertEquals('A tool with more complex parameters and inferred name/description.', $repeatActionTool->tool->description);
|
||||
|
||||
$simplePrompt = $this->registry->getPrompt('simpleQuestionPrompt');
|
||||
$this->assertEquals('simpleQuestionPrompt', $simplePrompt->prompt->name);
|
||||
$this->assertNull($simplePrompt->prompt->description);
|
||||
|
||||
$invokableCalc = $this->registry->getTool('InvokableCalculator');
|
||||
$this->assertEquals('InvokableCalculator', $invokableCalc->tool->name);
|
||||
$this->assertEquals('An invokable calculator tool.', $invokableCalc->tool->description);
|
||||
}
|
||||
|
||||
public function testDiscoversEnhancedCompletionProvidersWithValuesAndEnumAttributes()
|
||||
{
|
||||
$this->discoverer->discover(__DIR__, ['Fixtures']);
|
||||
|
||||
$contentPrompt = $this->registry->getPrompt('content_creator');
|
||||
$this->assertInstanceOf(RegisteredPrompt::class, $contentPrompt);
|
||||
$this->assertCount(3, $contentPrompt->completionProviders);
|
||||
|
||||
$typeProvider = $contentPrompt->completionProviders['type'];
|
||||
$this->assertInstanceOf(ListCompletionProvider::class, $typeProvider);
|
||||
|
||||
$statusProvider = $contentPrompt->completionProviders['status'];
|
||||
$this->assertInstanceOf(EnumCompletionProvider::class, $statusProvider);
|
||||
|
||||
$priorityProvider = $contentPrompt->completionProviders['priority'];
|
||||
$this->assertInstanceOf(EnumCompletionProvider::class, $priorityProvider);
|
||||
|
||||
$contentTemplate = $this->registry->getResourceTemplate('content://{category}/{slug}');
|
||||
$this->assertInstanceOf(RegisteredResourceTemplate::class, $contentTemplate);
|
||||
$this->assertCount(1, $contentTemplate->completionProviders);
|
||||
|
||||
$categoryProvider = $contentTemplate->completionProviders['category'];
|
||||
$this->assertInstanceOf(ListCompletionProvider::class, $categoryProvider);
|
||||
}
|
||||
}
|
||||
143
tests/Capability/Discovery/DocBlockParserTest.php
Normal file
143
tests/Capability/Discovery/DocBlockParserTest.php
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
<?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\Capability\Discovery;
|
||||
|
||||
use Mcp\Capability\Discovery\DocBlockParser;
|
||||
use phpDocumentor\Reflection\DocBlock;
|
||||
use phpDocumentor\Reflection\DocBlock\Tags\Deprecated;
|
||||
use phpDocumentor\Reflection\DocBlock\Tags\Param;
|
||||
use phpDocumentor\Reflection\DocBlock\Tags\See;
|
||||
use phpDocumentor\Reflection\DocBlock\Tags\Throws;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class DocBlockParserTest extends TestCase
|
||||
{
|
||||
private DocBlockParser $parser;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->parser = new DocBlockParser();
|
||||
}
|
||||
|
||||
public function testGetSummaryReturnsCorrectSummary()
|
||||
{
|
||||
$method = new \ReflectionMethod(DocBlockTestFixture::class, 'methodWithSummaryOnly');
|
||||
$docComment = $method->getDocComment() ?: null;
|
||||
$docBlock = $this->parser->parseDocBlock($docComment);
|
||||
$this->assertEquals('Simple summary line.', $this->parser->getSummary($docBlock));
|
||||
|
||||
$method2 = new \ReflectionMethod(DocBlockTestFixture::class, 'methodWithSummaryAndDescription');
|
||||
$docComment2 = $method2->getDocComment() ?: null;
|
||||
$docBlock2 = $this->parser->parseDocBlock($docComment2);
|
||||
$this->assertEquals('Summary line here.', $this->parser->getSummary($docBlock2));
|
||||
}
|
||||
|
||||
public function testGetDescriptionReturnsCorrectDescription()
|
||||
{
|
||||
$method = new \ReflectionMethod(DocBlockTestFixture::class, 'methodWithSummaryAndDescription');
|
||||
$docComment = $method->getDocComment() ?: null;
|
||||
$docBlock = $this->parser->parseDocBlock($docComment);
|
||||
$expectedDesc = "Summary line here.\n\nThis is a longer description spanning\nmultiple lines.\nIt might contain *markdown* or `code`.";
|
||||
$this->assertEquals($expectedDesc, $this->parser->getDescription($docBlock));
|
||||
|
||||
$method2 = new \ReflectionMethod(DocBlockTestFixture::class, 'methodWithSummaryOnly');
|
||||
$docComment2 = $method2->getDocComment() ?: null;
|
||||
$docBlock2 = $this->parser->parseDocBlock($docComment2);
|
||||
$this->assertEquals('Simple summary line.', $this->parser->getDescription($docBlock2));
|
||||
}
|
||||
|
||||
public function testGetParamTagsReturnsStructuredParamInfo()
|
||||
{
|
||||
$method = new \ReflectionMethod(DocBlockTestFixture::class, 'methodWithParams');
|
||||
$docComment = $method->getDocComment() ?: null;
|
||||
$docBlock = $this->parser->parseDocBlock($docComment);
|
||||
$params = $this->parser->getParamTags($docBlock);
|
||||
|
||||
$this->assertCount(6, $params);
|
||||
$this->assertArrayHasKey('$param1', $params);
|
||||
$this->assertArrayHasKey('$param2', $params);
|
||||
$this->assertArrayHasKey('$param3', $params);
|
||||
$this->assertArrayHasKey('$param4', $params);
|
||||
$this->assertArrayHasKey('$param5', $params);
|
||||
$this->assertArrayHasKey('$param6', $params);
|
||||
|
||||
$this->assertInstanceOf(Param::class, $params['$param1']);
|
||||
$this->assertEquals('param1', $params['$param1']->getVariableName());
|
||||
$this->assertEquals('string', $this->parser->getParamTypeString($params['$param1']));
|
||||
$this->assertEquals('description for string param', $this->parser->getParamDescription($params['$param1']));
|
||||
|
||||
$this->assertInstanceOf(Param::class, $params['$param2']);
|
||||
$this->assertEquals('param2', $params['$param2']->getVariableName());
|
||||
$this->assertEquals('int|null', $this->parser->getParamTypeString($params['$param2']));
|
||||
$this->assertEquals('description for nullable int param', $this->parser->getParamDescription($params['$param2']));
|
||||
|
||||
$this->assertInstanceOf(Param::class, $params['$param3']);
|
||||
$this->assertEquals('param3', $params['$param3']->getVariableName());
|
||||
$this->assertEquals('bool', $this->parser->getParamTypeString($params['$param3']));
|
||||
$this->assertEquals('nothing to say', $this->parser->getParamDescription($params['$param3']));
|
||||
|
||||
$this->assertInstanceOf(Param::class, $params['$param4']);
|
||||
$this->assertEquals('param4', $params['$param4']->getVariableName());
|
||||
$this->assertEquals('mixed', $this->parser->getParamTypeString($params['$param4']));
|
||||
$this->assertEquals('Missing type', $this->parser->getParamDescription($params['$param4']));
|
||||
|
||||
$this->assertInstanceOf(Param::class, $params['$param5']);
|
||||
$this->assertEquals('param5', $params['$param5']->getVariableName());
|
||||
$this->assertEquals('array<string,mixed>', $this->parser->getParamTypeString($params['$param5']));
|
||||
$this->assertEquals('array description', $this->parser->getParamDescription($params['$param5']));
|
||||
|
||||
$this->assertInstanceOf(Param::class, $params['$param6']);
|
||||
$this->assertEquals('param6', $params['$param6']->getVariableName());
|
||||
$this->assertEquals('stdClass', $this->parser->getParamTypeString($params['$param6']));
|
||||
$this->assertEquals('object param', $this->parser->getParamDescription($params['$param6']));
|
||||
}
|
||||
|
||||
public function testGetTagsByNameReturnsSpecificTags()
|
||||
{
|
||||
$method = new \ReflectionMethod(DocBlockTestFixture::class, 'methodWithMultipleTags');
|
||||
$docComment = $method->getDocComment() ?: null;
|
||||
$docBlock = $this->parser->parseDocBlock($docComment);
|
||||
|
||||
$this->assertInstanceOf(DocBlock::class, $docBlock);
|
||||
|
||||
$throwsTags = $docBlock->getTagsByName('throws');
|
||||
$this->assertCount(1, $throwsTags);
|
||||
$this->assertInstanceOf(Throws::class, $throwsTags[0]);
|
||||
$this->assertEquals('\\RuntimeException', (string) $throwsTags[0]->getType());
|
||||
$this->assertEquals('if processing fails', $throwsTags[0]->getDescription()->render());
|
||||
|
||||
$deprecatedTags = $docBlock->getTagsByName('deprecated');
|
||||
$this->assertCount(1, $deprecatedTags);
|
||||
$this->assertInstanceOf(Deprecated::class, $deprecatedTags[0]);
|
||||
$this->assertEquals('use newMethod() instead', $deprecatedTags[0]->getDescription()->render());
|
||||
|
||||
$seeTags = $docBlock->getTagsByName('see');
|
||||
$this->assertCount(1, $seeTags);
|
||||
$this->assertInstanceOf(See::class, $seeTags[0]);
|
||||
$this->assertStringContainsString('DocBlockTestFixture::newMethod()', (string) $seeTags[0]->getReference());
|
||||
|
||||
$nonExistentTags = $docBlock->getTagsByName('nosuchtag');
|
||||
$this->assertEmpty($nonExistentTags);
|
||||
}
|
||||
|
||||
public function testHandlesMethodWithNoDocblockGracefully()
|
||||
{
|
||||
$method = new \ReflectionMethod(DocBlockTestFixture::class, 'methodWithNoDocBlock');
|
||||
$docComment = $method->getDocComment() ?: null;
|
||||
$docBlock = $this->parser->parseDocBlock($docComment);
|
||||
|
||||
$this->assertNull($docBlock);
|
||||
$this->assertNull($this->parser->getSummary($docBlock));
|
||||
$this->assertNull($this->parser->getDescription($docBlock));
|
||||
$this->assertEmpty($this->parser->getParamTags($docBlock));
|
||||
}
|
||||
}
|
||||
96
tests/Capability/Discovery/DocBlockTestFixture.php
Normal file
96
tests/Capability/Discovery/DocBlockTestFixture.php
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
<?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\Capability\Discovery;
|
||||
|
||||
/**
|
||||
* A stub class for testing DocBlock parsing.
|
||||
*/
|
||||
class DocBlockTestFixture
|
||||
{
|
||||
/**
|
||||
* Simple summary line.
|
||||
*/
|
||||
public function methodWithSummaryOnly(): void
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Summary line here.
|
||||
*
|
||||
* This is a longer description spanning
|
||||
* multiple lines.
|
||||
* It might contain *markdown* or `code`.
|
||||
*
|
||||
* @since 1.0
|
||||
*/
|
||||
public function methodWithSummaryAndDescription(): void
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Method with various parameter tags.
|
||||
*
|
||||
* @param string $param1 description for string param
|
||||
* @param int|null $param2 description for nullable int param
|
||||
* @param bool $param3 nothing to say
|
||||
* @param $param4 Missing type
|
||||
* @param array<string, mixed> $param5 array description
|
||||
* @param \stdClass $param6 object param
|
||||
*/
|
||||
/* @phpstan-ignore-next-line missingType.parameter */
|
||||
public function methodWithParams(string $param1, ?int $param2, bool $param3, $param4, array $param5, \stdClass $param6): void
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Method with return tag.
|
||||
*
|
||||
* @return string the result of the operation
|
||||
*/
|
||||
public function methodWithReturn(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Method with multiple tags.
|
||||
*
|
||||
* @param float $value the value to process
|
||||
*
|
||||
* @return bool status of the operation
|
||||
*
|
||||
* @throws \RuntimeException if processing fails
|
||||
*
|
||||
* @deprecated use newMethod() instead
|
||||
* @see DocBlockTestFixture::newMethod()
|
||||
*/
|
||||
public function methodWithMultipleTags(float $value): bool /* @phpstan-ignore throws.unusedType */
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Malformed docblock - missing closing.
|
||||
*/
|
||||
public function methodWithMalformedDocBlock(): void
|
||||
{
|
||||
}
|
||||
|
||||
public function methodWithNoDocBlock(): void
|
||||
{
|
||||
}
|
||||
|
||||
// Some other method needed for a @see tag perhaps
|
||||
public function newMethod(): void
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
<?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\Capability\Discovery\Fixtures;
|
||||
|
||||
use Mcp\Capability\Attribute\CompletionProvider;
|
||||
use Mcp\Capability\Attribute\McpPrompt;
|
||||
use Mcp\Tests\Capability\Attribute\CompletionProviderFixture;
|
||||
|
||||
class DiscoverablePromptHandler
|
||||
{
|
||||
/**
|
||||
* Generates a creative story prompt.
|
||||
*
|
||||
* @param string $genre the genre of the story
|
||||
* @param int $lengthWords approximate length in words
|
||||
*
|
||||
* @return array the prompt messages
|
||||
*/
|
||||
#[McpPrompt(name: 'creative_story_prompt')]
|
||||
public function generateStoryPrompt(
|
||||
#[CompletionProvider(provider: CompletionProviderFixture::class)]
|
||||
string $genre,
|
||||
int $lengthWords = 200,
|
||||
): array {
|
||||
return [
|
||||
['role' => 'user', 'content' => "Write a {$genre} story about a lost robot, approximately {$lengthWords} words long."],
|
||||
];
|
||||
}
|
||||
|
||||
#[McpPrompt]
|
||||
public function simpleQuestionPrompt(string $question): array
|
||||
{
|
||||
return [
|
||||
['role' => 'user', 'content' => $question],
|
||||
['role' => 'assistant', 'content' => 'I will try to answer that.'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
<?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\Capability\Discovery\Fixtures;
|
||||
|
||||
use Mcp\Capability\Attribute\McpResource;
|
||||
use Mcp\Schema\Annotations;
|
||||
|
||||
class DiscoverableResourceHandler
|
||||
{
|
||||
/**
|
||||
* Provides the application's current version.
|
||||
*
|
||||
* @return string the version string
|
||||
*/
|
||||
#[McpResource(
|
||||
uri: 'app://info/version',
|
||||
name: 'app_version',
|
||||
description: 'The current version of the application.',
|
||||
mimeType: 'text/plain',
|
||||
size: 10
|
||||
)]
|
||||
public function getAppVersion(): string
|
||||
{
|
||||
return '1.2.3-discovered';
|
||||
}
|
||||
|
||||
#[McpResource(
|
||||
uri: 'config://settings/ui',
|
||||
name: 'ui_settings_discovered',
|
||||
mimeType: 'application/json',
|
||||
annotations: new Annotations(priority: 0.5)
|
||||
)]
|
||||
public function getUiSettings(): array
|
||||
{
|
||||
return ['theme' => 'dark', 'fontSize' => 14];
|
||||
}
|
||||
|
||||
public function someOtherMethod(): void
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
<?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\Capability\Discovery\Fixtures;
|
||||
|
||||
use Mcp\Capability\Attribute\CompletionProvider;
|
||||
use Mcp\Capability\Attribute\McpResourceTemplate;
|
||||
use Mcp\Tests\Capability\Attribute\CompletionProviderFixture;
|
||||
|
||||
class DiscoverableTemplateHandler
|
||||
{
|
||||
/**
|
||||
* Retrieves product details based on ID and region.
|
||||
*
|
||||
* @param string $productId the ID of the product
|
||||
* @param string $region the sales region
|
||||
*
|
||||
* @return array product details
|
||||
*/
|
||||
#[McpResourceTemplate(
|
||||
uriTemplate: 'product://{region}/details/{productId}',
|
||||
name: 'product_details_template',
|
||||
mimeType: 'application/json'
|
||||
)]
|
||||
public function getProductDetails(
|
||||
string $productId,
|
||||
#[CompletionProvider(provider: CompletionProviderFixture::class)]
|
||||
string $region,
|
||||
): array {
|
||||
return [
|
||||
'id' => $productId,
|
||||
'name' => 'Product '.$productId,
|
||||
'region' => $region,
|
||||
'price' => ('EU' === $region ? '€' : '$').(hexdec(substr(md5($productId), 0, 4)) / 100),
|
||||
];
|
||||
}
|
||||
|
||||
#[McpResourceTemplate(uriTemplate: 'file://{path}/{filename}.{extension}')]
|
||||
public function getFileContent(string $path, string $filename, string $extension): string
|
||||
{
|
||||
return "Content of {$path}/{$filename}.{$extension}";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
<?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\Capability\Discovery\Fixtures;
|
||||
|
||||
use Mcp\Capability\Attribute\McpTool;
|
||||
use Mcp\Schema\ToolAnnotations;
|
||||
use Mcp\Tests\Fixtures\Enum\BackedStringEnum;
|
||||
|
||||
class DiscoverableToolHandler
|
||||
{
|
||||
/**
|
||||
* A basic discoverable tool.
|
||||
*
|
||||
* @param string $name the name to greet
|
||||
*
|
||||
* @return string the greeting
|
||||
*/
|
||||
#[McpTool(name: 'greet_user', description: 'Greets a user by name.')]
|
||||
public function greet(string $name): string
|
||||
{
|
||||
return "Hello, {$name}!";
|
||||
}
|
||||
|
||||
/**
|
||||
* A tool with more complex parameters and inferred name/description.
|
||||
*
|
||||
* @param int $count the number of times to repeat
|
||||
* @param bool $loudly Should it be loud?
|
||||
* @param BackedStringEnum $mode the mode of operation
|
||||
*
|
||||
* @return array an array with results
|
||||
*/
|
||||
#[McpTool(annotations: new ToolAnnotations(readOnlyHint: true))]
|
||||
public function repeatAction(int $count, bool $loudly = false, BackedStringEnum $mode = BackedStringEnum::OptionA): array
|
||||
{
|
||||
return ['count' => $count, 'loudly' => $loudly, 'mode' => $mode->value, 'message' => 'Action repeated.'];
|
||||
}
|
||||
|
||||
// This method should NOT be discovered as a tool
|
||||
public function internalHelperMethod(int $value): int
|
||||
{
|
||||
return $value * 2;
|
||||
}
|
||||
|
||||
#[McpTool(name: 'private_tool_should_be_ignored')] // On private method
|
||||
private function aPrivateTool(): void
|
||||
{
|
||||
}
|
||||
|
||||
#[McpTool(name: 'protected_tool_should_be_ignored')] // On protected method
|
||||
protected function aProtectedTool(): void
|
||||
{
|
||||
}
|
||||
|
||||
#[McpTool(name: 'static_tool_should_be_ignored')] // On static method
|
||||
public static function aStaticTool(): void
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -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\Capability\Discovery\Fixtures;
|
||||
|
||||
use Mcp\Capability\Attribute\CompletionProvider;
|
||||
use Mcp\Capability\Attribute\McpPrompt;
|
||||
use Mcp\Capability\Attribute\McpResourceTemplate;
|
||||
use Mcp\Tests\Fixtures\Enum\PriorityEnum;
|
||||
use Mcp\Tests\Fixtures\Enum\StatusEnum;
|
||||
|
||||
class EnhancedCompletionHandler
|
||||
{
|
||||
/**
|
||||
* Create content with list and enum completion providers.
|
||||
*/
|
||||
#[McpPrompt(name: 'content_creator')]
|
||||
public function createContent(
|
||||
#[CompletionProvider(values: ['blog', 'article', 'tutorial', 'guide'])]
|
||||
string $type,
|
||||
#[CompletionProvider(enum: StatusEnum::class)]
|
||||
string $status,
|
||||
#[CompletionProvider(enum: PriorityEnum::class)]
|
||||
string $priority,
|
||||
): array {
|
||||
return [
|
||||
['role' => 'user', 'content' => "Create a {$type} with status {$status} and priority {$priority}"],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Resource template with list completion for categories.
|
||||
*/
|
||||
#[McpResourceTemplate(
|
||||
uriTemplate: 'content://{category}/{slug}',
|
||||
name: 'content_template'
|
||||
)]
|
||||
public function getContent(
|
||||
#[CompletionProvider(values: ['news', 'blog', 'docs', 'api'])]
|
||||
string $category,
|
||||
string $slug,
|
||||
): array {
|
||||
return [
|
||||
'category' => $category,
|
||||
'slug' => $slug,
|
||||
'url' => "https://example.com/{$category}/{$slug}",
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
<?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\Capability\Discovery\Fixtures;
|
||||
|
||||
use Mcp\Capability\Attribute\McpPrompt;
|
||||
|
||||
#[McpPrompt(name: 'InvokableGreeterPrompt')]
|
||||
class InvocablePromptFixture
|
||||
{
|
||||
public function __invoke(string $personName): array
|
||||
{
|
||||
return [['role' => 'user', 'content' => "Generate a short greeting for {$personName}."]];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
<?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\Capability\Discovery\Fixtures;
|
||||
|
||||
use Mcp\Capability\Attribute\McpResource;
|
||||
|
||||
#[McpResource(uri: 'invokable://config/status', name: 'invokable_app_status')]
|
||||
class InvocableResourceFixture
|
||||
{
|
||||
public function __invoke(): array
|
||||
{
|
||||
return ['status' => 'OK', 'load' => rand(1, 100) / 100.0];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
<?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\Capability\Discovery\Fixtures;
|
||||
|
||||
use Mcp\Capability\Attribute\McpResourceTemplate;
|
||||
|
||||
#[McpResourceTemplate(uriTemplate: 'invokable://user-profile/{userId}')]
|
||||
class InvocableResourceTemplateFixture
|
||||
{
|
||||
public function __invoke(string $userId): array
|
||||
{
|
||||
return ['id' => $userId, 'email' => "user{$userId}@example-invokable.com"];
|
||||
}
|
||||
}
|
||||
26
tests/Capability/Discovery/Fixtures/InvocableToolFixture.php
Normal file
26
tests/Capability/Discovery/Fixtures/InvocableToolFixture.php
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
<?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\Capability\Discovery\Fixtures;
|
||||
|
||||
use Mcp\Capability\Attribute\McpTool;
|
||||
|
||||
#[McpTool(name: 'InvokableCalculator', description: 'An invokable calculator tool.')]
|
||||
class InvocableToolFixture
|
||||
{
|
||||
/**
|
||||
* Adds two numbers.
|
||||
*/
|
||||
public function __invoke(int $a, int $b): int
|
||||
{
|
||||
return $a + $b;
|
||||
}
|
||||
}
|
||||
36
tests/Capability/Discovery/Fixtures/NonDiscoverableClass.php
Normal file
36
tests/Capability/Discovery/Fixtures/NonDiscoverableClass.php
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
<?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\Capability\Discovery\Fixtures;
|
||||
|
||||
class NonDiscoverableClass
|
||||
{
|
||||
public function someMethod(): string
|
||||
{
|
||||
return 'Just a regular method.';
|
||||
}
|
||||
}
|
||||
|
||||
interface MyDiscoverableInterface
|
||||
{
|
||||
}
|
||||
|
||||
trait MyDiscoverableTrait
|
||||
{
|
||||
public function traitMethod()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
enum MyDiscoverableEnum
|
||||
{
|
||||
case Alpha;
|
||||
}
|
||||
22
tests/Capability/Discovery/Fixtures/SubDir/HiddenTool.php
Normal file
22
tests/Capability/Discovery/Fixtures/SubDir/HiddenTool.php
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<?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\Capability\Discovery\Fixtures\SubDir;
|
||||
|
||||
use Mcp\Capability\Attribute\McpTool;
|
||||
|
||||
class HiddenTool
|
||||
{
|
||||
#[McpTool(name: 'hidden_subdir_tool')]
|
||||
public function run()
|
||||
{
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue