Skip to content

Priming streamable http events use hardcoded event id of: "0" #791

Closed

Description

@glicht

Describe the bug
From what I am seeing the event ID of priming events of an SSE event stream are always using an event id of: "0". I would expect the first priming event of the stream to contain the "http_request_id" as part of the event id: 0/<http_request_id> so it will be possible to resume the stream if disconnected.

To Reproduce
Run the counter example server:

cargo run -p mcp-server-examples --example servers_counter_streamhttp

Then run the following bash script with curl:

#!/usr/bin/env bash
set -euo pipefail

URL="${1:-http://localhost:8000/mcp}"

# POST initialize and capture session ID from response header
SESSION_ID=$(curl -s -D - -o /dev/null \
  -X POST "$URL" \
  -H "Accept: application/json, text/event-stream" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "initialize",
    "params": {
      "protocolVersion": "2025-06-18",
      "capabilities": {},
      "clientInfo": { "name": "test-client", "version": "1.0.0" }
    }
  }' | grep -i "^mcp-session-id:" | awk '{print $2}' | tr -d '\r')

if [[ -z "$SESSION_ID" ]]; then
  echo "ERROR: No Mcp-Session-Id header in response" >&2
  exit 1
fi

echo "Session ID: $SESSION_ID"
export MCP_SESSION_ID="$SESSION_ID"

# POST notifications/initialized to complete the MCP handshake
STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
  -X POST "$URL" \
  -H "Accept: application/json, text/event-stream" \
  -H "Content-Type: application/json" \
  -H "Mcp-Session-Id: $MCP_SESSION_ID" \
  -H "Mcp-Protocol-Version: 2025-06-18" \
  -d '{
    "jsonrpc": "2.0",
    "method": "notifications/initialized"
  }')

if [[ "$STATUS" != "202" ]]; then
  echo "ERROR: notifications/initialized returned $STATUS, expected 202" >&2
  exit 1
fi

echo "Handshake complete (202 Accepted)"
echo "MCP_SESSION_ID=$MCP_SESSION_ID"

for i in {1..2}; do
  echo "Calling tool ... (iteration $i)"
  
  curl 'http://127.0.0.1:8000/mcp' \
    -H 'accept: application/json, text/event-stream' \
    -H 'content-type: application/json' \
    -H "Mcp-Protocol-Version: 2025-06-18" \
    -H "mcp-session-id: $MCP_SESSION_ID" \
    --data-raw '{"method":"tools/call","params":{"name":"say_hello","arguments":{},"_meta":{"progressToken":1}},"jsonrpc":"2.0","id":'"$i"'}'

done

Output shows that both tool calls contain a priming event with "id: 0":

Session ID: ef43085b-0b2b-4b9f-ba59-6e5d26118376
Handshake complete (202 Accepted)
MCP_SESSION_ID=ef43085b-0b2b-4b9f-ba59-6e5d26118376
Calling tool ... (iteration 1)
data: 
id: 0
retry: 3000

data: {"jsonrpc":"2.0","id":1,"result":{"content":[{"type":"text","text":"hello"}],"isError":false}}
id: 0/0

Calling tool ... (iteration 2)
data: 
id: 0
retry: 3000

data: {"jsonrpc":"2.0","id":2,"result":{"content":[{"type":"text","text":"hello"}],"isError":false}}
id: 0/1

Expected behavior
Priming event id of: "0/<http_request_id>"

Additional context
From the spec: https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#resumability-and-redelivery

Event IDs SHOULD encode sufficient information to identify the originating stream, enabling the server to correlate a Last-Event-ID to the correct stream.

Activity

  1. glicht commented on Apr 10, 2026

    @glicht
    ContributorAuthor

    @DaleSeo thanks for taking this. The fix in your branch looks good. In a test I ran, I am able to resume on the priming event.

    I would like to raise another issue that the resume only works as long as the ongoing request is executing, which is sub optimal.

    For example using servers_counter_streamhttp example and the tool long_task (which runs for 10 seconds) with the following script works and I am able to get the result when performing a GET request to resume:

    #!/usr/bin/env bash
    
    URL="${1:-http://localhost:8000/mcp}"
    
    echo "Connecting to MCP server at $URL"
    
    # POST initialize and capture session ID from response header
    SESSION_ID=$(curl -s -D - -o /dev/null \
      -X POST "$URL" \
      -H "Accept: application/json, text/event-stream" \
      -H "Content-Type: application/json" \
      -d '{
        "jsonrpc": "2.0",
        "id": 1,
        "method": "initialize",
        "params": {
          "protocolVersion": "2025-06-18",
          "capabilities": {},
          "clientInfo": { "name": "test-client", "version": "1.0.0" }
        }
      }' | grep -i "^mcp-session-id:" | awk '{print $2}' | tr -d '\r')
    
    if [[ -z "$SESSION_ID" ]]; then
      echo "ERROR: No Mcp-Session-Id header in response"
      exit 1
    fi
    
    echo "Session ID: $SESSION_ID"
    export MCP_SESSION_ID="$SESSION_ID"
    
    # POST notifications/initialized to complete the MCP handshake
    STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
      -X POST "$URL" \
      -H "Accept: application/json, text/event-stream" \
      -H "Content-Type: application/json" \
      -H "Mcp-Session-Id: $MCP_SESSION_ID" \
      -H "Mcp-Protocol-Version: 2025-06-18" \
      -d '{
        "jsonrpc": "2.0",
        "method": "notifications/initialized"
      }')
    
    if [[ "$STATUS" != "202" ]]; then
      echo "ERROR: notifications/initialized returned $STATUS, expected 202" >&2
      exit 1
    fi
    
    echo "Handshake complete (202 Accepted)"
    echo "MCP_SESSION_ID=$MCP_SESSION_ID"
    
    
    echo "Calling tool long_task ..."
    
    curl "$URL" \
      -H 'accept: application/json, text/event-stream' \
      -H 'content-type: application/json' \
      -H "Mcp-Protocol-Version: 2025-06-18" \
      -H "mcp-session-id: $MCP_SESSION_ID" \
      --max-time 5 \
      --data-raw '{"method":"tools/call","params":{"name":"long_task","arguments":{},"_meta":{"progressToken":1}},"jsonrpc":"2.0","id":'"1"'}'
    
    echo "sleeping 3 seconds"
    sleep 3
    echo "calling resume on the stream after timeout..."
    
    curl "$URL" \
      -H 'accept: application/json, text/event-stream' \
      -H 'content-type: application/json' \
      -H "Mcp-Protocol-Version: 2025-06-18" \
      -H "mcp-session-id: $MCP_SESSION_ID" \
      -H "last-event-id: 0/0" \
      --max-time 2

    But if I change the script so the curl with the tools/call has a timeout of 8 and then with the sleep of 3., so total time since start of request is 11, the resume GET request will not work.

    From what I understand the cache of the results from the stream is discarded once the request completes processing and thus the resume doesn't work when it extends beyond the time of the request.

  2. DaleSeo commented on Apr 10, 2026

    @DaleSeo
    Member

    @glicht Thanks for confirming that the priming fix works and for suggesting the follow-up!

    I've added a second fix in the same PR #799 that keeps the cache after completion. The sender is replaced with a closed dummy, so no new events are routed, but the cached events remain available for resume. The stream ends cleanly after replaying.

    I'd appreciate it if you could take another look when you have a chance.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething is not working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions