modelcontextprotocol / modelcontextprotocol/python-sdk

ClientSession Error Handling

Aperta
#1,401 22 commenti 7 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

bug good first issue P1 ready for work
Lingua principale
Python
Stelle
24.3k
Fork
4k
Merge medio
1g 1h
PR unite (30g)
31

Descrizione

Question

I work on the Strands SDK, and we have an integration with MCP where we invoke tools through an MCP client.

Our design spins up a thread, creates an asyncio event loop, and schedules a task to create a session with an MCP server using the python-sdk mcp ClientSession: https://github.com/strands-agents/sdk-python/blob/main/src/strands/tools/mcp/mcp_client.py#L391

Then, when an LLM decides to call a tool, or multiple tools, we schedule additional tasks on this thread's event loop to call the mcp server: https://github.com/strands-agents/sdk-python/blob/main/src/strands/tools/mcp/mcp_client.py#L324-L330

We ran into an issue where when the mcp streamablehttp_client sse_read_timeout was lower than the time it took for a tool to return, the tool invocation tasks would hang due to an exception that was not propagated out of the ClientSession. We see an exception message (included in the additional context section), but the stack trace never enters our code, it stops in the mcp code.

After investigating further, I found that this exception is sent through the MCP ClientSession message_handler, and in the default implementation of this message handler, an exception is never raised: https://github.com/modelcontextprotocol/python-sdk/blob/71889d7387f070cd872cab7c9aa3d1ff1fa5a5d2/src/mcp/client/session.py#L57-L60

To work around this issue, I have introduced my own message_handler to raise an exception passed to it: https://github.com/strands-agents/sdk-python/pull/922

I wanted to know why these exceptions are not raised in the MCP ClientSession? This silent failure took a long time to debug, and I want able to find any documentation on this behavior. As a user of the ClientSession, I would expect these exceptions to be raised so that my code can handle them.

Additional Context

Error stack trace:

