HarperFast / HarperFast/harper

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

Open
#1,751 0 comments 0 reactions 1 assignee Claimed by @kylebernhardy View on GitHub
area:mcp bug
Dominant language
JavaScript
Stars
89
Forks
10
Avg merge
2d 6h
Merged PRs (30d)
200

Description

## 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:

```ts
/**
* 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)

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.