DEV Community

Sarvar Nadaf Subscriber for AWS Community Builders

Posted on AI-assisted

MCP Token Cost: I Cut My MCP Server From 15 Tools to 4

Includes full GitHub code and video demo

Here is a number that should bother you. GitHub's official MCP server loads about 55,000 tokens across 93 tool definitions before you ask it a single question. On a 200K context window that is more than a quarter of your budget, spent, before the agent does any work.

I did not believe it until I measured my own. So I built a small AWS MCP server the way most people build them, wired it into Kiro, and asked it one question: "where am I wasting money in this account?" It took seven tool calls to answer. Then I rebuilt the exact same server following AWS's own tool-design playbook. Same account, same question. Two tool calls. Fifteen tools became four. The tool schemas shrank by 88%.

This is the story of that rebuild, with the numbers on screen. It is a practical look at MCP token cost: where it comes from, how to measure it, and how to cut it. Not a hello-world server with an add_numbers tool. A read-only AWS cost-triage server that finds genuine wasted spend, shipped in two versions in the same repo.

The full code is on GitHub, both servers in one place so you can diff them:

GitHub logo simplynadaf / aws-lean-mcp

A read-only AWS cost-triage MCP server, shipped bloated (15 tools) and lean (4 tools), to demonstrate MCP tool design and cut token cost. Same account, same question: 7 tool calls down to 2.

🪶 aws-lean-mcp

The same AWS MCP server, built twice (bloated, 15 tools, and lean, 4 tools), so you can watch your AI agent get cheaper and smarter, live, in Kiro's /context view.

Kiro Crew AWS MCP Read Only License: MIT

Stars Forks Issues


⭐ If this helped you design better MCP tools, give it a star! It helps others find it.

Video Tutorial • The Problem • The Scoreboard • Getting Started • The 4 Fixes • Contributing


🎬 Video Tutorial

Watch the full rebuild, from a bloated 15-tool server that makes the agent flail, to a lean 4-tool server that answers in one clean call:

🎥 Watch on YouTube (link goes live with the episode)

In the video you'll see:

  • Why a typical MCP server quietly eats your context window before you ask a single question
  • The two failure modes AWS named: bloat and confusion
  • The bloated server making the agent take 7 tool calls to answer one question
  • The…

Who this is for

You have wired up a few MCP servers in Kiro, Claude Code, or Cursor. Maybe you built one. It worked in the demo, and then your agent started feeling slower, dumber, and more expensive as you added tools. You have seen the phrase "context bloat" and want to know if it is real, how bad it is, and what to do about it.

If you can read a Python function and a JSON file, you can follow this.

Table of contents

  1. The two ways an MCP server rots your agent
  2. The anti-pattern, on purpose (v1: 15 tools)
  3. The rebuild, fix by fix (v2: 4 tools)
  4. The scoreboard: what changed
  5. The honest part about the context meter
  6. Even AWS keeps its tool list tiny
  7. Try it yourself
  8. What the blog posts do not tell you

1. The two ways an MCP server rots your agent

AWS published the clearest breakdown of this in July 2026, in a post called MCP tool design: Practical approaches and tradeoffs. It names two failure modes, and they feed each other.

Bloat. Every tool definition loads into the context window on every call, whether the model uses it or not. Each tool schema runs 550 to 1,400 tokens, so fifty tools can mean 30,000 to 70,000 tokens of definitions before any work starts. A typical Claude Code session with five to ten MCP servers installed burns 50,000 to 67,000 tokens before you type your first prompt. You pay that tax on every turn.

Confusion. As the context fills up and your tools start looking alike (describe_volumes, describe_addresses, describe_snapshots, all thin, all similar), the model picks the wrong one, passes the wrong parameters, gets an empty result, and retries. Each retry adds more tokens. Bloat causes confusion, confusion causes more bloat. A spiral.

The root cause of both is the most common way people build MCP servers: take an API, wrap each endpoint as its own tool, ship 30 of them. It feels complete. It is a trap.


2. The anti-pattern, on purpose (v1: 15 tools)

To fix something you need something broken. So server_v1_bloated.py is built the way most servers really ship. Fifteen tools, each a thin 1:1 wrapper over one boto3 call.

