[Server] Make subscriptions/listen actually deliver (#451)
The stream acknowledged, slept thirty seconds and closed, so tools and resource changes never reached a client — and with resources/subscribe gone, that left the revision with no server-push at all. A notification bus carries them: in-memory where publisher and stream share a process, PSR-16 where php-fpm puts them in different ones. The conformance server proves the latter — a tools/call in one worker reaches a stream held in another.
This commit is contained in:
parent
7dd72d82de
commit
daac88d626
1 changed files with 907 additions and 13 deletions
74
src/Server/Subscription/InMemoryNotificationBus.php
Normal file
74
src/Server/Subscription/InMemoryNotificationBus.php
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* 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\Server\Subscription;
|
||||
|
||||
use Mcp\Exception\InvalidArgumentException;
|
||||
use Mcp\Schema\JsonRpc\Notification;
|
||||
|
||||
/**
|
||||
* A notification bus held in one process's memory.
|
||||
*
|
||||
* Correct for stdio and for persistent runtimes where the whole server lives in
|
||||
* one process (Swoole, FrankenPHP worker mode, ReactPHP). Under PHP-FPM it is
|
||||
* not: the worker that publishes and the worker holding the stream open are
|
||||
* different processes, and a notification published in one is invisible to the
|
||||
* other. {@see Psr16NotificationBus} is the answer there.
|
||||
*
|
||||
* @author Christopher Hertel <mail@christopher-hertel.de>
|
||||
*/
|
||||
final class InMemoryNotificationBus implements NotificationBusInterface
|
||||
{
|
||||
/** @var array<int, Notification> */
|
||||
private array $entries = [];
|
||||
|
||||
private int $next = 0;
|
||||
|
||||
/**
|
||||
* @param int $backlog how many notifications to keep before dropping the oldest
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly int $backlog = 256,
|
||||
) {
|
||||
if ($this->backlog < 1) {
|
||||
throw new InvalidArgumentException(\sprintf('The notification backlog must be at least one entry, got %d.', $this->backlog));
|
||||
}
|
||||
}
|
||||
|
||||
public function publish(Notification $notification): void
|
||||
{
|
||||
$this->entries[$this->next] = $notification;
|
||||
++$this->next;
|
||||
|
||||
// A stream that went away must not make this grow forever.
|
||||
if (\count($this->entries) > $this->backlog) {
|
||||
$this->entries = \array_slice($this->entries, -$this->backlog, preserve_keys: true);
|
||||
}
|
||||
}
|
||||
|
||||
public function cursor(): int
|
||||
{
|
||||
return $this->next;
|
||||
}
|
||||
|
||||
public function since(int $cursor): array
|
||||
{
|
||||
$found = [];
|
||||
|
||||
foreach ($this->entries as $sequence => $notification) {
|
||||
if ($sequence >= $cursor) {
|
||||
$found[] = $notification;
|
||||
}
|
||||
}
|
||||
|
||||
return [$found, $this->next];
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue