Skip to content

Commit 875d980

Browse files
authored
feat: add support for Prompt and Resource (#18)
feat: add capabilities and example app
1 parent 2e754bf commit 875d980

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+958
-35
lines changed

.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
/.git* export-ignore
2+
/examples export-ignore
23
/tests export-ignore
34
/.php-cs-fixer.dist.php export-ignore
45
/phpstan.dist.neon export-ignore

examples/cli/README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Example app with CLI
2+
3+
This is just for testing and debugging purposes.
4+
5+
6+
Install and create symlink with:
7+
8+
```bash
9+
cd /path/to/your/project/examples/cli
10+
composer update
11+
rm -rf vendor/php-llm/mcp-sdk/src
12+
ln -s /path/to/your/project/src /path/to/your/project/examples/cli/vendor/php-llm/mcp-sdk/src
13+
```
14+
15+
Run the CLI with:
16+
17+
```bash
18+
DEBUG=1 php index.php
19+
```
20+
21+
You will see debug outputs to help you understand what is happening.
22+
23+
In this terminal you can now test add some json strings. See `example-requests.json`.

examples/cli/composer.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"name": "php-llm/mcp-cli-example",
3+
"description": "An example applicationf for CLI",
4+
"license": "MIT",
5+
"type": "project",
6+
"authors": [
7+
{
8+
"name": "Tobias Nyholm",
9+
"email": "[email protected]"
10+
}
11+
],
12+
"require": {
13+
"php": ">=8.2",
14+
"php-llm/mcp-sdk": "@dev",
15+
"symfony/console": "^7.2"
16+
},
17+
"minimum-stability": "stable",
18+
"autoload": {
19+
"psr-4": {
20+
"App\\": "src/"
21+
}
22+
}
23+
}
24+

examples/cli/example-requests.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[
2+
{"jsonrpc": "2.0", "id": 1, "method": "resources/list", "params": []},
3+
{"jsonrpc": "2.0", "id": 2, "method": "resources/read", "params": {"uri": "file:///project/src/main.rs"}},
4+
5+
{"jsonrpc": "2.0", "id": 1, "method": "tools/list"},
6+
{"jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": {"name": "Current time"}},
7+
{"jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": {"name": "Current time","arguments": {"format": "Y-m-d"}}},
8+
9+
{"jsonrpc": "2.0", "id": 1, "method": "prompts/list"},
10+
{"jsonrpc": "2.0", "id": 2 ,"method": "prompts/get", "params": {"name": "Greet"}},
11+
{"jsonrpc": "2.0", "id": 2 ,"method": "prompts/get", "params": {"name": "Greet", "arguments": { "firstName": "Tobias" }}}
12+
]

examples/cli/index.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
require __DIR__.'/vendor/autoload.php';
4+
5+
use Symfony\Component\Console\Output\OutputInterface;
6+
7+
$debug = (bool) ($_SERVER['DEBUG'] ?? false);
8+
9+
// Setup input, output and logger
10+
$input = new Symfony\Component\Console\Input\ArgvInput($argv);
11+
$output = new Symfony\Component\Console\Output\ConsoleOutput($debug ? OutputInterface::VERBOSITY_VERY_VERBOSE : OutputInterface::VERBOSITY_NORMAL);
12+
$logger = new Symfony\Component\Console\Logger\ConsoleLogger($output);
13+
14+
// Configure the JsonRpcHandler
15+
$jsonRpcHandler = new PhpLlm\McpSdk\Server\JsonRpcHandler(
16+
new PhpLlm\McpSdk\Message\Factory(),
17+
App\Builder::buildRequestHandlers(),
18+
App\Builder::buildNotificationHandlers(),
19+
$logger
20+
);
21+
22+
// Set up the server
23+
$sever = new PhpLlm\McpSdk\Server($jsonRpcHandler, $logger);
24+
25+
// Create the transport layer using Symfony Console
26+
$transport = new PhpLlm\McpSdk\Server\Transport\Stdio\SymfonyConsoleTransport($input, $output);
27+
28+
// Start our application
29+
$sever->connect($transport);

