Describe the Bug
Problem
When an AGW MCP endpoint returns 200 text/event-stream with a plain-text body (e.g. Jwt issuer is not configured) instead of a valid SSE data: event, await session.list_tools()
in list_server_tools() blocks indefinitely.
The MCP SSE parser discards lines without a data: prefix — silently, with no error. The ClientSession then waits forever for a JSON-RPC response that will never arrive. The httpx
timeout does not protect against this because the HTTP response itself completes successfully in ~1s.
Since all fragments run concurrently via asyncio.gather, one hanging fragment blocks the entire list_mcp_tools() call until the outer app-level timeout fires (up to 180s).
Proposed fixes
1. Per-fragment timeout (defensive minimum)
result = await asyncio.wait_for(session.list_tools(), timeout=30.0)
2. Race against stream closure (detects the problem immediately)
done, _ = await asyncio.wait(
[session.list_tools(), stream_closed_event],
return_when=asyncio.FIRST_COMPLETED
)
# if stream closed before list_tools() responded → raise immediately
3. Validate SSE body before entering ClientSession
first_chunk = await response.aread(64)
if not first_chunk.startswith((b"data:", b"event:")):
raise McpError(f"Non-SSE response: {first_chunk.decode(errors='replace')}")
Context
- Reproduced on sap.btpn8n:apiResource:ManagedN8nMcpServer:v1, AGW EU12
- The upstream fix (return 4xx or a proper SSE error event) is tracked separately
- This SDK fix is defensive and needed regardless of the upstream fix
### Steps to Reproduce
### Reproduction
Send `initialize` followed by `tools/list` to any MCP endpoint that returns a plain-text error:
```bash
# initialize — works, returns valid SSE
curl -X POST "<mcp-endpoint>" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}'
# tools/list — returns 200 but body is plain text, not an SSE data: event
curl -X POST "<mcp-endpoint>" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
Actual tools/list response:
HTTP/2 200
content-type: text/event-stream
<plain text error message>
Expected tools/list response:
HTTP/2 200
content-type: text/event-stream
event: message
data: {"jsonrpc":"2.0","id":1,"result":{"tools":[...]}}
The body lacks the data: prefix required by the SSE spec. The MCP SSE parser silently discards it, leaving ClientSession blocked on a queue that will never receive a message.
Expected Behavior
Exception must be thrown, but it should not wait indefinitely for a response.
Screenshots
No response
Used Versions
- Python version via
python --version: ...
- SAP Cloud SDK for Python version: ...
- Framework version (if applicable, e.g., Flask, FastAPI): ...
Installed packages via pip list or uv pip list
Code Examples
Stack Trace
No response
Log File
Log file
...
Affected Development Phase
Getting Started
Impact
No Impact
Timeline
No response
Describe the Bug
Problem
When an AGW MCP endpoint returns
200 text/event-streamwith a plain-text body (e.g.Jwt issuer is not configured) instead of a valid SSEdata:event,await session.list_tools()in
list_server_tools()blocks indefinitely.The MCP SSE parser discards lines without a
data:prefix — silently, with no error. TheClientSessionthen waits forever for a JSON-RPC response that will never arrive. Thehttpxtimeout does not protect against this because the HTTP response itself completes successfully in ~1s.
Since all fragments run concurrently via
asyncio.gather, one hanging fragment blocks the entirelist_mcp_tools()call until the outer app-level timeout fires (up to 180s).Proposed fixes
1. Per-fragment timeout (defensive minimum)
Expected Behavior
Exception must be thrown, but it should not wait indefinitely for a response.
Screenshots
No response
Used Versions
python --version: ...Installed packages via
pip listoruv pip listCode Examples
# Your code hereStack Trace
No response
Log File
Log file
...Affected Development Phase
Getting Started
Impact
No Impact
Timeline
No response