Repository navigation
[v2] JSON-RPC error responses leave client OpenTelemetry spans UNSET #3174
Description
Activity
Hi, I'd like to work on this fix.
Root cause: In send_raw_request(), the otel_span context manager exits before ErrorData is converted to MCPError, so the span never observes an error — it stays UNSET with no error attributes.
Proposed fix: Inside the span, after receiving outcome, check for ErrorData and set error attributes (error.type,
pc.response.status_code, StatusCode.ERROR) on the span before exiting. This mirrors the server-side pattern in _otel.py:35-60.The change is ~5 lines in send_raw_request() plus a test using InMemorySpanExporter (adapting the reproducer from the issue).
Happy to take this on if no one else is assigned. Thanks!
Hi maintainers, I’d like to work on this if the proposed direction is welcome.
I don’t currently see an implementation linked to this issue. My plan is to first add an in-memory regression test covering successful and JSON-RPC error responses, then ensure the ErrorData to MCPError transition occurs within the active client span—or explicitly record the error status and semantic-convention attributes if that better matches the project’s telemetry design.
I’ll keep the change scoped to the v2 JSONRPCDispatcher, run the targeted tests, Ruff, and Pyright, and personally review and understand the complete implementation. I will also disclose AI assistance in the pull request.
Please assign it or mark it ready if this direction is acceptable.
Ecocitizenz commented
on Jul 29, 2026 on Jul 29, 2026 via email · Hidden as spamshow commentMore actionsThanks @Ecocitizenz for the additional operational context. I’ll wait for maintainer guidance on the implementation direction before proceeding.
Author here. Assignment is the maintainers' call, but two things I can confirm to unblock whoever picks it up.
@Ranga-Prasath-22 your root-cause reading matches mine:
outcomeis received inside the span, the span exits, and only afterward isErrorDataconverted toMCPError(lines 432-433 at the pinned commit), so the context manager never observes an error. Recording status anderror.type/rpc.response.status_codeinside the span, mirroringserver/_otel.py, is what I expected the fix to look like when I filed this.The in-memory reproducer in the issue body is free to adapt directly as the regression test. The acceptance criterion from my side is exactly: the failed CLIENT span is distinguishable from the success control (status ERROR,
error.typepresent, JSON-RPC code carried).One boundary worth preserving: this is client telemetry only. The request already fails correctly with
MCPError, so the fix should not alter the exception path, just what the span records before exit.Ecocitizenz commented
on Aug 4, 2026 on Aug 4, 2026 via email · Hidden as spamshow commentMore actionsEcocitizenz commented
on Aug 4, 2026 on Aug 4, 2026 via email · Hidden as spamshow commentMore actions- addedbugSomething isn't workingSomething isn't workingP2Moderate issues affecting some users, edge cases, potentially valuable featureModerate issues affecting some users, edge cases, potentially valuable featureneeds confirmationNeeds confirmation that the PR is actually required or needed.Needs confirmation that the PR is actually required or needed.v2Affects the v2 line (2.x on main)Affects the v2 line (2.x on main)
on Aug 14, 2026 Implemented in PR #3401.
- Changed: client JSON-RPC spans now record error status, error type, and the JSON-RPC response code before the span closes, while preserving the existing MCPError exception path.
- Tests:
uv run --frozen pytest tests/server/test_otel.py -q(17 passed), Ruff, formatting, Pyright, andgit diff --checkpassed. - Full suite: 5574 passed, 16 skipped, 1 xfailed; one unrelated local HTTP 502 failure occurred in
tests/client/test_transport_stream_cleanup.py::test_sse_client_closes_all_streams_on_connection_error.
The PR was automatically closed by the repository's linked-issue policy because the contributor is not assigned to this issue.
I independently reproduced this on the current main branch using the in-memory client/server example. JSON-RPC error responses currently leave the client span UNSET and omit �rror.type and
pc.response.status_code, while the request still raises MCPError.I opened PR #3401 with a focused fix that records the error while the client span is still active, together with regression coverage. The PR was auto-closed because I am not assigned to this issue. If this approach is useful, please feel free to assign the issue to me or reopen the PR.
Fixed in #3629, thanks for the careful report and the acceptance criterion.
A client span whose request is answered with a JSON-RPC error now ends with status
ERRORand carries the code inerror.typeandrpc.response.status_code. TheMCPErroryou catch is unchanged.One thing to expect: with the default
mode="auto", connecting to a server that doesn't knowserver/discovernow produces oneERRORspan for that probe. You can filter it out onmcp.method.name.If something still looks wrong in your traces, a new issue is welcome.
Initial Checks
Description
At current
main(11934c90aeff5e1e68aee223edd00c5d1fce1d5c), JSON-RPC error responses handled byJSONRPCDispatcherleave the corresponding client OpenTelemetry span looking like a normal exit:UNSETerror.type: absentrpc.response.status_code: absentThe request still fails correctly with
MCPError; this report is limited to client-side telemetry correctness.The ordering appears to explain the result:
send_raw_request()receivesoutcomeinside the client span, exits the span, and only afterward convertsErrorDataintoMCPErrorat lines 432–433. The span context manager therefore observes no exception.This differs from the current server OTel middleware, which records the error attributes and status, and from the design assumption documented in #2381 that a propagated
MCPErrorwould be recorded by the client span.I ran the same in-memory success/error pair five times. Every run produced:
Expected: the failed client span should be distinguishable from the successful control and carry the JSON-RPC error code/status consistently with the SDK's server-side instrumentation.
Scope and claim boundary: this concerns JSON-RPC/stream-backed dispatcher calls, not direct in-process dispatch generally. The reproducer is entirely in-memory. I have not tested or claimed anything about a deployed transport, collector, or production frequency.
Closest adjacent work found: #2132 targets the removed
BaseSessionarchitecture and predates the current dispatcher; #2854 adds GenAI attributes but does not handle JSON-RPC errors. I did not find an issue or current PR covering this ordering on the present dispatcher.Disclosure: I used AI assistance to help reproduce and analyze this issue. I reviewed the source, reran the evidence independently, and take responsibility for the report.
Example Code
Python & MCP Python SDK