You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
feat(mcp): bound retained stateful sessions through a supported lifecycle API #2157
Follow-up to #2084 R7. Request body and active HTTP concurrency limits do not bound retained stateful MCP sessions after their HTTP requests finish.
The current lock resolves FastMCP 3.4.4. Its public FastMCP.http_app does not accept session_idle_timeout; the underlying MCP SDK manager supports the option, but the pinned FastMCP composition does not forward it. Changing the private manager inside its lifespan would create an unsupported coupling.
PR #2162 provides an explicit stateless HTTP deployment option. That avoids retained transport sessions for tools-only deployments, but the default remains stateful and has no configured idle expiry.
Implementation boundary
Inspect src/powercontext/server/mcp.py, server/settings.py, the pinned FastMCP composition, and actual supported host workflows. Evaluate a compatible dependency update that exposes a public idle-timeout composition option, or another documented public session lifecycle boundary.
Define idle time, maximum retained sessions, active stream behavior, admission behavior, and reconnection/resumability before changing defaults. Scope limits per process explicitly; do not claim that HTTP request concurrency bounds retained session count.
Acceptance criteria
Real SDK clients initialize, list tools, call a tool, and disconnect in supported stateful and stateless modes.
Idle sessions expire and release retained tasks/state.
Live requests and supported streams are not incorrectly expired.
Expired session requests receive a protocol-compatible response and can initialize a new session.
Capacity, cleanup on cancellation, shutdown, and repeated reconnects are exercised through real HTTP.
Document configuration and host compatibility in both locales.
Related but separate
#2082 concerns session/principal isolation and must retain its own security acceptance criteria. A timeout or stateless opt-in does not establish that the default stateful identity issue is fixed.
Additional pinned-composition evidence for #2084 R7 / #2157.
Environment: FastMCP 3.4.4, MCP SDK 1.28.1, CPython 3.11.15 under Linux.
The probe calls public FastMCP.http_app(path="/") through real ASGI HTTP
requests. It does not access the private session manager or mutate its state.
Twenty GET requests without Mcp-Session-Id, accepting JSON and SSE, all
return HTTP 400 and each returns a newly allocated session header.
After those responses finish, the otherwise empty application has 60 TaskHandle._run_coro tasks, compared with zero before the requests.
Twenty protocol DELETE requests using the returned session IDs all return
200. After allowing scheduled cleanup to run, those tasks are gone.
A new initial POST is cancelled while its body receiver is waiting, before
any response headers are sent. The request exits, but 3 task handles remain.
Shutting down the application lifespan releases them.
The task counts are diagnostics for this pinned composition, not a stable
public metric or a general claim that every session always has three tasks.
The cancellation case exercises bare FastMCP. PR #2162 pre-buffers external
PowerContext bodies, so this exact body-read cancellation is not claimed as a
reproduction through the patched complete Server stack.
This establishes two additional acceptance cases for a supported stateful
lifecycle solution:
Rejected sessionless requests must not leave unbounded retained state.
Capacity ownership and expiry must cover initialization before a response
exposes its session identity, including cancellation and failed handshakes.
It also limits a possible response-header wrapper: public protocol DELETE
can clean up known IDs, but observing response headers alone does not account
for every allocated session. Releasing an initialization reservation merely
because its HTTP request exited can undercount retained state. Such a wrapper
is not by itself proof of a retained-session capacity bound.
FastMCP.http_app in the pinned version still has no session_idle_timeout
parameter. No private-manager patch, dependency change or new lifecycle default
has been submitted. Stateful idle expiry and retained capacity remain open.
The default-stateful premise is superseded by merged #2134 (339e1d0be3dcc935f74c82af4a222b7dc7b691f4, merged 2026-10-21). Current master unconditionally composes http_app(path="/", stateless_http=True) so tool execution receives each HTTP request's current authentication identity. It does not issue MCP transport session IDs or promise cross-request SSE resumption.
#2162 has now merged that change at 26ea7ba34fd64c08a0c4ff7d8c7685fae0debe34, removes its superseded stateful opt-in, and documents the actual transport in both locales. Linux validation through real HTTP/SDK clients, one/two admission slots, cross-user identity, revocation and reconnect paths passed (40 related cases). The request-body/response admission limit remains distinct from any hypothetical retained-state bound.
Closing this retained-stateful lifecycle proposal as not planned for the currently supported stateless transport. This does not claim an idle-expiry/capacity implementation for stateful MCP, nor that the earlier bare-FastMCP cancellation experiment was a complete Server reproduction. Reintroducing stateful transport would require a separate explicit design restoring per-request identity safety, supported lifecycle/capacity ownership, reconnection and cancellation acceptance; changing private SDK managers is not adopted here.
Activity
AlexStocks commentedon Oct 21, 2026
Additional pinned-composition evidence for #2084 R7 / #2157.
Environment: FastMCP 3.4.4, MCP SDK 1.28.1, CPython 3.11.15 under Linux.
The probe calls public
FastMCP.http_app(path="/")through real ASGI HTTPrequests. It does not access the private session manager or mutate its state.
Mcp-Session-Id, accepting JSON and SSE, allreturn HTTP 400 and each returns a newly allocated session header.
TaskHandle._run_corotasks, compared with zero before the requests.200. After allowing scheduled cleanup to run, those tasks are gone.
any response headers are sent. The request exits, but 3 task handles remain.
Shutting down the application lifespan releases them.
The task counts are diagnostics for this pinned composition, not a stable
public metric or a general claim that every session always has three tasks.
The cancellation case exercises bare FastMCP. PR #2162 pre-buffers external
PowerContext bodies, so this exact body-read cancellation is not claimed as a
reproduction through the patched complete Server stack.
This establishes two additional acceptance cases for a supported stateful
lifecycle solution:
exposes its session identity, including cancellation and failed handshakes.
It also limits a possible response-header wrapper: public protocol DELETE
can clean up known IDs, but observing response headers alone does not account
for every allocated session. Releasing an initialization reservation merely
because its HTTP request exited can undercount retained state. Such a wrapper
is not by itself proof of a retained-session capacity bound.
FastMCP.http_appin the pinned version still has nosession_idle_timeoutparameter. No private-manager patch, dependency change or new lifecycle default
has been submitted. Stateful idle expiry and retained capacity remain open.
AlexStocks commentedon Oct 21, 2026
The default-stateful premise is superseded by merged #2134 (
339e1d0be3dcc935f74c82af4a222b7dc7b691f4, merged 2026-10-21). Current master unconditionally composeshttp_app(path="/", stateless_http=True)so tool execution receives each HTTP request's current authentication identity. It does not issue MCP transport session IDs or promise cross-request SSE resumption.#2162 has now merged that change at
26ea7ba34fd64c08a0c4ff7d8c7685fae0debe34, removes its superseded stateful opt-in, and documents the actual transport in both locales. Linux validation through real HTTP/SDK clients, one/two admission slots, cross-user identity, revocation and reconnect paths passed (40 related cases). The request-body/response admission limit remains distinct from any hypothetical retained-state bound.Closing this retained-stateful lifecycle proposal as not planned for the currently supported stateless transport. This does not claim an idle-expiry/capacity implementation for stateful MCP, nor that the earlier bare-FastMCP cancellation experiment was a complete Server reproduction. Reintroducing stateful transport would require a separate explicit design restoring per-request identity safety, supported lifecycle/capacity ownership, reconnection and cancellation acceptance; changing private SDK managers is not adopted here.