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": { "autoload-dev": {
"psr-4": { "psr-4": {
"Mcp\\Example\\HttpCombinedRegistration\\": "examples/http-combined-registration/", "Mcp\\Example\\CachedDiscovery\\": "examples/cached-discovery/",
"Mcp\\Example\\HttpComplexToolSchema\\": "examples/http-complex-tool-schema/", "Mcp\\Example\\ClientCommunication\\": "examples/client-communication/",
"Mcp\\Example\\HttpDiscoveryUserProfile\\": "examples/http-discovery-userprofile/", "Mcp\\Example\\CombinedRegistration\\": "examples/combined-registration/",
"Mcp\\Example\\HttpSchemaShowcase\\": "examples/http-schema-showcase/", "Mcp\\Example\\ComplexToolSchema\\": "examples/complex-tool-schema/",
"Mcp\\Example\\StdioCachedDiscovery\\": "examples/stdio-cached-discovery/", "Mcp\\Example\\CustomDependencies\\": "examples/custom-dependencies/",
"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\\CustomMethodHandlers\\": "examples/custom-method-handlers/", "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/" "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) - [Getting Started](#getting-started)
- [Running Examples](#running-examples) - [Running Examples](#running-examples)
- [STDIO Examples](#stdio-examples) - [Examples](#examples)
- [HTTP Examples](#http-examples)
- [Advanced Patterns](#advanced-patterns)
- [Testing and Debugging](#testing-and-debugging)
## Getting Started ## Getting Started
@ -26,28 +23,30 @@ composer install
## Running Examples ## 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 ```bash
# Interactive testing with MCP Inspector # 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 # 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 # 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 ```bash
# Start the server # 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 # Test with MCP Inspector
npx @modelcontextprotocol/inspector http://localhost:8000 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":{}}}' -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 ### Discovery Calculator
**File**: `examples/stdio-discovery-calculator/` **File**: `examples/discovery-calculator/`
**What it demonstrates:** **What it demonstrates:**
- Attribute-based discovery using `#[McpTool]` and `#[McpResource]` - Attribute-based discovery using `#[McpTool]` and `#[McpResource]`
@ -87,14 +86,14 @@ public function getConfiguration(): array
**Usage:** **Usage:**
```bash ```bash
# Interactive testing # 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 ### Explicit Registration
**File**: `examples/stdio-explicit-registration/` **File**: `examples/explicit-registration/`
**What it demonstrates:** **What it demonstrates:**
- Manual registration of tools, resources, and prompts - Manual registration of tools, resources, and prompts
@ -111,7 +110,7 @@ $server = Server::builder()
### Environment Variables ### Environment Variables
**File**: `examples/stdio-env-variables/` **File**: `examples/env-variables/`
**What it demonstrates:** **What it demonstrates:**
- Environment variable integration - Environment variable integration
@ -125,7 +124,7 @@ $server = Server::builder()
### Custom Dependencies ### Custom Dependencies
**File**: `examples/stdio-custom-dependencies/` **File**: `examples/custom-dependencies/`
**What it demonstrates:** **What it demonstrates:**
- Dependency injection with PSR-11 containers - Dependency injection with PSR-11 containers
@ -145,7 +144,7 @@ $server = Server::builder()
### Cached Discovery ### Cached Discovery
**File**: `examples/stdio-cached-discovery/` **File**: `examples/cached-discovery/`
**What it demonstrates:** **What it demonstrates:**
- Discovery caching for improved performance - Discovery caching for improved performance
@ -165,7 +164,7 @@ $server = Server::builder()
### Client Communication ### Client Communication
**File**: `examples/stdio-client-communication/` **File**: `examples/client-communication/`
**What it demostrates:** **What it demostrates:**
- Server initiated communcation back to the client - 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 service class via `ClientAwareInterface` and corresponding trait
- Using `ClientGateway` in tool method via method argument injection - Using `ClientGateway` in tool method via method argument injection
## HTTP Examples
### Discovery User Profile ### Discovery User Profile
**File**: `examples/http-discovery-userprofile/` **File**: `examples/discovery-userprofile/`
**What it demonstrates:** **What it demonstrates:**
- HTTP transport with StreamableHttpTransport - HTTP transport with StreamableHttpTransport
@ -205,7 +202,7 @@ public function generateBio(string $userId, string $tone = 'professional'): arra
**Usage:** **Usage:**
```bash ```bash
# Start the HTTP server # 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 # Test with MCP Inspector
npx @modelcontextprotocol/inspector http://localhost:8000 npx @modelcontextprotocol/inspector http://localhost:8000
@ -215,7 +212,7 @@ npx @modelcontextprotocol/inspector http://localhost:8000
### Combined Registration ### Combined Registration
**File**: `examples/http-combined-registration/` **File**: `examples/combined-registration/`
**What it demonstrates:** **What it demonstrates:**
- Mixing attribute discovery with manual registration - Mixing attribute discovery with manual registration
@ -232,7 +229,7 @@ $server = Server::builder()
### Complex Tool Schema ### Complex Tool Schema
**File**: `examples/http-complex-tool-schema/` **File**: `examples/complex-tool-schema/`
**What it demonstrates:** **What it demonstrates:**
- Advanced JSON schema definitions - Advanced JSON schema definitions
@ -255,7 +252,7 @@ public function scheduleEvent(array $eventData): array
### Schema Showcase ### Schema Showcase
**File**: `examples/http-schema-showcase/` **File**: `examples/schema-showcase/`
**What it demonstrates:** **What it demonstrates:**
- Comprehensive JSON schema features - Comprehensive JSON schema features

View file

@ -3,14 +3,15 @@
This directory contains various examples of how to use the PHP MCP SDK. 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. 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: For running an example, you execute the `server.php` like this:
```bash ```bash
# For examples using STDIO transport # For using the STDIO transport:
php examples/stdio-discovery-calculator/server.php php examples/discovery-calculator/server.php
# For examples using Streamable HTTP transport # For using the Streamable HTTP transport:
php -S localhost:8000 examples/http-discovery-userprofile/server.php php -S localhost:8000 examples/discovery-userprofile/server.php
``` ```
You will see debug outputs to help you understand what is happening. 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: Run with Inspector:
```bash ```bash
npx @modelcontextprotocol/inspector php examples/stdio-discovery-calculator/server.php npx @modelcontextprotocol/inspector php examples/discovery-calculator/server.php
``` ```
## Debugging ## Debugging
@ -29,5 +30,5 @@ directory.
With the Inspector you can set the environment variables like this: With the Inspector you can set the environment variables like this:
```bash ```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. * file that was distributed with this source code.
*/ */
use Http\Discovery\Psr17Factory;
use Laminas\HttpHandlerRunner\Emitter\SapiEmitter;
use Mcp\Capability\Registry\Container; 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\AbstractLogger;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
@ -21,6 +27,31 @@ set_exception_handler(function (Throwable $t): never {
exit(1); 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 function logger(): LoggerInterface
{ {
return new class extends AbstractLogger { 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