<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Gérald Pereira</title>
    <description>The latest articles on DEV Community by Gérald Pereira (@kojiro_sazaki).</description>
    <link>https://dev.to/kojiro_sazaki</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4008165%2F5c620420-5b3d-4c94-81eb-0912b0bc4990.jpg</url>
      <title>DEV Community: Gérald Pereira</title>
      <link>https://dev.to/kojiro_sazaki</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kojiro_sazaki"/>
    <language>en</language>
    <item>
      <title>Designing a token-efficient MCP server: 5 patterns from shipping one with ~100 tools</title>
      <dc:creator>Gérald Pereira</dc:creator>
      <pubDate>Mon, 29 Jun 2026 13:06:03 +0000</pubDate>
      <link>https://dev.to/kojiro_sazaki/designing-a-token-efficient-mcp-server-5-patterns-from-shipping-one-with-100-tools-1i7p</link>
      <guid>https://dev.to/kojiro_sazaki/designing-a-token-efficient-mcp-server-5-patterns-from-shipping-one-with-100-tools-1i7p</guid>
      <description>&lt;h1&gt;
  
  
  Designing a token-efficient MCP server: 5 patterns from shipping one with ~100 tools
&lt;/h1&gt;

&lt;p&gt;Here is the thing nobody tells you when you start writing a &lt;a href="https://modelcontextprotocol.io" rel="noopener noreferrer"&gt;Model Context Protocol&lt;/a&gt; server: &lt;strong&gt;the hard part is not exposing your API to an LLM. The hard part is &lt;em&gt;not&lt;/em&gt; exposing too much of it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We just shipped an &lt;a href="https://api.octoperf.com/doc/mcp/" rel="noopener noreferrer"&gt;MCP server at OctoPerf&lt;/a&gt; that drives a full load-testing platform, &lt;strong&gt;around 100 tools&lt;/strong&gt;, and load testing turns out to be a brutal stress test for token budgets. A single virtual user is a deeply nested action tree. A test run produces gigabytes of results. &lt;strong&gt;One captured HTTP response body can be larger than the entire context window.&lt;/strong&gt; If you naively mirror a REST API into MCP tools, you get an agent that is expensive, slow, and confused.&lt;/p&gt;

&lt;p&gt;These are the five patterns we used to avoid that, plus the auth setup that makes it usable &lt;strong&gt;without an API key&lt;/strong&gt;. None of it is specific to load testing. If you are building your own server, you should be able to lift these directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the context window is the real budget
&lt;/h2&gt;

&lt;p&gt;A REST API and an MCP server look almost identical: both move JSON over HTTP. But they feed very different consumers. A web UI fetches a 200 KB entity, renders the 2% it needs, and throws the rest away for free. &lt;strong&gt;An LLM cannot throw anything away.&lt;/strong&gt; Everything a tool returns is read, token by token, on every reasoning step that follows.&lt;/p&gt;

&lt;p&gt;That hits you three ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Money.&lt;/strong&gt; Tool results are input tokens, and in an agentic loop they get re-read every turn. One oversized response is billed many times.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Latency.&lt;/strong&gt; Bigger context means slower responses, and an agent chains dozens of calls per task.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quality.&lt;/strong&gt; This is the sneaky one. A model digging through 50,000 tokens of irrelevant JSON is &lt;strong&gt;measurably worse&lt;/strong&gt; at finding the one field that matters.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So we made token cost a &lt;strong&gt;first-class design constraint&lt;/strong&gt; up front, and it shaped the whole API surface.&lt;/p&gt;

&lt;h2&gt;
  
  
  Auth first: OAuth 2.1, no API key
&lt;/h2&gt;

&lt;p&gt;Before the patterns, the part people ask about most. &lt;strong&gt;There is no API key anywhere.&lt;/strong&gt; Auth is &lt;a href="https://oauth.net/2.1/" rel="noopener noreferrer"&gt;OAuth 2.1&lt;/a&gt; with &lt;a href="https://oauth.net/2/pkce/" rel="noopener noreferrer"&gt;PKCE&lt;/a&gt; and &lt;a href="https://datatracker.ietf.org/doc/html/rfc7591" rel="noopener noreferrer"&gt;Dynamic Client Registration&lt;/a&gt;, over &lt;a href="https://modelcontextprotocol.io/specification/2025-06-18/basic/transports" rel="noopener noreferrer"&gt;Streamable HTTP&lt;/a&gt;. &lt;strong&gt;DCR&lt;/strong&gt; means a client self-registers instead of you minting credentials by hand, and &lt;strong&gt;every tool call runs as the connected user&lt;/strong&gt; with their own permissions, revocable at any time. We did not want yet another long-lived secret pasted into a config file, and an MCP server full of write tools is exactly where you do not want one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 1: presigned URLs, keep the bytes out of the conversation
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcvktqt05ps7wc79di65x.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcvktqt05ps7wc79di65x.jpg" alt="Ship the file to its destination, not through the messenger" width="800" height="514"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Load testing is file-heavy: CSV datasets go up, JTL results, &lt;a href="https://en.wikipedia.org/wiki/HAR_(file_format)" rel="noopener noreferrer"&gt;HAR&lt;/a&gt; archives, &lt;a href="https://playwright.dev/" rel="noopener noreferrer"&gt;Playwright&lt;/a&gt; traces and PDFs come down. Pushing file content through tool results would be absurd. &lt;strong&gt;A modest 2 MB results file is roughly half a million tokens&lt;/strong&gt;, spent on bytes the model would mostly never read.&lt;/p&gt;

