MCP client has no httpx timeout, so the event stream fails after 5 seconds

Aperta Adatta ai principianti
#228 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
2/5
Tempo stimato
1-3 ore
Idoneità per principianti
78/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
python
Ambito
api, backend

Direzione di ricerca

Inizia in src/azure_functions_agents/discovery/mcp.py, in _build_http_client, e verifica in cosa differisce il httpx.AsyncClient personalizzato dal client dell'MCP SDK. Aggiungi un timeout esplicito adatto al flusso di eventi di lunga durata, quindi verifica che le sessioni MCP autenticate possano inizializzarsi e completare una chiamata a uno strumento senza il ReadTimeout di cinque secondi.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

Summary

_build_http_client in src/azure_functions_agents/discovery/mcp.py creates its httpx.AsyncClient with no timeout. Thus httpx applies its 5 second default to all operations, including the long-lived MCP GET event stream. The stream then fails after 5 seconds, and the MCP session cannot start.

Affected code

src/azure_functions_agents/discovery/mcp.py, lines 88-99:

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 MCP Python SDK builds its own client with sse_read_timeout=300. This client replaces it and loses that value.

Blast radius

_build_http_client returns None when header_provider is None. _build_header_provider returns a provider when the server entry has headers or auth.

Thus every authenticated mcp.json entry hits this defect:

  • static headers, for example an Azure Functions system key in x-functions-key
  • Entra authentication through auth.scope and auth.client_id
  • both together

Only an unauthenticated mcp.json entry avoids the defect, because then the SDK default client stays in use.

Symptoms

On the MCP server:

GET stream disconnected, reconnecting in 1000ms...

This message repeats.

On the MCP client (the agent app):

MCP server failed to initialize: Cancelled via cancel scope <id>

or:

('Failed to enter context manager.', BrokenResourceError())

or the chat request does not answer at all. A built-in chat API call returns HTTP 500, or it hangs for more than 240 seconds.

The logs do not show the true cause, because the cancel scope message hides the ReadTimeout.

The failure comes late, and it looks like an authentication fault

This is the most confusing part of the defect. Every earlier step succeeds:

Step Result
Deployment of both apps Succeeds
Host start and function indexing on both apps Succeeds
MCP server discovery from mcp.json Succeeds
HTTP request to the MCP endpoint Succeeds. The server answers 200 and runs the handler
MCP session start and tool call Fails after about 5 seconds

Thus nothing looks wrong until an agent tries to use a tool. The error text
names a cancel scope, so a reader first suspects the authentication
configuration, the system key, or the Entra token. It is none of these. The
server-side logs show that the request passed all authentication layers and
that the handler completed.

The ReadTimeout happens on the long-lived GET event stream, not on the POST
that carries the tool call. That is why the request appears to succeed while
the session still fails.

How to reproduce

  1. Deploy two Azure Functions apps with azurefunctions-agents-runtime==0.1.0b15. One app exposes an agent through builtin_endpoints.mcp. The other app refers to it in mcp.json.
  2. Give the mcp.json entry an authentication block. Either form is enough:
{
  "servers": {
    "worker": {
      "type": "http",
      "url": "https://<host>/runtime/webhooks/mcp",
      "headers": { "x-functions-key": "<system key>" }
    }
  }
}
  1. Send a chat request to the client app. The request fails as shown above.

Isolated proof

An A/B test separates the transport from all other parts. The test starts the same MCP session twice against the same server, with the same headers. Only the client differs:

httpx client Result
MCP SDK default SUCCESSinitialize, tools/list, and tools/call all complete
Same shape as _build_http_client FAILED ReadTimeout -> Cancelled via cancel scope <id>; reason: deadline exceeded

The failure string of the second row matches the string that the deployed app writes to Application Insights.

The server-side logs also show that the server handler runs and completes. Thus authentication is correct, and the fault is in the client transport only.

Suggested fix

Give the client an explicit timeout. Keep the read budget near the SDK value of sse_read_timeout=300:

from httpx import AsyncClient, Timeout

return AsyncClient(
    follow_redirects=True,
    timeout=Timeout(300.0, connect=30.0),
    event_hooks={"request": [inject_headers]},
)

A configurable value in the mcp.json entry would also help, but a safe default is the important part.

Workaround

Applications can replace the function before they build the app. The replacement must run before create_function_app(), because create_function_app() calls discover_mcp_servers(), and the result goes into _DISCOVERED_MCP_SERVERS_CACHE:

from azure_functions_agents import create_function_app
from azure_functions_agents.discovery import mcp as _mcp_discovery

_mcp_discovery._build_http_client = _build_http_client_with_timeout

app = create_function_app()

This workaround uses a private name. Applications that use it must pin the runtime version.

Environment

  • azurefunctions-agents-runtime[monitor]==0.1.0b15
  • Python 3.13
  • Azure Functions Flex Consumption (FC1), Linux, Python 3.13 worker
  • Two Function Apps, MCP over HTTP between them
Lingua principale
Python
Stelle
9
Fork
7
Merge medio
2g 7h
PR unite (30g)
21

Guida per i contributori

Apri la guida per i contributori

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di Azure/azure-functions-agents-runtime

Tutte le issue di Azure/azure-functions-agents-runtime

Issue simili

Altre issue su Python

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.