Adopt server examples

This commit is contained in:
Christopher Hertel 2025-08-17 02:46:01 +02:00
commit 2ef051a355
No known key found for this signature in database
GPG key ID: 111F6BFF7B6B5AA3
34 changed files with 3639 additions and 756 deletions

View file

@ -0,0 +1,149 @@
<?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\StdioCalculatorExample;
use Mcp\Capability\Attribute\McpResource;
use Mcp\Capability\Attribute\McpTool;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
/**
* @phpstan-type Config array{precision: int, allow_negative: bool}
*/
class McpElements
{
/**
* @var Config
*/
private array $config = [
'precision' => 2,
'allow_negative' => true,
];
public function __construct(
private readonly LoggerInterface $logger = new NullLogger(),
) {
}
/**
* Performs a calculation based on the operation.
*
* Supports 'add', 'subtract', 'multiply', 'divide'.
* Obeys the 'precision' and 'allow_negative' settings from the config resource.
*
* @param float $a the first operand
* @param float $b the second operand
* @param string $operation the operation ('add', 'subtract', 'multiply', 'divide')
*
* @return float|string the result of the calculation, or an error message string
*/
#[McpTool(name: 'calculate')]
public function calculate(float $a, float $b, string $operation): float|string
{
$this->logger->info(\sprintf('Calculating: %f %s %f', $a, $operation, $b));
$op = strtolower($operation);
switch ($op) {
case 'add':
$result = $a + $b;
break;
case 'subtract':
$result = $a - $b;
break;
case 'multiply':
$result = $a * $b;
break;
case 'divide':
if (0 == $b) {
return 'Error: Division by zero.';
}
$result = $a / $b;
break;
default:
return "Error: Unknown operation '{$operation}'. Supported: add, subtract, multiply, divide.";
}
if (!$this->config['allow_negative'] && $result < 0) {
return 'Error: Negative results are disabled.';
}
return round($result, $this->config['precision']);
}
/**
* Provides the current calculator configuration.
* Can be read by clients to understand precision etc.
*
* @return Config the configuration array
*/
#[McpResource(
uri: 'config://calculator/settings',
name: 'calculator_config',
description: 'Current settings for the calculator tool (precision, allow_negative).',
mimeType: 'application/json',
)]
public function getConfiguration(): array
{
$this->logger->info('Resource config://calculator/settings read.');
return $this->config;
}
/**
* Updates a specific configuration setting.
* Note: This requires more robust validation in a real app.
*
* @param string $setting the setting key ('precision' or 'allow_negative')
* @param mixed $value the new value (int for precision, bool for allow_negative)
*
* @return array{
* success: bool,
* error?: string,
* message?: string
* } success message or error
*/
#[McpTool(name: 'update_setting')]
public function updateSetting(string $setting, mixed $value): array
{
$this->logger->info(\sprintf('Setting tool called: setting=%s, value=%s', $setting, var_export($value, true)));
if (!\array_key_exists($setting, $this->config)) {
return ['success' => false, 'error' => "Unknown setting '{$setting}'."];
}
if ('precision' === $setting) {
if (!\is_int($value) || $value < 0 || $value > 10) {
return ['success' => false, 'error' => 'Invalid precision value. Must be integer between 0 and 10.'];
}
$this->config['precision'] = $value;
// In real app, notify subscribers of config://calculator/settings change
// $registry->notifyResourceChanged('config://calculator/settings');
return ['success' => true, 'message' => "Precision updated to {$value}."];
}
if (!\is_bool($value)) {
// Attempt basic cast for flexibility
if (\in_array(strtolower((string) $value), ['true', '1', 'yes', 'on'])) {
$value = true;
} elseif (\in_array(strtolower((string) $value), ['false', '0', 'no', 'off'])) {
$value = false;
} else {
return ['success' => false, 'error' => 'Invalid allow_negative value. Must be boolean (true/false).'];
}
}
$this->config['allow_negative'] = $value;
// $registry->notifyResourceChanged('config://calculator/settings');
return ['success' => true, 'message' => 'Allow negative results set to '.($value ? 'true' : 'false').'.'];
}
}

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\Transport\StdioTransport;
logger()->info('Starting MCP Stdio Calculator Server...');
Server::make()
->withServerInfo('Stdio Calculator', '1.1.0', 'Basic Calculator over STDIO transport.')
->withContainer(container())
->withLogger(logger())
->withDiscovery(__DIR__, ['.'])
->build()
->connect(new StdioTransport(logger: logger()));
logger()->info('Server listener stopped gracefully.');

