Azure / Azure/azure-functions-agents-runtime
MCP HTTP client uses default 5s read timeout → hangs on SSE-streamed tool results (e.g. Teams PostMessageToConversation)
- Dominant language
- Python
- Stars
- 9
- Forks
- 7
- Avg merge
- 1d 21h
- Merged PRs (30d)
- 20
Description
## Summary
`azure_functions_agents.discovery.mcp._build_http_client` constructs the MCP
`httpx.AsyncClient` with httpx's **default 5s read timeout**. MCP
streamable-HTTP **tool results are delivered over a Server-Sent-Events (SSE)
stream**. For any tool whose response streams rather than returning immediate
JSON, the SSE read stalls at the 5s read cap, the JSON-RPC response is never
dispatched to the per-request reader, and the agent's tool call **hangs
indefinitely** (not an error — a hang).
This reliably reproduces with the Microsoft Teams connector's
`PostMessageToConversation` action. Small/immediate JSON responses (e.g. the
Teams `AtMentionUser` action, or the Outlook `SendEmailV2` action) are
unaffected, which masks the bug and makes it look like "Teams posting randomly
hangs."
- **Package:** `azurefunctions-agents-runtime==0.1.0b1` (latest on PyPI)
- **File:** `src/azure_functions_agents/discovery/mcp.py`
- **Function:** `_build_http_client`
- Still present on `main` at time of filing.
## Affected code
```python
def _build_http_client(header_provider: Any) -> Any:
if header_provider is None:
return None
from httpx import AsyncClient
async def inject_headers(request: Any) -> None:
headers = await asyncio.to_thread(header_provider, {})
for key, value in headers.items():
request.headers[key] = value
return AsyncClient(follow_redirects=True, event_hooks={"request": [inject_headers]})
```
The custom `http_client` overrides the `mcp` SDK's default transport client
(which is configured with an SSE-friendly read timeout), so the override
inherits httpx's default `Timeout(5.0)` for reads.
## Reproduction
Against a connector-gateway MCP server exposing the Teams
`PostMessageToConversation` action:
```python
from agent_framework import MCPStreamableHTTPTool
# header_provider returns {"Authorization": "Bearer "}
tool = MCPStreamableHTTPTool(
name="teams", url=TEAMS_MCP_ENDPOINT, load_tools=True, load_prompts=False,
header_provider=hp, http_client=_build_http_client(hp),
)
async with tool:
fn = next(f for f in tool.functions if f.name.endswith("PostMessageToConversation"))
await fn.invoke(arguments={"poster": "Flow bot", "location": "Channel",
"body": {"recipient": {"groupId": "...", "channelId": "..."},
"messageBody": "hi"}})
```
**Observed:** `mcp/shared/session.py: send_request -> response_stream_reader.receive()`
blocks forever. The agent host log freezes at `Function name:
teams_PostMessageToConversation` with no completion; client-side calls time out.
**Expected:** the call returns the posted message result (id / messageLink /
conversationId) in a few seconds.
A direct `mcp.client.streamable_http.streamablehttp_client` + `mcp.ClientSession`
call (i.e. the SDK's default transport, no custom `http_client`) returns the
result instantly — confirming the connector is healthy and the defect is the
custom client's read timeout.
## Proposed fix
Give the streaming reader an unbounded read timeout (keep the connect/write/pool
bounds):
```python
import httpx
return AsyncClient(
follow_redirects=True,
timeout=httpx.Timeout(30.0, read=None),
event_hooks={"request": [inject_headers]},
)
```
Verified: with `read=None`, `PostMessageToConversation` returns in ~8–17s and the
end-to-end agent run completes successfully.
## Workaround (for consumers, until fixed)
Wrap `_build_http_client` at host startup and set `client.timeout =
httpx.Timeout(30.0, read=None)` on the returned client.
## Environment
- `azurefunctions-agents-runtime==0.1.0b1`
- `agent-framework-core==1.3.0`, `mcp` (bundled dep), `httpx`
- Python 3.13, local `func start` and reproduced standalone.
Contributor guide
Research direction
Start in src/azure_functions_agents/discovery/mcp.py at _build_http_client, then run the provided PostMessageToConversation reproduction against the connector-gateway MCP server. Compare the custom client with the SDK's default streamable-HTTP transport. Done means the SSE-streamed tool call returns its posted-message result instead of hanging, while immediate JSON responses remain working.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 88/100