feat(session): propagate callback exceptions to the awaiter#2674
feat(session): propagate callback exceptions to the awaiter#2674Ar-maan05 wants to merge 14 commits into
Conversation
- Guard stream.aclose() in propagate handler with try/except to handle already-closed streams (mirrors existing connection-close cleanup pattern) - Include exception class name and message in the INTERNAL_ERROR response sent to the peer for better debuggability - Add explanatory comment on why the receive loop exits after propagation
…r error msg" This reverts commit ed5721c.
d511614 to
0de6e40
Compare
|
Merged the latest The only collision was a one-line coverage pragma in Verified locally: |
|
Friendly ping on this one; CI is green (27/27) and it's rebased on latest |
…ck-exceptions # Conflicts: # src/mcp/shared/session.py
|
Re-merged latest main to clear a fresh conflict. Main's "Remove Tasks (SEP-1686)" (#2714) took the ResponseRouter machinery out of session.py, which this branch still carried from an earlier base. The merge dropped all of that automatically except one spot: the class-attribute block where this PR adds _propagate_errors sat right next to the old _response_routers declaration. I kept _propagate_errors and dropped the now-orphaned _response_routers line; nothing else references ResponseRouter anymore. Re-verified on top of current main: tests/shared/test_session.py passes (11, including test_callback_exception_propagation and test_send_request_end_of_stream_without_propagated_error), and ./scripts/test reports 100% coverage with strict-no-cover clean. Back to mergeable. Happy to walk through the approach if a review slot opens up. |
Allow exceptions raised inside user-defined client callbacks (elicitation, sampling, list roots) to propagate back to the awaiter of the outgoing request (e.g.,
session.call_tool), instead of being silently swallowed by the receive loop and converted into a genericINVALID_PARAMSJSON-RPC error.Usage Example
Users opt-in to propagation by adding a
__mcp_propagate__marker attribute to their custom exceptions:Solution
• Added self._propagate_errors: dict[RequestId, BaseException] = {} to BaseSession to stash exceptions marked with mcp_propagate = True .
• In _receive_loop , if a callback exception has mcp_propagate = True :
• Send an INTERNAL_ERROR response back to the server so it doesn't hang.
• Populate _propagate_errors for active request IDs.
• Close active outgoing request streams and exit the receive loop.
• In send_request , catch anyio.EndOfStream and raise the stashed exception on the caller's task.
• Added test_callback_exception_propagation in tests/shared/test_session.py to prevent regressions.
Closes #2673