View file

@ -0,0 +1,141 @@
<?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\HttpUserProfileExample;
use Mcp\Capability\Attribute\CompletionProvider;
use Mcp\Capability\Attribute\McpPrompt;
use Mcp\Capability\Attribute\McpResource;
use Mcp\Capability\Attribute\McpResourceTemplate;
use Mcp\Capability\Attribute\McpTool;
use Psr\Log\LoggerInterface;
class McpElements
{
// Simulate a simple user database
private array $users = [
'101' => ['name' => 'Alice', 'email' => 'alice@example.com', 'role' => 'admin'],
'102' => ['name' => 'Bob', 'email' => 'bob@example.com', 'role' => 'user'],
'103' => ['name' => 'Charlie', 'email' => 'charlie@example.com', 'role' => 'user'],
];
public function __construct(
private LoggerInterface $logger,
) {
$this->logger->debug('HttpUserProfileExample McpElements instantiated.');
}
/**
* Retrieves the profile data for a specific user.
*
* @param string $userId the ID of the user (from URI)
*
* @return array user profile data
*
* @throws McpServerException if the user is not found
*/
#[McpResourceTemplate(
uriTemplate: 'user://{userId}/profile',
name: 'user_profile',
description: 'Get profile information for a specific user ID.',
mimeType: 'application/json'
)]
public function getUserProfile(
#[CompletionProvider(values: ['101', '102', '103'])]
string $userId,
): array {
$this->logger->info('Reading resource: user profile', ['userId' => $userId]);
if (!isset($this->users[$userId])) {
// Throwing an exception that Processor can turn into an error response
throw McpServerException::invalidParams("User profile not found for ID: {$userId}");
}
return $this->users[$userId];
}
/**
* Retrieves a list of all known user IDs.
*
* @return array list of user IDs
*/
#[McpResource(
uri: 'user://list/ids',
name: 'user_id_list',
description: 'Provides a list of all available user IDs.',
mimeType: 'application/json'
)]
public function listUserIds(): array
{
$this->logger->info('Reading resource: user ID list');
return array_keys($this->users);
}
/**
* Sends a welcome message to a user.
* (This is a placeholder - in a real app, it might queue an email).
*
* @param string $userId the ID of the user to message
* @param string|null $customMessage an optional custom message part
*
* @return array status of the operation
*/
#[McpTool(name: 'send_welcome')]
public function sendWelcomeMessage(string $userId, ?string $customMessage = null): array
{
$this->logger->info('Executing tool: send_welcome', ['userId' => $userId]);
if (!isset($this->users[$userId])) {
return ['success' => false, 'error' => "User ID {$userId} not found."];
}
$user = $this->users[$userId];
$message = "Welcome, {$user['name']}!";
if ($customMessage) {
$message .= ' '.$customMessage;
}
// Simulate sending
$this->logger->info("Simulated sending message to {$user['email']}: {$message}");
return ['success' => true, 'message_sent' => $message];
}
#[McpTool(name: 'test_tool_without_params')]
public function testToolWithoutParams(): array
{
return ['success' => true, 'message' => 'Test tool without params'];
}
/**
* Generates a prompt to write a bio for a user.
*
* @param string $userId the user ID to generate the bio for
* @param string $tone Desired tone (e.g., 'formal', 'casual').
*
* @return array prompt messages
*
* @throws McpServerException if user not found
*/
#[McpPrompt(name: 'generate_bio_prompt')]
public function generateBio(
#[CompletionProvider(provider: UserIdCompletionProvider::class)]
string $userId,
string $tone = 'professional',
): array {
$this->logger->info('Executing prompt: generate_bio', ['userId' => $userId, 'tone' => $tone]);
if (!isset($this->users[$userId])) {
throw McpServerException::invalidParams("User not found for bio prompt: {$userId}");
}
$user = $this->users[$userId];
return [
['role' => 'user', 'content' => "Write a short, {$tone} biography for {$user['name']} (Role: {$user['role']}, Email: {$user['email']}). Highlight their role within the system."],
];
}
}

