From 6925f61cfb7213e709bef2b125649e1dcd8f2530 Mon Sep 17 00:00:00 2001 From: Tobias Nyholm <tobias.nyholm@gmail.com> Date: Sun, 25 May 2025 22:48:59 +0200 Subject: [PATCH 1/4] cleanup --- examples/cli/index.php | 9 +++--- examples/cli/src/Builder.php | 14 +++++++-- examples/cli/src/ExamplePrompt.php | 17 +++++++++-- examples/cli/src/ExampleResource.php | 10 +++++++ examples/cli/src/ExampleTool.php | 10 +++++-- examples/cli/src/Manager/PromptManager.php | 23 ++++----------- examples/cli/src/Manager/ResourceManager.php | 31 +++++++++----------- examples/cli/src/Manager/ToolManager.php | 22 +++++--------- 8 files changed, 76 insertions(+), 60 deletions(-) diff --git a/examples/cli/index.php b/examples/cli/index.php index 13cdfc9..1c630d9 100644 --- a/examples/cli/index.php +++ b/examples/cli/index.php @@ -3,15 +3,16 @@ require __DIR__.'/vendor/autoload.php'; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console as SymfonyConsole; $debug = (bool) ($_SERVER['DEBUG'] ?? false); // Setup input, output and logger -$input = new Symfony\Component\Console\Input\ArgvInput($argv); -$output = new Symfony\Component\Console\Output\ConsoleOutput($debug ? OutputInterface::VERBOSITY_VERY_VERBOSE : OutputInterface::VERBOSITY_NORMAL); -$logger = new Symfony\Component\Console\Logger\ConsoleLogger($output); +$input = new SymfonyConsole\Input\ArgvInput($argv); +$output = new SymfonyConsole\Output\ConsoleOutput($debug ? OutputInterface::VERBOSITY_VERY_VERBOSE : OutputInterface::VERBOSITY_NORMAL); +$logger = new SymfonyConsole\Logger\ConsoleLogger($output); -// Configure the JsonRpcHandler +// Configure the JsonRpcHandler and build the functionality $jsonRpcHandler = new PhpLlm\McpSdk\Server\JsonRpcHandler( new PhpLlm\McpSdk\Message\Factory(), App\Builder::buildRequestHandlers(), diff --git a/examples/cli/src/Builder.php b/examples/cli/src/Builder.php index 20126e8..b40664e 100644 --- a/examples/cli/src/Builder.php +++ b/examples/cli/src/Builder.php @@ -24,9 +24,17 @@ class Builder */ public static function buildRequestHandlers(): array { - $promptManager = new PromptManager(); - $resourceManager = new ResourceManager(); - $toolManager = new ToolManager(); + $promptManager = new PromptManager([ + new ExamplePrompt(), + ]); + + $resourceManager = new ResourceManager([ + new ExampleResource(), + ]); + + $toolManager = new ToolManager([ + new ExampleTool(), + ]); return [ new InitializeHandler(), diff --git a/examples/cli/src/ExamplePrompt.php b/examples/cli/src/ExamplePrompt.php index b46de9c..6a6b6ce 100644 --- a/examples/cli/src/ExamplePrompt.php +++ b/examples/cli/src/ExamplePrompt.php @@ -3,12 +3,23 @@ namespace App; use PhpLlm\McpSdk\Capability\Prompt\MetadataInterface; +use PhpLlm\McpSdk\Capability\Prompt\PromptGet; +use PhpLlm\McpSdk\Capability\Prompt\PromptGetResult; +use PhpLlm\McpSdk\Capability\Prompt\PromptGetResultMessages; class ExamplePrompt implements MetadataInterface { - public function __invoke(?string $firstName = null): string + public function __invoke(PromptGet $request): PromptGetResult { - return sprintf('Hello %s', $firstName ?? 'World'); + $firstName = $request->arguments['first name'] ?? null; + + return new PromptGetResult( + $this->getDescription(), + [new PromptGetResultMessages( + 'user', + sprintf('Hello %s', $firstName ?? 'World') + )] + ); } public function getName(): string @@ -25,7 +36,7 @@ public function getArguments(): array { return [ [ - 'name' => 'firstName', + 'name' => 'first name', 'description' => 'The name of the person to greet', 'required' => false, ], diff --git a/examples/cli/src/ExampleResource.php b/examples/cli/src/ExampleResource.php index 28830c4..874c860 100644 --- a/examples/cli/src/ExampleResource.php +++ b/examples/cli/src/ExampleResource.php @@ -3,9 +3,19 @@ namespace App; use PhpLlm\McpSdk\Capability\Resource\MetadataInterface; +use PhpLlm\McpSdk\Capability\Resource\ResourceRead; +use PhpLlm\McpSdk\Capability\Resource\ResourceReadResult; class ExampleResource implements MetadataInterface { + public function __invoke(ResourceRead $request): ResourceReadResult + { + return new ResourceReadResult( + 'Content of '.$this->getName(), + $this->getUri(), + ); + } + public function getUri(): string { return 'file:///project/src/main.rs'; diff --git a/examples/cli/src/ExampleTool.php b/examples/cli/src/ExampleTool.php index b31a417..66b966b 100644 --- a/examples/cli/src/ExampleTool.php +++ b/examples/cli/src/ExampleTool.php @@ -3,12 +3,18 @@ namespace App; use PhpLlm\McpSdk\Capability\Tool\MetadataInterface; +use PhpLlm\McpSdk\Capability\Tool\ToolCall; +use PhpLlm\McpSdk\Capability\Tool\ToolCallResult; class ExampleTool implements MetadataInterface { - public function __invoke(string $format = 'Y-m-d H:i:s'): string + public function __invoke(ToolCall $toolCall): ToolCallResult { - return (new \DateTime('now', new \DateTimeZone('UTC')))->format($format); + $format = $toolCall->arguments['format'] ?? 'Y-m-d H:i:s'; + + return new ToolCallResult( + (new \DateTime('now', new \DateTimeZone('UTC')))->format($format) + ); } public function getName(): string diff --git a/examples/cli/src/Manager/PromptManager.php b/examples/cli/src/Manager/PromptManager.php index 0c09159..fce427c 100644 --- a/examples/cli/src/Manager/PromptManager.php +++ b/examples/cli/src/Manager/PromptManager.php @@ -2,27 +2,22 @@ namespace App\Manager; -use App\ExamplePrompt; use PhpLlm\McpSdk\Capability\Prompt\CollectionInterface; +use PhpLlm\McpSdk\Capability\Prompt\MetadataInterface; use PhpLlm\McpSdk\Capability\Prompt\PromptGet; use PhpLlm\McpSdk\Capability\Prompt\PromptGetResult; -use PhpLlm\McpSdk\Capability\Prompt\PromptGetResultMessages; use PhpLlm\McpSdk\Capability\Prompt\PromptGetterInterface; use PhpLlm\McpSdk\Exception\PromptGetException; use PhpLlm\McpSdk\Exception\PromptNotFoundException; class PromptManager implements PromptGetterInterface, CollectionInterface { - /** - * @var mixed[] - */ - private array $items; - public function __construct( + /** + * @var (MetadataInterface | callable(PromptGet):PromptGetResult)[] + */ + private array $items, ) { - $this->items = [ - new ExamplePrompt(), - ]; } public function getMetadata(): array @@ -35,13 +30,7 @@ public function get(PromptGet $request): PromptGetResult foreach ($this->items as $item) { if ($request->name === $item->getName()) { try { - return new PromptGetResult( - $item->getDescription(), - [new PromptGetResultMessages( - 'user', - $item->__invoke(...$request->arguments), - )] - ); + return $item($request); } catch (\Throwable $e) { throw new PromptGetException($request, $e); } diff --git a/examples/cli/src/Manager/ResourceManager.php b/examples/cli/src/Manager/ResourceManager.php index 64aa0e9..b19951c 100644 --- a/examples/cli/src/Manager/ResourceManager.php +++ b/examples/cli/src/Manager/ResourceManager.php @@ -2,25 +2,23 @@ namespace App\Manager; -use App\ExampleResource; use PhpLlm\McpSdk\Capability\Resource\CollectionInterface; +use PhpLlm\McpSdk\Capability\Resource\MetadataInterface; use PhpLlm\McpSdk\Capability\Resource\ResourceRead; use PhpLlm\McpSdk\Capability\Resource\ResourceReaderInterface; use PhpLlm\McpSdk\Capability\Resource\ResourceReadResult; use PhpLlm\McpSdk\Exception\ResourceNotFoundException; +use PhpLlm\McpSdk\Exception\ResourceReadException; class ResourceManager implements CollectionInterface, ResourceReaderInterface { - /** - * @var mixed[] - */ - private array $items; - public function __construct( + /** + * TODO this is bad design. What if we want to add resource/exists, then this becomes invalid and we need a BC break + * @var (MetadataInterface | callable(ResourceRead):ResourceReadResult)[] + */ + private array $items, ) { - $this->items = [ - new ExampleResource(), - ]; } public function getMetadata(): array @@ -30,14 +28,13 @@ public function getMetadata(): array public function read(ResourceRead $request): ResourceReadResult { - foreach ($this->items as $resource) { - if ($request->uri === $resource->getUri()) { - // In a real implementation, you would read the resource from its URI. - // Here we just return a dummy string for demonstration purposes. - return new ResourceReadResult( - 'Content of '.$resource->getName(), - $resource->getUri(), - ); + foreach ($this->items as $item) { + if ($item instanceof ReadInterface && $request->uri === $item->getUri()) { + try { + return $item($request); + } catch (\Throwable $e) { + throw new ResourceReadException($request, $e); + } } } diff --git a/examples/cli/src/Manager/ToolManager.php b/examples/cli/src/Manager/ToolManager.php index 3b2188a..7ec4530 100644 --- a/examples/cli/src/Manager/ToolManager.php +++ b/examples/cli/src/Manager/ToolManager.php @@ -2,8 +2,8 @@ namespace App\Manager; -use App\ExampleTool; use PhpLlm\McpSdk\Capability\Tool\CollectionInterface; +use PhpLlm\McpSdk\Capability\Tool\MetadataInterface; use PhpLlm\McpSdk\Capability\Tool\ToolCall; use PhpLlm\McpSdk\Capability\Tool\ToolCallResult; use PhpLlm\McpSdk\Capability\Tool\ToolExecutorInterface; @@ -12,16 +12,12 @@ class ToolManager implements ToolExecutorInterface, CollectionInterface { - /** - * @var mixed[] - */ - private array $items; - public function __construct( + /** + * @var (MetadataInterface | callable(ToolCall):ToolCallResult)[] $items + */ + private array $items, ) { - $this->items = [ - new ExampleTool(), - ]; } public function getMetadata(): array @@ -31,12 +27,10 @@ public function getMetadata(): array public function execute(ToolCall $toolCall): ToolCallResult { - foreach ($this->items as $tool) { - if ($toolCall->name === $tool->getName()) { + foreach ($this->items as $item) { + if ($toolCall->name === $item->getName()) { try { - return new ToolCallResult( - $tool->__invoke(...$toolCall->arguments), - ); + return $item($toolCall); } catch (\Throwable $e) { throw new ToolExecutionException($toolCall, $e); } From 6662d2f63ff73fe996cdc0535bd37c95d781451b Mon Sep 17 00:00:00 2001 From: Tobias Nyholm <tobias.nyholm@gmail.com> Date: Sun, 25 May 2025 23:16:20 +0200 Subject: [PATCH 2/4] feat: add Prompt/Resource/Tool Chain --- examples/cli/index.php | 2 +- examples/cli/src/Builder.php | 12 +++++------ examples/cli/src/ExamplePrompt.php | 7 ++++--- examples/cli/src/ExampleResource.php | 5 +++-- examples/cli/src/ExampleTool.php | 7 ++++--- src/Capability/Prompt/IdentifierInterface.php | 8 ++++++++ src/Capability/Prompt/MetadataInterface.php | 4 +--- .../Prompt/PromptGetterInterface.php | 2 +- .../Capability/PromptChain.php | 19 +++++++++--------- .../Resource/IdentifierInterface.php | 8 ++++++++ src/Capability/Resource/MetadataInterface.php | 4 +--- .../Resource/ResourceReaderInterface.php | 2 +- .../Capability/ResourceChain.php | 20 +++++++++---------- src/Capability/Tool/IdentifierInterface.php | 8 ++++++++ src/Capability/Tool/MetadataInterface.php | 4 +--- src/Capability/Tool/ToolExecutorInterface.php | 2 +- .../Capability/ToolChain.php | 19 +++++++++--------- src/Server/RequestHandler/ToolCallHandler.php | 2 +- 18 files changed, 79 insertions(+), 56 deletions(-) create mode 100644 src/Capability/Prompt/IdentifierInterface.php rename examples/cli/src/Manager/PromptManager.php => src/Capability/PromptChain.php (51%) create mode 100644 src/Capability/Resource/IdentifierInterface.php rename examples/cli/src/Manager/ResourceManager.php => src/Capability/ResourceChain.php (51%) create mode 100644 src/Capability/Tool/IdentifierInterface.php rename examples/cli/src/Manager/ToolManager.php => src/Capability/ToolChain.php (51%) diff --git a/examples/cli/index.php b/examples/cli/index.php index 1c630d9..bf7040d 100644 --- a/examples/cli/index.php +++ b/examples/cli/index.php @@ -2,8 +2,8 @@ require __DIR__.'/vendor/autoload.php'; -use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console as SymfonyConsole; +use Symfony\Component\Console\Output\OutputInterface; $debug = (bool) ($_SERVER['DEBUG'] ?? false); diff --git a/examples/cli/src/Builder.php b/examples/cli/src/Builder.php index b40664e..7b91d2b 100644 --- a/examples/cli/src/Builder.php +++ b/examples/cli/src/Builder.php @@ -2,9 +2,9 @@ namespace App; -use App\Manager\PromptManager; -use App\Manager\ResourceManager; -use App\Manager\ToolManager; +use PhpLlm\McpSdk\Capability\PromptChain; +use PhpLlm\McpSdk\Capability\ResourceChain; +use PhpLlm\McpSdk\Capability\ToolChain; use PhpLlm\McpSdk\Server\NotificationHandler; use PhpLlm\McpSdk\Server\NotificationHandler\InitializedHandler; use PhpLlm\McpSdk\Server\RequestHandler; @@ -24,15 +24,15 @@ class Builder */ public static function buildRequestHandlers(): array { - $promptManager = new PromptManager([ + $promptManager = new PromptChain([ new ExamplePrompt(), ]); - $resourceManager = new ResourceManager([ + $resourceManager = new ResourceChain([ new ExampleResource(), ]); - $toolManager = new ToolManager([ + $toolManager = new ToolChain([ new ExampleTool(), ]); diff --git a/examples/cli/src/ExamplePrompt.php b/examples/cli/src/ExamplePrompt.php index 6a6b6ce..bbe86c9 100644 --- a/examples/cli/src/ExamplePrompt.php +++ b/examples/cli/src/ExamplePrompt.php @@ -6,12 +6,13 @@ use PhpLlm\McpSdk\Capability\Prompt\PromptGet; use PhpLlm\McpSdk\Capability\Prompt\PromptGetResult; use PhpLlm\McpSdk\Capability\Prompt\PromptGetResultMessages; +use PhpLlm\McpSdk\Capability\Prompt\PromptGetterInterface; -class ExamplePrompt implements MetadataInterface +class ExamplePrompt implements MetadataInterface, PromptGetterInterface { - public function __invoke(PromptGet $request): PromptGetResult + public function get(PromptGet $input): PromptGetResult { - $firstName = $request->arguments['first name'] ?? null; + $firstName = $input->arguments['first name'] ?? null; return new PromptGetResult( $this->getDescription(), diff --git a/examples/cli/src/ExampleResource.php b/examples/cli/src/ExampleResource.php index 874c860..9e6cdba 100644 --- a/examples/cli/src/ExampleResource.php +++ b/examples/cli/src/ExampleResource.php @@ -4,11 +4,12 @@ use PhpLlm\McpSdk\Capability\Resource\MetadataInterface; use PhpLlm\McpSdk\Capability\Resource\ResourceRead; +use PhpLlm\McpSdk\Capability\Resource\ResourceReaderInterface; use PhpLlm\McpSdk\Capability\Resource\ResourceReadResult; -class ExampleResource implements MetadataInterface +class ExampleResource implements MetadataInterface, ResourceReaderInterface { - public function __invoke(ResourceRead $request): ResourceReadResult + public function read(ResourceRead $input): ResourceReadResult { return new ResourceReadResult( 'Content of '.$this->getName(), diff --git a/examples/cli/src/ExampleTool.php b/examples/cli/src/ExampleTool.php index 66b966b..618acb9 100644 --- a/examples/cli/src/ExampleTool.php +++ b/examples/cli/src/ExampleTool.php @@ -5,12 +5,13 @@ use PhpLlm\McpSdk\Capability\Tool\MetadataInterface; use PhpLlm\McpSdk\Capability\Tool\ToolCall; use PhpLlm\McpSdk\Capability\Tool\ToolCallResult; +use PhpLlm\McpSdk\Capability\Tool\ToolExecutorInterface; -class ExampleTool implements MetadataInterface +class ExampleTool implements MetadataInterface, ToolExecutorInterface { - public function __invoke(ToolCall $toolCall): ToolCallResult + public function call(ToolCall $input): ToolCallResult { - $format = $toolCall->arguments['format'] ?? 'Y-m-d H:i:s'; + $format = $input->arguments['format'] ?? 'Y-m-d H:i:s'; return new ToolCallResult( (new \DateTime('now', new \DateTimeZone('UTC')))->format($format) diff --git a/src/Capability/Prompt/IdentifierInterface.php b/src/Capability/Prompt/IdentifierInterface.php new file mode 100644 index 0000000..8096c17 --- /dev/null +++ b/src/Capability/Prompt/IdentifierInterface.php @@ -0,0 +1,8 @@ +<?php + +namespace PhpLlm\McpSdk\Capability\Prompt; + +interface IdentifierInterface +{ + public function getName(): string; +} diff --git a/src/Capability/Prompt/MetadataInterface.php b/src/Capability/Prompt/MetadataInterface.php index 6bc5f46..6dc3404 100644 --- a/src/Capability/Prompt/MetadataInterface.php +++ b/src/Capability/Prompt/MetadataInterface.php @@ -4,10 +4,8 @@ namespace PhpLlm\McpSdk\Capability\Prompt; -interface MetadataInterface +interface MetadataInterface extends IdentifierInterface { - public function getName(): string; - public function getDescription(): ?string; /** diff --git a/src/Capability/Prompt/PromptGetterInterface.php b/src/Capability/Prompt/PromptGetterInterface.php index cf537b1..75f2b86 100644 --- a/src/Capability/Prompt/PromptGetterInterface.php +++ b/src/Capability/Prompt/PromptGetterInterface.php @@ -11,5 +11,5 @@ interface PromptGetterInterface * @throws PromptGetException if the prompt execution fails * @throws PromptNotFoundException if the prompt is not found */ - public function get(PromptGet $request): PromptGetResult; + public function get(PromptGet $input): PromptGetResult; } diff --git a/examples/cli/src/Manager/PromptManager.php b/src/Capability/PromptChain.php similarity index 51% rename from examples/cli/src/Manager/PromptManager.php rename to src/Capability/PromptChain.php index fce427c..a312542 100644 --- a/examples/cli/src/Manager/PromptManager.php +++ b/src/Capability/PromptChain.php @@ -1,8 +1,9 @@ <?php -namespace App\Manager; +namespace PhpLlm\McpSdk\Capability; use PhpLlm\McpSdk\Capability\Prompt\CollectionInterface; +use PhpLlm\McpSdk\Capability\Prompt\IdentifierInterface; use PhpLlm\McpSdk\Capability\Prompt\MetadataInterface; use PhpLlm\McpSdk\Capability\Prompt\PromptGet; use PhpLlm\McpSdk\Capability\Prompt\PromptGetResult; @@ -10,11 +11,11 @@ use PhpLlm\McpSdk\Exception\PromptGetException; use PhpLlm\McpSdk\Exception\PromptNotFoundException; -class PromptManager implements PromptGetterInterface, CollectionInterface +class PromptChain implements PromptGetterInterface, CollectionInterface { public function __construct( /** - * @var (MetadataInterface | callable(PromptGet):PromptGetResult)[] + * @var (IdentifierInterface & (MetadataInterface | PromptGetterInterface))[] */ private array $items, ) { @@ -22,21 +23,21 @@ public function __construct( public function getMetadata(): array { - return $this->items; + return array_filter($this->items, fn ($item) => $item instanceof MetadataInterface); } - public function get(PromptGet $request): PromptGetResult + public function get(PromptGet $input): PromptGetResult { foreach ($this->items as $item) { - if ($request->name === $item->getName()) { + if ($item instanceof PromptGetterInterface && $input->name === $item->getName()) { try { - return $item($request); + return $item->get($input); } catch (\Throwable $e) { - throw new PromptGetException($request, $e); + throw new PromptGetException($input, $e); } } } - throw new PromptNotFoundException($request); + throw new PromptNotFoundException($input); } } diff --git a/src/Capability/Resource/IdentifierInterface.php b/src/Capability/Resource/IdentifierInterface.php new file mode 100644 index 0000000..e3ee06e --- /dev/null +++ b/src/Capability/Resource/IdentifierInterface.php @@ -0,0 +1,8 @@ +<?php + +namespace PhpLlm\McpSdk\Capability\Resource; + +interface IdentifierInterface +{ + public function getUri(): string; +} diff --git a/src/Capability/Resource/MetadataInterface.php b/src/Capability/Resource/MetadataInterface.php index 702d1eb..253ce8c 100644 --- a/src/Capability/Resource/MetadataInterface.php +++ b/src/Capability/Resource/MetadataInterface.php @@ -4,10 +4,8 @@ namespace PhpLlm\McpSdk\Capability\Resource; -interface MetadataInterface +interface MetadataInterface extends IdentifierInterface { - public function getUri(): string; - public function getName(): string; public function getDescription(): ?string; diff --git a/src/Capability/Resource/ResourceReaderInterface.php b/src/Capability/Resource/ResourceReaderInterface.php index 10808f7..3102ac8 100644 --- a/src/Capability/Resource/ResourceReaderInterface.php +++ b/src/Capability/Resource/ResourceReaderInterface.php @@ -11,5 +11,5 @@ interface ResourceReaderInterface * @throws ResourceReadException if the resource execution fails * @throws ResourceNotFoundException if the resource is not found */ - public function read(ResourceRead $request): ResourceReadResult; + public function read(ResourceRead $input): ResourceReadResult; } diff --git a/examples/cli/src/Manager/ResourceManager.php b/src/Capability/ResourceChain.php similarity index 51% rename from examples/cli/src/Manager/ResourceManager.php rename to src/Capability/ResourceChain.php index b19951c..a4cd6a7 100644 --- a/examples/cli/src/Manager/ResourceManager.php +++ b/src/Capability/ResourceChain.php @@ -1,8 +1,9 @@ <?php -namespace App\Manager; +namespace PhpLlm\McpSdk\Capability; use PhpLlm\McpSdk\Capability\Resource\CollectionInterface; +use PhpLlm\McpSdk\Capability\Resource\IdentifierInterface; use PhpLlm\McpSdk\Capability\Resource\MetadataInterface; use PhpLlm\McpSdk\Capability\Resource\ResourceRead; use PhpLlm\McpSdk\Capability\Resource\ResourceReaderInterface; @@ -10,12 +11,11 @@ use PhpLlm\McpSdk\Exception\ResourceNotFoundException; use PhpLlm\McpSdk\Exception\ResourceReadException; -class ResourceManager implements CollectionInterface, ResourceReaderInterface +class ResourceChain implements CollectionInterface, ResourceReaderInterface { public function __construct( /** - * TODO this is bad design. What if we want to add resource/exists, then this becomes invalid and we need a BC break - * @var (MetadataInterface | callable(ResourceRead):ResourceReadResult)[] + * @var (IdentifierInterface & (MetadataInterface | ResourceReaderInterface))[] */ private array $items, ) { @@ -23,21 +23,21 @@ public function __construct( public function getMetadata(): array { - return $this->items; + return array_filter($this->items, fn ($item) => $item instanceof MetadataInterface); } - public function read(ResourceRead $request): ResourceReadResult + public function read(ResourceRead $input): ResourceReadResult { foreach ($this->items as $item) { - if ($item instanceof ReadInterface && $request->uri === $item->getUri()) { + if ($item instanceof ResourceReaderInterface && $input->uri === $item->getUri()) { try { - return $item($request); + return $item->read($input); } catch (\Throwable $e) { - throw new ResourceReadException($request, $e); + throw new ResourceReadException($input, $e); } } } - throw new ResourceNotFoundException($request); + throw new ResourceNotFoundException($input); } } diff --git a/src/Capability/Tool/IdentifierInterface.php b/src/Capability/Tool/IdentifierInterface.php new file mode 100644 index 0000000..7dd5bb2 --- /dev/null +++ b/src/Capability/Tool/IdentifierInterface.php @@ -0,0 +1,8 @@ +<?php + +namespace PhpLlm\McpSdk\Capability\Tool; + +interface IdentifierInterface +{ + public function getName(): string; +} diff --git a/src/Capability/Tool/MetadataInterface.php b/src/Capability/Tool/MetadataInterface.php index a0dfea3..24d870d 100644 --- a/src/Capability/Tool/MetadataInterface.php +++ b/src/Capability/Tool/MetadataInterface.php @@ -4,10 +4,8 @@ namespace PhpLlm\McpSdk\Capability\Tool; -interface MetadataInterface +interface MetadataInterface extends IdentifierInterface { - public function getName(): string; - public function getDescription(): string; /** diff --git a/src/Capability/Tool/ToolExecutorInterface.php b/src/Capability/Tool/ToolExecutorInterface.php index 0834ad8..a80c201 100644 --- a/src/Capability/Tool/ToolExecutorInterface.php +++ b/src/Capability/Tool/ToolExecutorInterface.php @@ -11,5 +11,5 @@ interface ToolExecutorInterface * @throws ToolExecutionException if the tool execution fails * @throws ToolNotFoundException if the tool is not found */ - public function execute(ToolCall $toolCall): ToolCallResult; + public function call(ToolCall $input): ToolCallResult; } diff --git a/examples/cli/src/Manager/ToolManager.php b/src/Capability/ToolChain.php similarity index 51% rename from examples/cli/src/Manager/ToolManager.php rename to src/Capability/ToolChain.php index 7ec4530..c805614 100644 --- a/examples/cli/src/Manager/ToolManager.php +++ b/src/Capability/ToolChain.php @@ -1,8 +1,9 @@ <?php -namespace App\Manager; +namespace PhpLlm\McpSdk\Capability; use PhpLlm\McpSdk\Capability\Tool\CollectionInterface; +use PhpLlm\McpSdk\Capability\Tool\IdentifierInterface; use PhpLlm\McpSdk\Capability\Tool\MetadataInterface; use PhpLlm\McpSdk\Capability\Tool\ToolCall; use PhpLlm\McpSdk\Capability\Tool\ToolCallResult; @@ -10,11 +11,11 @@ use PhpLlm\McpSdk\Exception\ToolExecutionException; use PhpLlm\McpSdk\Exception\ToolNotFoundException; -class ToolManager implements ToolExecutorInterface, CollectionInterface +class ToolChain implements ToolExecutorInterface, CollectionInterface { public function __construct( /** - * @var (MetadataInterface | callable(ToolCall):ToolCallResult)[] $items + * @var (IdentifierInterface & (MetadataInterface | ToolExecutorInterface))[] $items */ private array $items, ) { @@ -22,21 +23,21 @@ public function __construct( public function getMetadata(): array { - return $this->items; + return array_filter($this->items, fn ($item) => $item instanceof MetadataInterface); } - public function execute(ToolCall $toolCall): ToolCallResult + public function call(ToolCall $input): ToolCallResult { foreach ($this->items as $item) { - if ($toolCall->name === $item->getName()) { + if ($item instanceof ToolExecutorInterface && $input->name === $item->getName()) { try { - return $item($toolCall); + return $item->call($input); } catch (\Throwable $e) { - throw new ToolExecutionException($toolCall, $e); + throw new ToolExecutionException($input, $e); } } } - throw new ToolNotFoundException($toolCall); + throw new ToolNotFoundException($input); } } diff --git a/src/Server/RequestHandler/ToolCallHandler.php b/src/Server/RequestHandler/ToolCallHandler.php index 35015a9..4cab6e1 100644 --- a/src/Server/RequestHandler/ToolCallHandler.php +++ b/src/Server/RequestHandler/ToolCallHandler.php @@ -24,7 +24,7 @@ public function createResponse(Request $message): Response|Error $arguments = $message->params['arguments'] ?? []; try { - $result = $this->toolExecutor->execute(new ToolCall(uniqid('', true), $name, $arguments)); + $result = $this->toolExecutor->call(new ToolCall(uniqid('', true), $name, $arguments)); } catch (ExceptionInterface) { return Error::internalError($message->id, 'Error while executing tool'); } From 9d8200e1976cc51122c8e65e3e72f5ea90c6a525 Mon Sep 17 00:00:00 2001 From: Tobias Nyholm <tobias.nyholm@gmail.com> Date: Sun, 25 May 2025 23:21:58 +0200 Subject: [PATCH 3/4] feat: update comments --- src/Capability/PromptChain.php | 5 ++++- src/Capability/ResourceChain.php | 5 ++++- src/Capability/ToolChain.php | 5 ++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/Capability/PromptChain.php b/src/Capability/PromptChain.php index a312542..cfad56a 100644 --- a/src/Capability/PromptChain.php +++ b/src/Capability/PromptChain.php @@ -11,11 +11,14 @@ use PhpLlm\McpSdk\Exception\PromptGetException; use PhpLlm\McpSdk\Exception\PromptNotFoundException; +/** + * A collection of prompts. All prompts need to implement IdentifierInterface. + */ class PromptChain implements PromptGetterInterface, CollectionInterface { public function __construct( /** - * @var (IdentifierInterface & (MetadataInterface | PromptGetterInterface))[] + * @var IdentifierInterface[] */ private array $items, ) { diff --git a/src/Capability/ResourceChain.php b/src/Capability/ResourceChain.php index a4cd6a7..6581269 100644 --- a/src/Capability/ResourceChain.php +++ b/src/Capability/ResourceChain.php @@ -11,11 +11,14 @@ use PhpLlm\McpSdk\Exception\ResourceNotFoundException; use PhpLlm\McpSdk\Exception\ResourceReadException; +/** + * A collection of resources. All resources need to implement IdentifierInterface. + */ class ResourceChain implements CollectionInterface, ResourceReaderInterface { public function __construct( /** - * @var (IdentifierInterface & (MetadataInterface | ResourceReaderInterface))[] + * @var IdentifierInterface[] */ private array $items, ) { diff --git a/src/Capability/ToolChain.php b/src/Capability/ToolChain.php index c805614..c5cd4b7 100644 --- a/src/Capability/ToolChain.php +++ b/src/Capability/ToolChain.php @@ -11,11 +11,14 @@ use PhpLlm\McpSdk\Exception\ToolExecutionException; use PhpLlm\McpSdk\Exception\ToolNotFoundException; +/** + * A collection of tools. All tools need to implement IdentifierInterface. + */ class ToolChain implements ToolExecutorInterface, CollectionInterface { public function __construct( /** - * @var (IdentifierInterface & (MetadataInterface | ToolExecutorInterface))[] $items + * @var IdentifierInterface[] $items */ private array $items, ) { From 22086135cc1ffaaf17cb086ad13ac4275b373d11 Mon Sep 17 00:00:00 2001 From: Tobias Nyholm <tobias.nyholm@gmail.com> Date: Sun, 25 May 2025 23:26:57 +0200 Subject: [PATCH 4/4] fix: rector --- src/Capability/PromptChain.php | 2 +- src/Capability/ResourceChain.php | 2 +- src/Capability/ToolChain.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Capability/PromptChain.php b/src/Capability/PromptChain.php index cfad56a..49f8e1d 100644 --- a/src/Capability/PromptChain.php +++ b/src/Capability/PromptChain.php @@ -20,7 +20,7 @@ public function __construct( /** * @var IdentifierInterface[] */ - private array $items, + private readonly array $items, ) { } diff --git a/src/Capability/ResourceChain.php b/src/Capability/ResourceChain.php index 6581269..e16d74c 100644 --- a/src/Capability/ResourceChain.php +++ b/src/Capability/ResourceChain.php @@ -20,7 +20,7 @@ public function __construct( /** * @var IdentifierInterface[] */ - private array $items, + private readonly array $items, ) { } diff --git a/src/Capability/ToolChain.php b/src/Capability/ToolChain.php index c5cd4b7..6d3eb9e 100644 --- a/src/Capability/ToolChain.php +++ b/src/Capability/ToolChain.php @@ -20,7 +20,7 @@ public function __construct( /** * @var IdentifierInterface[] $items */ - private array $items, + private readonly array $items, ) { }