Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
6e4c57d
refactor: Separate Registry concerns following SOLID principles [WIP]
butschster Sep 7, 2025
047004e
refactor: implement SOLID principles with separated execution concerns
butschster Sep 7, 2025
9bf2a7e
chore: remove extra comments
butschster Sep 7, 2025
4aafe6b
feat: introduce DispatchableRegistry for enhanced tool and resource m…
butschster Sep 7, 2025
bd4791a
refactor: use proper interfaces
butschster Sep 7, 2025
fc467d8
refactor: implement HandlerInterface for improved abstraction and fle…
butschster Sep 7, 2025
e6dc2af
test: add unit tests for DispatchableRegistry and Registry classes
butschster Sep 7, 2025
20de24f
refactor: cover with unit tests Resource reader, Prompt getter and To…
butschster Sep 7, 2025
7237665
cs fix
butschster Sep 7, 2025
1f160f9
phpstan fix
butschster Sep 7, 2025
e7494da
test: add unit tests for CallToolHandler, GetPromptHandler, PingHandl…
butschster Sep 8, 2025
3773e75
refactor: revert Handler
butschster Sep 9, 2025
8a66539
chore: remove style guide for tests
butschster Sep 9, 2025
156f842
refactor: remove "Default" prefix from classes
butschster Sep 9, 2025
4f64998
refactor: remove DispatchableRegistry
butschster Sep 9, 2025
d9cd7ba
refactor: use package specific exception classes
butschster Sep 9, 2025
addfd23
refactor: remove DispatchableRegistry
butschster Sep 9, 2025
f3110eb
refactor: add logger support to PromptGetter and ResourceReader classes
butschster Sep 9, 2025
79618f7
Update src/Capability/Prompt/PromptGetter.php
butschster Sep 9, 2025
f1edddc
Merge branch 'main' into feature/tool-execution
butschster Sep 9, 2025
e413594
refactor: Use FQN for Resource class to avoid cs-fixer misinterpretin…
butschster Sep 9, 2025
9e798ad
refactor: rename ToolExecutor to ToolCaller and related classes for c…
butschster Sep 9, 2025
e11c0c1
refactor: add missed docblock
butschster Sep 9, 2025
3c70c5c
refactor: rename ToolExecutor references to ToolCaller
butschster Sep 9, 2025
7bd23b3
refactor: rename ToolCallerTest property and variable references
butschster Sep 9, 2025
32f2de9
cs fix
butschster Sep 9, 2025
0ca952b
ignore some phpstan errors
butschster Sep 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions examples/09-standalone-cli/src/ExampleTool.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@
namespace App;

use Mcp\Capability\Tool\MetadataInterface;
use Mcp\Capability\Tool\ToolExecutorInterface;
use Mcp\Capability\Tool\ToolCallerInterface;
use Mcp\Schema\Content\TextContent;
use Mcp\Schema\Request\CallToolRequest;
use Mcp\Schema\Result\CallToolResult;

/**
* @author Tobias Nyholm <[email protected]>
*/
class ExampleTool implements MetadataInterface, ToolExecutorInterface
class ExampleTool implements MetadataInterface, ToolCallerInterface
{
public function call(CallToolRequest $request): CallToolResult
{
Expand Down
140 changes: 19 additions & 121 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -367,149 +367,53 @@ parameters:
path: examples/08-schema-showcase-streamable/server.php

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

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

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

-
message: '#^Parameter \#1 \$registry of class Mcp\\Server\\RequestHandler\\ListResourcesHandler constructor expects Mcp\\Capability\\Registry, Mcp\\Capability\\ResourceChain given\.$#'
identifier: argument.type
count: 1
path: examples/09-standalone-cli/src/Builder.php

-
message: '#^Parameter \#1 \$registry of class Mcp\\Server\\RequestHandler\\ListToolsHandler constructor expects Mcp\\Capability\\Registry, Mcp\\Capability\\ToolChain given\.$#'
identifier: argument.type
count: 1
path: examples/09-standalone-cli/src/Builder.php

-
message: '#^Parameter \#1 \$registry of class Mcp\\Server\\RequestHandler\\ReadResourceHandler constructor expects Mcp\\Capability\\Registry, Mcp\\Capability\\ResourceChain given\.$#'
identifier: argument.type
count: 1
path: examples/09-standalone-cli/src/Builder.php

-
message: '#^Call to protected method formatResult\(\) of class Mcp\\Capability\\Registry\\ResourceReference\.$#'
identifier: method.protected
count: 1
path: src/Capability/Registry.php

-
message: '#^Cannot import type alias CallableArray\: type alias does not exist in Mcp\\Capability\\Registry\\ElementReference\.$#'
identifier: typeAlias.notFound
count: 1
path: src/Capability/Registry.php

-
message: '#^Method Mcp\\Capability\\Registry\:\:handleCallTool\(\) has parameter \$arguments with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: src/Capability/Registry.php

-
message: '#^Method Mcp\\Capability\\Registry\:\:handleCallTool\(\) return type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: src/Capability/Registry.php

-
message: '#^Method Mcp\\Capability\\Registry\:\:handleGetPrompt\(\) has parameter \$arguments with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: src/Capability/Registry.php

-
message: '#^Method Mcp\\Capability\\Registry\:\:registerPrompt\(\) has parameter \$handler with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: src/Capability/Registry.php

-
message: '#^Method Mcp\\Capability\\Registry\:\:registerResource\(\) has parameter \$handler with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: src/Capability/Registry.php

-
message: '#^Method Mcp\\Capability\\Registry\:\:registerResourceTemplate\(\) has parameter \$handler with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: src/Capability/Registry.php

-
message: '#^Method Mcp\\Capability\\Registry\:\:registerTool\(\) has parameter \$handler with no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: src/Capability/Registry.php

-
message: '#^PHPDoc tag @param for parameter \$handler with type \(callable\)\|Mcp\\Capability\\CallableArray\|string is not subtype of native type array\|\(callable\)\|string\.$#'
identifier: parameter.phpDocType
count: 4
path: src/Capability/Registry.php

-
message: '#^Parameter \$handler of method Mcp\\Capability\\Registry\:\:registerPrompt\(\) has invalid type Mcp\\Capability\\CallableArray\.$#'
identifier: class.notFound
message: '#^PHPDoc tag @return with type array is incompatible with native type object\.$#'
identifier: return.phpDocType
count: 1
path: src/Capability/Registry.php
path: src/Schema/Result/EmptyResult.php

-
message: '#^Parameter \$handler of method Mcp\\Capability\\Registry\:\:registerResource\(\) has invalid type Mcp\\Capability\\CallableArray\.$#'
identifier: class.notFound
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\>\}\.$#'
identifier: return.type
count: 1
path: src/Capability/Registry.php
path: src/Schema/Result/ReadResourceResult.php

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

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

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

-
message: '#^PHPDoc tag @return with type array is incompatible with native type object\.$#'
identifier: return.phpDocType
count: 1
path: src/Schema/Result/EmptyResult.php

-
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\>\}\.$#'
identifier: return.type
count: 1
path: src/Schema/Result/ReadResourceResult.php

