refactor: remove dead code and obsolete examples (#76)

- Remove ToolChain, ResourceChain, PromptChain classes
- Remove Collection/Identifier/Metadata interfaces (only used by old Chains)
- Remove ToolCollectionInterface (unused)
- Delete obsolete examples 09-standalone-cli and 10-simple-http-transport
- Fix STDERR handling in bootstrap.php for web environments
- Clean up CHANGELOG.md
This commit is contained in:
Kyrian Obikwelu 2025-09-21 19:23:59 +01:00 • committed by GitHub
commit 9e168832ca
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 5 additions and 1050 deletions

View file

@ -1,24 +1,3 @@
CHANGELOG
=========
# Changelog
0.1
---
* Add Model Context Protocol (MCP) implementation for LLM-application communication
* Add JSON-RPC based protocol handling with `JsonRpcHandler`
* Add three core MCP capabilities:
- Resources: File-like data readable by clients (API responses, file contents)
- Tools: Functions callable by LLMs (with user approval)
- Prompts: Pre-written templates for specific tasks
* Add multiple transport implementations:
- Symfony Console Transport for testing and CLI applications
- Stream Transport supporting Server-Sent Events (SSE) and HTTP streaming
- STDIO transport for command-line interfaces
* Add capability chains for organizing features:
- `ToolChain` for tool management
- `ResourceChain` for resource management
- `PromptChain` for prompt template management
* Add Server component managing transport connections
* Add request/notification handlers for MCP operations
* Add standardized interface enabling LLMs to interact with external systems
* Add support for building LLM "plugins" with extra context capabilities
All notable changes to `mcp/sdk` will be documented in this file.

View file

@ -1,27 +0,0 @@
# Standalone example app with CLI
This is just for testing and debugging purposes. Different from the other examples, this one does not use the same
autoloader, but installs the SDK via path repository and therefore has mostly decoupled dependencies.
Install dependencies:
```bash
cd /path/to/your/project/examples/09-standalone-cli
composer update
```
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 by adding some JSON strings. See `example-requests.json`.
Run with Inspector:
```bash
npx @modelcontextprotocol/inspector php index.php
```

View file

@ -1,27 +0,0 @@
{
"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

@ -1,12 +0,0 @@
[
{"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" }}}
]

View file

@ -1,35 +0,0 @@
<?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 Mcp\Server;
use Mcp\Server\Transport\StdioTransport;
use Symfony\Component\Console as SymfonyConsole;
use Symfony\Component\Console\Output\OutputInterface;
$debug = (bool) ($_SERVER['DEBUG'] ?? false);
// Setup input, output and logger
$output = new SymfonyConsole\Output\ConsoleOutput($debug ? OutputInterface::VERBOSITY_VERY_VERBOSE : OutputInterface::VERBOSITY_NORMAL);
$logger = new SymfonyConsole\Logger\ConsoleLogger($output);
$server = Server::make()
->setServerInfo('Standalone CLI', '1.0.0')
->setLogger($logger)
->setDiscovery(__DIR__, ['.'])
->build();
$transport = new StdioTransport(logger: $logger);
$server->connect($transport);
$transport->listen();

View file

@ -1,55 +0,0 @@
<?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\MethodHandlerInterface;
use Mcp\Server\NotificationHandler;
use Mcp\Server\RequestHandler;
/**
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
*/
class Builder
{
/**
* @return list<MethodHandlerInterface>
*/
public static function buildMethodHandlers(): array
{
$promptManager = new PromptChain([
new ExamplePrompt(),
]);
$resourceManager = new ResourceChain([
new ExampleResource(),
]);
$toolManager = new ToolChain([
new ExampleTool(),
]);
return [
new NotificationHandler\InitializedHandler(),
new RequestHandler\InitializeHandler(),
new RequestHandler\PingHandler(),
new RequestHandler\ListPromptsHandler($promptManager),
new RequestHandler\GetPromptHandler($promptManager),
new RequestHandler\ListResourcesHandler($resourceManager),
new RequestHandler\ReadResourceHandler($resourceManager),
new RequestHandler\CallToolHandler($toolManager),
new RequestHandler\ListToolsHandler($toolManager),
];
}
}

