Skip to content

[Server] End a listen stream that can carry nothing right after its acknowledgment - #618

Open
BrocksiNet wants to merge 1 commit into
modelcontextprotocol:mainfrom
BrocksiNet:fix/listen-empty-filter
Open

BrocksiNet wants to merge 1 commit into
modelcontextprotocol:mainfrom
BrocksiNet:fix/listen-empty-filter

Conversation

@BrocksiNet

Copy link
Copy Markdown
Contributor

Summary

  • When the filter agreed for a subscriptions/listen request is empty, send the acknowledgment with the empty notifications object and the graceful resultType: complete closure right away, instead of polling every 250 ms until the subscription lifetime ends. This applies to HTTP and stdio.
  • Add NotificationFilter::isEmpty() for the check, and move the closure frame into a helper that both exits of the stream use.
  • The spec allows a server-initiated end with a completion result after the acknowledgment ("Graceful Closure" in the 2026-07-28 subscriptions pattern), so the client can still tell an intended end from a dropped transport.

Motivation: under PHP-FPM such a stream holds a worker for the full lifetime even though nothing can ever be delivered, and a reconnecting client holds one almost permanently. We ran into this in Shopware's MCP server: shopware/shopware#21919

Fixes #617

Test plan

  • New unit tests: a listen stream with an empty agreed filter (type not announced, resource updates without resources.subscribe, nothing requested, no notifications member) yields exactly the acknowledgment and the completion, with no poll tick and no waiting on the 60 s lifetime; the same over stdio. NotificationFilterTest covers isEmpty() and an intersection that ends up empty.
  • Without the fix the new test runs the full lifetime and fails (239 frames instead of 2)
  • make tests (2962 tests, 8 skipped)
  • make phpstan
  • make cs
  • composer validate --strict

…cknowledgment

When the agreed filter of a `subscriptions/listen` request is empty (the
server announces none of the requested types, or the client asks for
none), nothing can ever be delivered on the stream. It still polled every
250 ms until the subscription lifetime ended, holding a PHP-FPM worker for
the full lifetime, and a reconnecting client held one almost permanently.

Send the acknowledgment with the empty `notifications` filter and close the
subscription gracefully with `resultType: complete` right away. The spec
allows the server to end a subscription on its own initiative this way.
Adds `NotificationFilter::isEmpty()`.
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-26T09:46:21.025558Z 938512d PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chr-hertel chr-hertel added Server Issues & PRs related to the Server component bug Something isn't working 2026-07-28 All issues and PRs related to the spec release 2026-07-28 labels Oct 27, 2026
@chr-hertel
chr-hertel requested a balanced review from Copilot October 27, 2026 23:42

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.

🟢 Approval recommended

No active review issues were identified for the change to immediately close empty-filter subscription streams.

No open issues 🎉

What changed in this PR

Ends stateless subscriptions/listen streams immediately after acknowledgment when the agreed notification filter is empty, avoiding unnecessary polling and PHP-FPM worker usage. The behavior applies to both HTTP and stdio.

Changes:

  • Adds NotificationFilter::isEmpty() and reuses a completion-frame helper for both stream exits.
  • Adds regression tests for empty filters and immediate graceful closure.
  • Documents the behavior and records it in the changelog.
File Description
tests/​Unit/​Server/​Stateless/​StatelessProtocolTest.php Tests immediate acknowledgment and completion over HTTP and stdio.
tests/​Unit/​Server/​Stateless/​NotificationFilterTest.php Tests filter emptiness and empty capability intersections.
src/​Server/​Stateless/​StatelessProtocol.php Closes empty-filter streams immediately and extracts completion framing.
src/​Server/​Stateless/​NotificationFilter.php Adds the empty-filter check.
docs/​run/​subscriptions.md Documents immediate closure for streams that cannot deliver notifications.
CHANGELOG.md Records the fix and new filter method under version 0.9.1.

Review effort: Balanced

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

2026-07-28 All issues and PRs related to the spec release 2026-07-28 bug Something isn't working Server Issues & PRs related to the Server component

Projects

None yet

Development

Successfully merging this pull request may close these issues.

subscriptions/listen with an empty agreed filter holds the stream (and a PHP-FPM worker) for the full subscription lifetime

3 participants