Error reading SSE stream:
Traceback (most recent call last):
  File "/Users/ncclegg/Library/Application Support/hatch/env/virtual/strands-agents/X7vsTQrp/strands-agents/lib/python3.13/site-packages/httpx/_transports/default.py", line 101, in map_httpcore_exceptions
    yield
  File "/Users/ncclegg/Library/Application Support/hatch/env/virtual/strands-agents/X7vsTQrp/strands-agents/lib/python3.13/site-packages/httpx/_transports/default.py", line 271, in __aiter__
    async for part in self._httpcore_stream:
        yield part
  File "/Users/ncclegg/Library/Application Support/hatch/env/virtual/strands-agents/X7vsTQrp/strands-agents/lib/python3.13/site-packages/httpcore/_async/connection_pool.py", line 407, in __aiter__
    raise exc from None
  File "/Users/ncclegg/Library/Application Support/hatch/env/virtual/strands-agents/X7vsTQrp/strands-agents/lib/python3.13/site-packages/httpcore/_async/connection_pool.py", line 403, in __aiter__
    async for part in self._stream:
        yield part
  File "/Users/ncclegg/Library/Application Support/hatch/env/virtual/strands-agents/X7vsTQrp/strands-agents/lib/python3.13/site-packages/httpcore/_async/http11.py", line 342, in __aiter__
    raise exc
  File "/Users/ncclegg/Library/Application Support/hatch/env/virtual/strands-agents/X7vsTQrp/strands-agents/lib/python3.13/site-packages/httpcore/_async/http11.py", line 334, in __aiter__
    async for chunk in self._connection._receive_response_body(**kwargs):
        yield chunk
  File "/Users/ncclegg/Library/Application Support/hatch/env/virtual/strands-agents/X7vsTQrp/strands-agents/lib/python3.13/site-packages/httpcore/_async/http11.py", line 203, in _receive_response_body
    event = await self._receive_event(timeout=timeout)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/ncclegg/Library/Application Support/hatch/env/virtual/strands-agents/X7vsTQrp/strands-agents/lib/python3.13/site-packages/httpcore/_async/http11.py", line 217, in _receive_event
    data = await self._network_stream.read(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        self.READ_NUM_BYTES, timeout=timeout
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "/Users/ncclegg/Library/Application Support/hatch/env/virtual/strands-agents/X7vsTQrp/strands-agents/lib/python3.13/site-packages/httpcore/_backends/anyio.py", line 32, in read
    with map_exceptions(exc_map):
         ~~~~~~~~~~~~~~^^^^^^^^^
  File "/opt/homebrew/Cellar/python@3.13/3.13.5/Frameworks/Python.framework/Versions/3.13/lib/python3.13/contextlib.py", line 162, in __exit__
    self.gen.throw(value)
    ~~~~~~~~~~~~~~^^^^^^^
  File "/Users/ncclegg/Library/Application Support/hatch/env/virtual/strands-agents/X7vsTQrp/strands-agents/lib/python3.13/site-packages/httpcore/_exceptions.py", line 14, in map_exceptions
    raise to_exc(exc) from exc
httpcore.ReadTimeout

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/Users/ncclegg/Library/Application Support/hatch/env/virtual/strands-agents/X7vsTQrp/strands-agents/lib/python3.13/site-packages/mcp/client/streamable_http.py", line 326, in _handle_sse_response
    async for sse in event_source.aiter_sse():
    ...<10 lines>...
            break
  File "/Users/ncclegg/Library/Application Support/hatch/env/virtual/strands-agents/X7vsTQrp/strands-agents/lib/python3.13/site-packages/httpx_sse/_api.py", line 42, in aiter_sse
    async for line in lines:
    ...<3 lines>...
            yield sse
  File "/Users/ncclegg/Library/Application Support/hatch/env/virtual/strands-agents/X7vsTQrp/strands-agents/lib/python3.13/site-packages/httpx/_models.py", line 1031, in aiter_lines
    async for text in self.aiter_text():
        for line in decoder.decode(text):
            yield line
  File "/Users/ncclegg/Library/Application Support/hatch/env/virtual/strands-agents/X7vsTQrp/strands-agents/lib/python3.13/site-packages/httpx/_models.py", line 1018, in aiter_text
    async for byte_content in self.aiter_bytes():
    ...<2 lines>...
            yield chunk
  File "/Users/ncclegg/Library/Application Support/hatch/env/virtual/strands-agents/X7vsTQrp/strands-agents/lib/python3.13/site-packages/httpx/_models.py", line 997, in aiter_bytes
    async for raw_bytes in self.aiter_raw():
    ...<2 lines>...
            yield chunk
  File "/Users/ncclegg/Library/Application Support/hatch/env/virtual/strands-agents/X7vsTQrp/strands-agents/lib/python3.13/site-packages/httpx/_models.py", line 1055, in aiter_raw
    async for raw_stream_bytes in self.stream:
    ...<2 lines>...
            yield chunk
  File "/Users/ncclegg/Library/Application Support/hatch/env/virtual/strands-agents/X7vsTQrp/strands-agents/lib/python3.13/site-packages/httpx/_client.py", line 176, in __aiter__
    async for chunk in self._stream:
        yield chunk
  File "/Users/ncclegg/Library/Application Support/hatch/env/virtual/strands-agents/X7vsTQrp/strands-agents/lib/python3.13/site-packages/httpx/_transports/default.py", line 270, in __aiter__
    with map_httpcore_exceptions():
         ~~~~~~~~~~~~~~~~~~~~~~~^^
  File "/opt/homebrew/Cellar/python@3.13/3.13.5/Frameworks/Python.framework/Versions/3.13/lib/python3.13/contextlib.py", line 162, in __exit__
    self.gen.throw(value)
    ~~~~~~~~~~~~~~^^^^^^^
  File "/Users/ncclegg/Library/Application Support/hatch/env/virtual/strands-agents/X7vsTQrp/strands-agents/lib/python3.13/site-packages/httpx/_transports/default.py", line 118, in map_httpcore_exceptions
    raise mapped_exc(message) from exc
httpx.ReadTimeout

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.

Direzione di ricerca

Inizia da src/mcp/client/session.py, in particolare dal message_handler predefinito, e traccia il percorso con cui gli errori di _handle_sse_response in streamable_http.py raggiungono le chiamate a ClientSession. Confrontalo con i punti di ingresso per la creazione della sessione e le chiamate agli strumenti indicati in strands/tools/mcp/mcp_client.py. Il lavoro è completato quando il comportamento previsto per la propagazione delle eccezioni è stabilito e coperto da un test di regressione o chiaramente documentato.

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

Valutazione

Stack tecnologico
python
Ambito
api, networking
Tipo di issue
Bug
Difficoltà
4/5
Tempo stimato
3-5 giorni
Stato di attività
Attiva
Chiarezza
Abbastanza chiara
Idoneità per principianti
45/100

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.