Skip to content

Commit d776561

Browse files
committed
Merge branch 'main' of github.com:kaipiyann/php-sdk
2 parents 59095ea + e5e0b92 commit d776561

File tree

141 files changed

+2633
-528
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

141 files changed

+2633
-528
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,7 @@ $server = Server::builder()
254254
- [Server Builder](docs/server-builder.md) - Complete ServerBuilder reference and configuration
255255
- [Transports](docs/transports.md) - STDIO and HTTP transport setup and usage
256256
- [MCP Elements](docs/mcp-elements.md) - Creating tools, resources, and prompts
257+
- [Client Communiocation](docs/client-communication.md) - Communicating back to the client from server-side
257258

258259
**Learning:**
259260
- [Examples](docs/examples.md) - Comprehensive example walkthroughs

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
"Mcp\\Example\\HttpDiscoveryUserProfile\\": "examples/http-discovery-userprofile/",
5858
"Mcp\\Example\\HttpSchemaShowcase\\": "examples/http-schema-showcase/",
5959
"Mcp\\Example\\StdioCachedDiscovery\\": "examples/stdio-cached-discovery/",
60+
"Mcp\\Example\\StdioClientCommunication\\": "examples/stdio-client-communication/",
6061
"Mcp\\Example\\StdioCustomDependencies\\": "examples/stdio-custom-dependencies/",
6162
"Mcp\\Example\\StdioDiscoveryCalculator\\": "examples/stdio-discovery-calculator/",
6263
"Mcp\\Example\\StdioEnvVariables\\": "examples/stdio-env-variables/",

