Skip to content

feat(mcp): record response size of each tool call - #3408

Merged
kodiakhq[bot] merged 3 commits into
mainfrom
brandon/mcp-output-telemetry
Oct 19, 2026
Merged

kodiakhq[bot] merged 3 commits into
mainfrom
brandon/mcp-output-telemetry

Conversation

@brandon-pereira

Copy link
Copy Markdown
Member

Summary

Each MCP tool call now records how many bytes of text it returns to the model. Response size stands in for token usage, so this gives a per-tool KPI for tracking context cost and checking whether output trimming works.

  • Span attribute mcp.tool.response.size_bytes (UTF-8 bytes of the result's text blocks)
  • Histogram hyperdx.mcp.tool.response.size_bytes, attributes tool and outcome (success | error)
  • responseBytes on the "MCP tool completed" and "MCP tool error" log lines

Bytes rather than tokens: bytes are exact and model-agnostic, and any tokenizer we could bundle would only estimate Claude's counts.

Calls that throw record no size, since there is no response.

How to reproduce

Starting point: yarn dev, with an MCP client connected to /api/mcp using a personal API access key.

On main:

  1. Call any tool, for example clickstack_list_sources.
  2. The mcp.tool.clickstack_list_sources span has no response size.

On this branch:

  1. Call the same tool.
  2. The span carries mcp.tool.response.size_bytes, and the hyperdx.mcp.tool.response.size_bytes histogram has a point for tool=clickstack_list_sources.

How to test on Vercel preview

N/A — non-UI change.

References

  • Linear Issue: HDX-5750
  • Related PRs:

@vercel

vercel Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
hyperdx-oss Ready Ready Preview Oct 19, 2026 10:36pm UTC
hyperdx-storybook Ready Ready Preview Oct 19, 2026 10:36pm UTC

Request Review

@changeset-bot

changeset-bot Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 6401b60

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@hyperdx/api Patch
@hyperdx/app Patch
@hyperdx/otel-collector Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions

Copy link
Copy Markdown
Contributor

Duplicate code analysis

No new duplicate code introduced by this PR.


jscpd compared this branch against origin/main
across app, api and common-utils. Only new clones are listed; the codebase
currently holds 124 in total (1.67%).
Run yarn dupes locally to reproduce, or yarn dupes:all for the full picture.

@greptile-apps

greptile-apps Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The PR appears safe to merge; no actionable issues remain.

Summary

Adds UTF-8 response-byte counts to MCP spans, a histogram, and completion/error logs.

  • MCP tool traces now show how many bytes each result returns.

Reviews (3) · Last reviewed commit: "Merge branch 'main' into brandon/mcp-out..." · Reviewed by Greptile

Comment thread packages/api/src/mcp/__tests__/tracing.test.ts Outdated
@github-actions

github-actions Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

E2E Test Results

✅ All tests passed • 440 passed • 1 skipped • 1615s

Status Count
✅ Passed 440
❌ Failed 0
⚠️ Flaky 1
⏭️ Skipped 1

Tests ran across 4 shards in parallel.

View full report →

@github-actions

Copy link
Copy Markdown
Contributor

🔵 Tier 2 — Low Risk

Small, isolated change with no API route or data model modifications.

Why this tier:

  • Standard feature/fix — introduces new logic or modifies core functionality

Review process: AI review + quick human skim (target: 5–15 min). Reviewer validates AI assessment and checks for domain-specific concerns.
SLA: Resolve within 4 business hours.

Stats
  • Production files changed: 1
  • Production lines changed: 35 (+ 58 in test files, excluded from tier calculation)
  • Branch: brandon/mcp-output-telemetry
  • Author: brandon-pereira

To override this classification, remove the review/tier-2 label and apply a different review/tier-* label. Manual overrides are preserved on subsequent pushes.

'Count of MCP tool invocations that returned an error or threw an exception.',
});