examples/cli/src/Builder.php

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
namespace App;
4+
5+
use App\Manager\PromptManager;
6+
use App\Manager\ResourceManager;
7+
use App\Manager\ToolManager;
8+
use PhpLlm\McpSdk\Server\NotificationHandler;
9+
use PhpLlm\McpSdk\Server\NotificationHandler\InitializedHandler;
10+
use PhpLlm\McpSdk\Server\RequestHandler;
11+
use PhpLlm\McpSdk\Server\RequestHandler\InitializeHandler;
12+
use PhpLlm\McpSdk\Server\RequestHandler\PingHandler;
13+
use PhpLlm\McpSdk\Server\RequestHandler\PromptGetHandler;
14+
use PhpLlm\McpSdk\Server\RequestHandler\PromptListHandler;
15+
use PhpLlm\McpSdk\Server\RequestHandler\ResourceListHandler;
16+
use PhpLlm\McpSdk\Server\RequestHandler\ResourceReadHandler;
17+
use PhpLlm\McpSdk\Server\RequestHandler\ToolCallHandler;
18+
use PhpLlm\McpSdk\Server\RequestHandler\ToolListHandler;
19+
20+
class Builder
21+
{
22+
/**
23+
* @return list<RequestHandler>
24+
*/
25+
public static function buildRequestHandlers(): array
26+
{
27+
$promptManager = new PromptManager();
28+
$resourceManager = new ResourceManager();
29+
$toolManager = new ToolManager();
30+
31+
return [
32+
new InitializeHandler(),
33+
new PingHandler(),
34+
new PromptListHandler($promptManager),
35+
new PromptGetHandler($promptManager),
36+
new ResourceListHandler($resourceManager),
37+
new ResourceReadHandler($resourceManager),
38+
new ToolCallHandler($toolManager),
39+
new ToolListHandler($toolManager),
40+
];
41+
}
42+
43+
/**
44+
* @return list<NotificationHandler>
45+
*/
46+
public static function buildNotificationHandlers(): array
47+
{
48+
return [
49+
new InitializedHandler(),
50+
];
51+
}
52+
}

examples/cli/src/ExamplePrompt.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
namespace App;
4+
5+
use PhpLlm\McpSdk\Capability\Prompt\MetadataInterface;
6+
7+
class ExamplePrompt implements MetadataInterface
8+
{
9+
public function __invoke(?string $firstName = null): string
10+
{
11+
return sprintf('Hello %s', $firstName ?? 'World');
12+
}
13+
14+
public function getName(): string
15+
{
16+
return 'Greet';
17+
}
18+
19+
public function getDescription(): ?string
20+
{
21+
return 'Greet a person with a nice message';
22+
}
23+
24+
public function getArguments(): array
25+
{
26+
return [
27+
[
28+
'name' => 'firstName',
29+
'description' => 'The name of the person to greet',
30+
'required' => false,
31+
],
32+
];
33+
}
34+
}

examples/cli/src/ExampleResource.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
namespace App;
4+
5+
use PhpLlm\McpSdk\Capability\Resource\MetadataInterface;
6+
7+
class ExampleResource implements MetadataInterface
8+
{
9+
public function getUri(): string
10+
{
11+
return 'file:///project/src/main.rs';
12+
}
13+
14+
public function getName(): string
15+
{
16+
return 'My resource';
17+
}
18+
19+
public function getDescription(): ?string
20+
{
21+
return 'This is just an example';
22+
}
23+
24+
public function getMimeType(): ?string
25+
{
26+
return null;
27+
}
28+
29+
public function getSize(): ?int
30+
{
31+
return null;
32+
}
33+
}