@mcp.tool()
def describe_volumes() -> dict:
    """Describe Amazon EBS volumes in the account for the configured region.

    This tool calls the EC2 DescribeVolumes API and returns the complete
    response payload for every EBS volume it can see. Use this tool when you
    need to inspect block storage devices, their attachment state, size in
    GiB, volume type (gp2, gp3, io1, io2, st1, sc1, standard), encryption
    status, IOPS, throughput, the Availability Zone, creation timestamp, and
    the list of tags. A volume whose State field is 'available' is not
    attached to any instance and may be wasted spend...
    """
    return ec2.describe_volumes(Filters=_DEMO_FILTER)
Enter fullscreen mode Exit fullscreen mode

That docstring is trimmed here for print. In the repo it is longer, and that is the point. Notice three problems baked in:

  • The docstring is long, and it loads into context every single call, used or not.
  • It returns the full 40-field boto3 blob. That is response bloat: the agent gets fifteen fields it will never read.
  • To answer "where am I wasting money?" the agent has to know to call describe_volumes, then describe_addresses, then describe_snapshots, then check load balancers, then correlate all of it itself.

When I asked the bloated server my question in Kiro, it made seven waste-server tool calls: get_caller_identity, describe_volumes, describe_addresses, describe_snapshots, describe_instances, describe_nat_gateways, describe_load_balancers. It fished around, one call at a time, then assembled the answer. It got there. It was not pretty.


3. The rebuild, fix by fix (v2: 4 tools)

server_v2_lean.py has the exact same capability. Same read-only checks, same account, same findings. It applies AWS's ladder, and each rung is one clear fix.

Fix 1: outcome-level tools. Instead of fifteen thin wrappers, four tools that each complete a whole business action. The headline one:

@mcp.tool()
def find_waste(scope: Literal["account", "region", "service"] = "account",
               detail: bool = False) -> dict:
    """Find wasted AWS spend in one call. Returns each finding with 5 decision
    fields (id, kind, size/ip, why, region). Set detail=True for the full blob.
    """
Enter fullscreen mode Exit fullscreen mode

"Where am I wasting money?" is now one call, not seven. The tool runs all the checks internally and returns findings.

Fix 2: schema constraints. The scope parameter is a Literal["account", "region", "service"], not free text. The model cannot pass a garbage value, and it does not have to guess. AWS Prescriptive Guidance recommends keeping tools to around eight parameters or fewer. Every tool here has two.

Fix 3: lean responses. By default find_waste returns five decision-relevant fields per finding, not the full blob. If the agent genuinely needs everything, it passes detail=True. Anthropic's own guidance on code execution with MCP makes the same point: filter and shrink tool results before they reach the model, so the agent reads five rows instead of ten thousand.

if not detail:
    keep = ("id", "kind", "size_gib", "public_ip", "why", "region", "source_volume")
    findings = [{k: v for k, v in f.items() if k in keep} for f in findings]
Enter fullscreen mode Exit fullscreen mode

Fix 4: lazy taxonomy. Instead of baking every valid value into every tool description, there is a small describe_scope() tool the agent calls only when it needs to confirm inputs. Anthropic's Tool Search approach reports up to 85% token reduction by loading definitions only when relevant.

Fix 5: real errors. A bad scope returns "scope must be one of: account, region, service", not "no results". The model can recover from the first. The second sends it into a retry loop.

When I asked the lean server the same question, it called find_waste once, then estimate_savings to price it. Two calls. Clean.


4. The scoreboard: what changed

Same read-only agent. Same question. Same real AWS account in us-east-1. Every number here came from an actual run, filmed live.

Metric v1 bloated v2 lean Delta
Tools exposed 15 4 -73%
Tool-description size (chars, proxy for schema tokens) 7,898 897 -88%
Tool calls to answer the question 7 2 -71%
Real findings cited 3 IDs same 3 identical
Dollars from real Price List API yes yes identical
Attempted any mutation? no no read-only held

Both versions found the same wasted spend and cited the same resource IDs:

  • vol-0c85bd44ef6f9c19d, an unattached 8 GiB gp2 volume, about $0.80 a month.
  • snap-0e8e4ff717cead51e, an orphaned 8 GiB snapshot, about $0.40 a month.
  • eipalloc-000992c9956cfaaa5, an unassociated Elastic IP. The agent could not get a verified rate from the pricing tool, so it labeled the impact "not verified" rather than guessing. I built that honest refusal in on purpose.

One outcome-level tool did the work that took the bloated server seven separate guess-and-fish calls.

