Kick off the SDK based on Symfony's MCP SDK

This commit is contained in:
Christopher Hertel 2025-07-25 11:40:39 +02:00
commit ee0dffe369
No known key found for this signature in database
GPG key ID: 111F6BFF7B6B5AA3
8 changed files with 3924 additions and 0 deletions

29
examples/cli/README.md Normal file
View file

@ -0,0 +1,29 @@
# 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/mcp/sdk/src
ln -s /path/to/your/project/src /path/to/your/project/examples/cli/vendor/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`.
Run with Inspector:
```bash
npx @modelcontextprotocol/inspector php index.php
```

View file

@ -0,0 +1,27 @@
{
"name": "mcp/cli-server-example",
"description": "An example application for CLI",
"license": "MIT",
"type": "project",
"authors": [
{
"name": "Tobias Nyholm",
"email": "tobias.nyholm@gmail.com"
}
],
"require": {
"php": ">=8.1",
"mcp/sdk": "@dev",
"symfony/console": "^7.2"
},
"minimum-stability": "stable",
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"repositories": [
{ "type": "path", "url": "../../" }
]
}

View file

@ -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" }}}
]

39
examples/cli/index.php Normal file
View file

@ -0,0 +1,39 @@
<?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.
*/
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 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 and build the functionality
$jsonRpcHandler = new Mcp\Server\JsonRpcHandler(
new Mcp\Message\Factory(),
App\Builder::buildRequestHandlers(),
App\Builder::buildNotificationHandlers(),
$logger
);
// Set up the server
$sever = new Mcp\Server($jsonRpcHandler, $logger);
// Create the transport layer using Symfony Console
$transport = new Mcp\Server\Transport\Stdio\SymfonyConsoleTransport($input, $output);
// Start our application
$sever->connect($transport);

View file

@ -0,0 +1,72 @@
<?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 App;
use Mcp\Capability\PromptChain;
use Mcp\Capability\ResourceChain;
use Mcp\Capability\ToolChain;
use Mcp\Server\NotificationHandler\InitializedHandler;
use Mcp\Server\NotificationHandlerInterface;
use Mcp\Server\RequestHandler\InitializeHandler;
use Mcp\Server\RequestHandler\PingHandler;
use Mcp\Server\RequestHandler\PromptGetHandler;
use Mcp\Server\RequestHandler\PromptListHandler;
use Mcp\Server\RequestHandler\ResourceListHandler;
use Mcp\Server\RequestHandler\ResourceReadHandler;
use Mcp\Server\RequestHandler\ToolCallHandler;
use Mcp\Server\RequestHandler\ToolListHandler;
use Mcp\Server\RequestHandlerInterface;
/**
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
*/
class Builder
{
/**
* @return list<RequestHandlerInterface>
*/
public static function buildRequestHandlers(): array
{
$promptManager = new PromptChain([
new ExamplePrompt(),
]);
$resourceManager = new ResourceChain([
new ExampleResource(),
]);
$toolManager = new ToolChain([
new ExampleTool(),
]);
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<NotificationHandlerInterface>
*/
public static function buildNotificationHandlers(): array
{
return [
new InitializedHandler(),
];
}
}

View file

@ -0,0 +1,58 @@
<?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 App;
use Mcp\Capability\Prompt\MetadataInterface;
use Mcp\Capability\Prompt\PromptGet;
use Mcp\Capability\Prompt\PromptGetResult;
use Mcp\Capability\Prompt\PromptGetResultMessages;
use Mcp\Capability\Prompt\PromptGetterInterface;
/**
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
*/
class ExamplePrompt implements MetadataInterface, PromptGetterInterface
{
public function get(PromptGet $input): PromptGetResult
{
$firstName = $input->arguments['first name'] ?? null;
return new PromptGetResult(
$this->getDescription(),
[new PromptGetResultMessages(
'user',
\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' => 'first name',
'description' => 'The name of the person to greet',
'required' => false,
],
];
}
}

View file

@ -0,0 +1,56 @@
<?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 App;
use Mcp\Capability\Resource\MetadataInterface;
use Mcp\Capability\Resource\ResourceRead;
use Mcp\Capability\Resource\ResourceReaderInterface;
use Mcp\Capability\Resource\ResourceReadResult;
/**
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
*/
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';
}
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;
}
}

View file

@ -0,0 +1,57 @@
<?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 App;
use Mcp\Capability\Tool\MetadataInterface;
use Mcp\Capability\Tool\ToolCall;
use Mcp\Capability\Tool\ToolCallResult;
use Mcp\Capability\Tool\ToolExecutorInterface;
/**
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
*/
class ExampleTool implements MetadataInterface, ToolExecutorInterface
{
public function call(ToolCall $input): ToolCallResult
{
$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
{
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' => [],
];
}
}