Add opt-in stateless HTTP transport mode#43
Merged
Conversation
Design for an opt-in stateless HTTP transport (MISTMCP_STATELESS / --stateless) so an already-connected MCP client survives a server restart. Port of the private-fork feature, adapted to this repo (no URL mode / webui / Mongo). Centralizes write+write_delete build-time visibility and adds a request-scoped elicitation bypass for the stateless DANGER-ZONE path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- [P2] Add §5.8 fail-closed guard in config_elicitation_handler: raise ElicitationUnavailableError when stateless+http and state is not set, instead of relying on ctx.elicit() behavior. All 3 call sites already convert it to ToolError. Matrix/trade-offs/tests updated. - [P3] Soften "on_initialize does not fire" -> "cannot be relied on to set state for later tool calls" (SDK starts the per-request session initialized). - [P3] Drop .env.example (absent in this repo); README env tables are canonical. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Renumber: fail-closed guard 5.7, fatality 5.8 (match physical order); fix refs - "three write-ish tools" -> "four tools in the write/mutation surface" - Experimental matrix row labeled "write + ?experimental=true" (branch needs enable_write_tools) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
7-task TDD plan (config+validation, elicitation guard, build-time visibility, on_call_tool, start()+launch, env+main wiring, README). Task order keeps the full suite green at every step. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rtup gate Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s danger zone Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nfig Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds an opt-in stateless HTTP transport (
MISTMCP_STATELESSenv /--statelessflag). With--transport http, the server is served viamcp_server.http_app(stateless_http=True)under uvicorn — a fresh transport per request, no server session id — so an already-connected MCP client survives a server restart without reconnecting. Default off ⇒ existing behavior is byte-for-byte unchanged.Because the per-request transport starts already-initialized,
on_initializecan't be relied on to set per-session state, so the port also:create_mcp_server()(_configure_write_visibility): hideswrite+write_deleteby default and re-showswriteonly in the DANGER ZONE. Behavior-neutral in stateful mode (the elicitation middleware still re-resolves per session); required for stateless so the DELETE tool can't leak or auto-execute.on_call_tool(gated onconfig.stateless), so auto-accept works without an initialize and doesn't leak into the session store.config_elicitation_handlerraisesElicitationUnavailableError(converted to a cleanToolErrorby the existing tool wrappers) when elicitation is needed in stateless HTTP, instead of relying onctx.elicit()'s undefined stateless behavior.Safety gate: startup is refused (non-zero exit) for exactly
stateless AND http AND enable_write_tools AND NOT disable_elicitation— the one combo that would need in-band elicitation. Also adds aMISTMCP_DISABLE_ELICITATIONenv var (symmetric withMISTMCP_ENABLE_WRITE_TOOLS) so env-only deployments can reach the writes-in-stateless (DANGER ZONE) config.Accepted trade-offs (documented in the README): no server→client push (GET route dropped), and destructive upgrade/utility actions fail closed in read-only stateless.
Design spec and implementation plan are included under
docs/superpowers/.Test Plan
uv run python -m pytest— 97 passed, coverage 61% (gate ≥30%)uv run ruff check src tests/ruff format --check— clean on all changed filesstart()threading +main()non-zero exit; build-time visibility matrix + idempotency (via reallist_tools());on_call_toolrequest-scoped state; the fail-closed guard; and the_run_stateless_httplaunch (assertshttp_app(stateless_http=True)with no event_store + exact uvicorn args)stateless=False)uv run mistmcp --transport http --stateless(logs the stateless INFO line; restart-survival);--stateless --enable-write-toolswithout--disable-elicitationexits non-zero with the refusal message🤖 Generated with Claude Code