subscriptions/listenbelongs to protocol revision2026-07-28; see Protocol versions. Handshake-era clients useresources/subscribe, which the same server still answers. The notification bus below doesn't reach them, and the server doesn't send them change notifications by itself. Over HTTP, a notification queued for them goes out with the response to their nextPOSTrequest, and an HTTPGETis answered with405. Over HTTP,resources/subscribeneeds sessions.
subscriptions/listen replaces the HTTP GET stream and resources/subscribe. The client
opens a long-lived POST whose response stream carries the notification types it asked for;
the server acknowledges first with notifications/subscriptions/acknowledged, reporting the
subset it agreed to honour.
Delivery needs a bus, because the process that publishes and the process holding the stream open are often not the same one:
use Mcp\Server\Subscription\InMemoryNotificationBus;
use Mcp\Server\Subscription\Psr16NotificationBus;
use Mcp\Server\Subscription\Psr6NotificationBus;
// stdio, or a persistent runtime where the whole server is one process
->setNotificationBus(new InMemoryNotificationBus())
// PHP-FPM: the publisher and the stream are different workers
->setNotificationBus(new Psr6NotificationBus($cachePool)) // a PSR-6 pool
->setNotificationBus(new Psr16NotificationBus($cache)) // or a PSR-16 cacheWithout a bus, server/discover doesn't announce resources.subscribe unless setCapabilities() sets it.
A handshake-era initialize on a server with sessions still announces it, because resources/subscribe works there.
Registry changes (registerTool(), unregisterPrompt(), …) are published automatically, with or
without an event dispatcher, unless you pass your own registry with setRegistry(). Then you wire
it to the bus yourself, and the builder announces listChanged for it:
use Mcp\Server\Capability\Registry;
use Mcp\Server\Server;
use Mcp\Server\Subscription\InMemoryNotificationBus;
use Mcp\Server\Subscription\PublishingEventDispatcher;
$bus = new InMemoryNotificationBus();
$registry = new Registry(new PublishingEventDispatcher($bus));
$server = Server::builder()
->setRegistry($registry)
->setNotificationBus($bus)
->build();Anything else — notifications/resources/updated above all — is published by the
application:
use Mcp\Schema\Notification\ResourceUpdatedNotification;
$bus->publish(new ResourceUpdatedNotification('file:///project/config.json'));Builder::setSubscriptionLifetime() bounds how long a stream is held before the server
closes it gracefully. The default is 30 seconds. The real ceiling is the runtime's: under PHP-FPM,
request_terminate_timeout, once set, ends a stream without the closing message, see
Web servers. Pass 0 for "until the client
or the runtime ends it".
Over stdio the lifetime does not apply: a stream stays open until the client sends
notifications/cancelled for it, see Over stdio.
On both transports, a stream that can carry nothing is acknowledged and closed right away. This happens when the server has no bus, when it announces none of the types the client asked for, or when the client asked for none.