Skip to content

Latest commit

 

History

History
116 lines (87 loc) · 4.85 KB

custom-handlers.md

File metadata and controls

116 lines (87 loc) · 4.85 KB

Custom Message Handlers

Low-level escape hatch. Custom message handlers run before the SDK's built-in handlers and give you total control over individual JSON-RPC messages. They do not receive the builder's registry, container, or discovery output unless you pass those dependencies in yourself.

Warning: Custom message handlers bypass discovery, manual capability registration, and container lookups (unless you explicitly pass them). Tools, resources, and prompts you register elsewhere will not show up unless your handler loads and executes them manually. Reach for this API only when you need that level of control and are comfortable taking on the additional plumbing.

!!! note A custom handler can only serve a method the SDK knows. The SDK rejects any other method as unknown before a handler runs. To add a new method, write an extension that registers its message classes.

Request Handlers

Handle JSON-RPC requests (messages with an id that expect a response). Request handlers must return either a Response or an Error object.

Attach request handlers with addRequestHandler() (single) or addRequestHandlers() (multiple). You can call these methods as many times as needed; handlers are collected in call order, and the whole custom list runs before the built-in defaults:

$server = Server::builder()
    ->addRequestHandler(new CustomListToolsHandler())
    ->addRequestHandlers([
        new CustomCallToolHandler(),
        new CustomGetPromptHandler(),
    ])
    ->build();

Request handlers implement RequestHandlerInterface:

use Mcp\Schema\JsonRpc\Error;
use Mcp\Schema\JsonRpc\Request;
use Mcp\Schema\JsonRpc\Response;
use Mcp\Server\Handler\Request\RequestHandlerInterface;
use Mcp\Server\Session\SessionInterface;

interface RequestHandlerInterface
{
    public function supports(Request $request): bool;

    public function handle(Request $request, SessionInterface $session): Response|Error;
}
  • supports() decides if the handler should process the incoming request
  • handle() must return a Response (on success) or an Error (on failure)

Notification Handlers

Handle JSON-RPC notifications (messages without an id that don't expect a response). Notification handlers do not return anything - they perform side effects only.

Attach notification handlers with addNotificationHandler() (single) or addNotificationHandlers() (multiple):

// Handlers are your own classes implementing NotificationHandlerInterface;
// the SDK ships only its internal ones.
$server = Server::builder()
    ->addNotificationHandler(new AuditNotificationHandler($auditLog))
    ->addNotificationHandlers([
        new MetricsNotificationHandler($metrics),
        new CancellationNotificationHandler(),
    ])
    ->build();

Notification handlers implement NotificationHandlerInterface:

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

interface NotificationHandlerInterface
{
    public function supports(Notification $notification): bool;

    public function handle(Notification $notification, SessionInterface $session): void;
}
  • supports() decides if the handler should process the incoming notification
  • handle() performs side effects but does not return a value (notifications have no response)

Request handlers stop at the first match. Notification handlers don't: every handler whose supports() returns true runs, the built-in ones included.

Protocol 2026-07-28

Custom request handlers also serve requests on protocol version 2026-07-28. Each of these requests gets a new in-memory session, so nothing you store in $session reaches the next request. Notification handlers never run on this protocol version.

There are two exceptions. The SDK rejects the methods this revision removed (initialize, ping, logging/setLevel, resources/subscribe and resources/unsubscribe) before any handler runs. And the SDK answers server/discover and subscriptions/listen itself, so your handlers never see them.

Key Differences

Handler Type Interface Returns Use Case
Request Handler RequestHandlerInterface Response|Error Handle requests that need responses (e.g., tools/list, tools/call)
Notification Handler NotificationHandlerInterface void Handle fire-and-forget notifications (e.g., notifications/initialized, notifications/progress)

Example

Check out examples/server/custom-method-handlers/server.php for a complete example showing how to implement custom tools/list and tools/call request handlers independently of the registry.