A tool call that reads a config file and one that reads a 40MB credential dump are the same event in the trail today. Nothing about a tool response is recorded at either capture tier, so the trail can say what an agent asked for and never how much it got back.
docs/agentmetry-event-schema.md already admits this in the coverage table:
| Tool outputs | Not logged on bus v1 | Roadmap |
Where it stands today
Tier A (MCP proxy). tools/mcp_audit_proxy.py reads result in two places only: to accumulate tools/list pages for the schema fingerprint, and to match an error response so it becomes a tool_failed event. Successful responses pass through untouched and unrecorded.
Tier B (IDE hooks). scripts/agentmetry_ingest.py receives tool_response from PostToolUse and uses it solely to decide the outcome:
if data.get("tool_response") is not None and outcome == "success":
reason = reason or "tool_completed"
The payload is then discarded. So this is not a proxy-only gap, it is universal.
Why it matters
Exfiltration is visible in the response, not in the request. credential-exfil correlates an access with a later egress, which is the right shape and catches the sequence, but it cannot see magnitude. Magnitude is most of what separates a developer reading one key from a script walking a directory, and it is the single most useful number a responder wants when triaging a finding that is already on their screen.
It also closes a gap in the evidence story rather than the detection story. An audit record that omits how much data moved is answering half the question a compliance reviewer asks.
What to record
Metadata, never content. Same discipline as input_hash:
tool.output_bytes: size of the response payload
tool.output_hash: SHA-256, so two responses can be compared without either being stored
tool.output_truncated: whether the recorded size is the real size or a cap
A response body in the trail would be worse than not recording it. It would put file contents, query results and anything a poisoned tool returned into an append-only store that gets forwarded to a SIEM, which turns an audit trail into a data breach with good provenance. Hash and size answer the question that matters and carry none of that risk.
Open questions for whoever picks this up:
- Where the byte count is measured. The serialised JSON-RPC
result is the honest number at the proxy. At the hook tier the shape differs per IDE, so this may need a per-adapter helper rather than one rule.
- Whether a size cap is needed before hashing very large responses, and what the cost is on a slow disk.
- Whether
tool_failed responses should carry the same fields. Probably yes, since an error body has a size too and a burst of large errors is its own signal.
Sequencing, deliberately
Recording is freeze-safe. It touches the proxy, the ingest script, the canonical builder and the API model, and none of detection/rules.py, detection/traits.py, detection/engine.py or mitre.py, so it does not move the ruleset fingerprint or restart the dogfood gate.
A detection rule that keys on response volume is not freeze-safe and must wait. The gate closes no earlier than 2026-09-05, and #44 lands first.
So: record the fields now, write the rule later. Landing the fields early is also the only way the rule has any history to be tuned against when it arrives, since a threshold picked with no data is a guess.
Prior art
Noticed while reviewing mcp-agent-security-gateway, an inline MCP gateway whose egress layer thresholds on response data volume. Different posture from Agentmetry, which records rather than blocks, but it surfaces a real hole on the recording side.
A tool call that reads a config file and one that reads a 40MB credential dump are the same event in the trail today. Nothing about a tool response is recorded at either capture tier, so the trail can say what an agent asked for and never how much it got back.
docs/agentmetry-event-schema.mdalready admits this in the coverage table:| Tool outputs | Not logged on bus v1 | Roadmap |
Where it stands today
Tier A (MCP proxy).
tools/mcp_audit_proxy.pyreadsresultin two places only: to accumulatetools/listpages for the schema fingerprint, and to match an error response so it becomes atool_failedevent. Successful responses pass through untouched and unrecorded.Tier B (IDE hooks).
scripts/agentmetry_ingest.pyreceivestool_responsefrom PostToolUse and uses it solely to decide the outcome:The payload is then discarded. So this is not a proxy-only gap, it is universal.
Why it matters
Exfiltration is visible in the response, not in the request.
credential-exfilcorrelates an access with a later egress, which is the right shape and catches the sequence, but it cannot see magnitude. Magnitude is most of what separates a developer reading one key from a script walking a directory, and it is the single most useful number a responder wants when triaging a finding that is already on their screen.It also closes a gap in the evidence story rather than the detection story. An audit record that omits how much data moved is answering half the question a compliance reviewer asks.
What to record
Metadata, never content. Same discipline as
input_hash:tool.output_bytes: size of the response payloadtool.output_hash: SHA-256, so two responses can be compared without either being storedtool.output_truncated: whether the recorded size is the real size or a capA response body in the trail would be worse than not recording it. It would put file contents, query results and anything a poisoned tool returned into an append-only store that gets forwarded to a SIEM, which turns an audit trail into a data breach with good provenance. Hash and size answer the question that matters and carry none of that risk.
Open questions for whoever picks this up:
resultis the honest number at the proxy. At the hook tier the shape differs per IDE, so this may need a per-adapter helper rather than one rule.tool_failedresponses should carry the same fields. Probably yes, since an error body has a size too and a burst of large errors is its own signal.Sequencing, deliberately
Recording is freeze-safe. It touches the proxy, the ingest script, the canonical builder and the API model, and none of
detection/rules.py,detection/traits.py,detection/engine.pyormitre.py, so it does not move the ruleset fingerprint or restart the dogfood gate.A detection rule that keys on response volume is not freeze-safe and must wait. The gate closes no earlier than 2026-09-05, and #44 lands first.
So: record the fields now, write the rule later. Landing the fields early is also the only way the rule has any history to be tuned against when it arrives, since a threshold picked with no data is a guess.
Prior art
Noticed while reviewing mcp-agent-security-gateway, an inline MCP gateway whose egress layer thresholds on response data volume. Different posture from Agentmetry, which records rather than blocks, but it surfaces a real hole on the recording side.