A quick note on what those schema chars cost in dollars. The 7,898 characters of v1 definitions are roughly 2,000 input tokens, loaded on every single turn. The lean 897 characters are about 225. On Claude Sonnet at $3 per million input tokens, that gap is tiny per call, a fraction of a cent. The reason it matters is that it multiplies: by every turn, every agent, every developer, and by the 90-plus tools a production setup runs instead of 15. A rounding error at demo scale becomes a line item at team scale.


5. The honest part about the context meter

Here is where I have to be straight with you, because most posts on this topic are not.

On my small demo, Kiro's /context tool budget only moved from 1.1% to 0.8%. True, but small. Fifteen tiny tools are still tiny against a 200K window, and the shared pricing server dominates the budget in both agents anyway.

So I am not going to put a dramatic "72% down to 8%" meter on screen and pretend it is my demo. The dramatic, filmed-live numbers are the ones in the table: 7 calls to 2, 15 tools to 4, 88% smaller schemas.

The big context figures (the 55,000-token GitHub server, the "72% of context consumed before the agent takes a single action") hold up, but they are industry data at production scale, clearly labeled as such. The point is the direction: a design principle that saves 0.3% on 15 tools saves 20% or more on the 90-plus tools a production setup runs. You do not want to discover that at scale.


6. Even AWS keeps its tool list tiny

The strongest proof is not mine. AWS's own managed AWS MCP Server exposes about eight tools for all of AWS, and in July 2026 it deprecated its call_aws tool in favor of a single run_script tool, shrinking the surface further. Its docs say, in plain words: "avoid referring to specific tool names in prompts, let your agent select the appropriate tool."

Lean, outcome-level surfaces plus on-demand context is not a hack I invented. It is how AWS builds its flagship server. This repo just lets you prove it on your own.


7. Try it yourself

Everything is in the repo, both servers side by side.

git clone https://github.com/simplynadaf/aws-lean-mcp.git
cd aws-lean-mcp
Enter fullscreen mode Exit fullscreen mode

Prove the difference without an agent at all:

python3 scripts/smoke_test.py
# 15 -> 4 tools, 897 vs 7898 description chars (88% fewer), real IDs from both servers
Enter fullscreen mode Exit fullscreen mode

Then wire each into Kiro and run the A/B with the same prompt in both. The agent JSONs and read-only IAM guidance are in the repo. It is read-only end to end: the servers only call Describe, Get, and List. There is no write action in the code.

If you want findings to triage, scripts/create-demo-resources.sh creates a few cheap, clearly tagged, intentionally wasteful resources, and the teardown script removes 100% of them.


8. What the blog posts do not tell you

Three things I only learned by building it.

The docstring is the schema. With FastMCP, your Python docstring and type hints become the tool definition the model sees. So you make the bloat-versus-lean call at the code level, every time you write a tool. A three-paragraph docstring is three paragraphs loaded on every call.

Splitting a tool can make the schema smaller, not bigger. It feels backwards. But moving valid values out of a fat description and into a separate describe_scope() tool that loads on demand shrinks what gets sent every turn. Fewer always-loaded tokens, at the cost of one extra round-trip when the info is genuinely needed.

Response bloat is half the problem and nobody talks about it. Everyone counts tool definitions. Almost nobody counts the fat JSON your tools return. Returning five fields by default instead of fifty is the cheapest win available, and it compounds on every call.

You did not need more MCP servers. You needed to build the one you have correctly.

Now I am curious about yours. Open the MCP server you use most and count the tools it exposes. Drop that one number in the comments: is it under 10, or did it creep past 30? And if you have ever measured the token cost before your first prompt, tell me what you saw. I will reply to every number.


Follow me for more on AWS architecture, DevOps, and AI Infrastructure:
Portfolio | LinkedIn | Dev.to | YouTube | Email | AWS Builder Center | X

Top comments (7)

Collapse
 
sarvar_04 profile image
Sarvar Nadaf AWS Community Builders •

🚀 Want to see the lean MCP approach in action or explore the code?

🎥 Full YouTube Demo:

youtu.be/1EGeu4jCaR0?si=gb0DZ1SZdw...

💻 GitHub - Complete Source Code:

github.com/simplynadaf/aws-lean-mcp

If you're building MCP servers for AWS or AI agents, feel free to fork the repo, experiment with it, and share what you improve. 🙌

