refactor: transport agnostic examples (#131)

This commit is contained in:
Christopher Hertel 2025-11-02 18:07:43 +01:00 • committed by GitHub
commit ff26b4ff61
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
46 changed files with 157 additions and 289 deletions

View file

@ -52,17 +52,17 @@
},
"autoload-dev": {
"psr-4": {
"Mcp\\Example\\HttpCombinedRegistration\\": "examples/http-combined-registration/",
"Mcp\\Example\\HttpComplexToolSchema\\": "examples/http-complex-tool-schema/",
"Mcp\\Example\\HttpDiscoveryUserProfile\\": "examples/http-discovery-userprofile/",
"Mcp\\Example\\HttpSchemaShowcase\\": "examples/http-schema-showcase/",
"Mcp\\Example\\StdioCachedDiscovery\\": "examples/stdio-cached-discovery/",
"Mcp\\Example\\StdioClientCommunication\\": "examples/stdio-client-communication/",
"Mcp\\Example\\StdioCustomDependencies\\": "examples/stdio-custom-dependencies/",
"Mcp\\Example\\StdioDiscoveryCalculator\\": "examples/stdio-discovery-calculator/",
"Mcp\\Example\\StdioEnvVariables\\": "examples/stdio-env-variables/",
"Mcp\\Example\\StdioExplicitRegistration\\": "examples/stdio-explicit-registration/",
"Mcp\\Example\\CachedDiscovery\\": "examples/cached-discovery/",
"Mcp\\Example\\ClientCommunication\\": "examples/client-communication/",
"Mcp\\Example\\CombinedRegistration\\": "examples/combined-registration/",
"Mcp\\Example\\ComplexToolSchema\\": "examples/complex-tool-schema/",
"Mcp\\Example\\CustomDependencies\\": "examples/custom-dependencies/",
"Mcp\\Example\\CustomMethodHandlers\\": "examples/custom-method-handlers/",
"Mcp\\Example\\DiscoveryCalculator\\": "examples/discovery-calculator/",
"Mcp\\Example\\DiscoveryUserProfile\\": "examples/discovery-userprofile/",
"Mcp\\Example\\EnvVariables\\": "examples/env-variables/",
"Mcp\\Example\\ExplicitRegistration\\": "examples/explicit-registration/",
"Mcp\\Example\\SchemaShowcase\\": "examples/schema-showcase/",
"Mcp\\Tests\\": "tests/"
}
},

View file