docs/client-communication.md

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# Client Communication
2+
3+
MCP supports various ways a server can communicate back to a server on top of the main request-response flow.
4+
5+
## Table of Contents
6+
7+
- [ClientGateway](#client-gateway)
8+
- [Sampling](#sampling)
9+
- [Logging](#logging)
10+
- [Notification](#notification)
11+
- [Progress](#progress)
12+
13+
## ClientGateway
14+
15+
Every communication back to client is handled using the `Mcp\Server\ClientGateway` and its dedicated methods per
16+
operation. To use the `ClientGateway` in your code, there are two ways to do so:
17+
18+
### 1. Method Argument Injection
19+
20+
Every refernce of a MCP element, that translates to an actual method call, can just add an type-hinted argument for the
21+
`ClientGateway` and the SDK will take care to include the gateway in the arguments of the method call:
22+
23+
```php
24+
use Mcp\Capability\Attribute\McpTool;
25+
use Mcp\Server\ClientGateway;
26+
27+
class MyService
28+
{
29+
#[McpTool('my_tool', 'My Tool Description')]
30+
public function myTool(ClientGateway $client): string
31+
{
32+
$client->log(...);
33+
```
34+
35+
### 2. Implementing `ClientAwareInterface`
36+
37+
Whenever a service class of an MCP element implements the interface `Mcp\Server\ClientAwareInterface` the `setClient`
38+
method of that class will get called while handling the reference, and in combination with `Mcp\Server\ClientAwareTrait`
39+
this ends up with code like this:
40+
41+
```php
42+
use Mcp\Capability\Attribute\McpTool;
43+
use Mcp\Server\ClientAwareInterface;
44+
use Mcp\Server\ClientAwareTrait;
45+
46+
class MyService implements ClientAwareInterface
47+
{
48+
use ClientAwareTrait;
49+
50+
#[McpTool('my_tool', 'My Tool Description')]
51+
public function myTool(): string
52+
{
53+
$this->log(...);
54+
```
55+
56+
## Sampling
57+
58+
With [sampling](https://modelcontextprotocol.io/specification/2025-06-18/client/sampling) servers can request clients to
59+
execute "completions" or "generations" with a language model for them:
60+
61+
```php
62+
$result = $clientGateway->sample('Roses are red, violets are', 350, 90, ['temperature' => 0.5]);
63+
```
64+
65+
The `sample` method accepts four arguments:
66+
67+
1. `message`, which is **required** and accepts a string, an instance of `Content` or an array of `SampleMessage` instances.
68+
2. `maxTokens`, which defaults to `1000`
69+
3. `timeout` in seconds, which defaults to `120`
70+
4. `options` which might include `system_prompt`, `preferences` for model choice, `includeContext`, `temperature`, `stopSequences` and `metadata`
71+
72+
[Find more details to sampling payload in the specification.](https://modelcontextprotocol.io/specification/2025-06-18/client/sampling#protocol-messages)
73+
74+
## Logging
75+
76+
The [Logging](https://modelcontextprotocol.io/specification/2025-06-18/server/utilities/logging) utility enables servers
77+
to send structured log messages as notifcation to clients:
78+
79+
```php
80+
use Mcp\Schema\Enum\LoggingLevel;
81+
82+
$clientGateway->log(LoggingLevel::Warning, 'The end is near.');
83+
```
84+
85+
## Progress
86+
87+
With a [Progress](https://modelcontextprotocol.io/specification/2025-06-18/basic/utilities/progress#progress)
88+
notification a server can update a client while an operation is ongoing:
89+
90+
```php
91+
$clientGateway->progress(4.2, 10, 'Downloading needed images.');
92+
```
93+
94+
## Notification
95+
96+
Lastly, the server can push all kind of notifications, that implement the `Mcp\Schema\JsonRpc\Notification` interface
97+
to the client to:
98+
99+
```php
100+
$clientGateway->notify($yourNotification);
101+
```

docs/examples.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,16 @@ $server = Server::builder()
163163
->setDiscovery(__DIR__, ['.'], [], $cache)
164164
```
165165

166+
### Client Communication
167+
168+
**File**: `examples/stdio-client-communication/`
169+
170+
**What it demostrates:**
171+
- Server initiated communcation back to the client
172+
- Logging, sampling, progress and notifications
173+
- Using `ClientGateway` in service class via `ClientAwareInterface` and corresponding trait
174+
- Using `ClientGateway` in tool method via method argument injection
175+
166176
## HTTP Examples
167177

168178
### Discovery User Profile

docs/mcp-elements.md

Lines changed: 36 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -154,31 +154,37 @@ public function getMultipleContent(): array
154154

155155
#### Error Handling
156156

157-
Tools can throw exceptions which are automatically converted to proper JSON-RPC error responses:
157+
Tool handlers can throw any exception, but the type determines how it's handled:
158+
159+
- **`ToolCallException`**: Converted to JSON-RPC response with `CallToolResult` where `isError: true`, allowing the LLM to see the error message and self-correct
160+
- **Any other exception**: Converted to JSON-RPC error response, but with a generic error message
158161

159162
```php
163+
use Mcp\Exception\ToolCallException;
164+
160165
#[McpTool]
161166
public function divideNumbers(float $a, float $b): float
162167
{
163168
if ($b === 0.0) {
164-
throw new \InvalidArgumentException('Division by zero is not allowed');
169+
throw new ToolCallException('Division by zero is not allowed');
165170
}
166-
171+
167172
return $a / $b;
168173
}
169174

170175
#[McpTool]
171176
public function processFile(string $filename): string
172177
{
173178
if (!file_exists($filename)) {
174-
throw new \InvalidArgumentException("File not found: {$filename}");
179+
throw new ToolCallException("File not found: {$filename}");
175180
}
176-
181+
177182
return file_get_contents($filename);
178183
}
179184
```
180185

181-
The SDK will convert these exceptions into appropriate JSON-RPC error responses that MCP clients can understand.
186+
**Recommendation**: Use `ToolCallException` when you want to communicate specific errors to clients. Any other exception will still be converted to JSON-RPC compliant errors but with generic error messages.
187+
182188

183189
## Resources
184190

@@ -298,24 +304,31 @@ public function getMultipleResources(): array
298304

299305
#### Error Handling
300306

301-
Resource handlers can throw exceptions for error cases:
307+
Resource handlers can throw any exception, but the type determines how it's handled:
308+
309+
- **`ResourceReadException`**: Converted to JSON-RPC error response with the actual exception message
310+
- **Any other exception**: Converted to JSON-RPC error response, but with a generic error message
302311

303312
```php
313+
use Mcp\Exception\ResourceReadException;
314+
304315
#[McpResource(uri: 'file://{path}')]
305316
public function getFile(string $path): string
306317
{
307318
if (!file_exists($path)) {
308-
throw new \InvalidArgumentException("File not found: {$path}");
319+
throw new ResourceReadException("File not found: {$path}");
309320
}
310-
321+
311322
if (!is_readable($path)) {
312-
throw new \RuntimeException("File not readable: {$path}");
323+
throw new ResourceReadException("File not readable: {$path}");
313324
}
314-
325+
315326
return file_get_contents($path);
316327
}
317328
```
318329

330+
**Recommendation**: Use `ResourceReadException` when you want to communicate specific errors to clients. Any other exception will still be converted to JSON-RPC compliant errors but with generic error messages.
331+
319332
## Resource Templates
320333

321334
Resource templates are **dynamic resources** that use parameterized URIs with variables. They follow all the same rules
@@ -449,40 +462,44 @@ public function explicitMessages(): array
449462
}
450463
```
451464

465+
The SDK automatically validates that all messages have valid roles and converts the result into the appropriate MCP prompt message format.
466+
452467
#### Valid Message Roles
453468

454469
- **`user`**: User input or questions
455470
- **`assistant`**: Assistant responses/system
456471

457472
#### Error Handling
458473

459-
Prompt handlers can throw exceptions for invalid inputs:
474+
Prompt handlers can throw any exception, but the type determines how it's handled:
475+
- **`PromptGetException`**: Converted to JSON-RPC error response with the actual exception message
476+
- **Any other exception**: Converted to JSON-RPC error response, but with a generic error message
460477

461478
```php
479+
use Mcp\Exception\PromptGetException;
480+
462481
#[McpPrompt]
463482
public function generatePrompt(string $topic, string $style): array
464483
{
465484
$validStyles = ['casual', 'formal', 'technical'];
466-
485+
467486
if (!in_array($style, $validStyles)) {
468-
throw new \InvalidArgumentException(
487+
throw new PromptGetException(
469488
"Invalid style '{$style}'. Must be one of: " . implode(', ', $validStyles)
470489
);
471490
}
472-
491+
473492
return [
474493
['role' => 'user', 'content' => "Write about {$topic} in a {$style} style"]
475494
];
476495
}
477496
```
478497

479-
The SDK automatically validates that all messages have valid roles and converts the result into the appropriate MCP prompt message format.
498+
**Recommendation**: Use `PromptGetException` when you want to communicate specific errors to clients. Any other exception will still be converted to JSON-RPC compliant errors but with generic error messages.
480499

481500
## Completion Providers
482501

483-
Completion providers help MCP clients offer auto-completion suggestions for Resource Templates and Prompts. Unlike Tools
484-
and static Resources (which can be listed via `tools/list` and `resources/list`), Resource Templates and Prompts have
485-
dynamic parameters that benefit from completion hints.
502+
Completion providers help MCP clients offer auto-completion suggestions for Resource Templates and Prompts. Unlike Tools and static Resources (which can be listed via `tools/list` and `resources/list`), Resource Templates and Prompts have dynamic parameters that benefit from completion hints.
486503

487504
### Completion Provider Types
488505

examples/http-client-communication/server.php

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
use Laminas\HttpHandlerRunner\Emitter\SapiEmitter;
1717
use Mcp\Schema\Content\TextContent;
1818
use Mcp\Schema\Enum\LoggingLevel;
19-
use Mcp\Schema\JsonRpc\Error as JsonRpcError;
2019
use Mcp\Schema\ServerCapabilities;
2120
use Mcp\Server;
2221
use Mcp\Server\ClientGateway;
@@ -56,18 +55,13 @@ function (string $projectName, array $milestones, ClientGateway $client): array
5655
implode(', ', $milestones)
5756
);
5857

59-
$response = $client->sample(
60-
prompt: $prompt,
58+
$result = $client->sample(
59+
message: $prompt,
6160
maxTokens: 400,
6261
timeout: 90,
6362
options: ['temperature' => 0.4]
6463
);
6564

66-
if ($response instanceof JsonRpcError) {
67-
throw new RuntimeException(sprintf('Sampling request failed (%d): %s', $response->code, $response->message));
68-
}
69-
70-
$result = $response->result;
7165
$content = $result->content instanceof TextContent ? trim((string) $result->content->text) : '';
7266

7367
$client->log(LoggingLevel::Info, 'Briefing ready, returning to caller.');

examples/http-discovery-userprofile/McpElements.php

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
use Mcp\Capability\Attribute\McpResource;
1717
use Mcp\Capability\Attribute\McpResourceTemplate;
1818
use Mcp\Capability\Attribute\McpTool;
19-
use Mcp\Exception\InvalidArgumentException;
19+
use Mcp\Exception\PromptGetException;
20+
use Mcp\Exception\ResourceReadException;
2021
use Psr\Log\LoggerInterface;
2122

2223
/**
@@ -48,7 +49,7 @@ public function __construct(
4849
*
4950
* @return User user profile data
5051
*
51-
* @throws InvalidArgumentException if the user is not found
52+
* @throws ResourceReadException if the user is not found
5253
*/
5354
#[McpResourceTemplate(
5455
uriTemplate: 'user://{userId}/profile',
@@ -62,7 +63,7 @@ public function getUserProfile(
6263
): array {
6364
$this->logger->info('Reading resource: user profile', ['userId' => $userId]);
6465
if (!isset($this->users[$userId])) {
65-
throw new InvalidArgumentException("User profile not found for ID: {$userId}");
66+
throw new ResourceReadException("User not found for ID: {$userId}");
6667
}
6768

6869
return $this->users[$userId];
@@ -130,7 +131,7 @@ public function testToolWithoutParams(): array
130131
*
131132
* @return array<string, string>[] prompt messages
132133
*
133-
* @throws InvalidArgumentException if user not found
134+
* @throws PromptGetException if user not found
134135
*/
135136
#[McpPrompt(name: 'generate_bio_prompt')]
136137
public function generateBio(
@@ -140,7 +141,7 @@ public function generateBio(
140141
): array {
141142
$this->logger->info('Executing prompt: generate_bio', ['userId' => $userId, 'tone' => $tone]);
142143
if (!isset($this->users[$userId])) {
143-
throw new InvalidArgumentException("User not found for bio prompt: {$userId}");
144+
throw new PromptGetException("User not found for bio prompt: {$userId}");
144145
}
145146
$user = $this->users[$userId];
146147

examples/stdio-cached-discovery/CachedCalculatorElements.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
namespace Mcp\Example\StdioCachedDiscovery;
1515

1616
use Mcp\Capability\Attribute\McpTool;
17+
use Mcp\Exception\ToolCallException;
1718

1819
/**
1920
* Example MCP elements for demonstrating cached discovery.
@@ -39,7 +40,7 @@ public function multiply(int $a, int $b): int
3940
public function divide(int $a, int $b): float
4041
{
4142
if (0 === $b) {
42-
throw new \InvalidArgumentException('Division by zero is not allowed');
43+
throw new ToolCallException('Division by zero is not allowed');
4344
}
4445

4546
return $a / $b;

0 commit comments

Comments
 (0)