Rename example folders (#87)
This commit is contained in:
parent
cecfa0c3c6
commit
345b94dfbb
30 changed files with 32 additions and 32 deletions
|
|
@ -51,15 +51,15 @@
|
|||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Mcp\\Example\\StdioCalculatorExample\\": "examples/01-discovery-stdio-calculator/",
|
||||
"Mcp\\Example\\HttpUserProfileExample\\": "examples/02-discovery-http-userprofile/",
|
||||
"Mcp\\Example\\ManualStdioExample\\": "examples/03-manual-registration-stdio/",
|
||||
"Mcp\\Example\\CombinedHttpExample\\": "examples/04-combined-registration-http/",
|
||||
"Mcp\\Example\\StdioEnvVariables\\": "examples/05-stdio-env-variables/",
|
||||
"Mcp\\Example\\DependenciesStdioExample\\": "examples/06-custom-dependencies-stdio/",
|
||||
"Mcp\\Example\\ComplexSchemaHttpExample\\": "examples/07-complex-tool-schema-http/",
|
||||
"Mcp\\Example\\SchemaShowcaseExample\\": "examples/08-schema-showcase-streamable/",
|
||||
"Mcp\\Example\\CachedDiscoveryExample\\": "examples/09-cached-discovery-stdio/",
|
||||
"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\\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\\Tests\\": "tests/"
|
||||
}
|
||||
},
|
||||
|
|
|
|||
41
examples/http-combined-registration/DiscoveredElements.php
Normal file
41
examples/http-combined-registration/DiscoveredElements.php
Normal 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\HttpCombinedRegistration;
|
||||
|
||||
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';
|
||||
}
|
||||
}
|
||||
48
examples/http-combined-registration/ManualHandlers.php
Normal file
48
examples/http-combined-registration/ManualHandlers.php
Normal 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\HttpCombinedRegistration;
|
||||
|
||||
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)';
|
||||
}
|
||||
}
|
||||
49
examples/http-combined-registration/server.php
Normal file
49
examples/http-combined-registration/server.php
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
#!/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 Laminas\HttpHandlerRunner\Emitter\SapiEmitter;
|
||||
use Mcp\Example\HttpCombinedRegistration\ManualHandlers;
|
||||
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::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();
|
||||
|
||||
$transport = new StreamableHttpTransport($request, $psr17Factory, $psr17Factory);
|
||||
|
||||
$server->connect($transport);
|
||||
|
||||
$response = $transport->listen();
|
||||
|
||||
(new SapiEmitter())->emit($response);
|
||||
72
examples/http-complex-tool-schema/McpEventScheduler.php
Normal file
72
examples/http-complex-tool-schema/McpEventScheduler.php
Normal 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\HttpComplexToolSchema;
|
||||
|
||||
use Mcp\Capability\Attribute\McpTool;
|
||||
use Mcp\Example\HttpComplexToolSchema\Model\EventPriority;
|
||||
use Mcp\Example\HttpComplexToolSchema\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,
|
||||
];
|
||||
}
|
||||
}
|
||||
19
examples/http-complex-tool-schema/Model/EventPriority.php
Normal file
19
examples/http-complex-tool-schema/Model/EventPriority.php
Normal 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\HttpComplexToolSchema\Model;
|
||||
|
||||
enum EventPriority: int
|
||||
{
|
||||
case Low = 0;
|
||||
case Normal = 1;
|
||||
case High = 2;
|
||||
}
|
||||
20
examples/http-complex-tool-schema/Model/EventType.php
Normal file
20
examples/http-complex-tool-schema/Model/EventType.php
Normal 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\HttpComplexToolSchema\Model;
|
||||
|
||||
enum EventType: string
|
||||
{
|
||||
case Meeting = 'meeting';
|
||||
case Reminder = 'reminder';
|
||||
case Call = 'call';
|
||||
case Other = 'other';
|
||||
}
|
||||
42
examples/http-complex-tool-schema/server.php
Normal file
42
examples/http-complex-tool-schema/server.php
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
#!/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 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::builder()
|
||||
->setServerInfo('Event Scheduler Server', '1.0.0')
|
||||
->setLogger(logger())
|
||||
->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);
|
||||
151
examples/http-discovery-userprofile/McpElements.php
Normal file
151
examples/http-discovery-userprofile/McpElements.php
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
<?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\HttpDiscoveryUserProfile;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* @phpstan-type User array{name: string, email: string, role: string}
|
||||
*/
|
||||
final class McpElements
|
||||
{
|
||||
/**
|
||||
* Simulate a simple user database.
|
||||
*
|
||||
* @var array<int, User>
|
||||
*/
|
||||
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 readonly LoggerInterface $logger,
|
||||
) {
|
||||
$this->logger->debug('HttpDiscoveryUserProfile McpElements instantiated.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the profile data for a specific user.
|
||||
*
|
||||
* @param string $userId the ID of the user (from URI)
|
||||
*
|
||||
* @return User 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 int[] 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<string, bool|string> 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];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, bool|string>
|
||||
*/
|
||||
#[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<string, string>[] 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."],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -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\HttpDiscoveryUserProfile;
|
||||
|
||||
use Mcp\Capability\Prompt\Completion\ProviderInterface;
|
||||
|
||||
final class UserIdCompletionProvider implements ProviderInterface
|
||||
{
|
||||
private const AVAILABLE_USER_IDS = ['101', '102', '103'];
|
||||
|
||||
public function getCompletions(string $currentValue): array
|
||||
{
|
||||
return array_filter(self::AVAILABLE_USER_IDS, fn (string $userId) => str_contains($userId, $currentValue));
|
||||
}
|
||||
}
|
||||
84
examples/http-discovery-userprofile/server.php
Normal file
84
examples/http-discovery-userprofile/server.php
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
#!/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 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::builder()
|
||||
->setServerInfo('HTTP User Profiles', '1.0.0')
|
||||
->setLogger(logger())
|
||||
->setContainer(container())
|
||||
->setSession(new FileSessionStore(__DIR__.'/sessions'))
|
||||
->setDiscovery(__DIR__, ['.'])
|
||||
->addTool(
|
||||
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)'
|
||||
)
|
||||
->addResource(
|
||||
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();
|
||||
|
||||
$transport = new StreamableHttpTransport($request, $psr17Factory, $psr17Factory);
|
||||
|
||||
$server->connect($transport);
|
||||
|
||||
$response = $transport->listen();
|
||||
|
||||
(new SapiEmitter())->emit($response);
|
||||
465
examples/http-schema-showcase/SchemaShowcaseElements.php
Normal file
465
examples/http-schema-showcase/SchemaShowcaseElements.php
Normal file
|
|
@ -0,0 +1,465 @@
|
|||
<?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\HttpSchemaShowcase;
|
||||
|
||||
use Mcp\Capability\Attribute\McpTool;
|
||||
use Mcp\Capability\Attribute\Schema;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
final class SchemaShowcaseElements
|
||||
{
|
||||
public function __construct(
|
||||
private readonly LoggerInterface $logger,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates and formats text with string constraints.
|
||||
* Demonstrates: minLength, maxLength, pattern validation.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
#[McpTool(
|
||||
name: 'format_text',
|
||||
description: 'Formats text with validation constraints. Text must be 5-100 characters and contain only letters, numbers, spaces, and basic punctuation.'
|
||||
)]
|
||||
public function formatText(
|
||||
#[Schema(
|
||||
type: 'string',
|
||||
description: 'The text to format',
|
||||
minLength: 5,
|
||||
maxLength: 100,
|
||||
pattern: '^[a-zA-Z0-9\s\.,!?\-]+$'
|
||||
)]
|
||||
string $text,
|
||||
|
||||
#[Schema(
|
||||
type: 'string',
|
||||
description: 'Format style',
|
||||
enum: ['uppercase', 'lowercase', 'title', 'sentence']
|
||||
)]
|
||||
string $format = 'sentence',
|
||||
): array {
|
||||
$this->logger->info(\sprintf('Tool format_text called with text: %s and format: %s', $text, $format));
|
||||
|
||||
$formatted = match ($format) {
|
||||
'uppercase' => strtoupper($text),
|
||||
'lowercase' => strtolower($text),
|
||||
'title' => ucwords(strtolower($text)),
|
||||
'sentence' => ucfirst(strtolower($text)),
|
||||
default => $text,
|
||||
};
|
||||
|
||||
return [
|
||||
'original' => $text,
|
||||
'formatted' => $formatted,
|
||||
'length' => \strlen($text),
|
||||
'format_applied' => $format,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs mathematical operations with numeric constraints.
|
||||
*
|
||||
* Demonstrates: METHOD-LEVEL Schema
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
#[McpTool(name: 'calculate_range')]
|
||||
#[Schema(
|
||||
type: 'object',
|
||||
properties: [
|
||||
'first' => [
|
||||
'type' => 'number',
|
||||
'description' => 'First number (must be between 0 and 1000)',
|
||||
'minimum' => 0,
|
||||
'maximum' => 1000,
|
||||
],
|
||||
'second' => [
|
||||
'type' => 'number',
|
||||
'description' => 'Second number (must be between 0 and 1000)',
|
||||
'minimum' => 0,
|
||||
'maximum' => 1000,
|
||||
],
|
||||
'operation' => [
|
||||
'type' => 'string',
|
||||
'description' => 'Operation to perform',
|
||||
'enum' => ['add', 'subtract', 'multiply', 'divide', 'power'],
|
||||
],
|
||||
'precision' => [
|
||||
'type' => 'integer',
|
||||
'description' => 'Decimal precision (must be multiple of 2, between 0-10)',
|
||||
'minimum' => 0,
|
||||
'maximum' => 10,
|
||||
'multipleOf' => 2,
|
||||
],
|
||||
],
|
||||
required: ['first', 'second', 'operation'],
|
||||
)]
|
||||
public function calculateRange(float $first, float $second, string $operation, int $precision = 2): array
|
||||
{
|
||||
$this->logger->info(\sprintf('Tool calculate_range called with: %f %s %f (precision: %d)', $first, $operation, $second, $precision));
|
||||
|
||||
$result = match ($operation) {
|
||||
'add' => $first + $second,
|
||||
'subtract' => $first - $second,
|
||||
'multiply' => $first * $second,
|
||||
'divide' => 0 != $second ? $first / $second : null,
|
||||
'power' => $first ** $second,
|
||||
default => null,
|
||||
};
|
||||
|
||||
if (null === $result) {
|
||||
return [
|
||||
'error' => 'divide' === $operation ? 'Division by zero' : 'Invalid operation',
|
||||
'inputs' => compact('first', 'second', 'operation', 'precision'),
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'result' => round($result, $precision),
|
||||
'operation' => "$first $operation $second",
|
||||
'precision' => $precision,
|
||||
'within_bounds' => $result >= 0 && $result <= 1000000,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes user profile data with object schema validation.
|
||||
* Demonstrates: object properties, required fields, additionalProperties.
|
||||
*
|
||||
* @param array<string, mixed> $profile
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
#[McpTool(
|
||||
name: 'validate_profile',
|
||||
description: 'Validates and processes user profile data with strict schema requirements.'
|
||||
)]
|
||||
public function validateProfile(
|
||||
#[Schema(
|
||||
type: 'object',
|
||||
description: 'User profile information',
|
||||
properties: [
|
||||
'name' => [
|
||||
'type' => 'string',
|
||||
'minLength' => 2,
|
||||
'maxLength' => 50,
|
||||
'description' => 'Full name',
|
||||
],
|
||||
'email' => [
|
||||
'type' => 'string',
|
||||
'format' => 'email',
|
||||
'description' => 'Valid email address',
|
||||
],
|
||||
'age' => [
|
||||
'type' => 'integer',
|
||||
'minimum' => 13,
|
||||
'maximum' => 120,
|
||||
'description' => 'Age in years',
|
||||
],
|
||||
'role' => [
|
||||
'type' => 'string',
|
||||
'enum' => ['user', 'admin', 'moderator', 'guest'],
|
||||
'description' => 'User role',
|
||||
],
|
||||
'preferences' => [
|
||||
'type' => 'object',
|
||||
'properties' => [
|
||||
'notifications' => ['type' => 'boolean'],
|
||||
'theme' => ['type' => 'string', 'enum' => ['light', 'dark', 'auto']],
|
||||
],
|
||||
'additionalProperties' => false,
|
||||
],
|
||||
],
|
||||
required: ['name', 'email', 'age'],
|
||||
additionalProperties: true
|
||||
)]
|
||||
array $profile,
|
||||
): array {
|
||||
$this->logger->info(\sprintf('Tool validate_profile called: %s', json_encode($profile)));
|
||||
|
||||
$errors = [];
|
||||
$warnings = [];
|
||||
|
||||
// Additional business logic validation
|
||||
if (isset($profile['age']) && $profile['age'] < 18 && ($profile['role'] ?? 'user') === 'admin') {
|
||||
$errors[] = 'Admin role requires age 18 or older';
|
||||
}
|
||||
|
||||
if (isset($profile['email']) && !filter_var($profile['email'], \FILTER_VALIDATE_EMAIL)) {
|
||||
$errors[] = 'Invalid email format';
|
||||
}
|
||||
|
||||
if (!isset($profile['role'])) {
|
||||
$warnings[] = 'No role specified, defaulting to "user"';
|
||||
$profile['role'] = 'user';
|
||||
}
|
||||
|
||||
return [
|
||||
'valid' => empty($errors),
|
||||
'profile' => $profile,
|
||||
'errors' => $errors,
|
||||
'warnings' => $warnings,
|
||||
'processed_at' => date('Y-m-d H:i:s'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages a list of items with array constraints.
|
||||
* Demonstrates: array items, minItems, maxItems, uniqueItems.
|
||||
*
|
||||
* @param string[] $items
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
#[McpTool(
|
||||
name: 'manage_list',
|
||||
description: 'Manages a list of items with size and uniqueness constraints.'
|
||||
)]
|
||||
public function manageList(
|
||||
#[Schema(
|
||||
type: 'array',
|
||||
description: 'List of items to manage (2-10 unique strings)',
|
||||
items: [
|
||||
'type' => 'string',
|
||||
'minLength' => 1,
|
||||
'maxLength' => 30,
|
||||
],
|
||||
minItems: 2,
|
||||
maxItems: 10,
|
||||
uniqueItems: true
|
||||
)]
|
||||
array $items,
|
||||
|
||||
#[Schema(
|
||||
type: 'string',
|
||||
description: 'Action to perform on the list',
|
||||
enum: ['sort', 'reverse', 'shuffle', 'deduplicate', 'filter_short', 'filter_long']
|
||||
)]
|
||||
string $action = 'sort',
|
||||
): array {
|
||||
$this->logger->info(\sprintf('Tool manage_list called with %d items, action: %s', \count($items), $action));
|
||||
|
||||
$original = $items;
|
||||
$processed = $items;
|
||||
|
||||
switch ($action) {
|
||||
case 'sort':
|
||||
sort($processed);
|
||||
break;
|
||||
case 'reverse':
|
||||
$processed = array_reverse($processed);
|
||||
break;
|
||||
case 'shuffle':
|
||||
shuffle($processed);
|
||||
break;
|
||||
case 'deduplicate':
|
||||
$processed = array_unique($processed);
|
||||
break;
|
||||
case 'filter_short':
|
||||
$processed = array_filter($processed, fn ($item) => \strlen($item) <= 10);
|
||||
break;
|
||||
case 'filter_long':
|
||||
$processed = array_filter($processed, fn ($item) => \strlen($item) > 10);
|
||||
break;
|
||||
}
|
||||
|
||||
return [
|
||||
'original_count' => \count($original),
|
||||
'processed_count' => \count($processed),
|
||||
'action' => $action,
|
||||
'original' => $original,
|
||||
'processed' => array_values($processed), // Re-index array
|
||||
'stats' => [
|
||||
'average_length' => \count($processed) > 0 ? round(array_sum(array_map('strlen', $processed)) / \count($processed), 2) : 0,
|
||||
'shortest' => \count($processed) > 0 ? min(array_map('strlen', $processed)) : 0,
|
||||
'longest' => \count($processed) > 0 ? max(array_map('strlen', $processed)) : 0,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates configuration with format validation.
|
||||
* Demonstrates: format constraints (date-time, uri, etc).
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
#[McpTool(
|
||||
name: 'generate_config',
|
||||
description: 'Generates configuration with format-validated inputs.'
|
||||
)]
|
||||
public function generateConfig(
|
||||
#[Schema(
|
||||
type: 'string',
|
||||
description: 'Application name (alphanumeric with hyphens)',
|
||||
minLength: 3,
|
||||
maxLength: 20,
|
||||
pattern: '^[a-zA-Z0-9\-]+$'
|
||||
)]
|
||||
string $appName,
|
||||
|
||||
#[Schema(
|
||||
type: 'string',
|
||||
description: 'Valid URL for the application',
|
||||
format: 'uri'
|
||||
)]
|
||||
string $baseUrl,
|
||||
|
||||
#[Schema(
|
||||
type: 'string',
|
||||
description: 'Environment type',
|
||||
enum: ['development', 'staging', 'production']
|
||||
)]
|
||||
string $environment = 'development',
|
||||
|
||||
#[Schema(
|
||||
type: 'boolean',
|
||||
description: 'Enable debug mode'
|
||||
)]
|
||||
bool $debug = true,
|
||||
|
||||
#[Schema(
|
||||
type: 'integer',
|
||||
description: 'Port number (1024-65535)',
|
||||
minimum: 1024,
|
||||
maximum: 65535
|
||||
)]
|
||||
int $port = 8080,
|
||||
): array {
|
||||
$this->logger->info(\sprintf('Tool generate_config called for app: %s at %s', $appName, $baseUrl));
|
||||
|
||||
$config = [
|
||||
'app' => [
|
||||
'name' => $appName,
|
||||
'env' => $environment,
|
||||
'debug' => $debug,
|
||||
'url' => $baseUrl,
|
||||
'port' => $port,
|
||||
],
|
||||
'generated_at' => date('c'), // ISO 8601 format
|
||||
'version' => '1.0.0',
|
||||
'features' => [
|
||||
'logging' => 'production' !== $environment || $debug,
|
||||
'caching' => 'production' === $environment,
|
||||
'analytics' => 'production' === $environment,
|
||||
'rate_limiting' => 'development' !== $environment,
|
||||
],
|
||||
];
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'config' => $config,
|
||||
'validation' => [
|
||||
'app_name_valid' => 1 === preg_match('/^[a-zA-Z0-9\-]+$/', $appName),
|
||||
'url_valid' => false !== filter_var($baseUrl, \FILTER_VALIDATE_URL),
|
||||
'port_in_range' => $port >= 1024 && $port <= 65535,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes time-based data with date-time format validation.
|
||||
* Demonstrates: date-time format, exclusiveMinimum, exclusiveMaximum.
|
||||
*
|
||||
* @param string[] $attendees
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
#[McpTool(
|
||||
name: 'schedule_event',
|
||||
description: 'Schedules an event with time validation and constraints.'
|
||||
)]
|
||||
public function scheduleEvent(
|
||||
#[Schema(
|
||||
type: 'string',
|
||||
description: 'Event title (3-50 characters)',
|
||||
minLength: 3,
|
||||
maxLength: 50
|
||||
)]
|
||||
string $title,
|
||||
|
||||
#[Schema(
|
||||
type: 'string',
|
||||
description: 'Event start time in ISO 8601 format',
|
||||
format: 'date-time'
|
||||
)]
|
||||
string $startTime,
|
||||
|
||||
#[Schema(
|
||||
type: 'number',
|
||||
description: 'Duration in hours (minimum 0.5, maximum 24)',
|
||||
minimum: 0.5,
|
||||
maximum: 24,
|
||||
multipleOf: 0.5
|
||||
)]
|
||||
float $durationHours,
|
||||
|
||||
#[Schema(
|
||||
type: 'string',
|
||||
description: 'Event priority level',
|
||||
enum: ['low', 'medium', 'high', 'urgent']
|
||||
)]
|
||||
string $priority = 'medium',
|
||||
|
||||
#[Schema(
|
||||
type: 'array',
|
||||
description: 'List of attendee email addresses',
|
||||
items: [
|
||||
'type' => 'string',
|
||||
'format' => 'email',
|
||||
],
|
||||
maxItems: 20
|
||||
)]
|
||||
array $attendees = [],
|
||||
): array {
|
||||
$this->logger->info(\sprintf('Tool schedule_event called: %s at %s for %.1f hours', $title, $startTime, $durationHours));
|
||||
|
||||
$start = \DateTime::createFromFormat(\DateTime::ISO8601, $startTime);
|
||||
if (!$start) {
|
||||
$start = \DateTime::createFromFormat('Y-m-d\TH:i:s\Z', $startTime);
|
||||
}
|
||||
|
||||
if (!$start) {
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => 'Invalid date-time format. Use ISO 8601 format.',
|
||||
'example' => '2024-01-15T14:30:00Z',
|
||||
];
|
||||
}
|
||||
|
||||
$end = clone $start;
|
||||
$end->add(new \DateInterval('PT'.($durationHours * 60).'M'));
|
||||
|
||||
$event = [
|
||||
'id' => uniqid('event_'),
|
||||
'title' => $title,
|
||||
'start_time' => $start->format('c'),
|
||||
'end_time' => $end->format('c'),
|
||||
'duration_hours' => $durationHours,
|
||||
'priority' => $priority,
|
||||
'attendees' => $attendees,
|
||||
'created_at' => date('c'),
|
||||
];
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'event' => $event,
|
||||
'info' => [
|
||||
'attendee_count' => \count($attendees),
|
||||
'is_all_day' => $durationHours >= 24,
|
||||
'is_future' => $start > new \DateTime(),
|
||||
'timezone_note' => 'Times are in UTC',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
42
examples/http-schema-showcase/server.php
Normal file
42
examples/http-schema-showcase/server.php
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
#!/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 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::builder()
|
||||
->setServerInfo('Schema Showcase', '1.0.0')
|
||||
->setContainer(container())
|
||||
->setLogger(logger())
|
||||
->setSession(new FileSessionStore(__DIR__.'/sessions'))
|
||||
->setDiscovery(__DIR__, ['.'])
|
||||
->build();
|
||||
|
||||
$transport = new StreamableHttpTransport($request, $psr17Factory, $psr17Factory);
|
||||
|
||||
$server->connect($transport);
|
||||
|
||||
$response = $transport->listen();
|
||||
|
||||
(new SapiEmitter())->emit($response);
|
||||
53
examples/stdio-cached-discovery/CachedCalculatorElements.php
Normal file
53
examples/stdio-cached-discovery/CachedCalculatorElements.php
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
<?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\StdioCachedDiscovery;
|
||||
|
||||
use Mcp\Capability\Attribute\McpTool;
|
||||
|
||||
/**
|
||||
* 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 \InvalidArgumentException('Division by zero is not allowed');
|
||||
}
|
||||
|
||||
return $a / $b;
|
||||
}
|
||||
|
||||
#[McpTool(name: 'power')]
|
||||
public function power(int $base, int $exponent): int
|
||||
{
|
||||
return (int) $base ** $exponent;
|
||||
}
|
||||
}
|
||||
38
examples/stdio-cached-discovery/server.php
Normal file
38
examples/stdio-cached-discovery/server.php
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
#!/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\Transport\StdioTransport;
|
||||
use Symfony\Component\Cache\Adapter\ArrayAdapter;
|
||||
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())
|
||||
->setLogger(logger())
|
||||
->setDiscovery(__DIR__, ['.'], [], new Psr16Cache(new ArrayAdapter()))
|
||||
->build();
|
||||
|
||||
$transport = new StdioTransport(logger: logger());
|
||||
|
||||
$server->connect($transport);
|
||||
|
||||
$transport->listen();
|
||||
|
||||
logger()->info('Server listener stopped gracefully.');
|
||||
92
examples/stdio-custom-dependencies/McpTaskHandlers.php
Normal file
92
examples/stdio-custom-dependencies/McpTaskHandlers.php
Normal 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\StdioCustomDependencies;
|
||||
|
||||
use Mcp\Capability\Attribute\McpResource;
|
||||
use Mcp\Capability\Attribute\McpTool;
|
||||
use Mcp\Example\DependenciesStdioExample\Service\StatsServiceInterface;
|
||||
use Mcp\Example\DependenciesStdioExample\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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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\DependenciesStdioExample\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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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\DependenciesStdioExample\Service;
|
||||
|
||||
interface StatsServiceInterface
|
||||
{
|
||||
/**
|
||||
* @return array<string, int>
|
||||
*/
|
||||
public function getSystemStats(): array;
|
||||
}
|
||||
|
|
@ -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\DependenciesStdioExample\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
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -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\DependenciesStdioExample\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
Loading…
Reference in a new issue