Summary
MCP resources/subscribe (the native MCP server's live resource-change-notification mechanism) fails to establish 88% of the time (14/16 trials) under Harper's default multi-worker deployment shape, whenever a client's initialize/GET-SSE-stream/subscribe calls aren't sticky-routed to the same worker.
sessionRegistry.ts documents this as a known, deliberate v1 limitation:
/**
* Per-worker only. A session's GET stream is bound to the worker that
* accepted the GET; cross-worker fan-out isn't attempted in v1.
*/
That framing reads like a minor deferred nice-to-have. In practice, under Harper's actual default multi-worker topology (no external load-balancer session affinity documented or configured out of the box), a resources/subscribe call landing on a worker OTHER than the one holding the client's open SSE GET stream hard-rejects with:
-32602 "open the GET SSE stream before subscribing to resources"
— even though the stream is genuinely open, just on a sibling worker. Measured empirically at an 88% failure rate for session establishment across 16 trials with threads.count: 4. This makes resources/subscribe effectively unusable by default for anyone not manually pinning client sessions to a single worker.
What's clean, for contrast
Once a subscription IS successfully established, cross-worker notification delivery is flawless: 20/20 sequential cross-worker writes notified 1:1 (~2ms avg latency), 25/25 concurrent-burst writes to distinct records → exactly 25 notifications, zero loss/duplication. The gap is specifically in establishing a subscription across workers, not in delivering to an established one.
Pattern note
Same shape as two other independently-discovered issues this session: #1610's MCP per-client rate limiter (in-memory per worker, ~4× weaker than configured under the default multi-worker topology) and #1736's registerOperation() (per-worker OPERATION_FUNCTION_MAP, unreachable across workers). Three separate instances of MCP-layer state assumed process-wide but actually living per-worker, each discovered independently. Might be worth a structural fix (a shared cross-worker session/registry seam) rather than three point patches — flagging for visibility, not proposing scope here.
Suggested direction
Either implement the cross-worker fan-out the code comment already anticipates ("isn't attempted in v1"), or — if that's out of scope near-term — surface guidance that resources/subscribe requires session-affinity routing in front of a multi-worker deployment, since nothing in the default setup currently provides or documents that requirement.
Repro
Local repro (not yet in the tracked suite): integrationTests/qa-scratch/qa526-mcp-subscribe-allowread-multiworker.test.ts — the multi-worker establishment-failure rate is measured/logged there rather than hard-asserted; raw trial log available on request.
— KrAIs 🤖 (exploratory QA, on Kris's behalf)
Summary
MCP
resources/subscribe(the native MCP server's live resource-change-notification mechanism) fails to establish 88% of the time (14/16 trials) under Harper's default multi-worker deployment shape, whenever a client'sinitialize/GET-SSE-stream/subscribecalls aren't sticky-routed to the same worker.sessionRegistry.tsdocuments this as a known, deliberate v1 limitation:That framing reads like a minor deferred nice-to-have. In practice, under Harper's actual default multi-worker topology (no external load-balancer session affinity documented or configured out of the box), a
resources/subscribecall landing on a worker OTHER than the one holding the client's open SSE GET stream hard-rejects with:— even though the stream is genuinely open, just on a sibling worker. Measured empirically at an 88% failure rate for session establishment across 16 trials with
threads.count: 4. This makesresources/subscribeeffectively unusable by default for anyone not manually pinning client sessions to a single worker.What's clean, for contrast
Once a subscription IS successfully established, cross-worker notification delivery is flawless: 20/20 sequential cross-worker writes notified 1:1 (~2ms avg latency), 25/25 concurrent-burst writes to distinct records → exactly 25 notifications, zero loss/duplication. The gap is specifically in establishing a subscription across workers, not in delivering to an established one.
Pattern note
Same shape as two other independently-discovered issues this session:
#1610's MCP per-client rate limiter (in-memory per worker, ~4× weaker than configured under the default multi-worker topology) and#1736'sregisterOperation()(per-workerOPERATION_FUNCTION_MAP, unreachable across workers). Three separate instances of MCP-layer state assumed process-wide but actually living per-worker, each discovered independently. Might be worth a structural fix (a shared cross-worker session/registry seam) rather than three point patches — flagging for visibility, not proposing scope here.Suggested direction
Either implement the cross-worker fan-out the code comment already anticipates ("isn't attempted in v1"), or — if that's out of scope near-term — surface guidance that
resources/subscriberequires session-affinity routing in front of a multi-worker deployment, since nothing in the default setup currently provides or documents that requirement.Repro
Local repro (not yet in the tracked suite):
integrationTests/qa-scratch/qa526-mcp-subscribe-allowread-multiworker.test.ts— the multi-worker establishment-failure rate is measured/logged there rather than hard-asserted; raw trial log available on request.— KrAIs 🤖 (exploratory QA, on Kris's behalf)