View file

@ -1,60 +0,0 @@
<?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\PromptGetterInterface;
use Mcp\Schema\Content\PromptMessage;
use Mcp\Schema\Content\TextContent;
use Mcp\Schema\Enum\Role;
use Mcp\Schema\Request\GetPromptRequest;
use Mcp\Schema\Result\GetPromptResult;
/**
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
*/
class ExamplePrompt implements MetadataInterface, PromptGetterInterface
{
public function get(GetPromptRequest $request): GetPromptResult
{
$firstName = $request->arguments['firstName'] ?? null;
return new GetPromptResult(
[new PromptMessage(
Role::User,
new TextContent(\sprintf('Hello %s', $firstName ?? 'World')),
)],
$this->getDescription(),
);
}
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

@ -1,56 +0,0 @@
<?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\ResourceReaderInterface;
use Mcp\Schema\Content\TextResourceContents;
use Mcp\Schema\Request\ReadResourceRequest;
use Mcp\Schema\Result\ReadResourceResult;
/**
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
*/
class ExampleResource implements MetadataInterface, ResourceReaderInterface
{
public function read(ReadResourceRequest $request): ReadResourceResult
{
return new ReadResourceResult([
new TextResourceContents($this->getUri(), null, 'Content of My Resource'),
]);
}
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

@ -1,60 +0,0 @@
<?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\ToolCallerInterface;
use Mcp\Schema\Content\TextContent;
use Mcp\Schema\Request\CallToolRequest;
use Mcp\Schema\Result\CallToolResult;
/**
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
*/
class ExampleTool implements MetadataInterface, ToolCallerInterface
{
public function call(CallToolRequest $request): CallToolResult
{
$format = $request->arguments['format'] ?? 'Y-m-d H:i:s';
return new CallToolResult([
new TextContent(
(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' => [],
];
}
}

View file

@ -1,118 +0,0 @@
<?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 Mcp\Example\HttpTransportExample;
use Mcp\Capability\Attribute\McpPrompt;
use Mcp\Capability\Attribute\McpResource;
use Mcp\Capability\Attribute\McpTool;
class McpElements
{
public function __construct()
{
}
/**
* Get the current server time.
*/
#[McpTool(name: 'current_time')]
public function getCurrentTime(string $format = 'Y-m-d H:i:s'): string
{
try {
return date($format);
} catch (\Exception $e) {
return date('Y-m-d H:i:s'); // fallback
}
}
/**
* Calculate simple math operations.
*/
#[McpTool(name: 'calculate')]
public function calculate(float $a, float $b, string $operation): float|string
{
return match (strtolower($operation)) {
'add', '+' => $a + $b,
'subtract', '-' => $a - $b,
'multiply', '*' => $a * $b,
'divide', '/' => 0 != $b ? $a / $b : 'Error: Division by zero',
default => 'Error: Unknown operation. Use: add, subtract, multiply, divide',
};
}
/**
* Server information resource.
*
* @return array{status: string, timestamp: int, version: string, transport: string, uptime: int}
*/
#[McpResource(
uri: 'info://server/status',
name: 'server_status',
description: 'Current server status and information',
mimeType: 'application/json'
)]
public function getServerStatus(): array
{
return [
'status' => 'running',
'timestamp' => time(),
'version' => '1.0.0',
'transport' => 'HTTP',
'uptime' => time() - $_SERVER['REQUEST_TIME'],
];
}
/**
* Configuration resource.
*
* @return array{debug: bool, environment: string, timezone: string, locale: string}
*/
#[McpResource(
uri: 'config://app/settings',
name: 'app_config',
description: 'Application configuration settings',
mimeType: 'application/json'
)]
public function getAppConfig(): array
{
return [
'debug' => $_SERVER['DEBUG'] ?? false,
'environment' => $_SERVER['APP_ENV'] ?? 'production',
'timezone' => date_default_timezone_get(),
'locale' => 'en_US',
];
}
/**
* Greeting prompt.
*
* @return array{role: string, content: string}
*/
#[McpPrompt(
name: 'greet',
description: 'Generate a personalized greeting message'
)]
public function greetPrompt(string $firstName = 'World', string $timeOfDay = 'day'): array
{
$greeting = match (strtolower($timeOfDay)) {
'morning' => 'Good morning',
'afternoon' => 'Good afternoon',
'evening', 'night' => 'Good evening',
default => 'Hello',
};
return [
'role' => 'user',
'content' => "# {$greeting}, {$firstName}!\n\nWelcome to our MCP HTTP Server example. This demonstrates how to use the Model Context Protocol over HTTP transport.",
];
}
}