&lt;p&gt;So a file tool &lt;strong&gt;never returns content&lt;/strong&gt;. It returns instructions to fetch it: a &lt;strong&gt;presigned URL&lt;/strong&gt; with a single-use, short-lived token, the HTTP method, and an expiry. The agent host's code interpreter (or a plain &lt;code&gt;curl&lt;/code&gt;) pulls the bytes straight from our REST API, and the model only ever sees the few dozen tokens of the envelope.&lt;/p&gt;

&lt;p&gt;The payoff goes past cost. Because the bytes bypass the model, &lt;strong&gt;file size stops being the LLM's problem&lt;/strong&gt;. The agent can pull a 50 MB Playwright trace, unzip it locally, and grep the one failing selector, something no context window could absorb.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 2: return listings, not entities
&lt;/h2&gt;

&lt;p&gt;Files are the obvious offender. &lt;strong&gt;Entities are the insidious one.&lt;/strong&gt; In our REST API a &lt;a href="https://api.octoperf.com/doc/design/edit-virtual-user/" rel="noopener noreferrer"&gt;virtual user&lt;/a&gt; carries its full recursive &lt;code&gt;children&lt;/code&gt; action tree, which for a recorded checkout easily runs to &lt;strong&gt;thousands of lines of JSON&lt;/strong&gt;. An agent calling &lt;code&gt;list_virtual_users&lt;/code&gt; to find an id needs none of it.&lt;/p&gt;

&lt;p&gt;So every list, create, and import tool returns a &lt;strong&gt;compact projection&lt;/strong&gt; instead: &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt;, &lt;code&gt;tags&lt;/code&gt;, timestamps, and a &lt;code&gt;url&lt;/code&gt;. Keep what the agent &lt;strong&gt;reasons about&lt;/strong&gt; (the id to chain into the next call, the human-readable name), drop what it does not (the tree, ids it already knows, internal discriminators). We have &lt;strong&gt;fifteen&lt;/strong&gt; of these, one per entity family.&lt;/p&gt;

&lt;p&gt;That last field matters more than it looks. Every listing carries a &lt;strong&gt;deep-link to the matching page in the UI&lt;/strong&gt;. It costs a handful of tokens and buys two things: the agent can hand the user a clickable link in any summary, and it gives the agent a &lt;strong&gt;graceful exit&lt;/strong&gt;. When a question is better answered by an interactive chart than another round of tool calls, it can just point you at the page instead of burning tokens reproducing it.&lt;/p&gt;

&lt;p&gt;Ballpark: a full entity weighs &lt;strong&gt;tens of thousands of tokens&lt;/strong&gt;, its listing &lt;strong&gt;about sixty&lt;/strong&gt;. On a busy project that is two orders of magnitude, on the very first call.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 3: patch, don't replace
&lt;/h2&gt;

&lt;p&gt;Reading was the easy half. &lt;strong&gt;Editing is where a naive design really bleeds.&lt;/strong&gt; If the only write tool is &lt;code&gt;update(fullEntity)&lt;/code&gt;, then renaming one action in a 500-action tree forces the agent to read the whole tree, regenerate it with one field changed, and send it all back. Two full copies through the context, plus a real risk the model mangles a field it should not have touched.&lt;/p&gt;

