fix(mcp-sse): decouple /health liveness from upstream readiness - #151
Conversation
The /health endpoint introduced in verygoodplugins#114 returned HTTP 503 whenever the upstream AutoMem service reported anything other than "healthy" (e.g. FalkorDB/Qdrant disconnected, sync drift). Railway treats 503 as unavailable and blocks the deploy — so the SSE bridge became un-deployable any time its upstream was even partially degraded, even though the bridge itself was perfectly able to serve traffic and return useful errors. Split liveness from readiness: - GET /health now always returns 200 when the Node process is able to serve HTTP. Upstream status is still reported in the response body (status, upstream, upstream_error, upstream_details) for observability. - GET /ready keeps the strict behavior: 200 iff upstream is healthy, 503 otherwise. Use this for orchestrators that should gate traffic on upstream availability. Railway's healthcheckPath should remain /health. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adjusts the MCP SSE bridge’s health semantics so container liveness is not blocked by transient upstream AutoMem degradation, while still providing a strict upstream-gated readiness signal for orchestrators.
Changes:
- Changed
GET /healthto always return HTTP 200 while still reporting upstream status in the JSON body. - Added
GET /readyto return 200 only when upstream is healthy, otherwise 503. - Updated and expanded Node tests to cover the new
/healthand/readybehaviors.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| mcp-sse-server/server.js | Makes /health always 200 and introduces /ready with upstream-gated status codes. |
| mcp-sse-server/test/server.test.js | Updates existing /health test expectations and adds /ready tests for healthy/unreachable upstream. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0429796502
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Address Copilot review on PR verygoodplugins#151: - /health: replace await with void so the first request never blocks on an upstream probe. The body still reports upstream/checked_at for observability; status is always 200 while the process can serve HTTP. - /ready: drop the checked_at guard so readiness always reflects current upstream state. healthProbePromise dedups concurrent hits, so upstream load is bounded by orchestrator cadence rather than per-request fan-out. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Good catch @George-RD , thanks!! |
🤖 I have created a release *beep* *boop* --- ## [0.16.0](v0.15.2...v0.16.0) (2026-06-26) ### Features * **api:** add admin backup endpoint ([#162](#162)) ([8b1f264](8b1f264)) * **api:** support bulk memory associations ([1221e36](1221e36)) * **api:** support bulk memory associations ([#198](#198)) ([28eb916](28eb916)) * **benchmarks:** LongMemEval failure-mode diagnosis harness + judge quota preflight ([#183](#183)) ([f99bece](f99bece)) * **consolidation:** expose cluster threshold and min size as env vars ([#163](#163)) ([7e731f3](7e731f3)) * **enrichment:** expose classification fallback-rate metrics in /enrichment/status ([#188](#188)) ([0b522a9](0b522a9)) * **entity:** harden identity cleanup and repair tooling ([#176](#176)) ([827dfbc](827dfbc)) * **eval:** recall-quality optimization harness — lab foundation + design ([#197](#197)) ([431433e](431433e)) * **graph:** support unbounded visualizer snapshots ([#141](#141)) ([c730128](c730128)) * **lab:** add aged labelled distractor injection ([cc5d546](cc5d546)) * **lab:** add config_complexity simplicity metric ([dfb10d9](dfb10d9)) * **lab:** add distractor_rate_at_k precision guardrail metric ([872eab2](872eab2)) * **lab:** add lab_corpus with parameterized recall ([5e1e071](5e1e071)) * **lab:** add pick_winner scorecard decision rule ([3187eac](3187eac)) * **lab:** add real consolidation pass helper ([48a7d4a](48a7d4a)) * **lab:** isolate production clone restores ([#171](#171)) ([aef90c0](aef90c0)) * **lab:** wire scorecard, distractors, recall params, consolidation into runner ([589ec30](589ec30)) * **recall:** add metadata sidecar search ([#177](#177)) ([4e7956e](4e7956e)) * **recall:** add state_mode=current|history recall alias ([#173](#173)) ([b1df86c](b1df86c)) * **recall:** cap tag-score denominator to fix query-length bias ([#193](#193)) ([cefa516](cefa516)) * **recall:** date-aware ranking + latest-fact selection ([#158](#158), [#159](#159)) ([#187](#187)) ([a6ed945](a6ed945)) * **recall:** make recency decay window and curve configurable ([#182](#182)) ([dbb933f](dbb933f)) * **recall:** ranking release — recency config, tag-score cap, relevance gate, date-aware ranking ([#182](#182), [#193](#193), [#186](#186), [#187](#187), [#183](#183), [#184](#184), [#188](#188)) ([#194](#194)) ([337fe98](337fe98)) * **scripts:** safer reclassify_with_llm.py with provider flags + tighter prompt ([#164](#164)) ([a742602](a742602)) ### Bug Fixes * **api:** address copilot review on PR [#198](#198) ([0466a1e](0466a1e)) * **api:** handle grouped association write failures ([cd93df9](cd93df9)) * **backup:** make backup_automem.py runnable as `python scripts/backup_automem.py` ([#175](#175)) ([edd9742](edd9742)) * **benchmarks:** add publication verification bundle ([#166](#166)) ([420d721](420d721)) * **consolidation:** skip eager first tick at startup to avoid FalkorDB load race ([#165](#165)) ([1b812cf](1b812cf)) * **docs:** keep dispatch payload arrays stable ([df6e9e8](df6e9e8)) * **embedding:** fall back to per-item real embeddings before placeholders in batch path ([#189](#189)) ([6e9c62c](6e9c62c)) * **entity:** restore person-shape exemption on the slug validation path ([#179](#179)) ([5e29960](5e29960)) * **entity:** stop validator over-rejecting real people, code tools, and event categories ([#178](#178)) ([193b730](193b730)) * **lab:** address copilot review on PR [#197](#197) ([45f80d6](45f80d6)) * **lab:** align scorecard key contract (build_scorecard -> pick_winner) ([7d91530](7d91530)) * **mcp-sse:** decouple /health liveness from upstream readiness ([#151](#151)) ([5bcfb8b](5bcfb8b)) * **mcp:** cap association failure summary ([ea4e08f](ea4e08f)) * **mcp:** surface stored metadata and updated_at in detailed recall format ([#184](#184)) ([230416e](230416e)) * **recall:** address copilot review on PR [#194](#194) ([50b1647](50b1647)) * **recall:** canonicalize / and : separators in context_tag matching ([3afd9d3](3afd9d3)) * **recall:** canonicalize / and : separators in context_tag matching ([#203](#203)) ([ba5e9ff](ba5e9ff)) * **recall:** gate query-independent scoring on topical evidence within tag scope ([#130](#130)) ([#186](#186)) ([c11b594](c11b594)) * **recall:** hydrate semantic recall summaries ([#192](#192)) ([76e845d](76e845d)) * **recall:** normalize graph keyword scores into the 0-1 component range ([#191](#191)) ([3653ddf](3653ddf)) * **recall:** respect current memory state ([#170](#170)) ([ed36b98](ed36b98)), closes [#169](#169) [#158](#158) [#159](#159) * **scripts:** add sys.path guard to reembed_embeddings.py ([d333cf0](d333cf0)) ### Documentation * add scripts catalog, recall-quality-lab guide, and 0.16.0 migrations ([f20c664](f20c664)) * **bench:** log full judged 500q LongMemEval ship-config run with churn attribution ([41bf8d0](41bf8d0)) * **eval:** Plan A — lab metric foundation (TDD, 9 tasks) ([0087dda](0087dda)) * **eval:** Plan B — parallel matrix harness (TDD, 9 tasks) ([c8ddfb2](c8ddfb2)) * **evals:** mark Memora/FAMA/WRIT lifecycle diagnostics as diagnostic-only ([#174](#174)) ([e8a3285](e8a3285)) * **eval:** spec for recall-quality optimization harness ([b1a1995](b1a1995)) * fix stale claims and document gated flags for 0.16.0 ([b152d64](b152d64)) * note develop-branch contribution policy in README ([ccf02dd](ccf02dd)) * **positioning:** add scout reference ([#168](#168)) ([922d23b](922d23b)) * refresh benchmark currency for the neutral AMB run and prune stale archive docs ([3ff95bd](3ff95bd)) * refresh benchmark currency for the neutral AMB run and prune stale archive docs ([#204](#204)) ([89c30e0](89c30e0)) * refresh README and benchmark guidance ([#157](#157)) ([bba31cc](bba31cc)) * **runtime:** align Docker viewer paths and setup guidance ([#155](#155)) ([bbda79b](bbda79b)) * scripts catalog, recall quality lab guide, and 0.16.0 migration runbook ([#199](#199)) ([f190ae5](f190ae5)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please).
Summary
PR #114's
/healthendpoint returns HTTP 503 whenever upstream AutoMem reports anything other than"healthy"— including any non-zero FalkorDB/Qdrant sync drift. Railway treats 503 as unavailable and blocks the deploy, so the SSE bridge becomes undeployable during brief upstream degradation even though the Node process itself is perfectly able to serve traffic.In an eventually-consistent system, drift between FalkorDB writes and Qdrant embedding is normal under write load. Gating container liveness on zero drift causes the bridge to fail to deploy whenever writes are happening — defeating the point of a bridge (which should be able to serve errors upstream when its backend is unavailable).
This PR:
GET /healthnow always returns 200 when the Node process is able to serve HTTP. The response body still carries upstream status (status,upstream,upstream_error,upstream_details) for observability, so the richer info the PR fix: harden MCP bridge resilience, adopt stateless transport, and update cross-client docs #114 change wanted to expose is still there — just not as HTTP status.GET /ready(new) keeps the old strict behavior: 200 iff upstream is healthy, 503 otherwise. For orchestrators that explicitly want to gate traffic on upstream availability (e.g. Kubernetes readiness probes), this is the right endpoint.healthcheckPathshould remain/health.Test plan
/ready: 200 when upstream healthy, 503 when unreachablenode --test test/server.test.js)Context
Surfaced while trying to ship the
release 0.15.1deployment on Railway — build succeeded, but every deploy was killed by healthcheck failing with 503, even though the Node process was up and the upstream was only showing minor sync drift (17 in-flight memories out of 29,012 — 0.06%, and self-healing). The same failure mode will happen on any deployment where upstream is temporarily degraded for any reason, which this PR addresses.🤖 Generated with Claude Code