View file

@ -0,0 +1,24 @@
<?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\HttpUserProfileExample;
use Mcp\Capability\Prompt\Completion\ProviderInterface;
class UserIdCompletionProvider implements ProviderInterface
{
public function getCompletions(string $currentValue): array
{
$availableUserIds = ['101', '102', '103'];
return array_filter($availableUserIds, fn (string $userId) => str_contains($userId, $currentValue));
}
}

View file

@ -0,0 +1,76 @@
#!/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\Capability\Registry\Container;
use Mcp\Server;
use Psr\Log\LoggerInterface;
logger()->info('Starting MCP HTTP User Profile Server...');
// --- Setup DI Container for DI in McpElements class ---
$container = new Container();
$container->set(LoggerInterface::class, logger());
Server::make()
->withServerInfo('HTTP User Profiles', '1.0.0')
->withLogger(logger())
->withContainer($container)
->withDiscovery(__DIR__, ['.'])
->withTool(
function (float $a, float $b, string $operation = 'add'): array {
$result = match ($operation) {
'add' => $a + $b,
'subtract' => $a - $b,
'multiply' => $a * $b,
'divide' => 0 != $b ? $a / $b : throw new InvalidArgumentException('Cannot divide by zero'),
default => throw new InvalidArgumentException("Unknown operation: {$operation}"),
};
return [
'operation' => $operation,
'operands' => [$a, $b],
'result' => $result,
];
},
name: 'calculator',
description: 'Perform basic math operations (add, subtract, multiply, divide)'
)
->withResource(
function (): array {
$memoryUsage = memory_get_usage(true);
$memoryPeak = memory_get_peak_usage(true);
$uptime = time() - $_SERVER['REQUEST_TIME_FLOAT'] ?? time();
$serverSoftware = $_SERVER['SERVER_SOFTWARE'] ?? 'CLI';
return [
'server_time' => date('Y-m-d H:i:s'),
'uptime_seconds' => $uptime,
'memory_usage_mb' => round($memoryUsage / 1024 / 1024, 2),
'memory_peak_mb' => round($memoryPeak / 1024 / 1024, 2),
'php_version' => \PHP_VERSION,
'server_software' => $serverSoftware,
'operating_system' => \PHP_OS_FAMILY,
'status' => 'healthy',
];
},
uri: 'system://status',
name: 'system_status',
description: 'Current system status and runtime information',
mimeType: 'application/json'
)
->build()
->connect(new StreamableHttpServerTransport('127.0.0.1', 8080, 'mcp'));
logger()->info('Server listener stopped gracefully.');

View file

@ -0,0 +1,81 @@
<?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\ManualStdioExample;
use Psr\Log\LoggerInterface;
class SimpleHandlers
{
private string $appVersion = '1.0-manual';
public function __construct(
private readonly LoggerInterface $logger,
) {
$this->logger->info('SimpleHandlers instantiated for manual registration example.');
}
/**
* A manually registered tool to echo input.
*
* @param string $text the text to echo
*
* @return string the echoed text
*/
public function echoText(string $text): string
{
$this->logger->info("Manual tool 'echo_text' called.", ['text' => $text]);
return 'Echo: '.$text;
}
/**
* A manually registered resource providing app version.
*
* @return string the application version
*/
public function getAppVersion(): string
{
$this->logger->info("Manual resource 'app://version' read.");
return $this->appVersion;
}
/**
* A manually registered prompt template.
*
* @param string $userName the name of the user
*
* @return array the prompt messages
*/
public function greetingPrompt(string $userName): array
{
$this->logger->info("Manual prompt 'personalized_greeting' called.", ['userName' => $userName]);
return [
['role' => 'user', 'content' => "Craft a personalized greeting for {$userName}."],
];
}
/**
* A manually registered resource template.
*
* @param string $itemId the ID of the item
*
* @return array item details
*/
public function getItemDetails(string $itemId): array
{
$this->logger->info("Manual template 'item://{itemId}' resolved.", ['itemId' => $itemId]);
return ['id' => $itemId, 'name' => "Item {$itemId}", 'description' => "Details for item {$itemId} from manual template."];
}
}

View file

@ -0,0 +1,33 @@
#!/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\ManualStdioExample\SimpleHandlers;
use Mcp\Server;
use Mcp\Server\Transport\StdioTransport;
logger()->info('Starting MCP Manual Registration (Stdio) Server...');
Server::make()
->withServerInfo('Manual Reg Server', '1.0.0')
->withLogger(logger())
->withContainer(container())
->withTool([SimpleHandlers::class, 'echoText'], 'echo_text')
->withResource([SimpleHandlers::class, 'getAppVersion'], 'app://version', 'application_version', mimeType: 'text/plain')
->withPrompt([SimpleHandlers::class, 'greetingPrompt'], 'personalized_greeting')
->withResourceTemplate([SimpleHandlers::class, 'getItemDetails'], 'item://{itemId}/details', 'get_item_details', mimeType: 'application/json')
->build()
->connect(new StdioTransport(logger: logger()));
logger()->info('Server listener stopped gracefully.');

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\CombinedHttpExample;
use Mcp\Capability\Attribute\McpResource;
use Mcp\Capability\Attribute\McpTool;
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\CombinedHttpExample;
use Psr\Log\LoggerInterface;
class ManualHandlers
{
public function __construct(
private 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\CombinedHttpExample\Manual\ManualHandlers;
use Mcp\Server;
use Mcp\Server\Transports\HttpServerTransport;
logger()->info('Starting MCP Combined Registration (HTTP) Server...');
Server::make()
->withServerInfo('Combined HTTP Server', '1.0.0')
->withLogger(logger())
->withContainer(container())
->withDiscovery(__DIR__, ['.'])
->withTool([ManualHandlers::class, 'manualGreeter'])
->withResource(
[ManualHandlers::class, 'getPriorityConfigManual'],
'config://priority',
'priority_config_manual',
)
->build()
->connect(new HttpServerTransport('127.0.0.1', 8081, 'mcp_combined'));
logger()->info('Server listener stopped gracefully.');

View file

@ -0,0 +1,51 @@
<?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\StdioEnvVariables;
use Mcp\Capability\Attribute\McpTool;
class EnvToolHandler
{
/**
* Performs an action that can be modified by an environment variable.
* The MCP client should set 'APP_MODE' in its 'env' config for this server.
*
* @param string $input some input data
*
* @return array the result, varying by APP_MODE
*/
#[McpTool(name: 'process_data_by_mode')]
public function processData(string $input): array
{
$appMode = getenv('APP_MODE'); // Read from environment
if ('debug' === $appMode) {
return [
'mode' => 'debug',
'processed_input' => strtoupper($input),
'message' => 'Processed in DEBUG mode.',
];
} elseif ('production' === $appMode) {
return [
'mode' => 'production',
'processed_input_length' => \strlen($input),
'message' => 'Processed in PRODUCTION mode (summary only).',
];
} else {
return [
'mode' => $appMode ?: 'default',
'original_input' => $input,
'message' => 'Processed in default mode (APP_MODE not recognized or not set).',
];
}
}
}

View file

@ -0,0 +1,59 @@
#!/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\Transport\StdioTransport;
/*
|--------------------------------------------------------------------------
| MCP Stdio Environment Variable Example Server
|--------------------------------------------------------------------------
|
| This server demonstrates how to use environment variables to modify tool
| behavior. The MCP client can set the APP_MODE environment variable to
| control the server's behavior.
|
| Configure your MCP Client (eg. Cursor) for this server like this:
|
| {
| "mcpServers": {
| "my-php-env-server": {
| "command": "php",
| "args": ["/full/path/to/examples/05-stdio-env-variables/server.php"],
| "env": {
| "APP_MODE": "debug" // or "production", or leave it out
| }
| }
| }
| }
|
| The server will read the APP_MODE environment variable and use it to
| modify the behavior of the tools.
|
| If the APP_MODE environment variable is not set, the server will use the
| default behavior.
|
*/
logger()->info('Starting MCP Stdio Environment Variable Example Server...');
Server::make()
->withServerInfo('Env Var Server', '1.0.0')
->withLogger(logger())
->withDiscovery(__DIR__, ['.'])
->build()
->connect(new StdioTransport(logger: logger()));
logger()->info('Server listener stopped gracefully.');

View file

@ -0,0 +1,89 @@
<?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\DependenciesStdioExample;
use Mcp\Capability\Attribute\McpResource;
use Mcp\Capability\Attribute\McpTool;
use Mcp\DependenciesStdioExample\Services\StatsServiceInterface;
use Mcp\DependenciesStdioExample\Services\TaskRepositoryInterface;
use Psr\Log\LoggerInterface;
class McpTaskHandlers
{
public function __construct(
private TaskRepositoryInterface $taskRepo,
private StatsServiceInterface $statsService,
private 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 array 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 array 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 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 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,69 @@
<?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\DependenciesStdioExample\Service;
use Psr\Log\LoggerInterface;
class InMemoryTaskRepository implements TaskRepositoryInterface
{
private array $tasks = [];
private int $nextTaskId = 1;
private LoggerInterface $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $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,17 @@
<?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\DependenciesStdioExample\Service;
interface StatsServiceInterface
{
public function getSystemStats(): array;
}

View file

@ -0,0 +1,36 @@
<?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\DependenciesStdioExample\Service;
class SystemStatsService implements StatsServiceInterface
{
private TaskRepositoryInterface $taskRepository;
public function __construct(TaskRepositoryInterface $taskRepository)
{
$this->taskRepository = $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,23 @@
<?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\DependenciesStdioExample\Service;
interface TaskRepositoryInterface
{
public function addTask(string $userId, string $description): array;
public function getTasksForUser(string $userId): array;
public function getAllTasks(): array;
public function completeTask(int $taskId): bool;
}

View file

@ -0,0 +1,38 @@
#!/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\DependenciesStdioExample\Services;
use Mcp\Server;
use Mcp\Server\Transport\StdioTransport;
logger()->info('Starting MCP Custom Dependencies (Stdio) Server...');
$container = container();
$taskRepo = new Services\InMemoryTaskRepository(logger());
$container->set(Services\TaskRepositoryInterface::class, $taskRepo);
$statsService = new Services\SystemStatsService($taskRepo);
$container->set(Services\StatsServiceInterface::class, $statsService);
Server::make()
->withServerInfo('Task Manager Server', '1.0.0')
->withLogger(logger())
->withContainer($container)
->withDiscovery(__DIR__, ['.'])
->build()
->connect(new StdioTransport(logger: logger()));
logger()->info('Server listener stopped gracefully.');

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\ComplexSchemaHttpExample;
use Mcp\Capability\Attribute\McpTool;
use Mcp\ComplexSchemaHttpExample\Model\EventPriority;
use Mcp\ComplexSchemaHttpExample\Model\EventType;
use Psr\Log\LoggerInterface;
class McpEventScheduler
{
public function __construct(
private 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 confirmation of the scheduled event
*/
#[McpTool(name: 'schedule_event')]
public function scheduleEvent(
string $title,
string $date,
EventType $type,
?string $time = null, // Optional, nullable
EventPriority $priority = EventPriority::Normal, // Optional with enum default
?array $attendees = null, // Optional array of strings, nullable
bool $sendInvites = true, // Optional with default
): 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' => "Event '{$title}' scheduled successfully for {$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\ComplexSchemaHttpExample\Model;
enum EventPriority: int
{
case Low = 0;
case Normal = 1;
case High = 2;
}

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