Skip to content

fix: SSE resource subscribe endpoint yielding raw dicts instead of SSE-formatted strings#3595

Merged
crivetimihai merged 3 commits intomainfrom
sse-resource-subscribe-endpoint
Mar 11, 2026
Merged

fix: SSE resource subscribe endpoint yielding raw dicts instead of SSE-formatted strings#3595
crivetimihai merged 3 commits intomainfrom
sse-resource-subscribe-endpoint

Conversation

@marekdano
Copy link
Copy Markdown
Collaborator

@marekdano marekdano commented Mar 10, 2026

🐛 Bug-fix PR

📌 Summary

Found this bug while executing manual tests on Resources #2419

The POST /resources/subscribe SSE endpoint at main.py:4864 passes resource_service.subscribe_events() directly to StreamingResponse. However, subscribe_events() yields raw Python dicts, while StreamingResponse expects an async generator of strings or bytes.

This causes the SSE connection to remain open (HTTP 200) but no events are ever written to the stream. Clients stay connected but never receive resource change notifications (resource_updated, resource_added, resource_deleted, etc.) over SSE.

🔁 Reproduction Steps

  1. Terminal 1 — open SSE stream (should stay open now):
curl -N -X POST "http://localhost:8080/resources/subscribe" \
      -H "Authorization: Bearer $TOKEN" \
      -H "Accept: text/event-stream"
  1. Terminal 2 — update the resource to trigger a notification:
curl -s -X PUT "http://localhost:8080/resources/$RESOURCE_ID" \
      -H "Authorization: Bearer $TOKEN" \
      -H "Content-Type: application/json" \
      -d '{
        "resource": {
          "uri": "file:///config/settings.json",
          "name": "config-file",
          "description": "Updated settings",
          "mimeType": "application/json",
          "content": "{\"theme\": \"light\", \"lang\": \"fr\"}"
        },
        "visibility": "public"
      }' | jq .
  1. Terminal 1 should now show (but does not show):
data: {"type":"resource_updated","data":{"id":"...","uri":"file:///config/settings.json",...},"timestamp":"..."}

🐞 Root Cause

The EventService already has a correct event_generator() method that formats events as data: {...}\n\n, but it was not being used by this endpoint.

💡 Fix Description

Wrap the async generator in an SSE formatter that serializes each event dict to the standard SSE data: {...}\n\n format before yielding to StreamingResponse:

  async def sse_generator():
      async for event in resource_service.subscribe_events(user_email=user_email, token_teams=token_teams):
          yield f"data: {orjson.dumps(event).decode()}\n\n"

  return StreamingResponse(sse_generator(), media_type="text/event-stream")

This aligns with the pattern used by EventService.event_generator() and the SSE specification (each event prefixed with data: and terminated with \n\n).

🧪 Verification

Check Command Status
Lint suite make lint
Unit tests make test
Coverage ≥ 80 % make coverage
Manual regression no longer fails steps / screenshots

📐 MCP Compliance (if relevant)

  • Matches current MCP spec
  • No breaking change to MCP clients

✅ Checklist

  • Code formatted (make black isort pre-commit)
  • No secrets/credentials committed

@msureshkumar88 msureshkumar88 self-requested a review March 10, 2026 21:11
@marekdano marekdano force-pushed the sse-resource-subscribe-endpoint branch from ed74a15 to fbc2efe Compare March 11, 2026 09:47
msureshkumar88
msureshkumar88 previously approved these changes Mar 11, 2026
Copy link
Copy Markdown
Collaborator

@msureshkumar88 msureshkumar88 left a comment

Choose a reason for hiding this comment

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

PR #3595 review complete. Recommendation: ✅ Approve

Summary

This PR fixes a critical SSE protocol compliance bug in the /resources/subscribe endpoint. The implementation is clean, well-tested, and maintains all security controls.

Key Findings

✅ Strengths:

  • Correctly implements SSE format: data: {json}\n\n
  • Comprehensive test coverage with new test_subscribe_resource_events_sse_format
  • Maintains existing authentication/authorization via @require_permission("resources.read")
  • Uses efficient orjson serialization
  • No security vulnerabilities introduced

@marekdano marekdano added the release-fix Critical bugfix required for the release label Mar 11, 2026
Marek Dano and others added 3 commits March 11, 2026 17:52
…E-formatted strings

Signed-off-by: Marek Dano <Marek.Dano@ibm.com>
Signed-off-by: Marek Dano <Marek.Dano@ibm.com>
Move the orjson import from inside test function to module-level imports
(PEP 8 compliance). Align sse_generator() docstring with the existing
generate_events() pattern used by the roots subscribe endpoint.

Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
@crivetimihai
Copy link
Copy Markdown
Member

Rebased onto main (clean, no conflicts) and made minor cleanup:

  • Moved import orjson from inside test function to module-level imports (PEP 8)
  • Aligned sse_generator() docstring to match the generate_events() style used by the roots subscribe endpoint for consistency
  • Ran isort/black to fix import ordering and slice spacing

No logic changes — the fix itself is correct and consistent with existing SSE patterns in the codebase.

Copy link
Copy Markdown
Member

@crivetimihai crivetimihai left a comment

Choose a reason for hiding this comment

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

LGTM. The fix is correct — resource_service.subscribe_events() yields raw dicts, and StreamingResponse needs strings. The SSE formatting pattern (data: {orjson.dumps(event).decode()}\n\n) is consistent with the roots subscribe endpoint and EventService.event_generator(). No security or performance concerns. Tests cover both the endpoint contract and the wire format.

@crivetimihai crivetimihai merged commit 0ff26c3 into main Mar 11, 2026
39 checks passed
@crivetimihai crivetimihai deleted the sse-resource-subscribe-endpoint branch March 11, 2026 20:17
MohanLaksh pushed a commit that referenced this pull request Mar 12, 2026
…E-formatted strings (#3595)

* fix: SSE resource subscribe endpoint yielding raw dicts instead of SSE-formatted strings

Signed-off-by: Marek Dano <Marek.Dano@ibm.com>

* fix: lint issue in main.py

Signed-off-by: Marek Dano <Marek.Dano@ibm.com>

* fix: move orjson import to module level and align docstring style

Move the orjson import from inside test function to module-level imports
(PEP 8 compliance). Align sse_generator() docstring with the existing
generate_events() pattern used by the roots subscribe endpoint.

Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>

---------

Signed-off-by: Marek Dano <Marek.Dano@ibm.com>
Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
Co-authored-by: Marek Dano <Marek.Dano@ibm.com>
Co-authored-by: Mihai Criveti <crivetimihai@gmail.com>
Yosiefeyob pushed a commit that referenced this pull request Mar 13, 2026
…E-formatted strings (#3595)

* fix: SSE resource subscribe endpoint yielding raw dicts instead of SSE-formatted strings

Signed-off-by: Marek Dano <Marek.Dano@ibm.com>

* fix: lint issue in main.py

Signed-off-by: Marek Dano <Marek.Dano@ibm.com>

* fix: move orjson import to module level and align docstring style

Move the orjson import from inside test function to module-level imports
(PEP 8 compliance). Align sse_generator() docstring with the existing
generate_events() pattern used by the roots subscribe endpoint.

Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>

---------

Signed-off-by: Marek Dano <Marek.Dano@ibm.com>
Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
Co-authored-by: Marek Dano <Marek.Dano@ibm.com>
Co-authored-by: Mihai Criveti <crivetimihai@gmail.com>
Signed-off-by: Yosief Eyob <yosiefogbazion@gmail.com>
calculus-ask pushed a commit to calculus-ask/mcp-context-forge that referenced this pull request Mar 18, 2026
…E-formatted strings (IBM#3595)

* fix: SSE resource subscribe endpoint yielding raw dicts instead of SSE-formatted strings

Signed-off-by: Marek Dano <Marek.Dano@ibm.com>

* fix: lint issue in main.py

Signed-off-by: Marek Dano <Marek.Dano@ibm.com>

* fix: move orjson import to module level and align docstring style

Move the orjson import from inside test function to module-level imports
(PEP 8 compliance). Align sse_generator() docstring with the existing
generate_events() pattern used by the roots subscribe endpoint.

Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>

---------

Signed-off-by: Marek Dano <Marek.Dano@ibm.com>
Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
Co-authored-by: Marek Dano <Marek.Dano@ibm.com>
Co-authored-by: Mihai Criveti <crivetimihai@gmail.com>
Signed-off-by: KRISHNAN, SANTHANA <sk8069@exo.att.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release-fix Critical bugfix required for the release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants