Skip to content

Commit 99bf56e

Browse files
Registry Architecture Refactoring - Enhanced Separation of Concerns (#46)
* refactor: Separate Registry concerns following SOLID principles [WIP] - Extract ReferenceProvider and ReferenceRegistryInterface interfaces - Create DefaultToolExecutor with ReferenceHandlerInterface - Remove execution responsibility from Registry class - Enable custom handler and executor implementations * refactor: implement SOLID principles with separated execution concerns * Create DefaultResourceReader, DefaultPromptGetter * Refactor JsonRpc Handler and RequestHandlers to use dedicated executors * Update ServerBuilder to support custom executors via dependency injection * chore: remove extra comments * feat: introduce DispatchableRegistry for enhanced tool and resource management * refactor: use proper interfaces * refactor: implement HandlerInterface for improved abstraction and flexibility * test: add unit tests for DispatchableRegistry and Registry classes * refactor: cover with unit tests Resource reader, Prompt getter and Tool executor * cs fix * phpstan fix * test: add unit tests for CallToolHandler, GetPromptHandler, PingHandler, and ReadResourceHandler * refactor: revert Handler * chore: remove style guide for tests * refactor: remove "Default" prefix from classes * refactor: remove DispatchableRegistry * refactor: use package specific exception classes * refactor: remove DispatchableRegistry * refactor: add logger support to PromptGetter and ResourceReader classes * Update src/Capability/Prompt/PromptGetter.php Co-authored-by: Christopher Hertel <[email protected]> * refactor: Use FQN for Resource class to avoid cs-fixer misinterpreting it as `resource` * refactor: rename ToolExecutor to ToolCaller and related classes for clarity and consistency * refactor: add missed docblock * refactor: rename ToolExecutor references to ToolCaller * refactor: rename ToolCallerTest property and variable references * cs fix * ignore some phpstan errors --------- Co-authored-by: Christopher Hertel <[email protected]>
1 parent bfc3b82 commit 99bf56e

36 files changed

+4656
-334
lines changed

examples/09-standalone-cli/src/ExampleTool.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,15 @@
1212
namespace App;
1313

1414
use Mcp\Capability\Tool\MetadataInterface;
15-
use Mcp\Capability\Tool\ToolExecutorInterface;
15+
use Mcp\Capability\Tool\ToolCallerInterface;
1616
use Mcp\Schema\Content\TextContent;
1717
use Mcp\Schema\Request\CallToolRequest;
1818
use Mcp\Schema\Result\CallToolResult;
1919

2020
/**
2121
* @author Tobias Nyholm <[email protected]>
2222
*/
23-
class ExampleTool implements MetadataInterface, ToolExecutorInterface
23+
class ExampleTool implements MetadataInterface, ToolCallerInterface
2424
{
2525
public function call(CallToolRequest $request): CallToolResult
2626
{

phpstan-baseline.neon

Lines changed: 19 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -367,149 +367,53 @@ parameters:
367367
path: examples/08-schema-showcase-streamable/server.php
368368

369369
-
370-
message: '#^Parameter \#1 \$registry of class Mcp\\Server\\RequestHandler\\CallToolHandler constructor expects Mcp\\Capability\\Registry, Mcp\\Capability\\ToolChain given\.$#'
370+
message: '#^Parameter \#1 \$registry of class Mcp\\Server\\RequestHandler\\ListPromptsHandler constructor expects Mcp\\Capability\\Registry\\ReferenceProviderInterface, Mcp\\Capability\\PromptChain given\.$#'
371371
identifier: argument.type
372372
count: 1
373373
path: examples/09-standalone-cli/src/Builder.php
374374

375375
-
376-
message: '#^Parameter \#1 \$registry of class Mcp\\Server\\RequestHandler\\GetPromptHandler constructor expects Mcp\\Capability\\Registry, Mcp\\Capability\\PromptChain given\.$#'
376+
message: '#^Parameter \#1 \$registry of class Mcp\\Server\\RequestHandler\\ListResourcesHandler constructor expects Mcp\\Capability\\Registry\\ReferenceProviderInterface, Mcp\\Capability\\ResourceChain given\.$#'
377377
identifier: argument.type
378378
count: 1
379379
path: examples/09-standalone-cli/src/Builder.php
380380

381381
-
382-
message: '#^Parameter \#1 \$registry of class Mcp\\Server\\RequestHandler\\ListPromptsHandler constructor expects Mcp\\Capability\\Registry, Mcp\\Capability\\PromptChain given\.$#'
382+
message: '#^Parameter \#1 \$registry of class Mcp\\Server\\RequestHandler\\ListToolsHandler constructor expects Mcp\\Capability\\Registry\\ReferenceProviderInterface, Mcp\\Capability\\ToolChain given\.$#'
383383
identifier: argument.type
384384
count: 1
385385
path: examples/09-standalone-cli/src/Builder.php
386386

387387
-
388-
message: '#^Parameter \#1 \$registry of class Mcp\\Server\\RequestHandler\\ListResourcesHandler constructor expects Mcp\\Capability\\Registry, Mcp\\Capability\\ResourceChain given\.$#'
389-
identifier: argument.type
390-
count: 1
391-
path: examples/09-standalone-cli/src/Builder.php
392-
393-
-
394-
message: '#^Parameter \#1 \$registry of class Mcp\\Server\\RequestHandler\\ListToolsHandler constructor expects Mcp\\Capability\\Registry, Mcp\\Capability\\ToolChain given\.$#'
395-
identifier: argument.type
396-
count: 1
397-
path: examples/09-standalone-cli/src/Builder.php
398-
399-
-
400-
message: '#^Parameter \#1 \$registry of class Mcp\\Server\\RequestHandler\\ReadResourceHandler constructor expects Mcp\\Capability\\Registry, Mcp\\Capability\\ResourceChain given\.$#'
401-
identifier: argument.type
402-
count: 1
403-
path: examples/09-standalone-cli/src/Builder.php
404-
405-
-
406-
message: '#^Call to protected method formatResult\(\) of class Mcp\\Capability\\Registry\\ResourceReference\.$#'
407-
identifier: method.protected
408-
count: 1
409-
path: src/Capability/Registry.php
410-
411-
-
412-
message: '#^Cannot import type alias CallableArray\: type alias does not exist in Mcp\\Capability\\Registry\\ElementReference\.$#'
413-
identifier: typeAlias.notFound
414-
count: 1
415-
path: src/Capability/Registry.php
416-
417-
-
418-
message: '#^Method Mcp\\Capability\\Registry\:\:handleCallTool\(\) has parameter \$arguments with no value type specified in iterable type array\.$#'
419-
identifier: missingType.iterableValue
420-
count: 1
421-
path: src/Capability/Registry.php
422-
423-
-
424-
message: '#^Method Mcp\\Capability\\Registry\:\:handleCallTool\(\) return type has no value type specified in iterable type array\.$#'
425-
identifier: missingType.iterableValue
426-
count: 1
427-
path: src/Capability/Registry.php
428-
429-
-
430-
message: '#^Method Mcp\\Capability\\Registry\:\:handleGetPrompt\(\) has parameter \$arguments with no value type specified in iterable type array\.$#'
431-
identifier: missingType.iterableValue
432-
count: 1
433-
path: src/Capability/Registry.php
434-
435-
-
436-
message: '#^Method Mcp\\Capability\\Registry\:\:registerPrompt\(\) has parameter \$handler with no value type specified in iterable type array\.$#'
437-
identifier: missingType.iterableValue
438-
count: 1
439-
path: src/Capability/Registry.php
440-
441-
-
442-
message: '#^Method Mcp\\Capability\\Registry\:\:registerResource\(\) has parameter \$handler with no value type specified in iterable type array\.$#'
443-
identifier: missingType.iterableValue
444-
count: 1
445-
path: src/Capability/Registry.php
446-
447-
-
448-
message: '#^Method Mcp\\Capability\\Registry\:\:registerResourceTemplate\(\) has parameter \$handler with no value type specified in iterable type array\.$#'
449-
identifier: missingType.iterableValue
450-
count: 1
451-
path: src/Capability/Registry.php
452-
453-
-
454-
message: '#^Method Mcp\\Capability\\Registry\:\:registerTool\(\) has parameter \$handler with no value type specified in iterable type array\.$#'
455-
identifier: missingType.iterableValue
456-
count: 1
457-
path: src/Capability/Registry.php
458-
459-
-
460-
message: '#^PHPDoc tag @param for parameter \$handler with type \(callable\)\|Mcp\\Capability\\CallableArray\|string is not subtype of native type array\|\(callable\)\|string\.$#'
461-
identifier: parameter.phpDocType
462-
count: 4
463-
path: src/Capability/Registry.php
464-
465-
-
466-
message: '#^Parameter \$handler of method Mcp\\Capability\\Registry\:\:registerPrompt\(\) has invalid type Mcp\\Capability\\CallableArray\.$#'
467-
identifier: class.notFound
388+
message: '#^PHPDoc tag @return with type array is incompatible with native type object\.$#'
389+
identifier: return.phpDocType
468390
count: 1
469-
path: src/Capability/Registry.php
391+
path: src/Schema/Result/EmptyResult.php
470392

471393
-
472-
message: '#^Parameter \$handler of method Mcp\\Capability\\Registry\:\:registerResource\(\) has invalid type Mcp\\Capability\\CallableArray\.$#'
473-
identifier: class.notFound
394+
message: '#^Method Mcp\\Schema\\Result\\ReadResourceResult\:\:jsonSerialize\(\) should return array\{contents\: array\<Mcp\\Schema\\Content\\BlobResourceContents\|Mcp\\Schema\\Content\\TextResourceContents\>\} but returns array\{contents\: array\<Mcp\\Schema\\Content\\ResourceContents\>\}\.$#'
395+
identifier: return.type
474396
count: 1
475-
path: src/Capability/Registry.php
397+
path: src/Schema/Result/ReadResourceResult.php
476398

477399
-
478-
message: '#^Parameter \$handler of method Mcp\\Capability\\Registry\:\:registerResourceTemplate\(\) has invalid type Mcp\\Capability\\CallableArray\.$#'
479-
identifier: class.notFound
400+
message: '#^Result of && is always false\.$#'
401+
identifier: booleanAnd.alwaysFalse
480402
count: 1
481-
path: src/Capability/Registry.php
403+
path: src/Server/RequestHandler/ListResourcesHandler.php
482404

483405
-
484-
message: '#^Parameter \$handler of method Mcp\\Capability\\Registry\:\:registerTool\(\) has invalid type Mcp\\Capability\\CallableArray\.$#'
485-
identifier: class.notFound
406+
message: '#^Method Mcp\\Capability\\Registry\\ReferenceProviderInterface\:\:getPrompts\(\) invoked with 2 parameters, 0 required\.$#'
407+
identifier: arguments.count
486408
count: 1
487-
path: src/Capability/Registry.php
409+
path: src/Server/RequestHandler/ListPromptsHandler.php
488410

489411
-
490412
message: '#^Call to an undefined method Mcp\\Capability\\Registry\\ResourceTemplateReference\:\:handle\(\)\.$#'
491413
identifier: method.notFound
492414
count: 1
493415
path: src/Capability/Registry/ResourceTemplateReference.php
494416

495-
-
496-
message: '#^PHPDoc tag @return with type array is incompatible with native type object\.$#'
497-
identifier: return.phpDocType
498-
count: 1
499-
path: src/Schema/Result/EmptyResult.php
500-
501-
-
502-
message: '#^Method Mcp\\Schema\\Result\\ReadResourceResult\:\:jsonSerialize\(\) should return array\{contents\: array\<Mcp\\Schema\\Content\\BlobResourceContents\|Mcp\\Schema\\Content\\TextResourceContents\>\} but returns array\{contents\: array\<Mcp\\Schema\\Content\\ResourceContents\>\}\.$#'
503-
identifier: return.type
504-
count: 1
505-
path: src/Schema/Result/ReadResourceResult.php
506-
507-
-
508-
message: '#^Method Mcp\\Capability\\Registry\:\:getPrompts\(\) invoked with 2 parameters, 0 required\.$#'
509-
identifier: arguments.count
510-
count: 1
511-
path: src/Server/RequestHandler/ListPromptsHandler.php
512-
513417
-
514418
message: '#^Result of && is always false\.$#'
515419
identifier: booleanAnd.alwaysFalse
@@ -523,29 +427,23 @@ parameters:
523427
path: src/Server/RequestHandler/ListPromptsHandler.php
524428

525429
-
526-
message: '#^Method Mcp\\Capability\\Registry\:\:getResources\(\) invoked with 2 parameters, 0 required\.$#'
430+
message: '#^Method Mcp\\Capability\\Registry\\ReferenceProviderInterface\:\:getResources\(\) invoked with 2 parameters, 0 required\.$#'
527431
identifier: arguments.count
528432
count: 1
529433
path: src/Server/RequestHandler/ListResourcesHandler.php
530434

531435
-
532-
message: '#^Result of && is always false\.$#'
533-
identifier: booleanAnd.alwaysFalse
436+
message: '#^Method Mcp\\Capability\\Registry\\ReferenceProviderInterface\:\:getTools\(\) invoked with 2 parameters, 0 required\.$#'
437+
identifier: arguments.count
534438
count: 1
535-
path: src/Server/RequestHandler/ListResourcesHandler.php
439+
path: src/Server/RequestHandler/ListToolsHandler.php
536440

537441
-
538442
message: '#^Strict comparison using \!\=\= between null and null will always evaluate to false\.$#'
539443
identifier: notIdentical.alwaysFalse
540444
count: 1
541445
path: src/Server/RequestHandler/ListResourcesHandler.php
542446

543-
-
544-
message: '#^Method Mcp\\Capability\\Registry\:\:getTools\(\) invoked with 2 parameters, 0 required\.$#'
545-
identifier: arguments.count
546-
count: 1
547-
path: src/Server/RequestHandler/ListToolsHandler.php
548-
549447
-
550448
message: '#^Result of && is always false\.$#'
551449
identifier: booleanAnd.alwaysFalse

src/Capability/Discovery/Discoverer.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
use Mcp\Capability\Prompt\Completion\EnumCompletionProvider;
2020
use Mcp\Capability\Prompt\Completion\ListCompletionProvider;
2121
use Mcp\Capability\Prompt\Completion\ProviderInterface;
22-
use Mcp\Capability\Registry;
22+
use Mcp\Capability\Registry\ReferenceRegistryInterface;
2323
use Mcp\Exception\ExceptionInterface;
2424
use Mcp\Schema\Prompt;
2525
use Mcp\Schema\PromptArgument;
@@ -44,7 +44,7 @@
4444
class Discoverer
4545
{
4646
public function __construct(
47-
private readonly Registry $registry,
47+
private readonly ReferenceRegistryInterface $registry,
4848
private readonly LoggerInterface $logger = new NullLogger(),
4949
private ?DocBlockParser $docBlockParser = null,
5050
private ?SchemaGenerator $schemaGenerator = null,
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the official PHP MCP SDK.
5+
*
6+
* A collaboration between Symfony and the PHP Foundation.
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 Mcp\Capability\Prompt;
13+
14+
use Mcp\Capability\Registry\ReferenceHandlerInterface;
15+
use Mcp\Capability\Registry\ReferenceProviderInterface;
16+
use Mcp\Exception\PromptGetException;
17+
use Mcp\Exception\PromptNotFoundException;
18+
use Mcp\Schema\Request\GetPromptRequest;
19+
use Mcp\Schema\Result\GetPromptResult;
20+
use Psr\Log\LoggerInterface;
21+
use Psr\Log\NullLogger;
22+
23+
/**
24+
* @author Pavel Buchnev <[email protected]>
25+
*/
26+
final class PromptGetter implements PromptGetterInterface
27+
{
28+
public function __construct(
29+
private readonly ReferenceProviderInterface $referenceProvider,
30+
private readonly ReferenceHandlerInterface $referenceHandler,
31+
private readonly LoggerInterface $logger = new NullLogger(),
32+
) {
33+
}
34+
35+
public function get(GetPromptRequest $request): GetPromptResult
36+
{
37+
$promptName = $request->name;
38+
$arguments = $request->arguments ?? [];
39+
40+
$this->logger->debug('Getting prompt', ['name' => $promptName, 'arguments' => $arguments]);
41+
42+
$reference = $this->referenceProvider->getPrompt($promptName);
43+
44+
if (null === $reference) {
45+
$this->logger->warning('Prompt not found', ['name' => $promptName]);
46+
throw new PromptNotFoundException($request);
47+
}
48+
49+
try {
50+
$result = $this->referenceHandler->handle($reference, $arguments);
51+
$formattedResult = $reference->formatResult($result);
52+
53+
$this->logger->debug('Prompt retrieved successfully', [
54+
'name' => $promptName,
55+
'result_type' => \gettype($result),
56+
]);
57+
58+
return new GetPromptResult($formattedResult);
59+
} catch (\Throwable $e) {
60+
$this->logger->error('Prompt retrieval failed', [
61+
'name' => $promptName,
62+
'exception' => $e->getMessage(),
63+
'trace' => $e->getTraceAsString(),
64+
]);
65+
66+
throw new PromptGetException($request, $e);
67+
}
68+
}
69+
}

0 commit comments

Comments
 (0)