examples/cli/src/ExampleTool.php

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
namespace App;
4+
5+
use PhpLlm\McpSdk\Capability\Tool\MetadataInterface;
6+
7+
class ExampleTool implements MetadataInterface
8+
{
9+
public function __invoke(string $format = 'Y-m-d H:i:s'): string
10+
{
11+
return (new \DateTime('now', new \DateTimeZone('UTC')))->format($format);
12+
}
13+
14+
public function getName(): string
15+
{
16+
return 'Current time';
17+
}
18+
19+
public function getDescription(): string
20+
{
21+
return 'Returns the current time in UTC';
22+
}
23+
24+
public function getInputSchema(): array
25+
{
26+
return [
27+
'type' => 'object',
28+
'properties' => [
29+
'format' => [
30+
'type' => 'string',
31+
'description' => 'The format of the time, e.g. "Y-m-d H:i:s"',
32+
'default' => 'Y-m-d H:i:s',
33+
],
34+
],
35+
'required' => [],
36+
];
37+
}
38+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
3+
namespace App\Manager;
4+
5+
use App\ExamplePrompt;
6+
use PhpLlm\McpSdk\Capability\Prompt\CollectionInterface;
7+
use PhpLlm\McpSdk\Capability\Prompt\PromptGet;
8+
use PhpLlm\McpSdk\Capability\Prompt\PromptGetResult;
9+
use PhpLlm\McpSdk\Capability\Prompt\PromptGetResultMessages;
10+
use PhpLlm\McpSdk\Capability\Prompt\PromptGetterInterface;
11+
use PhpLlm\McpSdk\Exception\PromptGetException;
12+
use PhpLlm\McpSdk\Exception\PromptNotFoundException;
13+
14+
class PromptManager implements PromptGetterInterface, CollectionInterface
15+
{
16+
/**
17+
* @var mixed[]
18+
*/
19+
private array $items;
20+
21+
public function __construct(
22+
) {
23+
$this->items = [
24+
new ExamplePrompt(),
25+
];
26+
}
27+
28+
public function getMetadata(): array
29+
{
30+
return $this->items;
31+
}
32+
33+
public function get(PromptGet $request): PromptGetResult
34+
{
35+
foreach ($this->items as $item) {
36+
if ($request->name === $item->getName()) {
37+
try {
38+
return new PromptGetResult(
39+
$item->getDescription(),
40+
[new PromptGetResultMessages(
41+
'user',
42+
$item->__invoke(...$request->arguments),
43+
)]
44+
);
45+
} catch (\Throwable $e) {
46+
throw new PromptGetException($request, $e);
47+
}
48+
}
49+
}
50+
51+
throw new PromptNotFoundException($request);
52+
}
53+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
namespace App\Manager;
4+
5+
use App\ExampleResource;
6+
use PhpLlm\McpSdk\Capability\Resource\CollectionInterface;
7+
use PhpLlm\McpSdk\Capability\Resource\ResourceRead;
8+
use PhpLlm\McpSdk\Capability\Resource\ResourceReaderInterface;
9+
use PhpLlm\McpSdk\Capability\Resource\ResourceReadResult;
10+
use PhpLlm\McpSdk\Exception\ResourceNotFoundException;
11+
12+
class ResourceManager implements CollectionInterface, ResourceReaderInterface
13+
{
14+
/**
15+
* @var mixed[]
16+
*/
17+
private array $items;
18+
19+
public function __construct(
20+
) {
21+
$this->items = [
22+
new ExampleResource(),
23+
];
24+
}
25+
26+
public function getMetadata(): array
27+
{
28+
return $this->items;
29+
}
30+
31+
public function read(ResourceRead $request): ResourceReadResult
32+
{
33+
foreach ($this->items as $resource) {
34+
if ($request->uri === $resource->getUri()) {
35+
// In a real implementation, you would read the resource from its URI.
36+
// Here we just return a dummy string for demonstration purposes.
37+
return new ResourceReadResult(
38+
'Content of '.$resource->getName(),
39+
$resource->getUri(),
40+
);
41+
}
42+
}
43+
44+
throw new ResourceNotFoundException($request);
45+
}
46+
}

0 commit comments

Comments
 (0)