Skip to content

feat(mcp): correlated subscription streams (MCP 2026-07-28 subscriptions/listen) - #2512

Merged
topcheer merged 1 commit into
mainfrom
sa-69-subscriptions-listen
Sep 17, 2026
Merged

topcheer merged 1 commit into
mainfrom
sa-69-subscriptions-listen

Conversation

@topcheer

Copy link
Copy Markdown
Owner

Summary

Implements the client side of the MCP 2026-07-28 subscription revision (subscriptions/listen): correlated notification streams that make list-change notifications attributable to a specific connection when an agent talks to several MCP servers concurrently.

Changes

  • internal/mcp/subscriptions.go (new): full Subscription lifecycle —
    • ListenSubscriptions sends subscriptions/listen with the exact change set ggcode needs and waits for the spec notifications/subscriptions/acknowledged control message, which carries the granted subset.
    • Graceful stream termination and client-side cancel via notifications/cancelled referencing the listen request id.
    • Protocol MUST gating: notifications carrying a _meta subscription id that is unknown or unacknowledged are dropped, so a buggy server cannot poison cache invalidation; valid correlated traffic flows through the unchanged legacy dispatch.
    • EnableModernSubscriptions self-detects: a legacy server -32601 latches a permanent per-connection downgrade.
  • internal/mcp/client.go: subscription registry, a single routing hook at the top of the notification dispatch path (zero behavior change for uncorrelated traffic), Abort closes all subscriptions.
  • internal/plugin/mcp_loader.go: auto-enable on the shared connect path so auto-reconnect re-opens the stream.
  • Tests: pipe-based NDJSON stdio fake-server harness (ack/graceful, downgrade latching with wire-count, cancelled propagation, MUST-gating matrix, abort teardown, filter-diff). Race-clean under -race -count=2. HTTP mocks now echo real request ids and answer the probe with spec -32601.
  • Docs: new Subscription Streams section in docs/guide/mcp.md.

Compatibility

  • stdio: streaming listen supported; HTTP probes and gracefully downgrades; ws explicitly rejected; legacy servers unchanged after one probe.

🤖 Generated with ggcode

…ons/listen)

Implement the client side of the MCP 2026-07-28 subscription revision so
list-change notifications become attributable to a specific connection
when an agent talks to several MCP servers concurrently.

- internal/mcp/subscriptions.go: Subscription lifecycle (listen request,
  acknowledged control message with granted-filter subset, graceful
  stream closure, notifications/cancelled on client-side cancel, MUST
  gating that drops notifications correlating to unknown/unacked
  subscriptions), plus a self-detecting EnableModernSubscriptions entry
  point that latches a permanent downgrade on legacy -32601 servers.
  stdio transports stream; ws is explicitly rejected.
- internal/mcp/client.go: subscription registry fields, notification
  routing hook in the legacy dispatch path (uncorrelated traffic is
  untouched), Abort closes all open subscriptions.
- internal/plugin/mcp_loader.go: setupModernSubscriptions on the shared
  connect path so reconnects re-open the stream automatically.
- Test mocks now echo real request ids (the probe occupies an id slot);
  new pipe-based stdio fake-server tests cover ack/graceful, legacy
  downgrade latching, cancelled propagation, MUST-gating matrix, abort
  teardown; race-clean under -race -count=2.
- docs/guide/mcp.md: document the feature and its fallback semantics.

Co-Authored-By: ggcode <noreply@ggcode.dev>

Co-Authored-By: ggcode <noreply@ggcode.dev>

@topcheer topcheer left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

复审通过 ✅(三重点核销)

① 与 #2480 关联升级——叠加非替代:listen 流通知 correlate 后剥离 _meta 转发到既有 legacy handler(工具/资源刷新链零改动收 correlation 收益);#2480 的 per-URI 订阅在 modern 服务器上由 filter.ResourceSubscriptions 承载(升级面),legacy 服务器维持原路径——单订阅单流(modernSub 唯一+活流 short-circuit),无多 listen 扇出需求面(filter 合并即全量)。

② correlation ID 作用域:subscriptionId=listen 请求的 JSON-RPC id(服务器 echo 回)——client 实例内 nextID 单调分配=天然唯一;MUST-gating:未 ack/未知 subscriptionId 通知=协议违反 drop+log(服务器不得先发后 ack 的客户端侧强制)——乱序攻击面闭合。

③ 与 #2510 stateless 兼容:-32601 永久降级(对齐 GET 流 405 降级形态)——legacy 服务器零重复调用;listen 长流是请求内长响应(stdio waiter/HTTP SSE POST 内嵌)非跨请求会话——stateless 的 per-request _meta 与长流响应正交;15s ack 钳+取消走既有 notifyCancelled(复用 #2490 管道)。WS 显式不支持(legacy bridge 域声明)。

CI 全绿。可合并。

@topcheer
topcheer merged commit f3e3003 into main Sep 17, 2026
9 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.

1 participant