Skip to content

fix(core): set span.status to error when MCP tool returns JSON-RPC error response#20082

Open
betegon wants to merge 2 commits intodevelopfrom
fix/mcp-span-status-error
Open

fix(core): set span.status to error when MCP tool returns JSON-RPC error response#20082
betegon wants to merge 2 commits intodevelopfrom
fix/mcp-span-status-error

Conversation

@betegon
Copy link
Copy Markdown
Member

@betegon betegon commented Apr 1, 2026

When an MCP tool returns a JSON-RPC error response (i.e. the response has an error field instead of result), the span was being ended with status=ok. This meant failure_rate() read 0% in the MCP insights dashboard even when tools were consistently failing.

Root cause

completeSpanWithResults always ended the span without checking whether the response was an error. The captureError path uses getActiveSpan(), which returns null outside a withActiveSpan() context — so errors were never reflected in span status.

Fix

Pass !!message.error as a hasError flag to completeSpanWithResults. When true, set SPAN_STATUS_ERROR with message: 'internal_error' directly on the stored span before ending it.

Changes

  • correlation.tscompleteSpanWithResults accepts optional hasError param; sets error status when true
  • transport.ts — passes !!message.error when completing spans on outgoing send
  • transportInstrumentation.test.ts — two new tests: error response sets error status; success response does not

Closes #20083

…ror response

When a tool handler threw an error, completeSpanWithResults() was ending
the span without setting its status to error. This caused all MCP tool
spans to appear with span.status=ok in Sentry, breaking the failure_rate()
metric in the MCP insights dashboard.

