Repository navigation
Description
Summary
Every ClickHouse query an MCP tool sends carries log_comment = {"surface":"mcp","label":<tool>,"trace":<id>} (packages/api/src/mcp/utils/tracing.ts:113-120). That is very useful: we used it to find the cost of every MCP call in system.query_log.
But the user, the API key and the MCP client/session are only on the span and in the API log, not in log_comment (QueryAttribution in packages/common-utils/src/clickhouse/attribution.ts:49 has no field for them). So when several agents or people use the MCP at the same time, system.query_log alone can't say which session sent a query.
Environment
HyperDX 2-nightly image (late Oct 2026) with ClickHouse 26.10.3. Code references checked on main at d08b2fa (2026-11-03, 2.43.1).
Steps to reproduce
- Run two or more MCP clients at the same time, with the same or different API keys.
- Look at their queries in ClickHouse:
SELECT event_time, query_duration_ms, read_bytes, log_comment FROM system.query_log WHERE type = 'QueryFinish' AND JSONExtractString(log_comment, 'surface') = 'mcp' ORDER BY event_time DESC LIMIT 20
- All rows share
surface: "mcp". Only the per-calltracediffers, and mapping it back to a user or session needs a join to the API's own log.
Expected vs actual
| Expected | Actual | |
|---|---|---|
Who sent an MCP query, from query_log alone |
user (or API key id) and MCP session / client in log_comment |
only surface, label, trace |
Impact
In one hour, about 10 agents shared the MCP while investigating production logs. In the same minute, single calls read 34 GiB and 53 GiB. Finding out which session sent which expensive query meant joining query_log.log_comment.trace to the API log for every call. With the session in log_comment this is one GROUP BY, and it also opens the door to per-session limits on the ClickHouse side.
Proposed fix
Add to log_comment for the MCP surface:
- the user id, or the API key id once keys can be team-owned (feat: team-owned API keys #3435);
- the MCP session id (the transport's
Mcp-Session-Id) and the client name/version frominitialize(already on the span asmcp.client.name).
Optional follow-up: pass a per-user or per-session quota_key, so operators can set ClickHouse quotas (bytes read, queries) for MCP sessions.
Happy to send a PR for the log_comment fields.
Related
- feat: attribute ClickHouse queries to the surface that issued them #3155: query attribution by surface
- feat(mcp): record which source each MCP tool call reads #3522: record which source each MCP tool call reads (closed)
- feat: team-owned API keys #3435: team-owned API keys
- fix(mcp): add ClickHouse safety settings (max_execution_time, max_result_rows, readonly) #2427: ClickHouse safety settings for MCP queries
Activity
brandon-pereira commentedon Nov 4, 2026
Makes sense, open to a contribution for this change. Thanks for raising the issue :)
bcanvural commentedon Nov 4, 2026
Thanks! Before I open a PR, one design question, because it touches a rule in
attribution.ts(from #3155):What we need is to tell apart the MCP calls of different people: several people's agents use one HyperDX, and they all send the same client string (e.g.
claude-code/2.1.215), so the client name alone doesn't separate them. A session id isn't available either: the MCP endpoint is stateless (sessionIdGenerator: undefined).Two ways to do it, both next to a
clientfield (name/version from the User-Agent):system.query_logis normally readable only by ClickHouse operators. They can map the id to a user in HyperDX.callerinstead: HMAC-SHA256 of the user id, keyed with a sub-key derived from an existing server secret, truncated to 16 hex characters. The same person always gets the same value and different people get different ones, but it can't be reversed fromquery_logalone; mapping it back needs HyperDX's database and the secret. If no secret is configured, the field is left out.With either, cost per person in
query_logis oneGROUP BY, and a per-callerquota_keybecomes possible later.We'd lean towards 1, but are happy to do 2 if you'd rather keep the rule as it is. Which do you prefer?
brandon-pereira commentedon Nov 4, 2026
Gotcha, I didn't realize that constraint. Good flag. Based on the comment in the file, we should not expose either option. We can add an additional log comment signifying that it came from the MCP server, and what tool ran the query, but we should keep user and team data out as suggested
bcanvural commentedon Nov 4, 2026
Thanks, makes sense.
surface: "mcp"andlabel: <tool>are already there (#3155), so the only addition left is the client name/version (client: "claude-code/2.1.215"), which is "what asked", not "who". Happy to send that small PR if it's useful; otherwise we can close this. We'll do the per-person mapping on our side.brandon-pereira commentedon Nov 4, 2026
Lets close for now. We can revise this in the future if others see a need. Thanks for investigating this!
bcanvural commentedon Nov 4, 2026
Thank you! Also thanks for vouching for me!