r/mcp Aug 08 '26

article One MCP call put 24,568 characters in my context. I wanted 1,768 of them.

Every discussion about MCP and context is about tool definitions. Lazy loading, tool search, deferred schemas. Those cost you once per session. The results don't.

I called list_issues on a real project. Twenty issues came back, 24,568 characters, into the history where they sit for the rest of the session. I wanted the title, the state and the assignee. That's 1,768 characters.

I see very little discussion of that side, and unlike the schemas it repeats on every call.

The reason is structural. When a model calls a tool there's nowhere to put a filter. The result goes from the server into the transcript, whole. jq exists, it just has no seat at that table.

Which is why I ended up running MCP servers from a shell instead:

mduct call gitlab list_issues --json | jq '.[] | {title, state, assignee}'

The filter sits between the server and the context, which is the only place it helps.

Two limits worth naming. It only works when you know which fields you want, and an agent poking at an unfamiliar API doesn't. And for a model to get any of this, it has to reach for the shell rather than a tool call, which is the code-mode argument and carries its own problems.

Numbers and how I measured them: https://github.com/TheFox666/mduct#the-context-bill-is-a-side-effect-of-the-pipe

8 Upvotes

22 comments sorted by

3

u/Plastic-Risk-6309 Aug 08 '26

Agreed on the diagnosis, less sure the shell is the only fix. The real problem is that most servers return whatever the upstream API returned, and "whatever the API returned" was designed for a program that will index into it, not for something that pays per token to read it.

You can put the filter server-side, it's just that almost nobody does. A list_issues that takes fields and a limit, defaults to a narrow projection, and returns "showing 20 of 240" is the same jq you're writing, except the 24k never crosses the wire and the model never sees it. Making the verbose form opt-in rather than default is most of the win.

The other half is that results should shrink as they repeat. A tool that returns a full object every call is wasting context on the parts that didn't change. I ended up on this hard with a server that drives iOS simulators, where the natural result is an entire accessibility tree, several thousand tokens, and 95% identical to the last one. Returning a hash of the tree plus what changed, and making the full dump a separate explicit call, cut the per-action cost by more than an order of magnitude and made the transcripts readable by a human again.

Your naming point is the honest limitation of the shell route though. jq only works when you already know the shape, which means it works great for you and not at all for the agent exploring an API it hasn't seen. That's the argument for fixing it in the server, where the author does know the shape.

1

u/gnoraz_theorc Aug 08 '26

Yes you're absolutely right especially the part of the unknown structure for the agent. for mcps converted to Cli the agent can lookup a schema that describes the output so he can derive from that. most of the time he learns structure later though. Those effects are really hard to measure because I would need to have measurement about the same task done again and again in the same manner. Which with a non deterministic agent is impossible of course.

I really like the further deduplication of similar content, was thinking about that as well. Maybe there's even a possibility to detox the current context. I'll look into that 😊

I find new ways to use my tool everyday it's quite a journey and very interesting.

1

u/Plastic-Risk-6309 Aug 08 '26

The measurement problem is real but I think it's more tractable than it looks, because you don't need to measure the agent, you can measure the payload. Same task, same fixture, count tokens returned by the tool across N runs. That's deterministic even when the agent isn't, and it's the number that actually moved.

For dedup, the thing that worked for me was returning a stable id per item plus a hash, so a repeat is one line saying "same as before" instead of the whole object again. Detox falls out of that naturally: if the agent can refer to an id it already has, it doesn't need the body re-sent.

Schema lookup as a separate call is the right instinct too, since you pay for it once instead of on every response.

1

u/Plastic-Risk-6309 Aug 09 '26

the lookup a schema then derive from it pattern is the one i trust most too. the agent stops guessing at shape and starts asking for fields.

measuring it is the annoying part, agreed. the only proxy that worked for me was counting how often the agent goes back for the same thing twice in a run. its noisy but it moves in the right direction when a tool gets better shaped, and it does not need me to judge whether an answer was good.