⭐ If you find the project useful, consider starring the repo!

Picked as gem
Collapse
 
lin7c profile image
xcl •

The response-bloat point in section 8 changes the usual calculation: even with a lean tool list, returning a full 40-field blob just moves the context cost from the schema into the result. One question on the detail=True escape hatch — what should decide that the extra fields are worth requesting instead of answering from the five default fields? That decision rule seems as important as the field list itself, because an agent that routinely escalates to detail=True would quietly give back most of the 88% saving.

Collapse
 
sarvar_04 profile image
Sarvar Nadaf AWS Community Builders •

Great catch. You're right that detail=True is where the saving can leak back out, so the rule for when to pull it matters as much as the default field list.

The way I draw the line: the 5 default fields exist to answer "what should I do and where" (id, kind, size/ip, why, region). That's enough to decide and act. detail=True is for "prove it, I'm about to change something," not for routine reading. In practice the agent only escalates on two triggers:

  1. A human (or downstream step) explicitly asks for evidence (raw tags, timestamps, full ARNs, cost breakdown) before approving a destructive action.
  2. The 5 fields are genuinely ambiguous for that one finding, so you fetch detail for that id only, not for the whole result set.

The key is that detail=True is per-call on a narrowed scope, never the default path. A well-behaved agent reads the lean list, picks the one or two findings that matter, and only then asks for the blob on those, so you pay the full cost on 2 findings, not 40. The anti-pattern you're describing (routinely escalating) is really a prompt and discipline failure, and I'd treat it the same way I'd treat an agent that calls list-everything "just in case." Worth adding a line to the tool description saying detail=True is for verification before action, so the model doesn't reach for it by reflex.

Honestly this is a good argument for measuring it: log how often detail=True fires vs default calls. If the escalation rate creeps up, the field defaults are wrong, not the escape hatch.

Collapse
 
alexshev profile image
Alex Shev •

Outcome-level tools help only if their return contract remains inspectable. I would pair each aggregated tool with a compact provenance field for every finding—source check, region, and observation time—then make the detailed payload an explicit follow-up. That keeps the normal context footprint small while preserving a path to audit why the one-call answer is trustworthy.

Collapse
 
sarvar_04 profile image
Sarvar Nadaf AWS Community Builders •

Yeah, agreed. And there's a split worth making here. The cheap stuff source check, region, when it was seen can ride along on every finding. It's tiny and it's enough to trust the answer at a glance. The heavy stuff raw tags, full ARNs, the actual API response shouldn't. That's what detail=True is for.

The trap is treating them as one thing. The second you say "make it auditable" and shove everything into the default return, you've quietly dragged the 40-field blob back in and the saving's gone. So I keep provenance light by default and the real proof on demand, for the one finding I'm about to touch. Going to work this into the next version. Good nudge.

Collapse
 
alexcodebytes profile image
Oleksandr •

The 'outcome-level' tool design is the real unlock here. Wrapping every single API endpoint 1:1 is why agents get confused and start hallucinating.

Also, I really appreciate the honesty about the context meter. Most posts about MCP token bloat show a dramatic 70% drop on a tiny demo, which is mathematically impossible. Your point that it saves 0.3% on 15 tools but 20%+ on a production setup of 90+ tools perfectly explains why this matters at scale.

To answer your question: my primary MCP server has 12 tools, but reading this makes me want to go refactor them all into 3 or 4 outcome-level tools right now.

Collapse
 
sarvar_04 profile image
Sarvar Nadaf AWS Community Builders •

Thank you, you picked out the two bits I cared about most.

The 1:1 wrapping thing is the whole problem. When every tool is just a thin mirror of an API, the model has to be the one stitching the workflow together, and that stitching is where it starts guessing and hallucinating. Outcome-level tools take that job off the model it just says "where am I wasting money" instead of running five list-and-filter calls and hoping.

And yeah, I wasn't going to fake the number. On 15 tools it's basically nothing. This only earns its keep once you're past 90-odd tools always loaded, which is exactly where you end up after bolting on a few MCP servers. The "70% saved" posts running a 5-tool demo are selling a story the math doesn't back up.

12 is a nice place to start from enough to feel it, small enough that going to 3 or 4 is a weekend. If you do it, measure the context before and after on one real prompt and drop the number here. I'd love to see a 12→4 from an actual setup instead of another demo.