View file

@ -1,24 +0,0 @@
# HTTP MCP Server Example
This example demonstrates how to use the MCP SDK with HTTP transport using the StreamableHttpTransport. It provides a complete HTTP-based MCP server that can handle JSON-RPC requests over HTTP POST.
## Usage
**Step 1: Start the HTTP server**
```bash
cd examples/10-simple-http-transport
php -S localhost:8000 server.php
```
**Step 2: Connect with MCP Inspector**
```bash
npx @modelcontextprotocol/inspector http://localhost:8000
```
## Available Features
- **Tools**: `current_time`, `calculate`
- **Resources**: `info://server/status`, `config://app/settings`
- **Prompts**: `greet`

View file

@ -1,40 +0,0 @@
<?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_once dirname(__DIR__).'/bootstrap.php';
chdir(__DIR__);
use Laminas\HttpHandlerRunner\Emitter\SapiEmitter;
use Mcp\Server;
use Mcp\Server\Session\FileSessionStore;
use Mcp\Server\Transport\StreamableHttpTransport;
use Nyholm\Psr7\Factory\Psr17Factory;
use Nyholm\Psr7Server\ServerRequestCreator;
$psr17Factory = new Psr17Factory();
$creator = new ServerRequestCreator($psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory);
$request = $creator->fromGlobals();
$server = Server::make()
->setServerInfo('HTTP MCP Server', '1.0.0', 'MCP Server over HTTP transport')
->setContainer(container())
->setSession(new FileSessionStore(__DIR__.'/sessions'))
->setDiscovery(__DIR__, ['.'])
->build();
$transport = new StreamableHttpTransport($request, $psr17Factory, $psr17Factory);
$server->connect($transport);
$response = $transport->listen();
(new SapiEmitter())->emit($response);

View file

@ -44,9 +44,9 @@ function logger(): LoggerInterface
if ($_SERVER['FILE_LOG'] ?? false) {
file_put_contents('dev.log', $logMessage, \FILE_APPEND);
} elseif (defined('STDERR')) {
fwrite(\STDERR, $logMessage);
}
fwrite(\STDERR, $logMessage);
}
};
}

View file

@ -321,25 +321,6 @@ parameters:
count: 1
path: examples/08-schema-showcase-streamable/SchemaShowcaseElements.php
-
message: '#^Parameter \#1 \$registry of class Mcp\\Server\\RequestHandler\\ListPromptsHandler constructor expects Mcp\\Capability\\Registry\\ReferenceProviderInterface, Mcp\\Capability\\PromptChain given\.$#'
identifier: argument.type
count: 1
path: examples/09-standalone-cli/src/Builder.php
-
message: '#^Parameter \#1 \$registry of class Mcp\\Server\\RequestHandler\\ListResourcesHandler constructor expects Mcp\\Capability\\Registry\\ReferenceProviderInterface, Mcp\\Capability\\ResourceChain given\.$#'
identifier: argument.type
count: 1
path: examples/09-standalone-cli/src/Builder.php
-
message: '#^Parameter \#1 \$registry of class Mcp\\Server\\RequestHandler\\ListToolsHandler constructor expects Mcp\\Capability\\Registry\\ReferenceProviderInterface, Mcp\\Capability\\ToolChain given\.$#'
identifier: argument.type
count: 1
path: examples/09-standalone-cli/src/Builder.php
-
message: '#^PHPDoc tag @return with type array is incompatible with native type object\.$#'
identifier: return.phpDocType

