Skip to content

Commit 8bef410

Browse files
committed
feat(server): add server lifecycle events
Signed-off-by: Edouard Courty <edouard.courty2@gmail.com>
1 parent 940eb90 commit 8bef410

File tree

9 files changed

+1008
-0
lines changed

9 files changed

+1008
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@ $server = Server::builder()
255255
- [Transports](docs/transports.md) - STDIO and HTTP transport setup and usage
256256
- [MCP Elements](docs/mcp-elements.md) - Creating tools, resources, and prompts
257257
- [Client Communication](docs/client-communication.md) - Communicating back to the client from server-side
258+
- [Events](docs/events.md) - Hooking into server lifecycle with events
258259

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

docs/events.md

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# Events
2+
3+
The MCP SDK provides a PSR-14 compatible event system that allows you to hook into the server's lifecycle. Events enable request/response modification, and other user-defined behaviors.
4+
5+
## Table of Contents
6+
7+
- [Setup](#setup)
8+
- [Protocol Events](#protocol-events)
9+
- [RequestEvent](#requestevent)
10+
- [ResponseEvent](#responseevent)
11+
- [ErrorEvent](#errorevent)
12+
- [NotificationEvent](#notificationevent)
13+
- [List Change Events](#list-change-events)
14+
15+
## Setup
16+
17+
Configure an event dispatcher when building your server:
18+
19+
```php
20+
use Mcp\Server;
21+
use Symfony\Component\EventDispatcher\EventDispatcher;
22+
23+
$dispatcher = new EventDispatcher();
24+
25+
// Register your listeners
26+
$dispatcher->addListener(RequestEvent::class, function (RequestEvent $event) {
27+
// Handle any incoming request
28+
if ($event->getMethod() === 'tools/call') {
29+
// Handle tool call requests specifically
30+
}
31+
});
32+
33+
$server = Server::builder()
34+
->setEventDispatcher($dispatcher)
35+
->build();
36+
```
37+
38+
## Protocol Events
39+
40+
The SDK dispatches 4 broad event types at the protocol level, allowing you to observe and modify all server operations:
41+
42+
### RequestEvent
43+
44+
**Dispatched**: When any request is received from the client, before it's processed by handlers.
45+
46+
**Properties**:
47+
- `getRequest(): Request` - The incoming request
48+
- `setRequest(Request $request): void` - Modify the request before processing
49+
- `getSession(): SessionInterface` - The current session
50+
- `getMethod(): string` - Convenience method to get the request method
51+
52+
### ResponseEvent
53+
54+
**Dispatched**: When a successful response is ready to be sent to the client, after handler execution.
55+
56+
**Properties**:
57+
- `getResponse(): Response` - The response being sent
58+
- `setResponse(Response $response): void` - Modify the response before sending
59+
- `getRequest(): Request` - The original request
60+
- `getSession(): SessionInterface` - The current session
61+
- `getMethod(): string` - Convenience method to get the request method
62+
63+
### ErrorEvent
64+
65+
**Dispatched**: When an error occurs during request processing.
66+
67+
**Properties**:
68+
- `getError(): Error` - The error being sent
69+
- `setError(Error $error): void` - Modify the error before sending
70+
- `getRequest(): Request` - The original request (null for parse errors)
71+
- `getThrowable(): ?\Throwable` - The exception that caused the error (if any)
72+
- `getSession(): SessionInterface` - The current session
73+
74+
### NotificationEvent
75+
76+
**Dispatched**: When a notification is received from the client, before it's processed by handlers.
77+
78+
**Properties**:
79+
- `getNotification(): Notification` - The incoming notification
80+
- `setNotification(Notification $notification): void` - Modify the notification before processing
81+
- `getSession(): SessionInterface` - The current session
82+
- `getMethod(): string` - Convenience method to get the notification method
83+
84+
## List Change Events
85+
86+
These events are dispatched when the lists of available capabilities change:
87+
88+
| Event | Description |
89+
|------------------------------------|------------------------------------------------------------------|
90+
| `ToolListChangedEvent` | Dispatched when the list of available tools changes |
91+
| `ResourceListChangedEvent` | Dispatched when the list of available resources changes |
92+
| `ResourceTemplateListChangedEvent` | Dispatched when the list of available resource templates changes |
93+
| `PromptListChangedEvent` | Dispatched when the list of available prompts changes |
94+
95+
These events carry no data and are used to notify clients that they should refresh their capability lists.
96+
97+
```php
98+
use Mcp\Event\ToolListChangedEvent;
99+
100+
$dispatcher->addListener(ToolListChangedEvent::class, function (ToolListChangedEvent $event) {
101+
$logger->info('Tool list has changed, clients should refresh');
102+
});
103+
```

src/Event/ErrorEvent.php

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the official PHP MCP SDK.
7+
*
8+
* A collaboration between Symfony and the PHP Foundation.
9+
*
10+
* For the full copyright and license information, please view the LICENSE
11+
* file that was distributed with this source code.
12+
*/
13+
14+
namespace Mcp\Event;
15+
16+
use Mcp\Schema\JsonRpc\Error;
17+
use Mcp\Schema\JsonRpc\Request;
18+
use Mcp\Server\Session\SessionInterface;
19+
20+
/**
21+
* Event dispatched when an error occurs during request processing.
22+
*
23+
* Listeners can modify the error before it's sent to the client.
24+
*
25+
* @author Edouard Courty <edouard.courty2@gmail.com>
26+
*/
27+
final class ErrorEvent
28+
{
29+
public function __construct(
30+
private Error $error,
31+
private readonly Request $request,
32+
private readonly SessionInterface $session,
33+
private readonly ?\Throwable $throwable,
34+
) {
35+
}
36+
37+
public function getError(): Error
38+
{
39+
return $this->error;
40+
}
41+
42+
public function setError(Error $error): void
43+
{
44+
$this->error = $error;
45+
}
46+
47+
public function getRequest(): Request
48+
{
49+
return $this->request;
50+
}
51+
52+
public function getThrowable(): ?\Throwable
53+
{
54+
return $this->throwable;
55+
}
56+
57+
public function getSession(): SessionInterface
58+
{
59+
return $this->session;
60+
}
61+
}

src/Event/NotificationEvent.php

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the official PHP MCP SDK.
7+
*
8+
* A collaboration between Symfony and the PHP Foundation.
9+
*
10+
* For the full copyright and license information, please view the LICENSE
11+
* file that was distributed with this source code.
12+
*/
13+
14+
namespace Mcp\Event;
15+
16+
use Mcp\Schema\JsonRpc\Notification;
17+
use Mcp\Server\Session\SessionInterface;
18+
19+
/**
20+
* Event dispatched when any notification is received from the client.
21+
*
22+
* Listeners can modify the notification before it's processed by handlers.
23+
*
24+
* @author Edouard Courty <edouard.courty2@gmail.com>
25+
*/
26+
final class NotificationEvent
27+
{
28+
public function __construct(
29+
private Notification $notification,
30+
private readonly SessionInterface $session,
31+
) {
32+
}
33+
34+
public function getNotification(): Notification
35+
{
36+
return $this->notification;
37+
}
38+
39+
public function setNotification(Notification $notification): void
40+
{
41+
$this->notification = $notification;
42+
}
43+
44+
public function getSession(): SessionInterface
45+
{
46+
return $this->session;
47+
}
48+
49+
public function getMethod(): string
50+
{
51+
return $this->notification::getMethod();
52+
}
53+
}

src/Event/RequestEvent.php

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the official PHP MCP SDK.
7+
*
8+
* A collaboration between Symfony and the PHP Foundation.
9+
*
10+
* For the full copyright and license information, please view the LICENSE
11+
* file that was distributed with this source code.
12+
*/
13+
14+
namespace Mcp\Event;
15+
16+
use Mcp\Schema\JsonRpc\Request;
17+
use Mcp\Server\Session\SessionInterface;
18+
19+
/**
20+
* Event dispatched when any request is received from the client.
21+
*
22+
* Listeners can modify the request before it's processed by handlers.
23+
*
24+
* @author Edouard Courty <edouard.courty2@gmail.com>
25+
*/
26+
final class RequestEvent
27+
{
28+
public function __construct(
29+
private Request $request,
30+
private readonly SessionInterface $session,
31+
) {
32+
}
33+
34+
public function getRequest(): Request
35+
{
36+
return $this->request;
37+
}
38+
39+
public function setRequest(Request $request): void
40+
{
41+
$this->request = $request;
42+
}
43+
44+
public function getSession(): SessionInterface
45+
{
46+
return $this->session;
47+
}
48+
49+
public function getMethod(): string
50+
{
51+
return $this->request::getMethod();
52+
}
53+
}

src/Event/ResponseEvent.php

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the official PHP MCP SDK.
7+
*
8+
* A collaboration between Symfony and the PHP Foundation.
9+
*
10+
* For the full copyright and license information, please view the LICENSE
11+
* file that was distributed with this source code.
12+
*/
13+
14+
namespace Mcp\Event;
15+
16+
use Mcp\Schema\JsonRpc\Request;
17+
use Mcp\Schema\JsonRpc\Response;
18+
use Mcp\Server\Session\SessionInterface;
19+
20+
/**
21+
* Event dispatched when a successful response is ready to be sent to the client.
22+
*
23+
* Listeners can modify the response before it's sent.
24+
*
25+
* @author Edouard Courty <edouard.courty2@gmail.com>
26+
*/
27+
final class ResponseEvent
28+
{
29+
/**
30+
* @param Response<mixed> $response
31+
*/
32+
public function __construct(
33+
private Response $response,
34+
private readonly Request $request,
35+
private readonly SessionInterface $session,
36+
) {
37+
}
38+
39+
/**
40+
* @return Response<mixed>
41+
*/
42+
public function getResponse(): Response
43+
{
44+
return $this->response;
45+
}
46+
47+
/**
48+
* @param Response<mixed> $response
49+
*/
50+
public function setResponse(Response $response): void
51+
{
52+
$this->response = $response;
53+
}
54+
55+
public function getRequest(): Request
56+
{
57+
return $this->request;
58+
}
59+
60+
public function getSession(): SessionInterface
61+
{
62+
return $this->session;
63+
}
64+
65+
public function getMethod(): string
66+
{
67+
return $this->request::getMethod();
68+
}
69+
}

src/Server/Builder.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,7 @@ public function build(): Server
550550
sessionFactory: $sessionFactory,
551551
sessionStore: $sessionStore,
552552
logger: $logger,
553+
eventDispatcher: $this->eventDispatcher,
553554
);
554555

555556
return new Server($protocol, $logger);

0 commit comments

Comments
 (0)