Azure / Azure/azure-functions-agents-runtime

MCP HTTP client uses default 5s read timeout → hangs on SSE-streamed tool results (e.g. Teams PostMessageToConversation)

Open Beginner friendly
#63 0 comments 0 reactions 0 assignees View on GitHub
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.