const toolResponseSizeHistogram = getHistogram(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 major — Byte histogram uses the default bucket boundaries, which stop at 10,000, so every response over about 10 KB lands in the overflow bucket

Pass explicit byte-scale buckets when creating the instrument, e.g. getHistogram('hyperdx.mcp.tool.response.size_bytes', { ..., unit: 'By', advice: { explicitBucketBoundaries: [256, 1024, 4096, 16384, 65536, 262144, 1048576, 4194304] } }). @opentelemetry/api ^1.8 supports advice. Nothing in the repo sets a View or OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION; getHistogram in packages/api/src/utils/instrumentation.ts just calls meter.createHistogram, so the SDK's default explicit buckets apply. Those are [0,5,10,25,...,7500,10000], which suit milliseconds. Search and query tool results are often tens to hundreds of KB, so they all fall into the single >10000 bucket. Percentiles then can't tell a 12 KB response from a 2 MB one, which defeats the PR's stated purpose of checking whether output trimming works.

Nothing blocks merge automatically, but a maintainer will expect this fixed if it is a real defect in code this PR changes. If it is about surrounding code, reply and say so instead of patching. Do not widen the PR. How to respond

@github-actions

Copy link
Copy Markdown
Contributor

PR Review

If you are a coding agent acting for the author, read this first.
Nothing below blocks merge automatically; a maintainer decides. What they will expect:

  • 🔴 critical or 🟠 major in code this PR adds or changes: fix before asking for review.
  • 🔵 minor in code this PR changes: your call. Fix if small, otherwise reply.
  • Anything about surrounding code, or asking you to widen the change (hoist a helper,
    dedupe with another file, fix other call sites): never fix here, whatever the
    severity. Reply with one sentence; if it is critical, say so plainly so a human sees it.

One commit per review round. After two rounds, stop and ask a maintainer to review
scope rather than addressing more automated findings. Full rule: AGENTS.md.

1 finding(s): 🔴 0 critical · 🟠 1 major · 🔵 0 minor

1 posted as inline comment(s) on the changed lines.


Severity is the reviewer's own estimate and is used for ordering, not filtering. No finding blocks merge automatically; a maintainer decides.

@github-actions

Copy link
Copy Markdown
Contributor

Deep Review

✅ No critical issues found.

This is a tightly-scoped, well-tested observability change. Response size is computed by a pure guarded helper (content ?? [], block?.text ?? ''), recorded on both the span and a new hyperdx.mcp.tool.response.size_bytes histogram with a success/error outcome label, and the thrown-error path correctly records nothing. Metric naming, By unit, and low-cardinality attributes match the existing hyperdx.mcp.tool.* conventions, and the earlier shared-histogram-mock concern has been resolved by keying createHistogram on the metric name. No P0/P1/P2 findings with a concrete failure mode survived re-grading.

🔵 P3 nitpicks (3)
  • packages/api/src/mcp/utils/tracing.ts:123 — The size computation runs inside the result try block before the if/else, so a throw there would be caught at the catch, surfaced as a server error, and rethrown, discarding an otherwise-successful result; the only trigger is a non-string text block that violates the ToolResult.content type, so the path is not reachable from current handlers.
    • Fix: Coerce text to a string in getResponseSizeBytes or wrap the setAttribute/record calls so instrumentation can never alter the tool's returned result.
    • reliability, testing
  • packages/api/src/mcp/__tests__/tracing.test.ts:397 — The thrown-exception tests assert duration and error-counter calls but not that the response-size histogram is left unrecorded, so a regression that records a size on the throw path would pass silently.
    • Fix: Add expect(mockResponseSizeHistogram.record).not.toHaveBeenCalled() to the thrown-exception tests to lock in the no-size-on-throw contract.
    • testing
  • packages/api/src/mcp/utils/tracing.ts:40 — The defensive branches of getResponseSizeBytes (undefined content, and non-text or text-less blocks contributing 0 bytes) are not exercised by any test.
    • Fix: Add cases for a result with content: undefined and one containing a non-text block, asserting a recorded size of 0.
    • testing, reliability

Reviewers (10): correctness, testing, maintainability, project-standards, kieran-typescript, reliability, performance, previous-comments, agent-native, learnings-researcher.

Testing gaps: No assertion that responseBytes appears in the logger.info/logger.warn structured fields; only the span attribute and histogram are asserted.

No finding blocks merge automatically. A maintainer will expect P0/P1 findings in code this PR changes to be fixed; P2/P3 are your call -- fix or reply. Never fix findings about surrounding code here; reply instead. Do not widen the PR. How to respond

@brandon-pereira
brandon-pereira requested review from a team and karl-power and removed request for a team October 19, 2026 20:54
@kodiakhq
kodiakhq Bot merged commit ee9f84b into main Oct 19, 2026
29 checks passed
@kodiakhq
kodiakhq Bot deleted the brandon/mcp-output-telemetry branch October 19, 2026 22:40

This branch was successfully deployed

2 active deployments
Preview – hyperdx-storybook — 6401b60f Deployed Oct 19, 2026 by vercel[bot]
Preview – hyperdx-oss — 6401b60f Deployed Oct 19, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

automerge review/tier-2 Low risk — AI review + quick human skim

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants