Skip to content

[recipes] Fix ob-graph MCP GET route causing SSE reconnect storm - #452

Open
eazene wants to merge 1 commit into
NateBJones-Projects:mainfrom
eazene:contrib/eazene/ob-graph-sse-reconnect-fix
Open

[recipes] Fix ob-graph MCP GET route causing SSE reconnect storm#452
eazene wants to merge 1 commit into
NateBJones-Projects:mainfrom
eazene:contrib/eazene/ob-graph-sse-reconnect-fix

Conversation

@eazene

@eazene eazene commented Jul 14, 2026

Copy link
Copy Markdown

Problem

The ob-graph recipe serves MCP only on POST and returns a static {"status":"ok"} JSON on app.get("*"):

app.post("*", async (c) => { ... transport.handleRequest(c) });   // MCP
app.get("*",  (c) => c.json({ status: "ok", ... }));              // <- returns non-stream on the SSE GET

MCP's Streamable HTTP transport opens a long-lived GET SSE stream for server→client messages. Returning a plain JSON body (not text/event-stream) on that GET makes clients treat the stream as immediately dropped and reconnect in a tight loop.

Every other Open Brain MCP function routes all methods to the transport with app.all("*"). Only this recipe splits GET off, so a connected client hammers the endpoint continuously.

Measured on a live deployment: ~1.4 GET requests/sec to this one function — on the order of millions of Edge Function invocations/month, enough to blow the Supabase 500K free tier by itself, with zero real tool usage.

Fix

  • Route all methods (GET included) to the transport (app.postapp.all) so the GET opens a real SSE stream.
  • Remove the static wildcard health GET that shadowed the SSE stream.
  • Move the health probe to dedicated paths (/health and /:fn/health, covering Supabase's /{function-name} path prefix) so it can't intercept "*".

Verification

  • Deployed the equivalent fix to a live project. Before: GET /?key=… returned an instant non-stream JSON. After: the same GET holds the SSE stream open (verified via curl timing out on a held connection), the ~1/sec reconnect loop is gone, and auth is now enforced on GET.
  • deno check passes with the recipe's deno.json import map.
  • Diff is a single file (recipes/ob-graph/index.ts), +15/−4.

🤖 Generated with Claude Code

https://claude.ai/code/session_01LxP7M99m4EF5Ve7rn3SNKH

The ob-graph recipe served MCP only on POST and returned a static
`{status:"ok"}` JSON on `app.get("*")`. MCP's Streamable HTTP transport
opens a long-lived GET SSE stream for server->client messages; returning
a non-stream on that GET makes clients treat the stream as dropped and
reconnect immediately — a tight ~1 request/sec loop.

Every other Open Brain MCP function routes all methods to the transport
via `app.all("*")`. Only this recipe split GET off, so a connected client
hammered the endpoint continuously (measured ~1.4 req/sec, i.e. millions
of Edge Function invocations/month — enough to blow the 500K free tier on
its own).

Fix:
- Route all methods (GET included) to the transport (`app.post` -> `app.all`)
  so the GET opens a real SSE stream.
- Remove the static wildcard health GET that intercepted the SSE stream.
- Move the health probe to dedicated paths (`/health` and `/:fn/health`,
  covering Supabase's function-name path prefix) so it can't shadow "*".

Verified against a live deployment: after the fix the GET holds the SSE
stream open (was an instant non-stream response), the reconnect loop is
eliminated, and auth is now enforced on GET. `deno check` passes with the
recipe's import map.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LxP7M99m4EF5Ve7rn3SNKH
@github-actions github-actions Bot added the recipe Contribution: step-by-step recipe label Jul 14, 2026
@github-actions

Copy link
Copy Markdown

OB1 PR Gate

Folder structure — All files are in allowed directories
Required files — README.md and metadata.json found in all contribution folders
Metadata valid — All metadata.json files passed JSON Schema validation
No credentials — No API keys, tokens, or secrets detected
SQL safety — No destructive SQL or core table modifications
Category artifacts — Required file types present for each category
PR format — Title follows [category] Description format
No binary blobs — No oversized or binary files
README completeness — All READMEs include Prerequisites, Steps, and Expected Outcome
Contribution dependencies — All declared skill and primitive dependencies exist and are linked in README
LLM clarity review — Covered by Claude PR Review workflow
Remote MCP pattern — No local MCP server patterns detected — uses remote MCP correctly
Tool audit link — Extensions/integrations link to the MCP Tool Audit guide
Scope check — All changes are within the contribution folder(s)
Internal links — All relative links in READMEs resolve to existing files

Result: All 15 checks passed! Ready for human review.


Post-Merge Tasks

These don't block merge — they're reminders for admins after this PR lands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

recipe Contribution: step-by-step recipe

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant