Skip to content

Commit 7ac8c0a

Browse files
authored
Merge pull request #8 from llm-agents-php/feature/registry-refactoring
Registry refactoring
2 parents 88ee8aa + 56b2d94 commit 7ac8c0a

37 files changed

+1380
-213
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
},
4949
"autoload-dev": {
5050
"psr-4": {
51-
"Mcp\\Server\\Tests\\": "tests/Unit"
51+
"Mcp\\Server\\Tests\\": "tests"
5252
}
5353
},
5454
"scripts": {

context.yaml

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -27,30 +27,37 @@ documents:
2727
- tests
2828
- vendor
2929

30-
- description: Source codes
31-
outputPath: source-codes.md
30+
- description: Dispatcher
31+
outputPath: src/dispatcher.md
3232
sources:
3333
- type: file
3434
sourcePaths:
35-
- src
36-
notPath:
37-
- Attributes
38-
- Transports
39-
- Session
40-
- Defaults
41-
- Utils/DocBlockParser.php
42-
- Utils/SchemaGenerator.php
35+
- src/Dispatcher.php
36+
- src/Dispatcher
37+
- src/Contracts/DispatcherInterface.php
4338

44-
- description: Dispatcher
45-
outputPath: src/dispatcher.md
39+
- description: CompletionProvider
40+
outputPath: src/completion-provider.md
4641
sources:
4742
- type: file
4843
sourcePaths:
49-
- src/Dispatcher.php
50-
- src/DispatcherRoutesFactory.php
51-
- src/Contracts/DispatcherRoutesFactoryInterface.php
52-
- src/Routes
53-
- src/Contracts/RouteInterface.php
44+
- src/Defaults/EnumCompletionProvider.php
45+
- src/Defaults/ListCompletionProvider.php
46+
- src/Contracts/CompletionProviderInterface.php
47+
48+
- description: Tool executor
49+
outputPath: src/tool-executor.md
50+
sources:
51+
- type: file
52+
sourcePaths:
53+
- src/Contracts/ToolExecutorInterface.php
54+
- src/Defaults/ToolExecutor.php
55+
- vendor/php-mcp/schema/src/Content/Content.php
56+
- vendor/php-mcp/schema/src/Content/TextContent.php
57+
- src/Exception/ToolNotFoundException.php
58+
- src/Elements/RegisteredTool.php
59+
- vendor/php-mcp/schema/src/Tool.php
60+
- tests/Unit/Defaults/ToolExecutorTest.php
5461

5562
- description: Resources
5663
outputPath: src/resources.md
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Mcp\Server\Contracts;
6+
7+
use Mcp\Server\Context;
8+
use Mcp\Server\Exception\McpServerException;
9+
use PhpMcp\Schema\JsonRpc\Notification;
10+
use PhpMcp\Schema\JsonRpc\Request;
11+
use PhpMcp\Schema\JsonRpc\Result;
12+
13+
interface DispatcherInterface
14+
{
15+
/**
16+
* @throws McpServerException
17+
*/
18+
public function handleRequest(Request $request, Context $context): Result;
19+
20+
public function handleNotification(Notification $notification, Context $context): void;
21+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Mcp\Server\Contracts;
6+
7+
use Mcp\Server\Elements\RegisteredPrompt;
8+
use Mcp\Server\Elements\RegisteredResource;
9+
use Mcp\Server\Elements\RegisteredResourceTemplate;
10+
use Mcp\Server\Elements\RegisteredTool;
11+
use PhpMcp\Schema\Prompt;
12+
use PhpMcp\Schema\ResourceTemplate;
13+
use PhpMcp\Schema\Tool;
14+
15+
interface ReferenceProviderInterface
16+
{
17+
public function getTool(string $name): ?RegisteredTool;
18+
19+
public function getResource(
20+
string $uri,
21+
bool $includeTemplates = true,
22+
): RegisteredResource|RegisteredResourceTemplate|null;
23+
24+
public function getResourceTemplate(string $uriTemplate): ?RegisteredResourceTemplate;
25+
26+
public function getPrompt(string $name): ?RegisteredPrompt;
27+
28+
/**
29+
* @return array<string, Tool>
30+
*/
31+
public function getTools(): array;
32+
33+
/**
34+
* @return array<string, Resource>
35+
*/
36+
public function getResources(): array;
37+
38+
/**
39+
* @return array<string, Prompt>
40+
*/
41+
public function getPrompts(): array;
42+
43+
/**
44+
* @return array<string, ResourceTemplate>
45+
*/
46+
public function getResourceTemplates(): array;
47+
48+
/**
49+
* Checks if any references are currently registered.
50+
*/
51+
public function hasReferences(): bool;
52+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Mcp\Server\Contracts;
6+
7+
use Evenement\EventEmitterInterface;
8+
use PhpMcp\Schema\Prompt;
9+
use PhpMcp\Schema\Resource;
10+
use PhpMcp\Schema\ResourceTemplate;
11+
use PhpMcp\Schema\Tool;
12+
13+
interface ReferenceRegistryInterface extends EventEmitterInterface
14+
{
15+
public function registerTool(
16+
Tool $tool,
17+
HandlerInterface $handler,
18+
bool $isManual = false,
19+
): void;
20+
21+
public function registerResource(
22+
Resource $resource,
23+
HandlerInterface $handler,
24+
bool $isManual = false,
25+
): void;
26+
27+
public function registerResourceTemplate(
28+
ResourceTemplate $template,
29+
HandlerInterface $handler,
30+
array $completionProviders = [],
31+
bool $isManual = false,
32+
): void;
33+
34+
public function registerPrompt(
35+
Prompt $prompt,
36+
HandlerInterface $handler,
37+
array $completionProviders = [],
38+
bool $isManual = false,
39+
): void;
40+
}

src/Contracts/ToolExecutorInterface.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,19 @@
55
namespace Mcp\Server\Contracts;
66

77
use Mcp\Server\Context;
8-
use Mcp\Server\Elements\RegisteredTool;
8+
use Mcp\Server\Exception\ToolNotFoundException;
99
use Mcp\Server\Exception\ValidationException;
10+
use PhpMcp\Schema\Content\Content;
1011

1112
interface ToolExecutorInterface
1213
{
1314
/**
1415
* Call a registered tool with the given arguments.
1516
*
17+
* @return Content[] The content items for CallToolResult.
18+
*
1619
* @throws ValidationException If arguments do not match the tool's input schema.
20+
* @throws ToolNotFoundException If the tool is not registered.
1721
*/
18-
public function call(RegisteredTool $registeredTool, array $arguments, Context $context): array;
22+
public function call(string $toolName, array $arguments, Context $context): array;
1923
}

src/Defaults/EnumCompletionProvider.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,12 @@
99

1010
final readonly class EnumCompletionProvider implements CompletionProviderInterface
1111
{
12+
/** @var string[] */
1213
private array $values;
1314

15+
/**
16+
* @param class-string<\BackedEnum> $enumClass
17+
*/
1418
public function __construct(string $enumClass)
1519
{
1620
if (!\enum_exists($enumClass)) {

src/Defaults/ListCompletionProvider.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99

1010
final readonly class ListCompletionProvider implements CompletionProviderInterface
1111
{
12+
/**
13+
* @param string[] $values
14+
*/
1215
public function __construct(
1316
private array $values,
1417
) {}

src/Defaults/ToolExecutor.php

Lines changed: 93 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,111 @@
55
namespace Mcp\Server\Defaults;
66

77
use Mcp\Server\Context;
8+
use Mcp\Server\Contracts\ReferenceProviderInterface;
89
use Mcp\Server\Contracts\ToolExecutorInterface;
9-
use Mcp\Server\Elements\RegisteredTool;
10+
use Mcp\Server\Exception\ToolNotFoundException;
11+
use PhpMcp\Schema\Content\Content;
12+
use PhpMcp\Schema\Content\TextContent;
1013
use Psr\Log\LoggerInterface;
1114
use Psr\Log\NullLogger;
1215

1316
final readonly class ToolExecutor implements ToolExecutorInterface
1417
{
1518
public function __construct(
19+
private ReferenceProviderInterface $registry,
1620
private LoggerInterface $logger = new NullLogger(),
1721
) {}
1822

19-
public function call(
20-
RegisteredTool $registeredTool,
21-
array $arguments,
22-
Context $context,
23-
): array {
23+
public function call(string $toolName, array $arguments, Context $context): array
24+
{
25+
$tool = $this->registry->getTool($toolName);
26+
27+
if (!$tool) {
28+
throw new ToolNotFoundException("Tool '{$toolName}' not found.");
29+
}
30+
2431
$this->logger->debug('Calling tool', [
25-
'name' => $registeredTool->schema->name,
32+
'name' => $tool->schema->name,
2633
]);
2734

28-
return $registeredTool->call($arguments, $context);
35+
$result = $tool->handler->handle($arguments, $context);
36+
37+
return $this->formatResult($result);
38+
}
39+
40+
/**
41+
* Formats the result of a tool execution into an array of MCP Content items.
42+
*
43+
* - If the result is already a Content object, it's wrapped in an array.
44+
* - If the result is an array:
45+
* - If all elements are Content objects, the array is returned as is.
46+
* - If it's a mixed array (Content and non-Content items), non-Content items are
47+
* individually formatted (scalars to TextContent, others to JSON TextContent).
48+
* - If it's an array with no Content items, the entire array is JSON-encoded into a single TextContent.
49+
* - Scalars (string, int, float, bool) are wrapped in TextContent.
50+
* - null is represented as TextContent('(null)').
51+
* - Other objects are JSON-encoded and wrapped in TextContent.
52+
*
53+
* @param mixed $toolExecutionResult The raw value returned by the tool's PHP method.
54+
* @return Content[] The content items for CallToolResult.
55+
* @throws \JsonException
56+
*/
57+
protected function formatResult(mixed $toolExecutionResult): array
58+
{
59+
if ($toolExecutionResult instanceof Content) {
60+
return [$toolExecutionResult];
61+
}
62+
63+
if (\is_array($toolExecutionResult)) {
64+
if (empty($toolExecutionResult)) {
65+
return [TextContent::make('[]')];
66+
}
67+
68+
$allAreContent = true;
69+
$hasContent = false;
70+
71+
foreach ($toolExecutionResult as $item) {
72+
if ($item instanceof Content) {
73+
$hasContent = true;
74+
} else {
75+
$allAreContent = false;
76+
}
77+
}
78+
79+
if ($allAreContent && $hasContent) {
80+
return $toolExecutionResult;
81+
}
82+
83+
if ($hasContent) {
84+
$result = [];
85+
foreach ($toolExecutionResult as $item) {
86+
if ($item instanceof Content) {
87+
$result[] = $item;
88+
} else {
89+
$result = \array_merge($result, $this->formatResult($item));
90+
}
91+
}
92+
return $result;
93+
}
94+
}
95+
96+
if ($toolExecutionResult === null) {
97+
return [TextContent::make('(null)')];
98+
}
99+
100+
if (\is_bool($toolExecutionResult)) {
101+
return [TextContent::make($toolExecutionResult ? 'true' : 'false')];
102+
}
103+
104+
if (\is_scalar($toolExecutionResult)) {
105+
return [TextContent::make($toolExecutionResult)];
106+
}
107+
108+
$jsonResult = \json_encode(
109+
$toolExecutionResult,
110+
JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR | JSON_INVALID_UTF8_SUBSTITUTE,
111+
);
112+
113+
return [TextContent::make($jsonResult)];
29114
}
30115
}

src/Dispatcher.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Mcp\Server;
66

7+
use Mcp\Server\Contracts\DispatcherInterface;
78
use Mcp\Server\Contracts\DispatcherRoutesFactoryInterface;
89
use Mcp\Server\Contracts\RouteInterface;
910
use Mcp\Server\Exception\McpServerException;
@@ -12,7 +13,7 @@
1213
use PhpMcp\Schema\JsonRpc\Result;
1314
use Psr\Log\LoggerInterface;
1415

15-
final readonly class Dispatcher
16+
final readonly class Dispatcher implements DispatcherInterface
1617
{
1718
/** @var array<string, RouteInterface> */
1819
private array $routes;

0 commit comments

Comments
 (0)