&lt;p&gt;Instead every entity family gets a &lt;code&gt;patch_*&lt;/code&gt; tool built on &lt;a href="https://datatracker.ietf.org/doc/html/rfc6902" rel="noopener noreferrer"&gt;RFC 6902 JSON Patch&lt;/a&gt;. The agent sends &lt;strong&gt;only the operations&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"op"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"replace"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/children/3/name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Submit payment"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"op"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"add"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/children/7/enabled"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;A surgical edit costs a few dozen tokens no matter how big the entity is.&lt;/strong&gt; Server-side, the patch is applied and then re-deserialized through &lt;a href="https://github.com/FasterXML/jackson" rel="noopener noreferrer"&gt;Jackson&lt;/a&gt; before persisting, a round-trip that rejects any patch producing a structurally invalid entity. &lt;strong&gt;The agent can be wrong, but it cannot corrupt your script.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One catch: to write a correct patch against a polymorphic tree, the model needs the shape of every node type. Guessing burns tokens on failed attempts. So we publish the entity schemas as &lt;strong&gt;MCP resources&lt;/strong&gt; (&lt;a href="https://json-schema.org/" rel="noopener noreferrer"&gt;JSON Schema&lt;/a&gt; 2020-12, one &lt;code&gt;oneOf&lt;/code&gt; branch per subtype), with a &lt;a href="https://api.octoperf.com/doc/mcp/#json-schemas" rel="noopener noreferrer"&gt;plain-HTTP fallback&lt;/a&gt; for clients that do not read resources. The agent loads a schema &lt;strong&gt;once, on demand&lt;/strong&gt;, instead of rediscovering field names by trial and error, and a failed patch points back at the relevant schema so the retry is usually &lt;strong&gt;one shot&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 4: layered reads, index before detail before body
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkj3seq7yp1yrxbumt5zx.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkj3seq7yp1yrxbumt5zx.jpg" alt="Read the index first, drill into one representative second" width="800" height="514"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The patterns above are generic. This one is about shaping a &lt;strong&gt;workflow&lt;/strong&gt;. When we &lt;a href="https://api.octoperf.com/doc/design/edit-virtual-user/validation/" rel="noopener noreferrer"&gt;validate&lt;/a&gt; a script we replay it and capture, for every action, &lt;strong&gt;four HTTP entities&lt;/strong&gt;: the request as recorded, the request as replayed, and both responses. For a 24-action journey that is easily megabytes. &lt;strong&gt;The one thing the server must not do is hand all of it over at once.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So the validation API is layered on purpose:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;An index call&lt;/strong&gt; returns one tiny entry per action: success and failure counts, timestamps, &lt;strong&gt;no bodies&lt;/strong&gt;. A few hundred tokens, usually enough to group failures by root cause (auth, data, server-side).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A detail call&lt;/strong&gt; fetches the four HTTP entities for &lt;strong&gt;one representative action&lt;/strong&gt; of a group, the few KB that confirm or refute the diagnosis.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A body call&lt;/strong&gt; goes one level deeper, retrieving a single body of a single exchange, for the cases where one side fits in context but both would not.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;The agent reads the way a good engineer debugs:&lt;/strong&gt; global picture, then one representative failure, then one specific body if needed. Triaging a red validation costs &lt;strong&gt;a few thousand tokens instead of the hundreds of thousands&lt;/strong&gt; a return-everything design would burn. We applied the same general-to-specific shape to &lt;a href="https://api.octoperf.com/doc/analysis/" rel="noopener noreferrer"&gt;bench reports&lt;/a&gt;: there is deliberately &lt;strong&gt;no &lt;code&gt;get_full_report&lt;/code&gt; tool&lt;/strong&gt;, just a structure call plus a narrow value tool per widget family.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 5: skills, not just tools
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Tools define what is &lt;em&gt;possible&lt;/em&gt;. They do not tell the model what is &lt;em&gt;wise&lt;/em&gt;.&lt;/strong&gt; Faced with 100 tools and a red validation, a model can technically do the right thing, but it will often guess the order of operations and waste calls discovering it.&lt;/p&gt;

&lt;p&gt;So we ship &lt;a href="https://api.octoperf.com/doc/mcp/#public-http-downloads" rel="noopener noreferrer"&gt;skills&lt;/a&gt;: markdown playbooks the agent loads on demand, each encoding a complete workflow. &lt;strong&gt;Validation triage&lt;/strong&gt; knows to fix the dominant failure group first and re-validate. &lt;strong&gt;Auto-correlation&lt;/strong&gt; knows to snapshot the entity before rewriting it. &lt;strong&gt;Scenario diagnosis&lt;/strong&gt; knows to read global metrics before drilling into any per-action table. The tools are the API surface, &lt;strong&gt;the skills are the reading order&lt;/strong&gt;, and encoding that order is what keeps the agent cheap and on-rails.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it adds up to
&lt;/h2&gt;

&lt;p&gt;Take a real workflow: import a HAR, triage a red validation, auto-correlate, re-validate, run a 500-user scenario, diagnose the result. &lt;strong&gt;A naive mirror of the REST API would blow through the context window before the midpoint&lt;/strong&gt;, forcing summarization and losing precision. With these five patterns combined, the same workflow &lt;strong&gt;holds comfortably in one conversation&lt;/strong&gt;, and the context contains almost nothing but signal: listings, indexes, one confirmed failure detail, patch ops, insight verdicts.&lt;/p&gt;

&lt;p&gt;If you are building your own MCP server, the patterns to steal are simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Move bytes out of band.&lt;/strong&gt; Presigned URLs cost dozens of tokens, files cost millions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Project your entities.&lt;/strong&gt; Return what the model reasons about, never what your UI renders.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edit by patch, validate server-side,&lt;/strong&gt; and publish your schemas so the model patches right the first time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Layer your reads.&lt;/strong&gt; Index before detail, detail before body.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Encode the reading order.&lt;/strong&gt; Tools define what is possible, skills define what is wise.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;a href="https://blog.octoperf.com/designing-a-token-efficient-mcp-server-the-octoperf-approach/" rel="noopener noreferrer"&gt;full writeup with the actual record types&lt;/a&gt; is on our blog, and the server is live if you want to poke at it: &lt;a href="https://api.octoperf.com/doc/mcp/" rel="noopener noreferrer"&gt;docs&lt;/a&gt;, &lt;a href="https://github.com/OctoPerf/octoperf-claude-plugins" rel="noopener noreferrer"&gt;source and Claude Code plugin&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you have shipped an MCP server and found better patterns, I would genuinely like to compare notes in the comments.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>webdev</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
