Skip to content

MCP resources/subscribe fails to establish 88% of the time under default multi-worker deployment #1751

Description

@kriszyp

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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:mcpModel Context Protocol (MCP) server: protocol, profiles, stdio CLIbugSomething isn't working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions