Skip to content

Commit b4b21c2

Browse files
committed
Adopt server examples
1 parent 7ec1800 commit b4b21c2

File tree

31 files changed

+2305
-39
lines changed

31 files changed

+2305
-39
lines changed

composer.json

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@
3232
"symfony/console": "^6.4 || ^7.0",
3333
"psr/cache": "^3.0",
3434
"php-cs-fixer/shim": "^3.84",
35-
"nyholm/nsa": "^1.3"
35+
"nyholm/nsa": "^1.3",
36+
"symfony/dependency-injection": "^6.4 || ^7.2"
3637
},
3738
"suggest": {
3839
"symfony/console": "To use SymfonyConsoleTransport for STDIO",
@@ -45,6 +46,14 @@
4546
},
4647
"autoload-dev": {
4748
"psr-4": {
49+
"Mcp\\Example\\StdioCalculatorExample\\": "examples/01-discovery-stdio-calculator/",
50+
"Mcp\\Example\\HttpUserProfileExample\\": "examples/02-discovery-http-userprofile/",
51+
"Mcp\\Example\\ManualStdioExample\\": "examples/03-manual-registration-stdio/",
52+
"Mcp\\Example\\CombinedHttpExample\\": "examples/04-combined-registration-http/",
53+
"Mcp\\Example\\StdioEnvVariables\\": "examples/05-stdio-env-variables/",
54+
"Mcp\\Example\\DependenciesStdioExample\\": "examples/06-custom-dependencies-stdio/",
55+
"Mcp\\Example\\ComplexSchemaHttpExample\\": "examples/07-complex-tool-schema-http/",
56+
"Mcp\\Example\\SchemaShowcaseExample\\": "examples/08-schema-showcase-streamable/",
4857
"Mcp\\Tests\\": "tests/"
4958
}
5059
}
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the official PHP MCP SDK.
5+
*
6+
* A collaboration between Symfony and the PHP Foundation.
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Mcp\Example\StdioCalculatorExample;
13+
14+
use Mcp\Capability\Attribute\McpResource;
15+
use Mcp\Capability\Attribute\McpTool;
16+
17+
class McpElements
18+
{
19+
private array $config = [
20+
'precision' => 2,
21+
'allow_negative' => true,
22+
];
23+
24+
/**
25+
* Performs a calculation based on the operation.
26+
*
27+
* Supports 'add', 'subtract', 'multiply', 'divide'.
28+
* Obeys the 'precision' and 'allow_negative' settings from the config resource.
29+
*
30+
* @param float $a the first operand
31+
* @param float $b the second operand
32+
* @param string $operation the operation ('add', 'subtract', 'multiply', 'divide')
33+
*
34+
* @return float|string the result of the calculation, or an error message string
35+
*/
36+
#[McpTool(name: 'calculate')]
37+
public function calculate(float $a, float $b, string $operation): float|string
38+
{
39+
// Use STDERR for logs
40+
fwrite(\STDERR, "Calculate tool called: a=$a, b=$b, op=$operation\n");
41+
42+
$op = strtolower($operation);
43+
44+
switch ($op) {
45+
case 'add':
46+
$result = $a + $b;
47+
break;
48+
case 'subtract':
49+
$result = $a - $b;
50+
break;
51+
case 'multiply':
52+
$result = $a * $b;
53+
break;
54+
case 'divide':
55+
if (0 == $b) {
56+
return 'Error: Division by zero.';
57+
}
58+
$result = $a / $b;
59+
break;
60+
default:
61+
return "Error: Unknown operation '{$operation}'. Supported: add, subtract, multiply, divide.";
62+
}
63+
64+
if (!$this->config['allow_negative'] && $result < 0) {
65+
return 'Error: Negative results are disabled.';
66+
}
67+
68+
return round($result, $this->config['precision']);
69+
}
70+
71+
/**
72+
* Provides the current calculator configuration.
73+
* Can be read by clients to understand precision etc.
74+
*
75+
* @return array the configuration array
76+
*/
77+
#[McpResource(
78+
uri: 'config://calculator/settings',
79+
name: 'calculator_config',
80+
description: 'Current settings for the calculator tool (precision, allow_negative).',
81+
mimeType: 'application/json' // Return as JSON
82+
)]
83+
public function getConfiguration(): array
84+
{
85+
fwrite(\STDERR, "Resource config://calculator/settings read.\n");
86+
87+
return $this->config;
88+
}
89+
90+
/**
91+
* Updates a specific configuration setting.
92+
* Note: This requires more robust validation in a real app.
93+
*
94+
* @param string $setting the setting key ('precision' or 'allow_negative')
95+
* @param mixed $value the new value (int for precision, bool for allow_negative)
96+
*
97+
* @return array success message or error
98+
*/
99+
#[McpTool(name: 'update_setting')]
100+
public function updateSetting(string $setting, mixed $value): array
101+
{
102+
fwrite(\STDERR, "Update Setting tool called: setting=$setting, value=".var_export($value, true)."\n");
103+
if (!\array_key_exists($setting, $this->config)) {
104+
return ['success' => false, 'error' => "Unknown setting '{$setting}'."];
105+
}
106+
107+
if ('precision' === $setting) {
108+
if (!\is_int($value) || $value < 0 || $value > 10) {
109+
return ['success' => false, 'error' => 'Invalid precision value. Must be integer between 0 and 10.'];
110+
}
111+
$this->config['precision'] = $value;
112+
113+
// In real app, notify subscribers of config://calculator/settings change
114+
// $registry->notifyResourceChanged('config://calculator/settings');
115+
return ['success' => true, 'message' => "Precision updated to {$value}."];
116+
}
117+
118+
if ('allow_negative' === $setting) {
119+
if (!\is_bool($value)) {
120+
// Attempt basic cast for flexibility
121+
if (\in_array(strtolower((string) $value), ['true', '1', 'yes', 'on'])) {
122+
$value = true;
123+
} elseif (\in_array(strtolower((string) $value), ['false', '0', 'no', 'off'])) {
124+
$value = false;
125+
} else {
126+
return ['success' => false, 'error' => 'Invalid allow_negative value. Must be boolean (true/false).'];
127+
}
128+
}
129+
$this->config['allow_negative'] = $value;
130+
131+
// $registry->notifyResourceChanged('config://calculator/settings');
132+
return ['success' => true, 'message' => 'Allow negative results set to '.($value ? 'true' : 'false').'.'];
133+
}
134+
135+
return ['success' => false, 'error' => 'Internal error handling setting.']; // Should not happen
136+
}
137+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#!/usr/bin/env php
2+
<?php
3+
4+
/*
5+
* This file is part of the official PHP MCP SDK.
6+
*
7+
* A collaboration between Symfony and the PHP Foundation.
8+
*
9+
* For the full copyright and license information, please view the LICENSE
10+
* file that was distributed with this source code.
11+
*/
12+
13+
require_once dirname(__DIR__).'/bootstrap.php';
14+
chdir(__DIR__);
15+
16+
use Mcp\Server;
17+
use Mcp\Server\Transport\Stdio\NativeTransport;
18+
19+
logger()->info('Starting MCP Stdio Calculator Server...');
20+
21+
Server::make()
22+
->withServerInfo('Stdio Calculator', '1.1.0')
23+
->withLogger(logger())
24+
->withDiscovery(__DIR__, ['.'])
25+
->build()
26+
->connect(new NativeTransport());
27+
28+
logger()->info('Server listener stopped gracefully.');
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the official PHP MCP SDK.
5+
*
6+
* A collaboration between Symfony and the PHP Foundation.
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Mcp\Example\HttpUserProfileExample;
13+
14+
use Mcp\Capability\Attribute\CompletionProvider;
15+
use Mcp\Capability\Attribute\McpPrompt;
16+
use Mcp\Capability\Attribute\McpResource;
17+
use Mcp\Capability\Attribute\McpResourceTemplate;
18+
use Mcp\Capability\Attribute\McpTool;
19+
use Psr\Log\LoggerInterface;
20+
21+
class McpElements
22+
{
23+
// Simulate a simple user database
24+
private array $users = [
25+
'101' => ['name' => 'Alice', 'email' => '[email protected]', 'role' => 'admin'],
26+
'102' => ['name' => 'Bob', 'email' => '[email protected]', 'role' => 'user'],
27+
'103' => ['name' => 'Charlie', 'email' => '[email protected]', 'role' => 'user'],
28+
];
29+
30+
public function __construct(
31+
private LoggerInterface $logger,
32+
) {
33+
$this->logger->debug('HttpUserProfileExample McpElements instantiated.');
34+
}
35+
36+
/**
37+
* Retrieves the profile data for a specific user.
38+
*
39+
* @param string $userId the ID of the user (from URI)
40+
*
41+
* @return array user profile data
42+
*
43+
* @throws McpServerException if the user is not found
44+
*/
45+
#[McpResourceTemplate(
46+
uriTemplate: 'user://{userId}/profile',
47+
name: 'user_profile',
48+
description: 'Get profile information for a specific user ID.',
49+
mimeType: 'application/json'
50+
)]
51+
public function getUserProfile(
52+
#[CompletionProvider(values: ['101', '102', '103'])]
53+
string $userId,
54+
): array {
55+
$this->logger->info('Reading resource: user profile', ['userId' => $userId]);
56+
if (!isset($this->users[$userId])) {
57+
// Throwing an exception that Processor can turn into an error response
58+
throw McpServerException::invalidParams("User profile not found for ID: {$userId}");
59+
}
60+
61+
return $this->users[$userId];
62+
}
63+
64+
/**
65+
* Retrieves a list of all known user IDs.
66+
*
67+
* @return array list of user IDs
68+
*/
69+
#[McpResource(
70+
uri: 'user://list/ids',
71+
name: 'user_id_list',
72+
description: 'Provides a list of all available user IDs.',
73+
mimeType: 'application/json'
74+
)]
75+
public function listUserIds(): array
76+
{
77+
$this->logger->info('Reading resource: user ID list');
78+
79+
return array_keys($this->users);
80+
}
81+
82+
/**
83+
* Sends a welcome message to a user.
84+
* (This is a placeholder - in a real app, it might queue an email).
85+
*
86+
* @param string $userId the ID of the user to message
87+
* @param string|null $customMessage an optional custom message part
88+
*
89+
* @return array status of the operation
90+
*/
91+
#[McpTool(name: 'send_welcome')]
92+
public function sendWelcomeMessage(string $userId, ?string $customMessage = null): array
93+
{
94+
$this->logger->info('Executing tool: send_welcome', ['userId' => $userId]);
95+
if (!isset($this->users[$userId])) {
96+
return ['success' => false, 'error' => "User ID {$userId} not found."];
97+
}
98+
$user = $this->users[$userId];
99+
$message = "Welcome, {$user['name']}!";
100+
if ($customMessage) {
101+
$message .= ' '.$customMessage;
102+
}
103+
// Simulate sending
104+
$this->logger->info("Simulated sending message to {$user['email']}: {$message}");
105+
106+
return ['success' => true, 'message_sent' => $message];
107+
}
108+
109+
#[McpTool(name: 'test_tool_without_params')]
110+
public function testToolWithoutParams(): array
111+
{
112+
return ['success' => true, 'message' => 'Test tool without params'];
113+
}
114+
115+
/**
116+
* Generates a prompt to write a bio for a user.
117+
*
118+
* @param string $userId the user ID to generate the bio for
119+
* @param string $tone Desired tone (e.g., 'formal', 'casual').
120+
*
121+
* @return array prompt messages
122+
*
123+
* @throws McpServerException if user not found
124+
*/
125+
#[McpPrompt(name: 'generate_bio_prompt')]
126+
public function generateBio(
127+
#[CompletionProvider(provider: UserIdCompletionProvider::class)]
128+
string $userId,
129+
string $tone = 'professional',
130+
): array {
131+
$this->logger->info('Executing prompt: generate_bio', ['userId' => $userId, 'tone' => $tone]);
132+
if (!isset($this->users[$userId])) {
133+
throw McpServerException::invalidParams("User not found for bio prompt: {$userId}");
134+
}
135+
$user = $this->users[$userId];
136+
137+
return [
138+
['role' => 'user', 'content' => "Write a short, {$tone} biography for {$user['name']} (Role: {$user['role']}, Email: {$user['email']}). Highlight their role within the system."],
139+
];
140+
}
141+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the official PHP MCP SDK.
5+
*
6+
* A collaboration between Symfony and the PHP Foundation.
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Mcp\Example\HttpUserProfileExample;
13+
14+
use Mcp\Capability\Prompt\Completion\ProviderInterface;
15+
16+
class UserIdCompletionProvider implements ProviderInterface
17+
{
18+
public function getCompletions(string $currentValue): array
19+
{
20+
$availableUserIds = ['101', '102', '103'];
21+
22+
return array_filter($availableUserIds, fn (string $userId) => str_contains($userId, $currentValue));
23+
}
24+
}

0 commit comments

Comments
 (0)