diff --git a/examples/cli/index.php b/examples/cli/index.php index 13cdfc9..bf7040d 100644 --- a/examples/cli/index.php +++ b/examples/cli/index.php @@ -2,16 +2,17 @@ require __DIR__.'/vendor/autoload.php'; +use Symfony\Component\Console as SymfonyConsole; use Symfony\Component\Console\Output\OutputInterface; $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..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,9 +24,17 @@ class Builder */ public static function buildRequestHandlers(): array { - $promptManager = new PromptManager(); - $resourceManager = new ResourceManager(); - $toolManager = new ToolManager(); + $promptManager = new PromptChain([ + new ExamplePrompt(), + ]); + + $resourceManager = new ResourceChain([ + new ExampleResource(), + ]); + + $toolManager = new ToolChain([ + new ExampleTool(), + ]); return [ new InitializeHandler(), diff --git a/examples/cli/src/ExamplePrompt.php b/examples/cli/src/ExamplePrompt.php index b46de9c..bbe86c9 100644 --- a/examples/cli/src/ExamplePrompt.php +++ b/examples/cli/src/ExamplePrompt.php @@ -3,12 +3,24 @@ 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; +use PhpLlm\McpSdk\Capability\Prompt\PromptGetterInterface; -class ExamplePrompt implements MetadataInterface +class ExamplePrompt implements MetadataInterface, PromptGetterInterface { - public function __invoke(?string $firstName = null): string + public function get(PromptGet $input): PromptGetResult { - return sprintf('Hello %s', $firstName ?? 'World'); + $firstName = $input->arguments['first name'] ?? null; + + return new PromptGetResult( + $this->getDescription(), + [new PromptGetResultMessages( + 'user', + sprintf('Hello %s', $firstName ?? 'World') + )] + ); } public function getName(): string @@ -25,7 +37,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..9e6cdba 100644 --- a/examples/cli/src/ExampleResource.php +++ b/examples/cli/src/ExampleResource.php @@ -3,9 +3,20 @@ namespace App; 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 read(ResourceRead $input): 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..618acb9 100644 --- a/examples/cli/src/ExampleTool.php +++ b/examples/cli/src/ExampleTool.php @@ -3,12 +3,19 @@ namespace App; 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(string $format = 'Y-m-d H:i:s'): string + public function call(ToolCall $input): ToolCallResult { - return (new \DateTime('now', new \DateTimeZone('UTC')))->format($format); + $format = $input->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 deleted file mode 100644 index 0c09159..0000000 --- a/examples/cli/src/Manager/PromptManager.php +++ /dev/null @@ -1,53 +0,0 @@ -<?php - -namespace App\Manager; - -use App\ExamplePrompt; -use PhpLlm\McpSdk\Capability\Prompt\CollectionInterface; -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( - ) { - $this->items = [ - new ExamplePrompt(), - ]; - } - - public function getMetadata(): array - { - return $this->items; - } - - 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), - )] - ); - } catch (\Throwable $e) { - throw new PromptGetException($request, $e); - } - } - } - - throw new PromptNotFoundException($request); - } -} diff --git a/examples/cli/src/Manager/ResourceManager.php b/examples/cli/src/Manager/ResourceManager.php deleted file mode 100644 index 64aa0e9..0000000 --- a/examples/cli/src/Manager/ResourceManager.php +++ /dev/null @@ -1,46 +0,0 @@ -<?php - -namespace App\Manager; - -use App\ExampleResource; -use PhpLlm\McpSdk\Capability\Resource\CollectionInterface; -use PhpLlm\McpSdk\Capability\Resource\ResourceRead; -use PhpLlm\McpSdk\Capability\Resource\ResourceReaderInterface; -use PhpLlm\McpSdk\Capability\Resource\ResourceReadResult; -use PhpLlm\McpSdk\Exception\ResourceNotFoundException; - -class ResourceManager implements CollectionInterface, ResourceReaderInterface -{ - /** - * @var mixed[] - */ - private array $items; - - public function __construct( - ) { - $this->items = [ - new ExampleResource(), - ]; - } - - public function getMetadata(): array - { - return $this->items; - } - - 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(), - ); - } - } - - throw new ResourceNotFoundException($request); - } -} diff --git a/examples/cli/src/Manager/ToolManager.php b/examples/cli/src/Manager/ToolManager.php deleted file mode 100644 index 3b2188a..0000000 --- a/examples/cli/src/Manager/ToolManager.php +++ /dev/null @@ -1,48 +0,0 @@ -<?php - -namespace App\Manager; - -use App\ExampleTool; -use PhpLlm\McpSdk\Capability\Tool\CollectionInterface; -use PhpLlm\McpSdk\Capability\Tool\ToolCall; -use PhpLlm\McpSdk\Capability\Tool\ToolCallResult; -use PhpLlm\McpSdk\Capability\Tool\ToolExecutorInterface; -use PhpLlm\McpSdk\Exception\ToolExecutionException; -use PhpLlm\McpSdk\Exception\ToolNotFoundException; - -class ToolManager implements ToolExecutorInterface, CollectionInterface -{ - /** - * @var mixed[] - */ - private array $items; - - public function __construct( - ) { - $this->items = [ - new ExampleTool(), - ]; - } - - public function getMetadata(): array - { - return $this->items; - } - - public function execute(ToolCall $toolCall): ToolCallResult - { - foreach ($this->items as $tool) { - if ($toolCall->name === $tool->getName()) { - try { - return new ToolCallResult( - $tool->__invoke(...$toolCall->arguments), - ); - } catch (\Throwable $e) { - throw new ToolExecutionException($toolCall, $e); - } - } - } - - throw new ToolNotFoundException($toolCall); - } -} 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/src/Capability/PromptChain.php b/src/Capability/PromptChain.php new file mode 100644 index 0000000..49f8e1d --- /dev/null +++ b/src/Capability/PromptChain.php @@ -0,0 +1,46 @@ +<?php + +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; +use PhpLlm\McpSdk\Capability\Prompt\PromptGetterInterface; +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[] + */ + private readonly array $items, + ) { + } + + public function getMetadata(): array + { + return array_filter($this->items, fn ($item) => $item instanceof MetadataInterface); + } + + public function get(PromptGet $input): PromptGetResult + { + foreach ($this->items as $item) { + if ($item instanceof PromptGetterInterface && $input->name === $item->getName()) { + try { + return $item->get($input); + } catch (\Throwable $e) { + throw new PromptGetException($input, $e); + } + } + } + + 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/src/Capability/ResourceChain.php b/src/Capability/ResourceChain.php new file mode 100644 index 0000000..e16d74c --- /dev/null +++ b/src/Capability/ResourceChain.php @@ -0,0 +1,46 @@ +<?php + +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; +use PhpLlm\McpSdk\Capability\Resource\ResourceReadResult; +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[] + */ + private readonly array $items, + ) { + } + + public function getMetadata(): array + { + return array_filter($this->items, fn ($item) => $item instanceof MetadataInterface); + } + + public function read(ResourceRead $input): ResourceReadResult + { + foreach ($this->items as $item) { + if ($item instanceof ResourceReaderInterface && $input->uri === $item->getUri()) { + try { + return $item->read($input); + } catch (\Throwable $e) { + throw new ResourceReadException($input, $e); + } + } + } + + 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/src/Capability/ToolChain.php b/src/Capability/ToolChain.php new file mode 100644 index 0000000..6d3eb9e --- /dev/null +++ b/src/Capability/ToolChain.php @@ -0,0 +1,46 @@ +<?php + +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; +use PhpLlm\McpSdk\Capability\Tool\ToolExecutorInterface; +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[] $items + */ + private readonly array $items, + ) { + } + + public function getMetadata(): array + { + return array_filter($this->items, fn ($item) => $item instanceof MetadataInterface); + } + + public function call(ToolCall $input): ToolCallResult + { + foreach ($this->items as $item) { + if ($item instanceof ToolExecutorInterface && $input->name === $item->getName()) { + try { + return $item->call($input); + } catch (\Throwable $e) { + throw new ToolExecutionException($input, $e); + } + } + } + + 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'); }