python-sdk icon indicating copy to clipboard operation
python-sdk copied to clipboard

fix: add lifespan context manager to StreamableHTTP mounting examples

Open TheMailmans opened this issue 2 months ago • 1 comments

Summary

This PR fixes issue #1484 by adding the missing lifespan context manager to the StreamableHTTP mounting examples.

Problem

The current examples in examples/snippets/servers/ for mounting StreamableHTTP servers to existing ASGI applications are incomplete. When developers follow these examples, they encounter:

RuntimeError: Task group is not initialized. Make sure to use run().

This happens because the session manager is never properly initialized.

Solution

Added the proper lifespan context manager pattern to initialize the session manager before handling requests:

@contextlib.asynccontextmanager
async def lifespan(app: Starlette):
    async with mcp.session_manager.run():
        yield

This pattern was already correctly implemented in streamable_starlette_mount.py but was missing from:

  • streamable_http_basic_mounting.py
  • streamable_http_host_mounting.py
  • streamable_http_multiple_servers.py

Files Changed

  • examples/snippets/servers/streamable_http_basic_mounting.py - Added lifespan for single server
  • examples/snippets/servers/streamable_http_host_mounting.py - Added lifespan for host-based routing
  • examples/snippets/servers/streamable_http_multiple_servers.py - Added combined lifespan for multiple servers using AsyncExitStack

Test Plan

  • [x] Code passes ruff format
  • [x] Code passes ruff check
  • [x] Code passes pyright type checking
  • [x] Manual testing: Run each example with uvicorn and verify no RuntimeError

Closes #1484

TheMailmans avatar Nov 25 '25 17:11 TheMailmans

Manual Verification

I manually tested the fix by running the examples with uvicorn:

With the Fix (Fixed Example)

uvicorn examples.snippets.servers.streamable_http_basic_mounting:app --port 8765

Server logs:

INFO:     Started server process
INFO:     Waiting for application startup.
INFO:     StreamableHTTP session manager started    <-- Key: session manager initializes!
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8765

Test request:

curl -X POST http://localhost:8765/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{"jsonrpc": "2.0", "method": "initialize", "params": {"protocolVersion": "2024-11-05", "capabilities": {}, "clientInfo": {"name": "test", "version": "1.0"}}, "id": 1}'

Response: ✅ Success

{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2024-11-05","capabilities":{...},"serverInfo":{"name":"My App","version":"1.22.1.dev10+02b7889"}}}

Without the Fix (Reproducing the Bug)

Created a test file without the lifespan context manager:

# Missing lifespan - reproduces the bug
app = Starlette(
    routes=[Mount("/", app=mcp.streamable_http_app())]
    # No lifespan parameter!
)

Server logs:

INFO:     Started server process
INFO:     Waiting for application startup.
INFO:     Application startup complete.    <-- Note: NO "session manager started" message
INFO:     Uvicorn running on http://0.0.0.0:8766

Same test request returns: ❌ Failure

Internal Server Error

Error in logs:

RuntimeError: Task group is not initialized. Make sure to use run().

Conclusion

The fix is verified working. Adding the lifespan context manager properly initializes the session manager, which resolves the RuntimeError reported in #1484.

TheMailmans avatar Nov 25 '25 18:11 TheMailmans