fastmcp icon indicating copy to clipboard operation
fastmcp copied to clipboard

Message router error after successful tools list call

Open joshight opened this issue 2 months ago • 5 comments

Description

Using OAuth proxy w/ Keycloak for authentication and authorization. Implementation works as expected but after a successful list tools call a message router error is returned in the logs.

Example Code

INFO     Completed message: {"event": "request_success", "timestamp": "2025-10-13T16:05:12.581667+00:00", "method": "tools/list", "type":    logging.py:133
                             "request", "source": "client"}                                                                                                                    
                    INFO     Completed message: event=request_success timestamp=2025-10-13T16:05:12.581667+00:00 method=tools/list type=request source=client    logging.py:133
                    INFO     List tools completed in 5.39ms                                                                                                        timing.py:99
                    INFO     Request tools/list completed in 5.79ms                                                                                                timing.py:47
INFO - 2025-10-13 09:05:12,592 uvicorn.access:L473 [c14068faaa714b51b3cac1e5ed08c67a] [PID: 42876] [TID: 8712315200] 127.0.0.1:55936 - "POST /mcp HTTP/1.1" 200
INFO - 2025-10-13 09:05:12,592 mcp.server.streamable_http:L630 [c14068faaa714b51b3cac1e5ed08c67a] [PID: 42876] [TID: 8712315200] Terminating session: None
ERROR - 2025-10-13 09:05:12,592 mcp.server.streamable_http:L880 [c14068faaa714b51b3cac1e5ed08c67a] [PID: 42876] [TID: 8712315200] Error in message router
Traceback (most recent call last):
  File "/Users/josh.albright/.pyenv/versions/teradata-mcp-server/lib/python3.11/site-packages/mcp/server/streamable_http.py", line 831, in message_router
    async for session_message in write_stream_reader:
  File "/Users/josh.albright/.pyenv/versions/teradata-mcp-server/lib/python3.11/site-packages/anyio/abc/_streams.py", line 41, in __anext__
    return await self.receive()
           ^^^^^^^^^^^^^^^^^^^^
  File "/Users/josh.albright/.pyenv/versions/teradata-mcp-server/lib/python3.11/site-packages/anyio/streams/memory.py", line 111, in receive
    return self.receive_nowait()
           ^^^^^^^^^^^^^^^^^^^^^
  File "/Users/josh.albright/.pyenv/versions/teradata-mcp-server/lib/python3.11/site-packages/anyio/streams/memory.py", line 93, in receive_nowait
    raise ClosedResourceError
anyio.ClosedResourceError

Version Information

FastMCP version:                                                                                               2.12.4
MCP version:                                                                                                   1.12.4
Python version:                                                                                               3.11.11
Platform:                                                                                macOS-15.7.1-arm64-arm-64bit
FastMCP root path: /Users/josh.albright/.pyenv/versions/3.11.11/envs/teradata-mcp-server/lib/python3.11/site-packages

joshight avatar Oct 13 '25 16:10 joshight

any luck with this?

bprahulsolanki avatar Oct 15 '25 05:10 bprahulsolanki

I would like to help with this but there's not enough here to triage. Can you provide an MRE? I have a hunch that it's not Keycloak is related; this error pops up sometimes and is difficult to track down. I think it is a race condition in the SDK.

jlowin avatar Oct 15 '25 13:10 jlowin

Found a PR in the MCP Python SDK to fix the race condition bug by properly handling the expected stream closure case: https://github.com/modelcontextprotocol/python-sdk/pull/1384

I added a log filter for now to exclude the error since functionality is working as expected.

Here's a bit more detail after debugging:

  1. Successful Request Completion

    • HTTP POST request completes successfully
    • Response (200 OK) sent to client
    • Request processing finishes
  2. Context Manager Cleanup Triggered

    • connect() context manager exits
    • finally block executes (streamable_http.py:888-901)
    • Stream cleanup begins
  3. Stream Closure Sequence (streamable_http.py:893-898)

    await read_stream_writer.aclose()
    await read_stream.aclose()
    await write_stream_reader.aclose()  # ← This closes the stream
    await write_stream.aclose()
    
  4. Background Task Still Running

    • message_router() task runs in background via tg.start_soon() (line 883)
    • Task attempts to iterate over write_stream_reader (line 831)
    • Stream was just closed by cleanup code
    • Result: ClosedResourceError raised

joshight avatar Oct 15 '25 16:10 joshight

Hey @jlowin hopefully this help for MRE using "fastmcp>=2.12.5" I am specifically facing this issue when I am running FAST API server with multiple workers the mcp app in stateless mode

import uvicorn
from contextlib import asynccontextmanager
from fastapi import FastAPI
from fastmcp import FastMCP


@asynccontextmanager
async def lifespan(app: FastAPI):
    logger.info("Application lifespan: Initiating startup sequence...")
    try:
        run_startup_logic()
        yield

    except Exception as e:
        logger.exception(f"Application lifespan: CRITICAL error during startup: {e}")
        raise HTTPException(
            status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
            detail=f"Application failed to initialize critical services: {str(e)}",
        ) from e
    finally:
        await run_shutdown_logic()
        logger.info("Application lifespan: Shutdown sequence completed.")


app = FastAPI(
    title="FAST API  APP",
    lifespan=lifespan,        # its own normal lifespan function 
)

mcp_server = FastMCP.from_fastapi(
        app=app,
        # route_maps As shown here  https://gofastmcp.com/integrations/fastapi#custom-route-mapping 
        route_maps=CUSTOM_ROUTE_MAPS,    
)

mcp_app = mcp_server.http_app(
       path="/mcp",
       json_response=True,
       stateless_http=True, 
       transport="streamable-http",  # or "http" (both hit the same factory)
)

combined_app = FastAPI(
    title="API with MCP",
    routes=[
        *mcp_app.routes, 
        *app.routes,            # normal FAST API app routes 
    ],
    # # as shown in the doc: https://gofastmcp.com/integrations/fastapi#combining-lifespans 
    lifespan=combined_lifespan,  
)

uvicorn.run(
        app="app.main:combined_app",
        host=settings.SERVER_HOST,
        port=port,
        workers=5,
)

when I run client.py I get the output no problem here but in logs I see the error anyio.ClosedResourceError as mentioned by @joshight

from fastmcp.client import Client
import asyncio


async def get_tools(client: Client):
    tools = await client.list_tools()
      print("Available tools:")
    for tool in tools:
        print(f"- {tool.name}")
    return tools


async def main():
    """
    An example client to interact with the MCP server.
    """
    async with Client(MCP_SERVER_URL) as client:
        tools = await get_tools(client)

if __name__ == "__main__":
    asyncio.run(main())

chetan-jarande avatar Oct 29 '25 11:10 chetan-jarande

Hi all, I've added a test case for this current issue in this PR. The main cause is a race condition triggered by json_response=True in stateless mode. For details, please refer to this reply .

I think this issue needs to be fixed in the mcp package.

Thanks

Edison-A-N avatar Oct 29 '25 14:10 Edison-A-N