The fix passes hasError=true to completeSpanWithResults() when the
outgoing JSON-RPC response contains an error object, setting the span
status to internal_error directly on the stored span (bypassing
getActiveSpan() which doesn't return the right span at send() time).

Co-Authored-By: claude-sonnet-4-6 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 1, 2026

Semver Impact of This PR

🟢 Patch (bug fixes)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


New Features ✨

  • (core, node) Portable Express integration by isaacs in #19928
  • (deno) Add denoRuntimeMetricsIntegration by chargome in #20023
  • (deps) Bump @xmldom/xmldom from 0.8.3 to 0.8.12 by dependabot in #20066

Bug Fixes 🐛

  • (aws-serverless) Add timeout to _endSpan forceFlush to prevent Lambda hanging by logaretm in #20064
  • (cloudflare) Ensure every request instruments functions by JPeer264 in #20044
  • (core) Set span.status to error when MCP tool returns JSON-RPC error response by betegon in #20082
  • (gatsby) Fix errorHandler signature to match bundler-plugin-core API by JPeer264 in #20048

Internal Changes 🔧

Core

  • Extract shared endStreamSpan for AI integrations by nicohrubec in #20021
  • Remove provider-specific AI span attributes in favor of gen_ai attributes in sentry conventions by nicohrubec in #20011

Other

  • Update validate-pr workflow by stephanie-anderson in #20072
  • Remove unused tsconfig-template folder by mydea in #20067

🤖 This preview updates automatically when you update the PR.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 1, 2026

size-limit report 📦

⚠️ Warning: Base artifact is not the latest one, because the latest workflow run is not done yet. This may lead to incorrect results. Try to re-run all tests to get up to date results.

Path Size % Change Change
@sentry/browser 25.65 kB +0.02% +5 B 🔺
@sentry/browser - with treeshaking flags 24.14 kB +0.03% +5 B 🔺
@sentry/browser (incl. Tracing) 42.16 kB +0.02% +7 B 🔺
@sentry/browser (incl. Tracing, Profiling) 46.77 kB +0.02% +9 B 🔺
@sentry/browser (incl. Tracing, Replay) 80.94 kB +0.01% +5 B 🔺
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 70.56 kB +0.01% +5 B 🔺
@sentry/browser (incl. Tracing, Replay with Canvas) 85.66 kB +0.01% +8 B 🔺
@sentry/browser (incl. Tracing, Replay, Feedback) 97.92 kB +0.01% +5 B 🔺
@sentry/browser (incl. Feedback) 42.42 kB +0.02% +6 B 🔺
@sentry/browser (incl. sendFeedback) 30.31 kB +0.02% +6 B 🔺
@sentry/browser (incl. FeedbackAsync) 35.29 kB +0.02% +6 B 🔺
@sentry/browser (incl. Metrics) 26.96 kB +0.03% +7 B 🔺
@sentry/browser (incl. Logs) 27.11 kB +0.03% +7 B 🔺
@sentry/browser (incl. Metrics & Logs) 27.78 kB +0.03% +7 B 🔺
@sentry/react 27.41 kB +0.03% +6 B 🔺
@sentry/react (incl. Tracing) 44.48 kB +0.02% +5 B 🔺
@sentry/vue 30.08 kB +0.02% +5 B 🔺
@sentry/vue (incl. Tracing) 44.05 kB +0.02% +8 B 🔺
@sentry/svelte 25.67 kB +0.02% +5 B 🔺
CDN Bundle 28.32 kB +0.03% +6 B 🔺
CDN Bundle (incl. Tracing) 43.11 kB +0.02% +6 B 🔺
CDN Bundle (incl. Logs, Metrics) 29.68 kB +0.02% +4 B 🔺
CDN Bundle (incl. Tracing, Logs, Metrics) 44.16 kB +0.02% +7 B 🔺
CDN Bundle (incl. Replay, Logs, Metrics) 68.48 kB +0.01% +6 B 🔺
CDN Bundle (incl. Tracing, Replay) 80.01 kB +0.01% +7 B 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) 81.05 kB +0.01% +6 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback) 85.55 kB +0.01% +5 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) 86.58 kB +0.01% +6 B 🔺
CDN Bundle - uncompressed 82.68 kB +0.03% +22 B 🔺
CDN Bundle (incl. Tracing) - uncompressed 127.83 kB +0.02% +22 B 🔺
CDN Bundle (incl. Logs, Metrics) - uncompressed 86.83 kB +0.03% +22 B 🔺
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed 131.24 kB +0.02% +22 B 🔺
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed 209.81 kB +0.02% +22 B 🔺
CDN Bundle (incl. Tracing, Replay) - uncompressed 244.7 kB +0.01% +22 B 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed 248.1 kB +0.01% +22 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 257.62 kB +0.01% +22 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed 261 kB +0.01% +22 B 🔺
@sentry/nextjs (client) 46.9 kB +0.02% +7 B 🔺
@sentry/sveltekit (client) 42.62 kB +0.02% +7 B 🔺
@sentry/node-core 55.77 kB +0.03% +16 B 🔺
@sentry/node 172.13 kB -0.37% -624 B 🔽
@sentry/node - without tracing 96.05 kB +0.05% +40 B 🔺
@sentry/aws-serverless 112.85 kB +0.07% +78 B 🔺

View base workflow run

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 1, 2026

node-overhead report 🧳

Note: This is a synthetic benchmark with a minimal express app and does not necessarily reflect the real-world performance impact in an application.

Scenario Requests/s % of Baseline Prev. Requests/s Change %
GET Baseline 9,430 - 11,213 -16%
GET With Sentry 1,790 19% 1,972 -9%
GET With Sentry (error only) 6,127 65% 7,611 -19%
POST Baseline 1,212 - 1,281 -5%
POST With Sentry 614 51% 627 -2%
POST With Sentry (error only) 1,078 89% 1,132 -5%
MYSQL Baseline 3,189 - 3,494 -9%
MYSQL With Sentry 501 16% 456 +10%
MYSQL With Sentry (error only) 2,624 82% 2,814 -7%

View base workflow run

Adds an `always-error` tool to the MCP e2e test apps and a test step
that verifies the resulting transaction has span.status=internal_error
when a tool throws. This covers the fix in the span status correlation
path end-to-end, complementing the existing unit tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@betegon betegon marked this pull request as ready for review April 1, 2026 20:18
@betegon betegon requested review from JPeer264 and nicohrubec April 1, 2026 20:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MCP tool spans report span.status=ok even when the tool returns a JSON-RPC error

1 participant