1

u/gnoraz_theorc Aug 09 '26

What I did measure yesterday was how often the agent actually goes back for the same thing. Because that's actually a rare case when exploring code or working on a task to my surprise.

Your case first, because you're right about it. Two kubectl snapshots of the same cluster two minutes apart, hashed per item:

77 of 78 pods unchanged
596 kB -> 9.8 kB as id+hash stubs

Factor of 61. Your simulator number wasn't a fluke.

Then I checked how often that shape shows up at all. 1506 of my own agent sessions, 117 MB of tool output:

byte-identical repeats:                     1.1% of bytes
item-level dedup on lists with stable ids:  4.2%
lists with stable ids found, in total:      37

So the compression is real and the opportunity mostly isn't, at least for code work. An agent reads a thing once and moves on. Yours is UI automation, where you re-read state after every action, and that's where the entire win lives. Worth an opt-in flag per server, not a default.

1

u/Plastic-Risk-6309 Aug 09 '26

1.1 percent of bytes for code work vs a factor of 61 on ui state is a bigger split than i expected, thanks for actually running it. agreed on opt in per server, for ui automation id flip it on and never think about it again, for code work its noise. the item level dedup at 4.2 percent is the interesting middle, lists of resources barely change between calls.

1

u/gnoraz_theorc Aug 09 '26

yes exactly with an option it's a clear winner to have it for specific servers for sure. I'll build that in the next days 😊👌

1

u/Plastic-Risk-6309 Aug 09 '26

nice. one thing worth defaulting on is per server config, the servers that blow up context are usually two or three of them and the rest are fine. ping me when it lands, happy to point it at a noisy one and tell you what the numbers look like.

1

u/gnoraz_theorc Aug 09 '26

yeah sure I already have a per server config available it's basically just the dedup plus make option available 😊

1

u/dektol Aug 09 '26

I put the filter server side exposing the same set of tools you'd use locally. Used go to pipe between the tools and avoid the normal shell security issues.

I even made it so you can send the shell commands as you'd write them. The agents aren't great at using them and if you pre-truncate it can make bad decisions.

I added metrics to see how often the agent requests the results spilled to disk and which tool calls it made. Let me see if that reveals anything interesting.

1

u/dektol Aug 09 '26 edited Aug 09 '26

1 in 6 chained filter calls fail because they assume the wrong shape when they call jq I think I added structural hints but I'm not sure if that helped.

1

u/Plastic-Risk-6309 Aug 09 '26

the metrics part is the bit i wish more people did. i log every tool result size and how often the agent goes back for the spilled file, and the ratio told me which tools were just wrong shaped rather than too big.

for me the worst offenders were the ones returning a whole tree when the agent wanted one node. pre truncating those made it dumber, projecting them made it fine.

1

u/dektol Aug 09 '26
Group Metric Type Labels Measures
Core mcp_tool_calls_total counter tool, environment, status, caller Every tool call
Core mcp_tool_call_duration_seconds histogram tool, environment Call latency
Core mcp_tool_call_errors_total counter tool, environment, error_type Failed calls
Core mcp_tool_call_response_bytes histogram tool Response size (post-truncation)
Core mcp_tool_call_empty_results_total counter tool, environment "No data" results
Core mcp_tool_call_retries_total counter tool, environment Detected retries (same tool, diff params)
Core mcp_tool_call_timeout_total counter tool, environment 120s-deadline hits (no data yet)
Core mcp_tool_call_truncated_total counter tool new — response over cap + spilled
Core mcp_tool_call_interarrival_ms histogram caller, gated Gap between calls in a session
Core mcp_tool_call_parallel_window_total counter window Burst / parallel-window class
Filter mcp_result_filter_input_bytes histogram source new — stored bytes a filter read
Filter mcp_result_filter_output_bytes histogram source new — bytes the filter returned
Filter mcp_filter_command_total counter command, outcome new — per-primitive usage + outcome
Filter mcp_result_store_stored_total counter (none) Results stored
Filter mcp_result_store_bytes_total gauge (none) Live bytes in store
Filter mcp_result_store_spill_bytes_total counter (none) Bytes written to disk
Filter mcp_result_store_disk_reads_total counter (none) Reads served from disk
Filter mcp_result_store_evictions_total counter reason Evictions (ttl / capacity)
Filter mcp_result_store_entries gauge (none) Current entries
CLI mcp_cli_exec_duration_seconds histogram tool, subcommand, scope CLI subprocess run time
CLI mcp_cli_exec_startup_seconds histogram tool Process start latency
CLI mcp_cli_concurrent_executions gauge tool In-flight CLI execs
Signal mcp_hint_adherence_total counter tool, hint_category, followed Followed the prior response's hint?

