Skip to content
This repository was archived by the owner on Jul 16, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/.git* export-ignore
/examples export-ignore
/tests export-ignore
/.php-cs-fixer.dist.php export-ignore
/phpstan.dist.neon export-ignore
Expand Down
23 changes: 23 additions & 0 deletions examples/cli/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Example app with CLI

This is just for testing and debugging purposes.


Install and create symlink with:

```bash
cd /path/to/your/project/examples/cli
composer update
rm -rf vendor/php-llm/mcp-sdk/src
ln -s /path/to/your/project/src /path/to/your/project/examples/cli/vendor/php-llm/mcp-sdk/src
```

Run the CLI with:

```bash
DEBUG=1 php index.php
```

You will see debug outputs to help you understand what is happening.

In this terminal you can now test add some json strings. See `example-requests.json`.
24 changes: 24 additions & 0 deletions examples/cli/composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"name": "php-llm/mcp-cli-example",
"description": "An example applicationf for CLI",
"license": "MIT",
"type": "project",
"authors": [
{
"name": "Tobias Nyholm",
"email": "[email protected]"
}
],
"require": {
"php": ">=8.2",
"php-llm/mcp-sdk": "@dev",
"symfony/console": "^7.2"
},
"minimum-stability": "stable",
"autoload": {
"psr-4": {
"App\\": "src/"
}
}
}

12 changes: 12 additions & 0 deletions examples/cli/example-requests.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[
{"jsonrpc": "2.0", "id": 1, "method": "resources/list", "params": []},
{"jsonrpc": "2.0", "id": 2, "method": "resources/read", "params": {"uri": "file:///project/src/main.rs"}},

{"jsonrpc": "2.0", "id": 1, "method": "tools/list"},
{"jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": {"name": "Current time"}},
{"jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": {"name": "Current time","arguments": {"format": "Y-m-d"}}},

{"jsonrpc": "2.0", "id": 1, "method": "prompts/list"},
{"jsonrpc": "2.0", "id": 2 ,"method": "prompts/get", "params": {"name": "Greet"}},
{"jsonrpc": "2.0", "id": 2 ,"method": "prompts/get", "params": {"name": "Greet", "arguments": { "firstName": "Tobias" }}}
]
29 changes: 29 additions & 0 deletions examples/cli/index.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

require __DIR__.'/vendor/autoload.php';

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);

// Configure the JsonRpcHandler
$jsonRpcHandler = new PhpLlm\McpSdk\Server\JsonRpcHandler(
new PhpLlm\McpSdk\Message\Factory(),
App\Builder::buildRequestHandlers(),
App\Builder::buildNotificationHandlers(),
$logger
);

// Set up the server
$sever = new PhpLlm\McpSdk\Server($jsonRpcHandler, $logger);

// Create the transport layer using Symfony Console
$transport = new PhpLlm\McpSdk\Server\Transport\Stdio\SymfonyConsoleTransport($input, $output);

// Start our application
$sever->connect($transport);
52 changes: 52 additions & 0 deletions examples/cli/src/Builder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

namespace App;

use App\Manager\PromptManager;
use App\Manager\ResourceManager;
use App\Manager\ToolManager;
use PhpLlm\McpSdk\Server\NotificationHandler;
use PhpLlm\McpSdk\Server\NotificationHandler\InitializedHandler;
use PhpLlm\McpSdk\Server\RequestHandler;
use PhpLlm\McpSdk\Server\RequestHandler\InitializeHandler;
use PhpLlm\McpSdk\Server\RequestHandler\PingHandler;
use PhpLlm\McpSdk\Server\RequestHandler\PromptGetHandler;
use PhpLlm\McpSdk\Server\RequestHandler\PromptListHandler;
use PhpLlm\McpSdk\Server\RequestHandler\ResourceListHandler;
use PhpLlm\McpSdk\Server\RequestHandler\ResourceReadHandler;
use PhpLlm\McpSdk\Server\RequestHandler\ToolCallHandler;
use PhpLlm\McpSdk\Server\RequestHandler\ToolListHandler;

class Builder
{
/**
* @return list<RequestHandler>
*/
public static function buildRequestHandlers(): array
{
$promptManager = new PromptManager();
$resourceManager = new ResourceManager();
$toolManager = new ToolManager();

return [
new InitializeHandler(),
new PingHandler(),
new PromptListHandler($promptManager),
new PromptGetHandler($promptManager),
new ResourceListHandler($resourceManager),
new ResourceReadHandler($resourceManager),
new ToolCallHandler($toolManager),
new ToolListHandler($toolManager),
];
}

/**
* @return list<NotificationHandler>
*/
public static function buildNotificationHandlers(): array
{
return [
new InitializedHandler(),
];
}
}
34 changes: 34 additions & 0 deletions examples/cli/src/ExamplePrompt.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace App;

use PhpLlm\McpSdk\Capability\Prompt\MetadataInterface;

class ExamplePrompt implements MetadataInterface
{
public function __invoke(?string $firstName = null): string
{
return sprintf('Hello %s', $firstName ?? 'World');
}

public function getName(): string
{
return 'Greet';
}

public function getDescription(): ?string
{
return 'Greet a person with a nice message';
}

public function getArguments(): array
{
return [
[
'name' => 'firstName',
'description' => 'The name of the person to greet',
'required' => false,
],
];
}
}
33 changes: 33 additions & 0 deletions examples/cli/src/ExampleResource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

namespace App;

use PhpLlm\McpSdk\Capability\Resource\MetadataInterface;

class ExampleResource implements MetadataInterface
{
public function getUri(): string
{
return 'file:///project/src/main.rs';
}

public function getName(): string
{
return 'My resource';
}

public function getDescription(): ?string
{
return 'This is just an example';
}

public function getMimeType(): ?string
{
return null;
}

public function getSize(): ?int
{
return null;
}
}
38 changes: 38 additions & 0 deletions examples/cli/src/ExampleTool.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

namespace App;

use PhpLlm\McpSdk\Capability\Tool\MetadataInterface;

class ExampleTool implements MetadataInterface
{
public function __invoke(string $format = 'Y-m-d H:i:s'): string
{
return (new \DateTime('now', new \DateTimeZone('UTC')))->format($format);
}

public function getName(): string
{
return 'Current time';
}

public function getDescription(): string
{
return 'Returns the current time in UTC';
}

public function getInputSchema(): array
{
return [
'type' => 'object',
'properties' => [
'format' => [
'type' => 'string',
'description' => 'The format of the time, e.g. "Y-m-d H:i:s"',
'default' => 'Y-m-d H:i:s',
],
],
'required' => [],
];
}
}
53 changes: 53 additions & 0 deletions examples/cli/src/Manager/PromptManager.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?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);
}
}
46 changes: 46 additions & 0 deletions examples/cli/src/Manager/ResourceManager.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?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);
}
}
Loading