diff --git a/src/mcp-sdk/src/Server/RequestHandler/PromptListHandler.php b/src/mcp-sdk/src/Server/RequestHandler/PromptListHandler.php index 8aeff94e..b56d5c5a 100644 --- a/src/mcp-sdk/src/Server/RequestHandler/PromptListHandler.php +++ b/src/mcp-sdk/src/Server/RequestHandler/PromptListHandler.php @@ -12,7 +12,6 @@ namespace Symfony\AI\McpSdk\Server\RequestHandler; use Symfony\AI\McpSdk\Capability\Prompt\CollectionInterface; -use Symfony\AI\McpSdk\Capability\Prompt\MetadataInterface; use Symfony\AI\McpSdk\Message\Request; use Symfony\AI\McpSdk\Message\Response; @@ -27,7 +26,14 @@ public function __construct( public function createResponse(Request $message): Response { $nextCursor = null; - $prompts = array_map(function (MetadataInterface $metadata) use (&$nextCursor) { + $prompts = []; + + $metadataList = $this->collection->getMetadata( + $this->pageSize, + $message->params['cursor'] ?? null + ); + + foreach ($metadataList as $metadata) { $nextCursor = $metadata->getName(); $result = [ 'name' => $metadata->getName(), @@ -55,8 +61,8 @@ public function createResponse(Request $message): Response $result['arguments'] = $arguments; } - return $result; - }, $this->collection->getMetadata($this->pageSize, $message->params['cursor'] ?? null)); + $prompts[] = $result; + } $result = [ 'prompts' => $prompts, diff --git a/src/mcp-sdk/src/Server/RequestHandler/ResourceListHandler.php b/src/mcp-sdk/src/Server/RequestHandler/ResourceListHandler.php index 16151f9f..00ec68eb 100644 --- a/src/mcp-sdk/src/Server/RequestHandler/ResourceListHandler.php +++ b/src/mcp-sdk/src/Server/RequestHandler/ResourceListHandler.php @@ -12,7 +12,6 @@ namespace Symfony\AI\McpSdk\Server\RequestHandler; use Symfony\AI\McpSdk\Capability\Resource\CollectionInterface; -use Symfony\AI\McpSdk\Capability\Resource\MetadataInterface; use Symfony\AI\McpSdk\Message\Request; use Symfony\AI\McpSdk\Message\Response; @@ -27,7 +26,14 @@ public function __construct( public function createResponse(Request $message): Response { $nextCursor = null; - $resources = array_map(function (MetadataInterface $metadata) use (&$nextCursor) { + $resources = []; + + $metadataList = $this->collection->getMetadata( + $this->pageSize, + $message->params['cursor'] ?? null + ); + + foreach ($metadataList as $metadata) { $nextCursor = $metadata->getUri(); $result = [ 'uri' => $metadata->getUri(), @@ -49,8 +55,8 @@ public function createResponse(Request $message): Response $result['size'] = $size; } - return $result; - }, $this->collection->getMetadata($this->pageSize, $message->params['cursor'] ?? null)); + $resources[] = $result; + } $result = [ 'resources' => $resources, diff --git a/src/mcp-sdk/src/Server/RequestHandler/ToolListHandler.php b/src/mcp-sdk/src/Server/RequestHandler/ToolListHandler.php index b5c6b481..d2c0efe9 100644 --- a/src/mcp-sdk/src/Server/RequestHandler/ToolListHandler.php +++ b/src/mcp-sdk/src/Server/RequestHandler/ToolListHandler.php @@ -12,7 +12,6 @@ namespace Symfony\AI\McpSdk\Server\RequestHandler; use Symfony\AI\McpSdk\Capability\Tool\CollectionInterface; -use Symfony\AI\McpSdk\Capability\Tool\MetadataInterface; use Symfony\AI\McpSdk\Message\Request; use Symfony\AI\McpSdk\Message\Response; @@ -27,11 +26,17 @@ public function __construct( public function createResponse(Request $message): Response { $nextCursor = null; - $tools = array_map(function (MetadataInterface $tool) use (&$nextCursor) { + $tools = []; + + $metadataList = $this->collection->getMetadata( + $this->pageSize, + $message->params['cursor'] ?? null + ); + + foreach ($metadataList as $tool) { $nextCursor = $tool->getName(); $inputSchema = $tool->getInputSchema(); - - return [ + $tools[] = [ 'name' => $tool->getName(), 'description' => $tool->getDescription(), 'inputSchema' => [] === $inputSchema ? [ @@ -39,7 +44,7 @@ public function createResponse(Request $message): Response '$schema' => 'http://json-schema.org/draft-07/schema#', ] : $inputSchema, ]; - }, $this->collection->getMetadata($this->pageSize, $message->params['cursor'] ?? null)); + } $result = [ 'tools' => $tools, diff --git a/src/mcp-sdk/tests/Server/RequestHandler/PromptListHandlerTest.php b/src/mcp-sdk/tests/Server/RequestHandler/PromptListHandlerTest.php new file mode 100644 index 00000000..9b720918 --- /dev/null +++ b/src/mcp-sdk/tests/Server/RequestHandler/PromptListHandlerTest.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\AI\McpSdk\Tests\Server\RequestHandler; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; +use Symfony\AI\McpSdk\Capability\Prompt\MetadataInterface; +use Symfony\AI\McpSdk\Capability\PromptChain; +use Symfony\AI\McpSdk\Message\Request; +use Symfony\AI\McpSdk\Server\RequestHandler\PromptListHandler; + +#[Small] +#[CoversClass(PromptListHandler::class)] +class PromptListHandlerTest extends TestCase +{ + public function testHandleEmpty(): void + { + $handler = new PromptListHandler(new PromptChain([])); + $message = new Request(1, 'prompts/list', []); + $response = $handler->createResponse($message); + $this->assertEquals(1, $response->id); + $this->assertEquals(['prompts' => []], $response->result); + } + + public function testHandleReturnAll(): void + { + $item = self::createMetadataItem(); + $handler = new PromptListHandler(new PromptChain([$item])); + $message = new Request(1, 'prompts/list', []); + $response = $handler->createResponse($message); + $this->assertCount(1, $response->result['prompts']); + $this->assertArrayNotHasKey('nextCursor', $response->result); + } + + public function testHandlePagination(): void + { + $item = self::createMetadataItem(); + $handler = new PromptListHandler(new PromptChain([$item, $item]), 2); + $message = new Request(1, 'prompts/list', []); + $response = $handler->createResponse($message); + $this->assertCount(2, $response->result['prompts']); + $this->assertArrayHasKey('nextCursor', $response->result); + } + + private static function createMetadataItem(): MetadataInterface + { + return new class implements MetadataInterface { + public function getName(): string + { + return 'greet'; + } + + public function getDescription(): string + { + return 'Greet a person with a nice message'; + } + + public function getArguments(): array + { + return [ + [ + 'name' => 'first name', + 'description' => 'The name of the person to greet', + 'required' => false, + ], + ]; + } + }; + } +} diff --git a/src/mcp-sdk/tests/Server/RequestHandler/ResourceListHandlerTest.php b/src/mcp-sdk/tests/Server/RequestHandler/ResourceListHandlerTest.php new file mode 100644 index 00000000..c5e63959 --- /dev/null +++ b/src/mcp-sdk/tests/Server/RequestHandler/ResourceListHandlerTest.php @@ -0,0 +1,117 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\AI\McpSdk\Tests\Server\RequestHandler; + +use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\Small; +use PHPUnit\Framework\TestCase; +use Symfony\AI\McpSdk\Capability\Resource\CollectionInterface; +use Symfony\AI\McpSdk\Capability\Resource\MetadataInterface; +use Symfony\AI\McpSdk\Message\Request; +use Symfony\AI\McpSdk\Server\RequestHandler\ResourceListHandler; + +#[Small] +#[CoversClass(ResourceListHandler::class)] +class ResourceListHandlerTest extends TestCase +{ + public function testHandleEmpty(): void + { + $collection = $this->getMockBuilder(CollectionInterface::class) + ->disableOriginalConstructor() + ->onlyMethods(['getMetadata']) + ->getMock(); + $collection->expects($this->once())->method('getMetadata')->willReturn([]); + + $handler = new ResourceListHandler($collection); + $message = new Request(1, 'resources/list', []); + $response = $handler->createResponse($message); + $this->assertEquals(1, $response->id); + $this->assertEquals(['resources' => []], $response->result); + } + + /** + * @param iterable $metadataList + */ + #[DataProvider('metadataProvider')] + public function testHandleReturnAll(iterable $metadataList): void + { + $collection = $this->getMockBuilder(CollectionInterface::class) + ->disableOriginalConstructor() + ->onlyMethods(['getMetadata']) + ->getMock(); + $collection->expects($this->once())->method('getMetadata')->willReturn($metadataList); + $handler = new ResourceListHandler($collection); + $message = new Request(1, 'resources/list', []); + $response = $handler->createResponse($message); + $this->assertCount(1, $response->result['resources']); + $this->assertArrayNotHasKey('nextCursor', $response->result); + } + + /** + * @return array> + */ + public static function metadataProvider(): array + { + $item = self::createMetadataItem(); + + return [ + 'array' => [[$item]], + 'generator' => [(function () use ($item) { yield $item; })()], + ]; + } + + public function testHandlePagination(): void + { + $item = self::createMetadataItem(); + $collection = $this->getMockBuilder(CollectionInterface::class) + ->disableOriginalConstructor() + ->onlyMethods(['getMetadata']) + ->getMock(); + $collection->expects($this->once())->method('getMetadata')->willReturn([$item, $item]); + $handler = new ResourceListHandler($collection, 2); + $message = new Request(1, 'resources/list', []); + $response = $handler->createResponse($message); + $this->assertCount(2, $response->result['resources']); + $this->assertArrayHasKey('nextCursor', $response->result); + } + + private static function createMetadataItem(): MetadataInterface + { + return new class implements MetadataInterface { + public function getUri(): string + { + return 'file:///src/SomeFile.php'; + } + + public function getName(): string + { + return 'src/SomeFile.php'; + } + + public function getDescription(): string + { + return 'File src/SomeFile.php'; + } + + public function getMimeType(): string + { + return 'text/plain'; + } + + public function getSize(): int + { + return 1024; + } + }; + } +} diff --git a/src/mcp-sdk/tests/Server/RequestHandler/ToolListHandlerTest.php b/src/mcp-sdk/tests/Server/RequestHandler/ToolListHandlerTest.php index d4349034..2eb27a97 100644 --- a/src/mcp-sdk/tests/Server/RequestHandler/ToolListHandlerTest.php +++ b/src/mcp-sdk/tests/Server/RequestHandler/ToolListHandlerTest.php @@ -12,6 +12,7 @@ namespace Symfony\AI\McpSdk\Tests\Server\RequestHandler; use PHPUnit\Framework\Attributes\CoversClass; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Small; use PHPUnit\Framework\TestCase; use Symfony\AI\McpSdk\Capability\Tool\CollectionInterface; @@ -38,32 +39,17 @@ public function testHandleEmpty(): void $this->assertEquals(['tools' => []], $response->result); } - public function testHandleReturnAll(): void + /** + * @param iterable $metadataList + */ + #[DataProvider('metadataProvider')] + public function testHandleReturnAll(iterable $metadataList): void { - $item = new class implements MetadataInterface { - public function getName(): string - { - return 'test_tool'; - } - - public function getDescription(): string - { - return 'A test tool'; - } - - public function getInputSchema(): array - { - return [ - 'type' => 'object', - ]; - } - }; $collection = $this->getMockBuilder(CollectionInterface::class) ->disableOriginalConstructor() ->onlyMethods(['getMetadata']) ->getMock(); - $collection->expects($this->once())->method('getMetadata')->willReturn([$item]); - + $collection->expects($this->once())->method('getMetadata')->willReturn($metadataList); $handler = new ToolListHandler($collection); $message = new Request(1, 'tools/list', []); $response = $handler->createResponse($message); @@ -71,9 +57,37 @@ public function getInputSchema(): array $this->assertArrayNotHasKey('nextCursor', $response->result); } + /** + * @return array> + */ + public static function metadataProvider(): array + { + $item = self::createMetadataItem(); + + return [ + 'array' => [[$item]], + 'generator' => [(function () use ($item) { yield $item; })()], + ]; + } + public function testHandlePagination(): void { - $item = new class implements MetadataInterface { + $item = self::createMetadataItem(); + $collection = $this->getMockBuilder(CollectionInterface::class) + ->disableOriginalConstructor() + ->onlyMethods(['getMetadata']) + ->getMock(); + $collection->expects($this->once())->method('getMetadata')->willReturn([$item, $item]); + $handler = new ToolListHandler($collection, 2); + $message = new Request(1, 'tools/list', []); + $response = $handler->createResponse($message); + $this->assertCount(2, $response->result['tools']); + $this->assertArrayHasKey('nextCursor', $response->result); + } + + private static function createMetadataItem(): MetadataInterface + { + return new class implements MetadataInterface { public function getName(): string { return 'test_tool'; @@ -86,21 +100,8 @@ public function getDescription(): string public function getInputSchema(): array { - return [ - 'type' => 'object', - ]; + return ['type' => 'object']; } }; - $collection = $this->getMockBuilder(CollectionInterface::class) - ->disableOriginalConstructor() - ->onlyMethods(['getMetadata']) - ->getMock(); - $collection->expects($this->once())->method('getMetadata')->willReturn([$item, $item]); - - $handler = new ToolListHandler($collection, 2); - $message = new Request(1, 'tools/list', []); - $response = $handler->createResponse($message); - $this->assertCount(2, $response->result['tools']); - $this->assertArrayHasKey('nextCursor', $response->result); } }