Modern push delivery: honor subscriptions and deliver notifications over subscriptions/listen
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 2.2k
- Forks
- 300
- Avg merge
- 1d 15h
- Merged PRs (30d)
- 184
Description
Summary
#6050 implements subscriptions/listen as a capability-negative handler: it acknowledges the client's requested subscription set intersected against vMCP's advertised capabilities, and since vMCP advertises all four push capabilities as false, the honored set is always empty and the stream closes immediately.
That is conformant and deliberate — the specification's acknowledgement is the designated mechanism for declining, and schema/draft/schema.ts:1354-1363 says the honored set "only includes notification types the server actually supports; if the client requested an unsupported type … it is omitted from this set." But it means vMCP has nothing to push. This issue tracks making delivery real.
What has to change
1. Start advertising the capabilities. newModernCapabilities builds zero-value structs; Tools.ListChanged, Prompts.ListChanged, Resources.ListChanged and Resources.Subscribe are never set true anywhere in the tree, and no config field, env var, CRD field or YAML key reaches them. Flipping any of them is a source change.
2. Build per-subscription backend fan-in. The delivery source does not exist on the Modern path:
- Resource subscriptions are acknowledgement-level only — vMCP records
resources/subscribebut never forwards a backend'snotifications/resources/updated. list_changedpropagation depends on a persistent per-backend connection thatpkg/vmcp/session/factory.godeliberately does not open for Modern backends.buildListChangedSinkhas a single production call site, inside the SDKClientSessionregistration hook.dispatchModerncreates no session, so for a Modern client the sink is never built at all, regardless of backend revision.
3. Honour the delivery-side MUST. schema/draft/schema.ts:120-133 requires the io.modelcontextprotocol/subscriptionId key on every notification delivered via a subscriptions/listen stream, not just the acknowledgement. A correct acknowledgement followed by a bare notifications/tools/list_changed is non-conformant. This is recorded at the hand-off WARN in modern_subscriptions.go.
4. Decide whether serverStreamRegistry is the fan-out seam. #6050 deliberately did not reuse it — with an empty honored set there is no fan-out, so adapting an unexported registry from pkg/transport/proxy/streamable would have added an abstraction with zero consumers. docs/arch/03-transport-architecture.md now frames it as the seam for delivery rather than for the handler, so that question becomes live here.
Coverage that must be restored at the same time
Three code paths are currently unreachable because the honored set is always empty, and #6050 removed or omitted tests for them rather than assert on unenterable branches. Whoever flips a capability flag re-enables all three and must restore coverage:
- the
DiscoverCapabilitiescall itself (skipped today by a ceiling pre-check, so the fan-out is not paid for a result that would be discarded) - that call's error path, whose test was removed
- the non-empty-honored branch of the handler
This is recorded in the negative capability-regression test's comment, which asserts all four flags are false precisely so that a flip cannot happen silently.
Note on DoS
The pre-check exists because subscriptions/listen otherwise performs an un-metered backend fan-out whose result is provably always discarded. Once delivery is real, a long-lived POST per subscription becomes a genuine resource-consumption surface — concurrent stream caps and per-subscription accounting want designing in, not bolting on. Also note resourceSubscriptions URIs arrive from the client and are currently echoed back unfiltered; per-URI authorization needs to apply before any subscription is honored.
Refs #5743, #6050.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with modern_subscriptions.go, newModernCapabilities, and pkg/vmcp/session/factory.go; compare their current behavior with schema/draft/schema.ts:120-133 and 1354-1363. Review buildListChangedSink, the negative capability-regression test, and docs/arch/03-transport-architecture.md. Done means supported subscriptions deliver conformant notifications, authorization and resource limits are addressed, and coverage restores the DiscoverCapabilities paths and non-empty branch.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go, typescript
- Domain
- api, backend, distributed-systems
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100