-
message: '#^Method Mcp\\Capability\\Registry\:\:getPrompts\(\) invoked with 2 parameters, 0 required\.$#'
identifier: arguments.count
count: 1
path: src/Server/RequestHandler/ListPromptsHandler.php

-
message: '#^Result of && is always false\.$#'
identifier: booleanAnd.alwaysFalse
Expand All @@ -523,29 +427,23 @@ parameters:
path: src/Server/RequestHandler/ListPromptsHandler.php

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

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

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

-
message: '#^Method Mcp\\Capability\\Registry\:\:getTools\(\) invoked with 2 parameters, 0 required\.$#'
identifier: arguments.count
count: 1
path: src/Server/RequestHandler/ListToolsHandler.php

-
message: '#^Result of && is always false\.$#'
identifier: booleanAnd.alwaysFalse
Expand Down
4 changes: 2 additions & 2 deletions src/Capability/Discovery/Discoverer.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
use Mcp\Capability\Prompt\Completion\EnumCompletionProvider;
use Mcp\Capability\Prompt\Completion\ListCompletionProvider;
use Mcp\Capability\Prompt\Completion\ProviderInterface;
use Mcp\Capability\Registry;
use Mcp\Capability\Registry\ReferenceRegistryInterface;
use Mcp\Exception\ExceptionInterface;
use Mcp\Schema\Prompt;
use Mcp\Schema\PromptArgument;
Expand All @@ -44,7 +44,7 @@
class Discoverer
{
public function __construct(
private readonly Registry $registry,
private readonly ReferenceRegistryInterface $registry,
private readonly LoggerInterface $logger = new NullLogger(),
private ?DocBlockParser $docBlockParser = null,
private ?SchemaGenerator $schemaGenerator = null,
Expand Down
69 changes: 69 additions & 0 deletions src/Capability/Prompt/PromptGetter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?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\Capability\Prompt;

use Mcp\Capability\Registry\ReferenceHandlerInterface;
use Mcp\Capability\Registry\ReferenceProviderInterface;
use Mcp\Exception\PromptGetException;
use Mcp\Exception\PromptNotFoundException;
use Mcp\Schema\Request\GetPromptRequest;
use Mcp\Schema\Result\GetPromptResult;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;

/**
* @author Pavel Buchnev <[email protected]>
*/
final class PromptGetter implements PromptGetterInterface
{
public function __construct(
private readonly ReferenceProviderInterface $referenceProvider,
private readonly ReferenceHandlerInterface $referenceHandler,
private readonly LoggerInterface $logger = new NullLogger(),
) {
}

public function get(GetPromptRequest $request): GetPromptResult
{
$promptName = $request->name;
$arguments = $request->arguments ?? [];

$this->logger->debug('Getting prompt', ['name' => $promptName, 'arguments' => $arguments]);

$reference = $this->referenceProvider->getPrompt($promptName);

if (null === $reference) {
$this->logger->warning('Prompt not found', ['name' => $promptName]);
throw new PromptNotFoundException($request);
}

try {
$result = $this->referenceHandler->handle($reference, $arguments);
$formattedResult = $reference->formatResult($result);

$this->logger->debug('Prompt retrieved successfully', [
'name' => $promptName,
'result_type' => \gettype($result),
]);

return new GetPromptResult($formattedResult);
} catch (\Throwable $e) {
$this->logger->error('Prompt retrieval failed', [
'name' => $promptName,
'exception' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);

throw new PromptGetException($request, $e);
}
}
}
Loading