View file

@ -98,7 +98,7 @@ class Discoverer
$this->processFile($file, $discoveredCount);
}
} catch (\Throwable $e) {
$this->logger->error('Error during file finding process for MCP discovery', [
$this->logger->error('Error during file finding process for MCP discovery'.json_encode($e->getTrace(), \JSON_PRETTY_PRINT), [
'exception' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);

View file

@ -1,29 +0,0 @@
<?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 Mcp\Capability\Prompt;
use Mcp\Exception\InvalidCursorException;
/**
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
*/
interface CollectionInterface
{
/**
* @param int $count the number of metadata items to return
*
* @return iterable<MetadataInterface>
*
* @throws InvalidCursorException if no item with $lastIdentifier was found
*/
public function getMetadata(int $count, ?string $lastIdentifier = null): iterable;
}

View file

@ -1,20 +0,0 @@
<?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 Mcp\Capability\Prompt;
/**
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
*/
interface IdentifierInterface
{
public function getName(): string;
}

View file

@ -1,29 +0,0 @@
<?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 Mcp\Capability\Prompt;
/**
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
*/
interface MetadataInterface extends IdentifierInterface
{
public function getDescription(): ?string;
/**
* @return list<array{
* name: string,
* description?: string,
* required?: bool,
* }>
*/
public function getArguments(): array;
}

View file

@ -1,77 +0,0 @@
<?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 Mcp\Capability;
use Mcp\Capability\Prompt\CollectionInterface;
use Mcp\Capability\Prompt\IdentifierInterface;
use Mcp\Capability\Prompt\MetadataInterface;
use Mcp\Capability\Prompt\PromptGetterInterface;
use Mcp\Exception\InvalidCursorException;
use Mcp\Exception\PromptGetException;
use Mcp\Exception\PromptNotFoundException;
use Mcp\Schema\Request\GetPromptRequest;
use Mcp\Schema\Result\GetPromptResult;
/**
* A collection of prompts. All prompts need to implement IdentifierInterface.
*
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
*/
class PromptChain implements PromptGetterInterface, CollectionInterface
{
public function __construct(
/**
* @var IdentifierInterface[]
*/
private readonly array $items,
) {
}
public function getMetadata(int $count, ?string $lastIdentifier = null): iterable
{
$found = null === $lastIdentifier;
foreach ($this->items as $item) {
if (!$item instanceof MetadataInterface) {
continue;
}
if (false === $found) {
$found = $item->getName() === $lastIdentifier;
continue;
}
yield $item;
if (--$count <= 0) {
break;
}
}
if (!$found) {
throw new InvalidCursorException($lastIdentifier);
}
}
public function get(GetPromptRequest $request): GetPromptResult
{
foreach ($this->items as $item) {
if ($item instanceof PromptGetterInterface && $request->name === $item->getName()) {
try {
return $item->get($request);
} catch (\Throwable $e) {
throw new PromptGetException($request, $e);
}
}
}
throw new PromptNotFoundException($request);
}
}

View file

@ -1,29 +0,0 @@
<?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 Mcp\Capability\Resource;
use Mcp\Exception\InvalidCursorException;
/**
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
*/
interface CollectionInterface
{
/**
* @param int $count the number of metadata items to return
*
* @return iterable<MetadataInterface>
*
* @throws InvalidCursorException if no item with $lastIdentifier was found
*/
public function getMetadata(int $count, ?string $lastIdentifier = null): iterable;
}

Some files were not shown because too many files have changed in this diff Show more