@ -7,10 +7,7 @@ specific features and can be run independently to understand how the SDK works.
- [Getting Started](#getting-started)
- [Running Examples](#running-examples)
- [STDIO Examples](#stdio-examples)
- [HTTP Examples](#http-examples)
- [Advanced Patterns](#advanced-patterns)
- [Testing and Debugging](#testing-and-debugging)
- [Examples](#examples)
## Getting Started
@ -26,28 +23,30 @@ composer install
## Running Examples
### STDIO Examples
The bootstrapping of the example will choose the used transport based on the SAPI you use.
STDIO examples use standard input/output for communication:
### STDIO Transport
The STDIO transport will use standard input/output for communication:
```bash
# Interactive testing with MCP Inspector
npx @modelcontextprotocol/inspector php examples/stdio-discovery-calculator/server.php
npx @modelcontextprotocol/inspector php examples/discovery-calculator/server.php
# Run with debugging enabled
npx @modelcontextprotocol/inspector -e DEBUG=1 -e FILE_LOG=1 php examples/stdio-discovery-calculator/server.php
npx @modelcontextprotocol/inspector -e DEBUG=1 -e FILE_LOG=1 php examples/discovery-calculator/server.php
# Or configure the script path in your MCP client
# Path: php examples/stdio-discovery-calculator/server.php
# Path: php examples/discovery-calculator/server.php
```
### HTTP Examples
### HTTP Transport
HTTP examples run as web servers:
The Streamable HTTP transport will be chosen if running examples with a web servers:
```bash
# Start the server
php -S localhost:8000 examples/http-discovery-userprofile/server.php
php -S localhost:8000 examples/discovery-userprofile/server.php
# Test with MCP Inspector
npx @modelcontextprotocol/inspector http://localhost:8000
@ -59,11 +58,11 @@ curl -X POST http://localhost:8000 \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","clientInfo":{"name":"test","version":"1.0.0"},"capabilities":{}}}'
```
## STDIO Examples
## Examples
### Discovery Calculator
**File**: `examples/stdio-discovery-calculator/`
**File**: `examples/discovery-calculator/`
**What it demonstrates:**
- Attribute-based discovery using `#[McpTool]` and `#[McpResource]`
@ -87,14 +86,14 @@ public function getConfiguration(): array
**Usage:**
```bash
# Interactive testing
npx @modelcontextprotocol/inspector php examples/stdio-discovery-calculator/server.php
npx @modelcontextprotocol/inspector php examples/discovery-calculator/server.php
# Or configure in MCP client: php examples/stdio-discovery-calculator/server.php
# Or configure in MCP client: php examples/discovery-calculator/server.php
```
### Explicit Registration
**File**: `examples/stdio-explicit-registration/`
**File**: `examples/explicit-registration/`
**What it demonstrates:**
- Manual registration of tools, resources, and prompts
@ -111,7 +110,7 @@ $server = Server::builder()
### Environment Variables
**File**: `examples/stdio-env-variables/`
**File**: `examples/env-variables/`
**What it demonstrates:**
- Environment variable integration
@ -125,7 +124,7 @@ $server = Server::builder()
### Custom Dependencies
**File**: `examples/stdio-custom-dependencies/`
**File**: `examples/custom-dependencies/`
**What it demonstrates:**
- Dependency injection with PSR-11 containers
@ -145,7 +144,7 @@ $server = Server::builder()
### Cached Discovery
**File**: `examples/stdio-cached-discovery/`
**File**: `examples/cached-discovery/`
**What it demonstrates:**
- Discovery caching for improved performance
@ -165,7 +164,7 @@ $server = Server::builder()
### Client Communication
**File**: `examples/stdio-client-communication/`
**File**: `examples/client-communication/`
**What it demostrates:**
- Server initiated communcation back to the client
@ -173,11 +172,9 @@ $server = Server::builder()
- Using `ClientGateway` in service class via `ClientAwareInterface` and corresponding trait
- Using `ClientGateway` in tool method via method argument injection
## HTTP Examples
### Discovery User Profile
**File**: `examples/http-discovery-userprofile/`
**File**: `examples/discovery-userprofile/`
**What it demonstrates:**
- HTTP transport with StreamableHttpTransport
@ -205,7 +202,7 @@ public function generateBio(string $userId, string $tone = 'professional'): arra
**Usage:**
```bash
# Start the HTTP server
php -S localhost:8000 examples/http-discovery-userprofile/server.php
php -S localhost:8000 examples/discovery-userprofile/server.php
# Test with MCP Inspector
npx @modelcontextprotocol/inspector http://localhost:8000
@ -215,7 +212,7 @@ npx @modelcontextprotocol/inspector http://localhost:8000
### Combined Registration
**File**: `examples/http-combined-registration/`
**File**: `examples/combined-registration/`
**What it demonstrates:**
- Mixing attribute discovery with manual registration
@ -232,7 +229,7 @@ $server = Server::builder()
### Complex Tool Schema
**File**: `examples/http-complex-tool-schema/`
**File**: `examples/complex-tool-schema/`
**What it demonstrates:**
- Advanced JSON schema definitions
@ -255,7 +252,7 @@ public function scheduleEvent(array $eventData): array
### Schema Showcase
**File**: `examples/http-schema-showcase/`
**File**: `examples/schema-showcase/`
**What it demonstrates:**
- Comprehensive JSON schema features

View file

@ -3,14 +3,15 @@
This directory contains various examples of how to use the PHP MCP SDK.
You can run the examples with the dependencies already installed in the root directory of the SDK.
The bootstrapping of the example will choose the used transport based on the SAPI you use.
For running an example, you execute the `server.php` like this:
```bash
# For examples using STDIO transport
php examples/stdio-discovery-calculator/server.php
# For using the STDIO transport:
php examples/discovery-calculator/server.php
# For examples using Streamable HTTP transport
php -S localhost:8000 examples/http-discovery-userprofile/server.php
# For using the Streamable HTTP transport:
php -S localhost:8000 examples/discovery-userprofile/server.php
```
You will see debug outputs to help you understand what is happening.
@ -18,7 +19,7 @@ You will see debug outputs to help you understand what is happening.
Run with Inspector:
```bash
npx @modelcontextprotocol/inspector php examples/stdio-discovery-calculator/server.php
npx @modelcontextprotocol/inspector php examples/discovery-calculator/server.php
```
## Debugging
@ -29,5 +30,5 @@ directory.
With the Inspector you can set the environment variables like this:
```bash
npx @modelcontextprotocol/inspector -e DEBUG=1 -e FILE_LOG=1 php examples/stdio-discovery-calculator/server.php
npx @modelcontextprotocol/inspector -e DEBUG=1 -e FILE_LOG=1 php examples/discovery-calculator/server.php
```

View file

@ -9,7 +9,13 @@
* file that was distributed with this source code.
*/
use Http\Discovery\Psr17Factory;
use Laminas\HttpHandlerRunner\Emitter\SapiEmitter;
use Mcp\Capability\Registry\Container;
use Mcp\Server\Transport\StdioTransport;
use Mcp\Server\Transport\StreamableHttpTransport;
use Mcp\Server\Transport\TransportInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Log\AbstractLogger;
use Psr\Log\LoggerInterface;
@ -21,6 +27,31 @@ set_exception_handler(function (Throwable $t): never {
exit(1);
});
/**
* @return TransportInterface<int>|TransportInterface<ResponseInterface>
*/
function transport(): TransportInterface
{
if ('cli' === \PHP_SAPI) {
return new StdioTransport(logger: logger());
}
return new StreamableHttpTransport(
(new Psr17Factory())->createServerRequestFromGlobals(),
logger: logger(),
);
}
function shutdown(ResponseInterface|int $result): never
{
if ('cli' === \PHP_SAPI) {
exit($result);
}
(new SapiEmitter())->emit($result);
exit(0);
}
function logger(): LoggerInterface
{
return new class extends AbstractLogger {

View file

@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
/*
* 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\CachedDiscovery;
use Mcp\Capability\Attribute\McpTool;
use Mcp\Exception\ToolCallException;
/**
* Example MCP elements for demonstrating cached discovery.
*
* This class contains simple calculator tools that will be discovered
* and cached for improved performance on subsequent server starts.
*/
class CachedCalculatorElements
{
#[McpTool(name: 'add_numbers')]
public function add(int $a, int $b): int
{
return $a + $b;
}
#[McpTool(name: 'multiply_numbers')]
public function multiply(int $a, int $b): int
{
return $a * $b;
}
#[McpTool(name: 'divide_numbers')]
public function divide(int $a, int $b): float
{
if (0 === $b) {
throw new ToolCallException('Division by zero is not allowed');
}
return $a / $b;
}
#[McpTool(name: 'power')]
public function power(int $base, int $exponent): int
{
return (int) $base ** $exponent;
}
}

View file

@ -0,0 +1,37 @@
#!/usr/bin/env php
<?php
declare(strict_types=1);
/*
* 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 Mcp\Server;
use Mcp\Server\Session\FileSessionStore;
use Symfony\Component\Cache\Adapter\PhpFilesAdapter;
use Symfony\Component\Cache\Psr16Cache;
logger()->info('Starting MCP Cached Discovery Calculator Server...');
$server = Server::builder()
->setServerInfo('Cached Discovery Calculator', '1.0.0', 'Calculator with cached discovery for better performance.')
->setContainer(container())
->setSession(new FileSessionStore(__DIR__.'/sessions'))
->setLogger(logger())
->setDiscovery(__DIR__, cache: new Psr16Cache(new PhpFilesAdapter(directory: __DIR__.'/cache')))
->build();
$result = $server->run(transport());
logger()->info('Server listener stopped gracefully.', ['result' => $result]);
shutdown($result);

View file

@ -0,0 +1,71 @@
<?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\ClientCommunication;
use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Content\TextContent;
use Mcp\Schema\Enum\LoggingLevel;
use Mcp\Server\ClientAwareInterface;
use Mcp\Server\ClientAwareTrait;
use Psr\Log\LoggerInterface;
final class ClientAwareService implements ClientAwareInterface
{
use ClientAwareTrait;
public function __construct(
private readonly LoggerInterface $logger,
) {
$this->logger->info('SamplingTool instantiated for sampling example.');
}
/**
* @return array{incident: string, recommended_actions: string, model: string}
*/
#[McpTool('coordinate_incident_response', 'Coordinate an incident response with logging, progress, and sampling.')]
public function coordinateIncident(string $incidentTitle): array
{
$this->log(LoggingLevel::Warning, \sprintf('Incident triage started: %s', $incidentTitle));
$steps = [
'Collecting telemetry',
'Assessing scope',
'Coordinating responders',
];
foreach ($steps as $index => $step) {
$progress = ($index + 1) / \count($steps);
$this->progress($progress, 1, $step);
usleep(180_000); // Simulate work being done
}
$prompt = \sprintf(
'Provide a concise response strategy for incident "%s" based on the steps completed: %s.',
$incidentTitle,
implode(', ', $steps)
);
$result = $this->sample($prompt, 350, 90, ['temperature' => 0.5]);
$recommendation = $result->content instanceof TextContent ? trim((string) $result->content->text) : '';
$this->log(LoggingLevel::Info, \sprintf('Incident triage completed for %s', $incidentTitle));
return [
'incident' => $incidentTitle,
'recommended_actions' => $recommendation,
'model' => $result->model,
];
}
}

View file

@ -0,0 +1,62 @@
#!/usr/bin/env php
<?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 Mcp\Schema\Enum\LoggingLevel;
use Mcp\Schema\ServerCapabilities;
use Mcp\Server;
use Mcp\Server\ClientGateway;
use Mcp\Server\Session\FileSessionStore;
$server = Server::builder()
->setServerInfo('Client Communication Demo', '1.0.0')
->setLogger(logger())
->setContainer(container())
->setSession(new FileSessionStore(__DIR__.'/sessions'))
->setCapabilities(new ServerCapabilities(logging: true, tools: true))
->setDiscovery(__DIR__)
->addTool(
function (string $dataset, ClientGateway $client): array {
$client->log(LoggingLevel::Info, sprintf('Running quality checks on dataset "%s"', $dataset));
$tasks = [
'Validating schema',
'Scanning for anomalies',
'Reviewing statistical summary',
];
foreach ($tasks as $index => $task) {
$progress = ($index + 1) / count($tasks);
$client->progress(progress: $progress, total: 1, message: $task);
usleep(140_000); // Simulate work being done
}
$client->log(LoggingLevel::Info, sprintf('Dataset "%s" passed automated checks.', $dataset));
return [
'dataset' => $dataset,
'status' => 'passed',
'notes' => 'No significant integrity issues detected during automated checks.',
];
},
name: 'run_dataset_quality_checks',
description: 'Perform dataset quality checks with progress updates and logging.'
)
->build();
$result = $server->run(transport());
shutdown($result);

View file

@ -0,0 +1,41 @@
<?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\CombinedRegistration;
use Mcp\Capability\Attribute\McpResource;
use Mcp\Capability\Attribute\McpTool;
final class DiscoveredElements
{
/**
* A tool discovered via attributes.
*
* @return string a status message
*/
#[McpTool(name: 'discovered_status_check')]
public function checkSystemStatus(): string
{
return 'System status: OK (discovered)';
}
/**
* A resource discovered via attributes.
* This will be overridden by a manual registration with the same URI.
*
* @return string content
*/
#[McpResource(uri: 'config://priority', name: 'priority_config_discovered')]
public function getPriorityConfigDiscovered(): string
{
return 'Discovered Priority Config: Low';
}
}

View file

@ -0,0 +1,48 @@
<?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\CombinedRegistration;
use Psr\Log\LoggerInterface;
final class ManualHandlers
{
public function __construct(
private readonly LoggerInterface $logger,
) {
}
/**
* A manually registered tool.
*
* @param string $user the user to greet
*
* @return string greeting
*/
public function manualGreeter(string $user): string
{
$this->logger->info("Manual tool 'manual_greeter' called for {$user}");
return "Hello {$user}, from manual registration!";
}
/**
* Manually registered resource that overrides a discovered one.
*
* @return string content
*/
public function getPriorityConfigManual(): string
{
$this->logger->info("Manual resource 'config://priority' read.");
return 'Manual Priority Config: HIGH (overrides discovered)';
}
}

View file

@ -0,0 +1,36 @@
#!/usr/bin/env php
<?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 Mcp\Example\CombinedRegistration\ManualHandlers;
use Mcp\Server;
use Mcp\Server\Session\FileSessionStore;
$server = Server::builder()
->setServerInfo('Combined HTTP Server', '1.0.0')
->setLogger(logger())
->setContainer(container())
->setSession(new FileSessionStore(__DIR__.'/sessions'))
->setDiscovery(__DIR__)
->addTool([ManualHandlers::class, 'manualGreeter'])
->addResource(
[ManualHandlers::class, 'getPriorityConfigManual'],
'config://priority',
'priority_config_manual',
)
->build();
$response = $server->run(transport());
shutdown($response);

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 Mcp\Example\ComplexToolSchema;
use Mcp\Capability\Attribute\McpTool;
use Mcp\Example\ComplexToolSchema\Model\EventPriority;
use Mcp\Example\ComplexToolSchema\Model\EventType;
use Psr\Log\LoggerInterface;
final class McpEventScheduler
{
public function __construct(
private readonly LoggerInterface $logger,
) {
}
/**
* Schedules a new event.
* The inputSchema for this tool will reflect all parameter types and defaults.
*
* @param string $title the title of the event
* @param string $date the date of the event (YYYY-MM-DD)
* @param EventType $type the type of event
* @param string|null $time the time of the event (HH:MM), optional
* @param EventPriority $priority The priority of the event. Defaults to Normal.
* @param string[]|null $attendees an optional list of attendee email addresses
* @param bool $sendInvites send calendar invites to attendees? Defaults to true if attendees are provided
*
* @return array<string, mixed> confirmation of the scheduled event
*/
#[McpTool(name: 'schedule_event')]
public function scheduleEvent(
string $title,
string $date,
EventType $type,
?string $time = null,
EventPriority $priority = EventPriority::Normal,
?array $attendees = null,
bool $sendInvites = true,
): array {
$this->logger->info("Tool 'schedule_event' called", compact('title', 'date', 'type', 'time', 'priority', 'attendees', 'sendInvites'));
// Simulate scheduling logic
$eventDetails = [
'title' => $title,
'date' => $date,
'type' => $type->value, // Use enum value
'time' => $time ?? 'All day',
'priority' => $priority->name, // Use enum name
'attendees' => $attendees ?? [],
'invites_will_be_sent' => ($attendees && $sendInvites),
];
// In a real app, this would interact with a calendar service
$this->logger->info('Event scheduled', ['details' => $eventDetails]);
return [
'success' => true,
'message' => \sprintf('Event "%s" scheduled successfully for "%s".', $title, $date),
'event_details' => $eventDetails,
];
}
}

View file

@ -0,0 +1,19 @@
<?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\ComplexToolSchema\Model;
enum EventPriority: int
{
case Low = 0;
case Normal = 1;
case High = 2;
}

View file

@ -0,0 +1,20 @@
<?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\ComplexToolSchema\Model;
enum EventType: string
{
case Meeting = 'meeting';
case Reminder = 'reminder';
case Call = 'call';
case Other = 'other';
}

View file

@ -0,0 +1,29 @@
#!/usr/bin/env php
<?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 Mcp\Server;
use Mcp\Server\Session\FileSessionStore;
$server = Server::builder()
->setServerInfo('Event Scheduler Server', '1.0.0')
->setLogger(logger())
->setContainer(container())
->setSession(new FileSessionStore(__DIR__.'/sessions'))
->setDiscovery(__DIR__)
->build();
$response = $server->run(transport());
shutdown($response);

View file

@ -0,0 +1,92 @@
<?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\CustomDependencies;
use Mcp\Capability\Attribute\McpResource;
use Mcp\Capability\Attribute\McpTool;
use Mcp\Example\CustomDependencies\Service\StatsServiceInterface;
use Mcp\Example\CustomDependencies\Service\TaskRepositoryInterface;
use Psr\Log\LoggerInterface;
/**
* @phpstan-import-type Task from TaskRepositoryInterface
*/
final class McpTaskHandlers
{
public function __construct(
private readonly TaskRepositoryInterface $taskRepo,
private readonly StatsServiceInterface $statsService,
private readonly LoggerInterface $logger,
) {
$this->logger->info('McpTaskHandlers instantiated with dependencies.');
}
/**
* Adds a new task for a given user.
*
* @param string $userId the ID of the user
* @param string $description the task description
*
* @return Task the created task details
*/
#[McpTool(name: 'add_task')]
public function addTask(string $userId, string $description): array
{
$this->logger->info("Tool 'add_task' invoked", ['userId' => $userId]);
return $this->taskRepo->addTask($userId, $description);
}
/**
* Lists pending tasks for a specific user.
*
* @param string $userId the ID of the user
*
* @return Task[] a list of tasks
*/
#[McpTool(name: 'list_user_tasks')]
public function listUserTasks(string $userId): array
{
$this->logger->info("Tool 'list_user_tasks' invoked", ['userId' => $userId]);
return $this->taskRepo->getTasksForUser($userId);
}
/**
* Marks a task as complete.
*
* @param int $taskId the ID of the task to complete
*
* @return array<string, mixed> status of the operation
*/
#[McpTool(name: 'complete_task')]
public function completeTask(int $taskId): array
{
$this->logger->info("Tool 'complete_task' invoked", ['taskId' => $taskId]);
$success = $this->taskRepo->completeTask($taskId);
return ['success' => $success, 'message' => $success ? "Task {$taskId} completed." : "Task {$taskId} not found."];
}
/**
* Provides current system statistics.
*
* @return array<string, int> system statistics
*/
#[McpResource(uri: 'stats://system/overview', name: 'system_stats', mimeType: 'application/json')]
public function getSystemStatistics(): array
{
$this->logger->info("Resource 'stats://system/overview' invoked");
return $this->statsService->getSystemStats();
}
}

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 Mcp\Example\CustomDependencies\Service;
use Psr\Log\LoggerInterface;
/**
* @phpstan-import-type Task from TaskRepositoryInterface
*/
final class InMemoryTaskRepository implements TaskRepositoryInterface
{
/**
* @var array<int, Task>
*/
private array $tasks = [];
private int $nextTaskId = 1;
public function __construct(
private readonly LoggerInterface $logger,
) {
// Add some initial tasks
$this->addTask('user1', 'Buy groceries');
$this->addTask('user1', 'Write MCP example');
$this->addTask('user2', 'Review PR');
}
public function addTask(string $userId, string $description): array
{
$task = [
'id' => $this->nextTaskId++,
'userId' => $userId,
'description' => $description,
'completed' => false,
'createdAt' => date('c'),
];
$this->tasks[$task['id']] = $task;
$this->logger->info('Task added', ['id' => $task['id'], 'user' => $userId]);
return $task;
}
public function getTasksForUser(string $userId): array
{
return array_values(array_filter($this->tasks, fn ($task) => $task['userId'] === $userId && !$task['completed']));
}
public function getAllTasks(): array
{
return array_values($this->tasks);
}
public function completeTask(int $taskId): bool
{
if (isset($this->tasks[$taskId])) {
$this->tasks[$taskId]['completed'] = true;
$this->logger->info('Task completed', ['id' => $taskId]);
return true;
}
return false;
}
}

View file

@ -0,0 +1,20 @@
<?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\CustomDependencies\Service;
interface StatsServiceInterface
{
/**
* @return array<string, int>
*/
public function getSystemStats(): array;
}

View file

@ -0,0 +1,34 @@
<?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\CustomDependencies\Service;
final class SystemStatsService implements StatsServiceInterface
{
public function __construct(
private readonly TaskRepositoryInterface $taskRepository,
) {
}
public function getSystemStats(): array
{
$allTasks = $this->taskRepository->getAllTasks();
$completed = \count(array_filter($allTasks, fn ($task) => $task['completed']));
$pending = \count($allTasks) - $completed;
return [
'total_tasks' => \count($allTasks),
'completed_tasks' => $completed,
'pending_tasks' => $pending,
'server_uptime_seconds' => time() - $_SERVER['REQUEST_TIME_FLOAT'], // Approx uptime for CLI script
];
}
}

View file

@ -0,0 +1,35 @@
<?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\CustomDependencies\Service;
/**
* @phpstan-type Task array{id: int, userId: string, description: string, completed: bool, createdAt: string}
*/
interface TaskRepositoryInterface
{
/**
* @return Task
*/
public function addTask(string $userId, string $description): array;
/**
* @return Task[]
*/
public function getTasksForUser(string $userId): array;
/**
* @return Task[]
*/
public function getAllTasks(): array;
public function completeTask(int $taskId): bool;
}

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