Skip to content

Commit 368b3aa

Browse files
committed
feat(server): add server lifecycle events
1 parent 940eb90 commit 368b3aa

File tree

9 files changed

+959
-0
lines changed

9 files changed

+959
-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: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Mcp\Event;
6+
7+
use Mcp\Schema\JsonRpc\Error;
8+
use Mcp\Schema\JsonRpc\Request;
9+
use Mcp\Server\Session\SessionInterface;
10+
11+
/**
12+
* Event dispatched when an error occurs during request processing.
13+
*
14+
* Listeners can modify the error before it's sent to the client.
15+
*
16+
* @author Edouard Courty <[email protected]>
17+
*/
18+
final class ErrorEvent
19+
{
20+
public function __construct(
21+
private Error $error,
22+
private readonly Request $request,
23+
private readonly SessionInterface $session,
24+
private readonly ?\Throwable $throwable,
25+
) {
26+
}
27+
28+
public function getError(): Error
29+
{
30+
return $this->error;
31+
}
32+
33+
public function setError(Error $error): void
34+
{
35+
$this->error = $error;
36+
}
37+
38+
public function getRequest(): ?Request
39+
{
40+
return $this->request;
41+
}
42+
43+
public function getThrowable(): ?\Throwable
44+
{
45+
return $this->throwable;
46+
}
47+
48+
public function getSession(): ?SessionInterface
49+
{
50+
return $this->session;
51+
}
52+
}

src/Event/NotificationEvent.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Mcp\Event;
6+
7+
use Mcp\Schema\JsonRpc\Notification;
8+
use Mcp\Server\Session\SessionInterface;
9+
10+
/**
11+
* Event dispatched when any notification is received from the client.
12+
*
13+
* Listeners can modify the notification before it's processed by handlers.
14+
*
15+
* @author Edouard Courty <[email protected]>
16+
*/
17+
final class NotificationEvent
18+
{
19+
public function __construct(
20+
private Notification $notification,
21+
private readonly SessionInterface $session,
22+
) {
23+
}
24+
25+
public function getNotification(): Notification
26+
{
27+
return $this->notification;
28+
}
29+
30+
public function setNotification(Notification $notification): void
31+
{
32+
$this->notification = $notification;
33+
}
34+
35+
public function getSession(): SessionInterface
36+
{
37+
return $this->session;
38+
}
39+
40+
public function getMethod(): string
41+
{
42+
return $this->notification::getMethod();
43+
}
44+
}

src/Event/RequestEvent.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Mcp\Event;
6+
7+
use Mcp\Schema\JsonRpc\Request;
8+
use Mcp\Server\Session\SessionInterface;
9+
10+
/**
11+
* Event dispatched when any request is received from the client.
12+
*
13+
* Listeners can modify the request before it's processed by handlers.
14+
*
15+
* @author Edouard Courty <[email protected]>
16+
*/
17+
final class RequestEvent
18+
{
19+
public function __construct(
20+
private Request $request,
21+
private readonly SessionInterface $session,
22+
) {
23+
}
24+
25+
public function getRequest(): Request
26+
{
27+
return $this->request;
28+
}
29+
30+
public function setRequest(Request $request): void
31+
{
32+
$this->request = $request;
33+
}
34+
35+
public function getSession(): SessionInterface
36+
{
37+
return $this->session;
38+
}
39+
40+
public function getMethod(): string
41+
{
42+
return $this->request::getMethod();
43+
}
44+
}

src/Event/ResponseEvent.php

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Mcp\Event;
6+
7+
use Mcp\Schema\JsonRpc\Request;
8+
use Mcp\Schema\JsonRpc\Response;
9+
use Mcp\Server\Session\SessionInterface;
10+
11+
/**
12+
* Event dispatched when a successful response is ready to be sent to the client.
13+
*
14+
* Listeners can modify the response before it's sent.
15+
*
16+
* @author Edouard Courty <[email protected]>
17+
*/
18+
final class ResponseEvent
19+
{
20+
/**
21+
* @param Response<mixed> $response
22+
*/
23+
public function __construct(
24+
private Response $response,
25+
private readonly Request $request,
26+
private readonly SessionInterface $session,
27+
) {
28+
}
29+
30+
/**
31+
* @return Response<mixed>
32+
*/
33+
public function getResponse(): Response
34+
{
35+
return $this->response;
36+
}
37+
38+
/**
39+
* @param Response<mixed> $response
40+
*/
41+
public function setResponse(Response $response): void
42+
{
43+
$this->response = $response;
44+
}
45+
46+
public function getRequest(): Request
47+
{
48+
return $this->request;
49+
}
50+
51+
public function getSession(): SessionInterface
52+
{
53+
return $this->session;
54+
}
55+
56+
public function getMethod(): string
57+
{
58+
return $this->request::getMethod();
59+
}
60+
}

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)