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

FastMCP 2.5.1 does not recognize X-Session-ID header (MCP 1.9.1, minimal example)

Open dasafo opened this issue 7 months ago • 4 comments

I'm using MCP 1.9.1 and FastMCP 2.5.1.
Even with the most minimal server, FastMCP always returns "Missing session ID", even though the header is sent correctly.

Minimal server code:

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("Depuracion-MCP")

@mcp.tool(name="ping", description="Ping de prueba MCP")
async def ping(ctx=None):
    return {"pong": True}

fastmcp_asgi_app = mcp.streamable_http_app()

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(fastmcp_asgi_app, host="127.0.0.1", port=8000)

Tested with this curl command:

curl -v -X POST http://127.0.0.1:8000/mcp/ \  -H "Content-Type: application/json" \  -H "Accept: application/json, text/event-stream" \  -H "X-Session-ID: test" \  -d '{"jsonrpc":"2.0","id":1,"method":"ping","params":{}}'

Result: {"jsonrpc":"2.0","id":"server-error","error":{"code":-32600,"message":"Bad Request: Missing session ID"}}

The header is present in the request (checked with -v). I also tried x-session-id (lowercase), same result. No proxy or reverse proxy is used. This happens even with the minimal server and no extra imports. Versions:

mcp: 1.9.1 fastmcp: 2.5.1

Is there a workaround or a fix for this? Thank you!

dasafo avatar May 26 '25 15:05 dasafo

I've had some issues with this when opening multiple sessions.

If you add stateless_http=True when you instantiate the MCP server, it won't care about the session id anymore.

mcp = FastMCP("Depuracion-MCP", stateless_http=True)

BrandonShar avatar May 27 '25 14:05 BrandonShar

Thank you for your quick feedback!

I just tested your suggestion and it works perfectly. Setting stateless_http=True on the MCP server completely solved the issue with multiple sessions and session IDs.

Really appreciate your help!

dasafo avatar May 27 '25 15:05 dasafo

wait. this might be a workaround - just turning the server stateless.

the core of the problem is that a client (in my case MCP Inspector) sends a POST request to your server. that request is missing the "mcp-session-id" header with the Session ID in it.

as it turns out (at least for my case): the client calls the server with two subsequent requests - first a preflight request of type OPTION and then the real POST one. the OPTION request already contains the Session ID.

the way to fix this is to expose the Session ID header from the OPTIONS request so the subsequent POST request can adopt it. the way you do this is to update your CORS config by enabling this header exposure:

starlette_app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    blah-blah....,
    expose_headers=["mcp-session-id"]
)

now, as you follow the requests you will see how it skips from OPTIONS to POST and it's all 200 OK everywhere.

that's all inspired by another post somewhere around here.

that's all based on my personal experience and tech: the low-level server of MCP, a Starlette server, uvicorn to run it all in the end, this CORS config. hope this helps!

anton-mladenov avatar May 27 '25 23:05 anton-mladenov

I get same issue to resume a chat via a Java IDE plugin (devoxx Genie). I've tried to debug by adding some logs in sse.py. No result yet

INFO:     127.0.0.1:45166 - "GET /sse HTTP/1.1" 200 OK
2025-05-30 23:03:30,578 - INFO - [SESSION CLOSING] ID: a2f5093a-776a-4815-b0fa-31c99dac038e
2025-05-30 23:03:30,578 - INFO - [SSE_LIB_DEBUG] response_wrapper (Instance ID: 138698476172400): Attempting to delete session a2f5093a-776a-4815-b0fa-31c99dac038e. _read_stream_writers BEFORE delete: [UUID('a2f5093a-776a-4815-b0fa-31c99dac038e')]
2025-05-30 23:03:30,578 - INFO - [SSE_LIB_DEBUG] response_wrapper (Instance ID: 138698476172400): Session a2f5093a-776a-4815-b0fa-31c99dac038e DELETED. _read_stream_writers AFTER delete: []
2025-05-30 23:03:38,725 - INFO - [SSE_LIB_DEBUG] handle_post_message (Instance ID: 138698476172400): Entered. Current _read_stream_writers: []
2025-05-30 23:03:38,725 - INFO - Session ID demandé: a2f5093a-776a-4815-b0fa-31c99dac038e
2025-05-30 23:03:38,725 - INFO - Sessions actives: []
2025-05-30 23:03:38,725 - WARNING - Could not find session for ID: a2f5093a-776a-4815-b0fa-31c99dac038e
INFO:     127.0.0.1:51492 - "POST /messages/?session_id=a2f5093a776a4815b0fa31c99dac038e HTTP/1.1" 404 Not Found`

gmaOCR avatar May 30 '25 21:05 gmaOCR

I'm using MCP 1.9.1 and FastMCP 2.5.1. Even with the most minimal server, FastMCP always returns "Missing session ID", even though the header is sent correctly.

Minimal server code:

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("Depuracion-MCP")

@mcp.tool(name="ping", description="Ping de prueba MCP") async def ping(ctx=None): return {"pong": True}

fastmcp_asgi_app = mcp.streamable_http_app()

if name == "main": import uvicorn uvicorn.run(fastmcp_asgi_app, host="127.0.0.1", port=8000)

Tested with this curl command:

curl -v -X POST http://127.0.0.1:8000/mcp/ \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -H "X-Session-ID: test" \ -d '{"jsonrpc":"2.0","id":1,"method":"ping","params":{}}'

Result: {"jsonrpc":"2.0","id":"server-error","error":{"code":-32600,"message":"Bad Request: Missing session ID"}}

The header is present in the request (checked with -v). I also tried x-session-id (lowercase), same result. No proxy or reverse proxy is used. This happens even with the minimal server and no extra imports. Versions:

mcp: 1.9.1 fastmcp: 2.5.1

Is there a workaround or a fix for this? Thank you!

Muhammad-Saad-2 avatar Jul 30 '25 11:07 Muhammad-Saad-2

I've been facing the exact error, can't figure out what's the core issue if someone could help, though the video I followed had done the exact same way and the server returned the tool schema of the tool that was defined

minimal server code

from mcp.server.fastmcp import FastMCP
from pydantic import Field

mcp = FastMCP(
    name = "hello mcp",
    stateless_HTTP = True
)


@mcp.tool()
def search_query(
    query: str 
):
    return {f"result", {query}}


mcp_app = mcp.streamable_http_app()

basic code to initiate a request to the server

import requests

url = "http://127.0.0.1:8000/mcp"


headers = {
    "Accept" : "application/json,text/event-stream"
}

body = {
    "jsonrpc": "2.0",
    "method": "tools/list",
    "id": 1,
    "params": {}
}


response = requests.post(url, headers=headers, json=body )
print(response.text)

server response

{
    "jsonrpc": "2.0",
    "id": "server-error",
    "error": {
        "code": -32600,
        "message": "Bad Request: Missing session ID"
    }
}

Muhammad-Saad-2 avatar Jul 30 '25 11:07 Muhammad-Saad-2

@Muhammad-Saad-2 did you try what I proposed above on 28th May? to expose a "mcp-session-id" header? you are now exposing some "x-session-id" which I am not sure where it comes from. also, don't watch videos - read the docs instead and inspect network activity in the Network tab in DevTools.

anton-mladenov avatar Jul 30 '25 11:07 anton-mladenov

So yeah, the correct header is Mcp-Session-Id, also the session ID should be generated by the server as part of the initialisation handshake, it's not something that the client should be generating. See https://modelcontextprotocol.io/specification/2025-06-18/basic/lifecycle - I'd recommend using the Client SDKs instead of raw HTTP requests so that the protocol is handled correctly (the protocol is also subject to change, so using the SDKs will give you a better chance of keeping things working).

Will close this, but feel free to open again if there's more to discuss here.

pja-ant avatar Sep 19 '25 10:09 pja-ant

https://stackoverflow.com/questions/79713188/issue-on-call-fastmcp-server-using-postman (initialization required)

nvermamathworks-netizen avatar Nov 07 '25 14:11 nvermamathworks-netizen