Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ $server = Server::builder()
- [Transports](docs/transports.md) - STDIO and HTTP transport setup and usage
- [MCP Elements](docs/mcp-elements.md) - Creating tools, resources, and prompts
- [Client Communication](docs/client-communication.md) - Communicating back to the client from server-side
- [Events](docs/events.md) - Hooking into server lifecycle with events

**Learning:**
- [Examples](docs/examples.md) - Comprehensive example walkthroughs
Expand Down
103 changes: 103 additions & 0 deletions docs/events.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# Events

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.

## Table of Contents

- [Setup](#setup)
- [Protocol Events](#protocol-events)
- [RequestEvent](#requestevent)
- [ResponseEvent](#responseevent)
- [ErrorEvent](#errorevent)
- [NotificationEvent](#notificationevent)
- [List Change Events](#list-change-events)

## Setup

Configure an event dispatcher when building your server:

```php
use Mcp\Server;
use Symfony\Component\EventDispatcher\EventDispatcher;

$dispatcher = new EventDispatcher();

// Register your listeners
$dispatcher->addListener(RequestEvent::class, function (RequestEvent $event) {
// Handle any incoming request
if ($event->getMethod() === 'tools/call') {
// Handle tool call requests specifically
}
});

$server = Server::builder()
->setEventDispatcher($dispatcher)
->build();
```

## Protocol Events

The SDK dispatches 4 broad event types at the protocol level, allowing you to observe and modify all server operations:

### RequestEvent

**Dispatched**: When any request is received from the client, before it's processed by handlers.

**Properties**:
- `getRequest(): Request` - The incoming request
- `setRequest(Request $request): void` - Modify the request before processing
- `getSession(): SessionInterface` - The current session
- `getMethod(): string` - Convenience method to get the request method

### ResponseEvent

**Dispatched**: When a successful response is ready to be sent to the client, after handler execution.

**Properties**:
- `getResponse(): Response` - The response being sent
- `setResponse(Response $response): void` - Modify the response before sending
- `getRequest(): Request` - The original request
- `getSession(): SessionInterface` - The current session
- `getMethod(): string` - Convenience method to get the request method

### ErrorEvent

**Dispatched**: When an error occurs during request processing.

**Properties**:
- `getError(): Error` - The error being sent
- `setError(Error $error): void` - Modify the error before sending
- `getRequest(): Request` - The original request (null for parse errors)
- `getThrowable(): ?\Throwable` - The exception that caused the error (if any)
- `getSession(): SessionInterface` - The current session

### NotificationEvent

**Dispatched**: When a notification is received from the client, before it's processed by handlers.

**Properties**:
- `getNotification(): Notification` - The incoming notification
- `setNotification(Notification $notification): void` - Modify the notification before processing
- `getSession(): SessionInterface` - The current session
- `getMethod(): string` - Convenience method to get the notification method

## List Change Events

These events are dispatched when the lists of available capabilities change:

| Event | Description |
|------------------------------------|------------------------------------------------------------------|
| `ToolListChangedEvent` | Dispatched when the list of available tools changes |
| `ResourceListChangedEvent` | Dispatched when the list of available resources changes |
| `ResourceTemplateListChangedEvent` | Dispatched when the list of available resource templates changes |
| `PromptListChangedEvent` | Dispatched when the list of available prompts changes |

These events carry no data and are used to notify clients that they should refresh their capability lists.

```php
use Mcp\Event\ToolListChangedEvent;

$dispatcher->addListener(ToolListChangedEvent::class, function (ToolListChangedEvent $event) {
$logger->info('Tool list has changed, clients should refresh');
});
```
61 changes: 61 additions & 0 deletions src/Event/ErrorEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?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\Event;

use Mcp\Schema\JsonRpc\Error;
use Mcp\Schema\JsonRpc\Request;
use Mcp\Server\Session\SessionInterface;

/**
* Event dispatched when an error occurs during request processing.
*
* Listeners can modify the error before it's sent to the client.
*
* @author Edouard Courty <edouard.courty2@gmail.com>
*/
final class ErrorEvent
{
public function __construct(
private Error $error,
private readonly Request $request,
private readonly SessionInterface $session,
private readonly ?\Throwable $throwable,
) {
}

public function getError(): Error
{
return $this->error;
}

public function setError(Error $error): void
{
$this->error = $error;
}

public function getRequest(): Request
{
return $this->request;
}

public function getThrowable(): ?\Throwable
{
return $this->throwable;
}

public function getSession(): SessionInterface
{
return $this->session;
}
}
53 changes: 53 additions & 0 deletions src/Event/NotificationEvent.php
Original file line number Diff line number Diff line change
@@ -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\Event;

use Mcp\Schema\JsonRpc\Notification;
use Mcp\Server\Session\SessionInterface;

/**
* Event dispatched when any notification is received from the client.
*
* Listeners can modify the notification before it's processed by handlers.
*
* @author Edouard Courty <edouard.courty2@gmail.com>
*/
final class NotificationEvent
{
public function __construct(
private Notification $notification,
private readonly SessionInterface $session,
) {
}

public function getNotification(): Notification
{
return $this->notification;
}

public function setNotification(Notification $notification): void
{
$this->notification = $notification;
}

public function getSession(): SessionInterface
{
return $this->session;
}

public function getMethod(): string
{
return $this->notification::getMethod();
}
}
53 changes: 53 additions & 0 deletions src/Event/RequestEvent.php
Original file line number Diff line number Diff line change
@@ -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\Event;

use Mcp\Schema\JsonRpc\Request;
use Mcp\Server\Session\SessionInterface;

/**
* Event dispatched when any request is received from the client.
*
* Listeners can modify the request before it's processed by handlers.
*
* @author Edouard Courty <edouard.courty2@gmail.com>
*/
final class RequestEvent
{
public function __construct(
private Request $request,
private readonly SessionInterface $session,
) {
}

public function getRequest(): Request
{
return $this->request;
}

public function setRequest(Request $request): void
{
$this->request = $request;
}

public function getSession(): SessionInterface
{
return $this->session;
}

public function getMethod(): string
{
return $this->request::getMethod();
}
}
69 changes: 69 additions & 0 deletions src/Event/ResponseEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?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\Event;

use Mcp\Schema\JsonRpc\Request;
use Mcp\Schema\JsonRpc\Response;
use Mcp\Server\Session\SessionInterface;

/**
* Event dispatched when a successful response is ready to be sent to the client.
*
* Listeners can modify the response before it's sent.
*
* @author Edouard Courty <edouard.courty2@gmail.com>
*/
final class ResponseEvent
{
/**
* @param Response<mixed> $response
*/
public function __construct(
private Response $response,
private readonly Request $request,
private readonly SessionInterface $session,
) {
}

/**
* @return Response<mixed>
*/
public function getResponse(): Response
{
return $this->response;
}

/**
* @param Response<mixed> $response
*/
public function setResponse(Response $response): void
{
$this->response = $response;
}

public function getRequest(): Request
{
return $this->request;
}

public function getSession(): SessionInterface
{
return $this->session;
}

public function getMethod(): string
{
return $this->request::getMethod();
}
}
1 change: 1 addition & 0 deletions src/Server/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,7 @@ public function build(): Server
sessionFactory: $sessionFactory,
sessionStore: $sessionStore,
logger: $logger,
eventDispatcher: $this->eventDispatcher,
);

return new Server($protocol, $logger);
Expand Down
Loading