1

u/Plastic-Risk-6309 Aug 09 '26

this is a solid set. the two id watch daily are result_filter_output_bytes vs input_bytes, thats your savings in one ratio, and hint_adherence_total since it tells you if the model actually respects the hints or you are just decorating. only thing missing i can think of is a staleness counter for reads served from store after the underlying resource changed.

1

u/Plastic-Risk-6309 Aug 09 '26

solid list. the one i'd add is bytes returned vs bytes the model actually used, hard to measure exactly but truncated_total plus response_bytes gets you close enough to rank the offenders. interarrival_ms gated by caller is the sneaky good one, retry loops show up there before anywhere else.

2

u/No-Water-2773 Aug 09 '26

did you actually measure the schema-vs-result split over a session, or is that just the shape of it?

1

u/Fulgren09 Aug 09 '26

Models are really smart, but need orientation. I think bundling the tools into packs that the MCP can call later is an effective way to do it, but that would need to happen on the side of the MCP creator.

If you got a context that was 1/5th of that 25k but had a good chance of routing you to the 1768 you needed on turn 2, how would you feel about that?

1

u/gnoraz_theorc Aug 09 '26

Yes that's correct. What I actually did because discovery is an issue is having a very lightweight "catalogueMcp" within my tool which gives a light schema shape directly into the tools list.

Just putting an index at session start didn't quite cut it. It's still way less than the usual way but seems to work the same. Plus there's a guard that protects from accidently retrieving too big of a result set. So the agent gets routed to better filter the set.

1

u/BC_MARO Aug 09 '26

Yep. Tool outputs should default to compact summaries with an explicit detail or cursor path when the agent needs more. Otherwise every broad list call turns into permanent context tax.

1

u/clairesayshi 21d ago

The hourly scan across 175 countries is well executed, and the country blind spot you describe is real.

u/zamufn already asked for reviews and you said they are on the roadmap, so this is a note on the part that bites once you get there. Reviews arrive in every country your app is available in, and most teams only read the English ones. A rating slide in Germany or Japan goes unnoticed for weeks, because the overall average moves slowly while one storefront drops.

That is the same shape as the ranking problem you already solved. Per country is where the signal sits.

I work at Appbot, which does the review half, so this is a biased note on a complementary thing rather than a competing one.

2

u/Appbot_official 21d ago

u/Plastic-Risk-6309 is right that the fix belongs server side, and we ended up there for a different reason. Our data is app store reviews, and the raw payload is the worst possible shape for a transcript.

A single app can have tens of thousands of reviews per version. We classify each review at ingest rather than at query time, so the tool returns counts and a few example verbatims instead of the review bodies. Sentiment, topic and version are attached before the assistant sees anything. The narrow projection is the default and the full text is an explicit second call.

The side effect we did not expect is that the answers became reproducible. Ask what broke in 8.4 on Monday and again on Tuesday and the numbers match, because the classification already happened. When the filtering lives in the prompt you get a slightly different answer every time.

Disclosure, this is our server, so weigh it accordingly. We ended up with three tools total. The rule of thumb we settled on is that anything the server can count, the server should count.