Skip to content

fix(mcp): make streamable-http session mode configurable via MCP_STATELESS_HTTP - #42814

Merged
justinpark merged 2 commits into
apache:masterfrom
justinpark:fix--mcp-stateless-http-disconnect
Aug 6, 2026
Merged

fix(mcp): make streamable-http session mode configurable via MCP_STATELESS_HTTP#42814
justinpark merged 2 commits into
apache:masterfrom
justinpark:fix--mcp-stateless-http-disconnect

Conversation

@justinpark

@justinpark justinpark commented Aug 5, 2026

Copy link
Copy Markdown
Member

SUMMARY

Adds MCP_STATELESS_HTTP (default True, matching current behavior) to make FastMCP's streamable-HTTP session mode configurable instead of hardcoded. In stateless mode, each request's transport is torn down as soon as that single HTTP round trip finishes while the tool call keeps running in the background; if a client gives up on a still-running call and the tool tries to send another progress notification, it hits the now-closed transport and crashes the session, disconnecting other concurrent clients on the same worker. Setting this to False keeps the session (and its transport) alive for the session's lifetime, fixing the crash — confirmed via a live A/B repro where the identical client-disconnect trigger crashed under True and completed cleanly under False.

BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF

  Result
stateless_http=True BrokenResourceError → ToolError crash (same category as production)
stateless_http=False All 6 notifications sent successfully, completed with success=True after 3.58s

TESTING INSTRUCTIONS

  • Full tests/unit_tests/mcp_service/ suite: 3135 passed, same 5 pre-existing failures as before this change (verified unrelated via git stash)
  • test_mcp_e2e_smoke.py updated to reflect the new config-driven default and explicitly pin the ASGI app under test to stateless_http=False
  • Live dev-instance repro: identical generate_chart call with a client disconnecting mid-execution crashes with BrokenResourceError/ToolError under stateless_http=True, completes successfully (all progress notifications delivered) under stateless_http=False

ADDITIONAL INFORMATION

  • Has associated issue:
  • Required feature flags:
  • Changes UI
  • Includes DB Migration (follow approval process in SIP-59)
    • Migration is atomic, supports rollback & is backwards-compatible
    • Confirm DB migration upgrade and downgrade tested
    • Runtime estimates and downtime expectations provided
  • Introduces new feature or API
  • Removes existing feature or API

…disconnects

Stateless mode tears down a request's transport as soon as that single
HTTP round trip finishes, while the tool call it started keeps running
as a background task. Tools that send more than one progress
notification per call (generate_chart, get_chart_data, etc.) hit a
ClosedResourceError on the second notification, crashing that session
and disconnecting other concurrent clients on the same worker -- this
is the direct cause of the mid-session MCP disconnects seen in
production. Stateful sessions keep the transport alive for the
session's lifetime, so this no longer races.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@dosubot dosubot Bot added infra Namespace | Anything related to infrastructure install:config Installation - Configuration settings labels Aug 5, 2026
@bito-code-review

bito-code-review Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Bito Review Skipped - Source Branch Not Found

Bito didn’t review this change because the pull request is no longer valid. It may have been merged, or the source/target branch may no longer exist.

@netlify

netlify Bot commented Aug 5, 2026

Copy link
Copy Markdown

Deploy Preview for superset-docs-preview ready!

Name Link
🔨 Latest commit ff5041b
🔍 Latest deploy log https://app.netlify.com/projects/superset-docs-preview/deploys/6a73a9258701580008b3019c
😎 Deploy Preview https://deploy-preview-42814--superset-docs-preview.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@codecov

codecov Bot commented Aug 5, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 16.66667% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 65.49%. Comparing base (457cd34) to head (ed224c1).
⚠️ Report is 69 commits behind head on master.

Files with missing lines Patch % Lines
superset/mcp_service/__main__.py 0.00% 4 Missing ⚠️
superset/mcp_service/server.py 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #42814      +/-   ##
==========================================
- Coverage   65.59%   65.49%   -0.10%     
==========================================
  Files        2819     2822       +3     
  Lines      160166   160923     +757     
  Branches    36569    36682     +113     
==========================================
+ Hits       105059   105396     +337     
- Misses      53059    53435     +376     
- Partials     2048     2092      +44     
Flag Coverage Δ
hive 37.95% <16.66%> (-0.14%) ⬇️
mysql 57.79% <16.66%> (-0.14%) ⬇️
postgres 57.83% <16.66%> (-0.14%) ⬇️
presto 39.87% <16.66%> (-0.16%) ⬇️
python 59.20% <16.66%> (-0.14%) ⬇️
sqlite 57.47% <16.66%> (-0.13%) ⬇️
unit 100.00% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@aminghadersohi aminghadersohi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Config plumbing is correct. Both read sites — run_server() (server.py:1038) and the CLI entrypoint (__main__.py:175) — resolve via flask_app.config.get("MCP_STATELESS_HTTP", MCP_STATELESS_HTTP), passing the module constant itself as the .get() fallback rather than a re-hardcoded literal. That's the single-source form: flipping the constant in mcp_config.py moves every reader with it, so there's no drifting second default. The value comes from Flask config (a real bool), not an env var, so there's no bool("False") string-parsing hazard.

Default preserves production behavior: MCP_STATELESS_HTTP = True matches the pre-PR hardcoded stateless_http=True at all three call sites (the two here plus the multi-pod http_app path), so upgrades are behavior-preserving and stateful mode (False) is strictly opt-in. Both readers actually feed the resolved value into mcp.run/http_app — not a defined-but-unread no-op.

Isolation is unaffected: the default is unchanged and user identity is resolved per HTTP request from the JWT context; the stateful path only keeps the streamable-HTTP transport alive per Mcp-Session-Id (with the documented session-affinity requirement) and does not share DB session or identity across tool calls, so no cross-request leak is introduced.

Two nits, neither blocking:

  1. Test doesn't pin the new wiring. test_mcp_e2e_smoke.py hardcodes stateless_http=False directly in _real_asgi_client and its docstring notes it does so "rather than reading MCP_STATELESS_HTTP's True default." So it never exercises the flask_app.config.get(...) resolution in run_server()/__main__, never asserts the True default, and covers only the False mode — reverting the production .get() wiring would leave this test green. A flipped default or broken resolution wouldn't be caught by this change.

  2. MCP_STATELESS_HTTP isn't in get_mcp_config()'s defaults allow-list (mcp_config.py:704) the way MCP_RBAC_ENABLED/MCP_DEBUG are. It still works — the reads fall back to the module constant and config.update() won't clobber an operator's superset_config.py override — but it's inconsistent with the module's other operator-facing flags.

Add MCP_STATELESS_HTTP to get_mcp_config()'s defaults allow-list so an
operator override in superset_config.py is actually surfaced via
flask_app.config, consistent with MCP_DEBUG/MCP_RBAC_ENABLED. Add tests
pinning run_server()'s True default and its False override, since the
existing e2e smoke test hardcodes stateless_http=False and never
exercises that resolution.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@pull-request-size pull-request-size Bot added size/L and removed size/M labels Aug 5, 2026
@bito-code-review

bito-code-review Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Code Review Agent Run #af1089

Actionable Suggestions - 0
Additional Suggestions - 1
  • superset/mcp_service/server.py - 1
    • Missing unit test for multi-pod path · Line 1045-1063
      The new `stateless_http=stateless_http` kwarg on the `http_app()` call (line 1049) has no unit-test coverage: `_run_server_dependencies` patches `create_event_store` to `None`, so the multi-pod `if event_store is not None` branch is never reached. The two existing tests (`test_run_server_defaults_stateless_http_to_true_when_unset`, `test_run_server_respects_mcp_stateless_http_false_override`) only assert on `mcp_instance.run()` (single-pod). Add a parallel test for the `http_app()` path to catch mis-wiring in multi-pod deployments.
Review Details
  • Files reviewed - 6 · Commit Range: ff5041b..ed224c1
    • superset/mcp_service/__main__.py
    • superset/mcp_service/mcp_config.py
    • superset/mcp_service/server.py
    • tests/unit_tests/mcp_service/test_mcp_config.py
    • tests/unit_tests/mcp_service/test_mcp_e2e_smoke.py
    • tests/unit_tests/mcp_service/test_mcp_server.py
  • Files skipped - 0
  • Tools
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ Successful
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful

Bito Usage Guide

Commands

Type the following command in the pull request comment and save the comment.

  • /review - Manually triggers a full AI review.

  • /pause - Pauses automatic reviews on this pull request.

  • /resume - Resumes automatic reviews.

  • /resolve - Marks all Bito-posted review comments as resolved.

  • /abort - Cancels all in-progress reviews.

Refer to the documentation for additional commands.

Configuration

This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.

Documentation & Help

AI Code Review powered by Bito Logo

@justinpark
justinpark merged commit d594a4d into apache:master Aug 6, 2026
75 checks passed
justinpark added a commit that referenced this pull request Aug 6, 2026
…ELESS_HTTP (#42814)

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit d594a4d)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

infra Namespace | Anything related to infrastructure install:config Installation - Configuration settings size/L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants