Skip to content

fix: SessionRegistryMiddleware compatible with MCP SDK 1.27.0 SSE#177

Merged
cmeans-claude-dev[bot] merged 7 commits into
mainfrom
fix/session-registry-sse
Apr 8, 2026
Merged

fix: SessionRegistryMiddleware compatible with MCP SDK 1.27.0 SSE#177
cmeans-claude-dev[bot] merged 7 commits into
mainfrom
fix/session-registry-sse

Conversation

@cmeans-claude-dev
Copy link
Copy Markdown
Contributor

@cmeans-claude-dev cmeans-claude-dev Bot commented Apr 8, 2026

Summary

  • Root cause: MCP SDK 1.27.0 changed initialize responses from JSON to SSE (text/event-stream). The middleware's replay_receive returned synthetic http.disconnect after body replay, causing EventSourceResponse to abort before sending headers (500 error)
  • _buffer_body: forwards to real receive after replay instead of synthetic disconnect
  • _handle_subsequent: streams 2xx responses immediately (SSE-safe), only buffers errors for re-init interception
  • _reinitialize: init_receive/replay_receive block with anyio.sleep(3600) loop instead of faking disconnect

Fixes #176.

QA

Prerequisites

  • pip install -e ".[dev]"
  • Deploy to test instance on alternate port (AWARENESS_PORT=8421) with AWARENESS_SESSION_DATABASE_URL set

Manual tests (via MCP tools)

    • Initialize with session registry — SSE init test verifies 200 with text/event-stream and session registered
    • Subsequent request — SSE subsequent test verifies streaming response passes through
    • Cross-node re-init — SSE re-init test verifies 3-call flow (400 → init → replay) with SSE responses
    • Claude.ai connection — requires production/staging deployment
    • 725 tests passpytest tests/ — 725 passed

🤖 Generated with Claude Code

MCP SDK 1.27.0 changed initialize responses from JSON to SSE
(text/event-stream). The middleware's ASGI wrappers were incompatible:

1. _buffer_body: replay_receive returned synthetic http.disconnect after
   replay, which caused EventSourceResponse to abort before sending
   headers ("ASGI callable returned without starting response" 500).
   Fix: forward to the real receive after replay so SSE disconnect
   detection works against the actual client connection.

2. _handle_subsequent: buffered ALL response parts before sending,
   which breaks SSE streaming. Fix: stream 2xx responses immediately,
   only buffer error responses (400/404) for potential cross-node
   re-init interception. Error responses are always JSON, not SSE.

3. _reinitialize: init_receive and replay_receive returned body once
   then nothing, causing the same SSE abort. Fix: block with
   anyio.sleep_forever() after body delivery so the SSE transport
   has a live receive for disconnect detection.

Fixes #176.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@cmeans-claude-dev cmeans-claude-dev Bot added the Dev Active Developer is actively working on this PR; QA should not start label Apr 8, 2026
@github-actions github-actions Bot added the Awaiting CI Dev complete, waiting for CI/Codecov to pass before QA label Apr 8, 2026
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 8, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

cmeans-claude-dev[bot] and others added 4 commits April 8, 2026 15:30
… 725

Exercises the _reinitialize code path with SSE responses that call
receive() for disconnect detection. The two remaining uncovered lines
(494, 541) are unreachable returns after anyio.sleep_forever() — they
exist only to satisfy the type checker.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace anyio.sleep_forever() + unreachable return with while/sleep
loop that mypy and coverage both handle correctly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@cmeans-claude-dev cmeans-claude-dev Bot added Ready for QA Dev work complete — QA can begin review and removed Dev Active Developer is actively working on this PR; QA should not start labels Apr 8, 2026
@github-actions github-actions Bot removed the Awaiting CI Dev complete, waiting for CI/Codecov to pass before QA label Apr 8, 2026
@cmeans cmeans added QA Active QA is actively reviewing; Dev should not push changes and removed Ready for QA Dev work complete — QA can begin review labels Apr 8, 2026
@cmeans
Copy link
Copy Markdown
Owner

cmeans commented Apr 8, 2026

Adding QA Active — reviewing SSE compatibility fix.

Copy link
Copy Markdown
Owner

@cmeans cmeans left a comment

Choose a reason for hiding this comment

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

QA Review — Round 1

Critical fix for MCP SDK 1.27.0 SSE compatibility. The three changes are well-targeted:

  1. _buffer_body — forwards to real receive() after replay instead of synthetic disconnect. Prevents EventSourceResponse from aborting.
  2. _handle_subsequent — 2xx streams immediately (SSE-safe), errors buffered for re-init. Clean split.
  3. _reinitializeinit_receive/replay_receive block with anyio.sleep(3600) loop instead of returning disconnect.

SSE test stubs faithfully reproduce the EventSourceResponse task group pattern (stream + disconnect listener), which validates the fix against the actual failure mode.

Checks

Check Result
57/57 session tests pass
725/725 full suite pass
README test count matches (725)
CHANGELOG entry present
CI all green

Findings

1. [Substantive] CHANGELOG says anyio.sleep_forever() but code uses while True: await anyio.sleep(3600)

The CHANGELOG entry reads "uses anyio.sleep_forever() for disconnect detection" but the actual code uses while True: await anyio.sleep(3600). Either update the CHANGELOG to match the code, or use anyio.sleep_forever() in the code (which would be cleaner — single call, no loop).

PR checkboxes

4/5 checked. Test 4 (Claude.ai connection) requires production deployment.

Verdict

Finding #1 is doc drift — CHANGELOG doesn't match code. Everything else is clean.

@cmeans cmeans added QA Failed QA found issues — needs dev attention and removed QA Active QA is actively reviewing; Dev should not push changes labels Apr 8, 2026
@cmeans
Copy link
Copy Markdown
Owner

cmeans commented Apr 8, 2026

Applying QA Failed — CHANGELOG says anyio.sleep_forever() but code uses anyio.sleep(3600) loop. Fix either to match the other.

…rever)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@cmeans-claude-dev
Copy link
Copy Markdown
Contributor Author

Dev response

QA finding: CHANGELOG says sleep_forever but code uses sleep loop

  • Fixed in 6d111e2 — CHANGELOG updated to say "blocks until task group cancellation" instead of referencing anyio.sleep_forever()

Ready for re-review.

@cmeans-claude-dev cmeans-claude-dev Bot added Awaiting CI Dev complete, waiting for CI/Codecov to pass before QA and removed QA Failed QA found issues — needs dev attention labels Apr 8, 2026
@github-actions github-actions Bot added Ready for QA Dev work complete — QA can begin review and removed Awaiting CI Dev complete, waiting for CI/Codecov to pass before QA labels Apr 8, 2026
@cmeans cmeans added QA Active QA is actively reviewing; Dev should not push changes and removed Ready for QA Dev work complete — QA can begin review labels Apr 8, 2026
Copy link
Copy Markdown
Owner

@cmeans cmeans left a comment

Choose a reason for hiding this comment

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

QA Review — Round 2

CHANGELOG fixed — now says "blocks until task group cancellation" instead of naming anyio.sleep_forever(). Matches the actual implementation. CI green. Zero findings. Verdict: Pass.

@cmeans cmeans added Ready for QA Signoff QA passed — ready for maintainer final review and merge and removed QA Active QA is actively reviewing; Dev should not push changes labels Apr 8, 2026
@cmeans
Copy link
Copy Markdown
Owner

cmeans commented Apr 8, 2026

Applying Ready for QA Signoff — CHANGELOG fixed, CI green, zero findings.

@cmeans cmeans removed the Ready for QA Signoff QA passed — ready for maintainer final review and merge label Apr 8, 2026
Copy link
Copy Markdown
Owner

@cmeans cmeans left a comment

Choose a reason for hiding this comment

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

LGTM

@cmeans cmeans added the QA Approved Manual QA testing completed and passed label Apr 8, 2026
@cmeans-claude-dev cmeans-claude-dev Bot merged commit 33ab911 into main Apr 8, 2026
43 checks passed
@cmeans-claude-dev cmeans-claude-dev Bot deleted the fix/session-registry-sse branch April 8, 2026 21:37
@cmeans cmeans mentioned this pull request Apr 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

QA Approved Manual QA testing completed and passed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix: SessionRegistryMiddleware incompatible with MCP SDK 1.27.0

1 participant