Skip to content

Commit fd07ce1

Browse files
committed
bug #143 [MCP SDK] Use foreach instead of array_map because array_map does not accept generators (dorrogeray)
This PR was merged into the main branch. Discussion ---------- [MCP SDK] Use foreach instead of `array_map` because `array_map` does not accept generators | Q | A | ------------- | --- | Bug fix? | yes | New feature? | no | Docs? | no | Issues | N/A | License | MIT Use foreach instead of array_map because array_map does not accept generators and add test coverage. Commits ------- 61b0f0d [mcp-sdk] Use foreach instead of array_map because array_map does not accept generators
2 parents 45ea78c + 61b0f0d commit fd07ce1

File tree

6 files changed

+264
-49
lines changed

6 files changed

+264
-49
lines changed

src/mcp-sdk/src/Server/RequestHandler/PromptListHandler.php

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
namespace Symfony\AI\McpSdk\Server\RequestHandler;
1313

1414
use Symfony\AI\McpSdk\Capability\Prompt\CollectionInterface;
15-
use Symfony\AI\McpSdk\Capability\Prompt\MetadataInterface;
1615
use Symfony\AI\McpSdk\Message\Request;
1716
use Symfony\AI\McpSdk\Message\Response;
1817

@@ -27,7 +26,14 @@ public function __construct(
2726
public function createResponse(Request $message): Response
2827
{
2928
$nextCursor = null;
30-
$prompts = array_map(function (MetadataInterface $metadata) use (&$nextCursor) {
29+
$prompts = [];
30+
31+
$metadataList = $this->collection->getMetadata(
32+
$this->pageSize,
33+
$message->params['cursor'] ?? null
34+
);
35+
36+
foreach ($metadataList as $metadata) {
3137
$nextCursor = $metadata->getName();
3238
$result = [
3339
'name' => $metadata->getName(),
@@ -55,8 +61,8 @@ public function createResponse(Request $message): Response
5561
$result['arguments'] = $arguments;
5662
}
5763

58-
return $result;
59-
}, $this->collection->getMetadata($this->pageSize, $message->params['cursor'] ?? null));
64+
$prompts[] = $result;
65+
}
6066

6167
$result = [
6268
'prompts' => $prompts,

src/mcp-sdk/src/Server/RequestHandler/ResourceListHandler.php

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
namespace Symfony\AI\McpSdk\Server\RequestHandler;
1313

1414
use Symfony\AI\McpSdk\Capability\Resource\CollectionInterface;
15-
use Symfony\AI\McpSdk\Capability\Resource\MetadataInterface;
1615
use Symfony\AI\McpSdk\Message\Request;
1716
use Symfony\AI\McpSdk\Message\Response;
1817

@@ -27,7 +26,14 @@ public function __construct(
2726
public function createResponse(Request $message): Response
2827
{
2928
$nextCursor = null;
30-
$resources = array_map(function (MetadataInterface $metadata) use (&$nextCursor) {
29+
$resources = [];
30+
31+
$metadataList = $this->collection->getMetadata(
32+
$this->pageSize,
33+
$message->params['cursor'] ?? null
34+
);
35+
36+
foreach ($metadataList as $metadata) {
3137
$nextCursor = $metadata->getUri();
3238
$result = [
3339
'uri' => $metadata->getUri(),
@@ -49,8 +55,8 @@ public function createResponse(Request $message): Response
4955
$result['size'] = $size;
5056
}
5157

52-
return $result;
53-
}, $this->collection->getMetadata($this->pageSize, $message->params['cursor'] ?? null));
58+
$resources[] = $result;
59+
}
5460

5561
$result = [
5662
'resources' => $resources,

src/mcp-sdk/src/Server/RequestHandler/ToolListHandler.php

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
namespace Symfony\AI\McpSdk\Server\RequestHandler;
1313

1414
use Symfony\AI\McpSdk\Capability\Tool\CollectionInterface;
15-
use Symfony\AI\McpSdk\Capability\Tool\MetadataInterface;
1615
use Symfony\AI\McpSdk\Message\Request;
1716
use Symfony\AI\McpSdk\Message\Response;
1817

@@ -27,19 +26,25 @@ public function __construct(
2726
public function createResponse(Request $message): Response
2827
{
2928
$nextCursor = null;
30-
$tools = array_map(function (MetadataInterface $tool) use (&$nextCursor) {
29+
$tools = [];
30+
31+
$metadataList = $this->collection->getMetadata(
32+
$this->pageSize,
33+
$message->params['cursor'] ?? null
34+
);
35+
36+
foreach ($metadataList as $tool) {
3137
$nextCursor = $tool->getName();
3238
$inputSchema = $tool->getInputSchema();
33-
34-
return [
39+
$tools[] = [
3540
'name' => $tool->getName(),
3641
'description' => $tool->getDescription(),
3742
'inputSchema' => [] === $inputSchema ? [
3843
'type' => 'object',
3944
'$schema' => 'http://json-schema.org/draft-07/schema#',
4045
] : $inputSchema,
4146
];
42-
}, $this->collection->getMetadata($this->pageSize, $message->params['cursor'] ?? null));
47+
}
4348

4449
$result = [
4550
'tools' => $tools,
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\AI\McpSdk\Tests\Server\RequestHandler;
13+
14+
use PHPUnit\Framework\Attributes\CoversClass;
15+
use PHPUnit\Framework\Attributes\Small;
16+
use PHPUnit\Framework\TestCase;
17+
use Symfony\AI\McpSdk\Capability\Prompt\MetadataInterface;
18+
use Symfony\AI\McpSdk\Capability\PromptChain;
19+
use Symfony\AI\McpSdk\Message\Request;
20+
use Symfony\AI\McpSdk\Server\RequestHandler\PromptListHandler;
21+
22+
#[Small]
23+
#[CoversClass(PromptListHandler::class)]
24+
class PromptListHandlerTest extends TestCase
25+
{
26+
public function testHandleEmpty(): void
27+
{
28+
$handler = new PromptListHandler(new PromptChain([]));
29+
$message = new Request(1, 'prompts/list', []);
30+
$response = $handler->createResponse($message);
31+
$this->assertEquals(1, $response->id);
32+
$this->assertEquals(['prompts' => []], $response->result);
33+
}
34+
35+
public function testHandleReturnAll(): void
36+
{
37+
$item = self::createMetadataItem();
38+
$handler = new PromptListHandler(new PromptChain([$item]));
39+
$message = new Request(1, 'prompts/list', []);
40+
$response = $handler->createResponse($message);
41+
$this->assertCount(1, $response->result['prompts']);
42+
$this->assertArrayNotHasKey('nextCursor', $response->result);
43+
}
44+
45+
public function testHandlePagination(): void
46+
{
47+
$item = self::createMetadataItem();
48+
$handler = new PromptListHandler(new PromptChain([$item, $item]), 2);
49+
$message = new Request(1, 'prompts/list', []);
50+
$response = $handler->createResponse($message);
51+
$this->assertCount(2, $response->result['prompts']);
52+
$this->assertArrayHasKey('nextCursor', $response->result);
53+
}
54+
55+
private static function createMetadataItem(): MetadataInterface
56+
{
57+
return new class implements MetadataInterface {
58+
public function getName(): string
59+
{
60+
return 'greet';
61+
}
62+
63+
public function getDescription(): string
64+
{
65+
return 'Greet a person with a nice message';
66+
}
67+
68+
public function getArguments(): array
69+
{
70+
return [
71+
[
72+
'name' => 'first name',
73+
'description' => 'The name of the person to greet',
74+
'required' => false,
75+
],
76+
];
77+
}
78+
};
79+
}
80+
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\AI\McpSdk\Tests\Server\RequestHandler;
13+
14+
use PHPUnit\Framework\Attributes\CoversClass;
15+
use PHPUnit\Framework\Attributes\DataProvider;
16+
use PHPUnit\Framework\Attributes\Small;
17+
use PHPUnit\Framework\TestCase;
18+
use Symfony\AI\McpSdk\Capability\Resource\CollectionInterface;
19+
use Symfony\AI\McpSdk\Capability\Resource\MetadataInterface;
20+
use Symfony\AI\McpSdk\Message\Request;
21+
use Symfony\AI\McpSdk\Server\RequestHandler\ResourceListHandler;
22+
23+
#[Small]
24+
#[CoversClass(ResourceListHandler::class)]
25+
class ResourceListHandlerTest extends TestCase
26+
{
27+
public function testHandleEmpty(): void
28+
{
29+
$collection = $this->getMockBuilder(CollectionInterface::class)
30+
->disableOriginalConstructor()
31+
->onlyMethods(['getMetadata'])
32+
->getMock();
33+
$collection->expects($this->once())->method('getMetadata')->willReturn([]);
34+
35+
$handler = new ResourceListHandler($collection);
36+
$message = new Request(1, 'resources/list', []);
37+
$response = $handler->createResponse($message);
38+
$this->assertEquals(1, $response->id);
39+
$this->assertEquals(['resources' => []], $response->result);
40+
}
41+
42+
/**
43+
* @param iterable<MetadataInterface> $metadataList
44+
*/
45+
#[DataProvider('metadataProvider')]
46+
public function testHandleReturnAll(iterable $metadataList): void
47+
{
48+
$collection = $this->getMockBuilder(CollectionInterface::class)
49+
->disableOriginalConstructor()
50+
->onlyMethods(['getMetadata'])
51+
->getMock();
52+
$collection->expects($this->once())->method('getMetadata')->willReturn($metadataList);
53+
$handler = new ResourceListHandler($collection);
54+
$message = new Request(1, 'resources/list', []);
55+
$response = $handler->createResponse($message);
56+
$this->assertCount(1, $response->result['resources']);
57+
$this->assertArrayNotHasKey('nextCursor', $response->result);
58+
}
59+
60+
/**
61+
* @return array<string, iterable<MetadataInterface>>
62+
*/
63+
public static function metadataProvider(): array
64+
{
65+
$item = self::createMetadataItem();
66+
67+
return [
68+
'array' => [[$item]],
69+
'generator' => [(function () use ($item) { yield $item; })()],
70+
];
71+
}
72+
73+
public function testHandlePagination(): void
74+
{
75+
$item = self::createMetadataItem();
76+
$collection = $this->getMockBuilder(CollectionInterface::class)
77+
->disableOriginalConstructor()
78+
->onlyMethods(['getMetadata'])
79+
->getMock();
80+
$collection->expects($this->once())->method('getMetadata')->willReturn([$item, $item]);
81+
$handler = new ResourceListHandler($collection, 2);
82+
$message = new Request(1, 'resources/list', []);
83+
$response = $handler->createResponse($message);
84+
$this->assertCount(2, $response->result['resources']);
85+
$this->assertArrayHasKey('nextCursor', $response->result);
86+
}
87+
88+
private static function createMetadataItem(): MetadataInterface
89+
{
90+
return new class implements MetadataInterface {
91+
public function getUri(): string
92+
{
93+
return 'file:///src/SomeFile.php';
94+
}
95+
96+
public function getName(): string
97+
{
98+
return 'src/SomeFile.php';
99+
}
100+
101+
public function getDescription(): string
102+
{
103+
return 'File src/SomeFile.php';
104+
}
105+
106+
public function getMimeType(): string
107+
{
108+
return 'text/plain';
109+
}
110+
111+
public function getSize(): int
112+
{
113+
return 1024;
114+
}
115+
};
116+
}
117+
}

0 commit comments

Comments
 (0)