Skip to content

[Server] Add Psr6SessionStore and Psr6NotificationBus - #635

Merged
chr-hertel merged 1 commit into
modelcontextprotocol:mainfrom
nicolas-grekas:psr6-cache-stores
Oct 28, 2026
Merged

chr-hertel merged 1 commit into
modelcontextprotocol:mainfrom
nicolas-grekas:psr6-cache-stores

Conversation

@nicolas-grekas

@nicolas-grekas nicolas-grekas commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

As discussed in symfony/ai#2853: the Symfony MCP Bundle wraps cache.app, a PSR-6 pool, in Psr16Cache to use Psr16SessionStore and Psr16NotificationBus. That's the only reason it requires psr/simple-cache. With these two classes, the bundle can use the pool directly.

Psr6SessionStore and Psr6NotificationBus take the same arguments as their PSR-16 siblings and use the same keys. With symfony/cache, a session written by Psr16SessionStore through Psr16Cache can be read by Psr6SessionStore from the pool, so apps keep their sessions when they switch.

Unlike Psr16SessionStore, Psr6SessionStore catches no exception: PSR-6 doesn't allow pools to throw anything but InvalidArgumentException for an invalid key, while PSR-16 has no such rule.

Builder::setDiscovery() also accepts a PSR-6 pool, since that's a few lines in CachedDiscoverer.

It also fixes Psr16NotificationBus starting its sequence over after being quiet for longer than its TTL, which made subscribers miss the next notifications (spotted by the review below).

Both notification buses read their backlog with one getMultiple() or getItems() call, not one call per entry.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Cursor expiry can reset the sequence in both notification buses, causing listeners to silently miss newly published notifications.

1 open issue
What changed in this PR

Adds direct PSR-6 cache support to the SDK’s session storage, notification bus, and discovery caching, allowing integrations to use cache pools without a PSR-16 wrapper.

Changes:

  • Introduces Psr6SessionStore and Psr6NotificationBus alongside their PSR-16 counterparts.
  • Extends discovery caching to accept PSR-6 pools.
  • Adds tests and updates documentation, examples, and the changelog.
File Description
tests/​Unit/​Server/​Subscription/​NotificationBusTest.php Adds PSR-6 coverage and shared backlog tests.
tests/​Unit/​Server/​Session/​SessionTest.php Includes PSR-6 in shared session tests.
tests/​Unit/​Server/​Session/​Psr6SessionStoreTest.php Tests failures, TTL, and PSR-16 session compatibility.
tests/​Unit/​Capability/​Discovery/​CachedDiscovererTest.php Tests PSR-6 discovery caching and clearing.
src/​Server/​Subscription/​Psr6NotificationBus.php Adds a PSR-6 notification bus.
src/​Server/​Subscription/​InMemoryNotificationBus.php Documents the PSR-6 alternative.
src/​Server/​Session/​Psr6SessionStore.php Adds PSR-6 session storage.
src/​Server/​Builder.php Accepts PSR-6 discovery pools and documents the new bus.
src/​Capability/​Discovery/​CachedDiscoverer.php Supports PSR-6 cache reads and writes.
examples/​server/​stateless-lifecycle/​server.php Updates comments to mention the PSR-6 bus.
docs/​run/​subscriptions.md Shows PSR-6 notification bus configuration.
docs/​run/​sessions.md Documents PSR-6 storage and Redis examples.
docs/​run/​server-builder.md Updates discovery types and cache examples.
docs/​get-started/​installation.md Lists PSR-6 cache implementation options.
CHANGELOG.md Records the new PSR-6 support.

Review effort: Balanced

{
$item = $this->cache->getItem($this->key($suffix));

$this->cache->save($item->set($value)->expiresAfter($this->ttl));

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 53d28ee, in both buses: the counter has no TTL anymore, and a reader whose cursor is ahead of a counter that got lost (evicted or cleared) reads what the bus holds.

@nicolas-grekas
nicolas-grekas force-pushed the psr6-cache-stores branch 4 times, most recently from bbb8dd8 to ae96f10 Compare October 28, 2026 10:21
They work like their PSR-16 siblings over a PSR-6 cache pool. `Builder::setDiscovery()` also accepts a PSR-6 pool.

The sequence counter of `Psr16NotificationBus` no longer expires, so that its subscribers do not miss the notifications published after a quiet period.

Both notification buses read their backlog with one `getMultiple()` or `getItems()` call, not one call per entry.
$this->cache->set($this->key((string) $sequence), json_encode($notification, \JSON_THROW_ON_ERROR), $this->ttl);
$this->cache->set($this->key(self::CURSOR_KEY), $sequence + 1, $this->ttl);
// No TTL, so that the cursors of subscribers stay valid when the bus goes quiet
$this->cache->set($this->key(self::CURSOR_KEY), $sequence + 1);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did you run into an issue with the ttl - maybe 120 is just too short? not sure about not having one at all

@chr-hertel chr-hertel left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @nicolas-grekas!

@chr-hertel
chr-hertel merged commit 9a7fea0 into modelcontextprotocol:main Oct 28, 2026
28 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants