Skip to content

Conversation

@TheMailmans
Copy link

@TheMailmans TheMailmans commented Nov 25, 2025

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

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

Closes #1484

The ASGI mounting examples were missing the lifespan context manager
needed to properly initialize the session manager. Without this,
requests fail with "RuntimeError: Task group is not initialized".

This adds the proper lifespan pattern to:
- streamable_http_basic_mounting.py
- streamable_http_host_mounting.py
- streamable_http_multiple_servers.py

Github-Issue:modelcontextprotocol#1484
@TheMailmans
Copy link
Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Documentation on mounting to existing ASGI server is missing information

1 participant