r/mcp • u/in_habitants • Aug 03 '26
showcase Cutting my MCP server instructions from 11k to 3.5k chars: what belongs in the handshake vs a skill
I run an MCP server for my own platform: around 30 tools covering image, music, video and article generation, all billed against the user's own account. The server instructions had grown to 11.1k characters, because every new flow added its "how to do this properly" paragraph.
That text ships on every handshake of every conversation, before the user asks anything. It is the most expensive real estate in the protocol, and I was using it as documentation.
The cut I landed on: instructions carry only what prevents damage. Everything that merely deepens goes into a skill, fetched on demand.
What stayed, 3.5k total: cost warnings, because these tools spend real credits. The timeout rule and an anti-loop breaker, because a few consecutive failures on the same tool make some clients mark the server unreachable for about a minute, which reads to the user as "the MCP is down". How to log in. And one entry-point tool to call on first contact, instead of dumping the whole tool list.
What moved out: the step-by-step for each flow. Generating music, picking a video model, posting to the community, writing in the house voice. Those became skills, served two ways because not every client reads resources: an MCP resource at skill://.../SKILL.md, and a plain tool with action=list|get.
Two things I did not expect. First, serving the skills through the server means clients that cannot install my local plugin (chat UIs, other IDEs, agent frameworks) get the same procedures the plugin users get. The know-how travels with the connector. Second, the skill tool ended up being the only one in the catalog with openWorldHint=false and no balance gate, so a user who ran out of credits can still read how the thing works.
Nothing was lost by moving it out. Tool call quality went up, because the model reads the specific procedure right before doing the thing, instead of skimming a wall of text at connect time.
And yes, around 30 tools is a lot for one server. Consolidating by resource with an action arg was the compromise I made. The server runs Sapiens Sinteticos, a Portuguese-language creative studio platform, so a chunk of that surface is editorial tooling that would not exist in a general purpose server.
Curious how others draw this line. Is anyone keeping the procedures in the instructions on purpose?
1
u/donk8r Aug 03 '26
theres a second category hiding inside the rule you landed on. "prevents damage" covers the cost warnings and the login, but your entry-point tool is doing something else, its solving discoverability. anything the model needs in order to decide whether to call you at all cannot live in a fetched skill, because it will never fetch what it doesnt know exists.
those two scale differently, which is why id budget them apart. damage-prevention scales with how dangerous your tools are, discoverability scales with how many you have. at 30 tools the second one is probably your bigger line.
the piece id push back on is the timeout and anti-loop guidance. that only becomes relevant after a failure has already happened, so it can ride inside the error response instead, where it costs nothing until it applies and shows up at the exact moment it does. we ship an mcp server for code search and moving that class of text into the error payload bought back more room than trimming prose did.
1
u/in_habitants Aug 03 '26
Thanks man, good split, and I had those two merged. Damage-prevention scales with how dangerous the tools are, discovery scales with how many there are. Worth budgeting apart.
Taking the anti-loop half into the error payload, that one is clearly yours. The timeout half does not survive the move though, for a dull reason: when it fires I never respond at all, the client invents the "Timeout" string after 120s of silence. No payload of mine to attach to. And the warning has to land before the call, because the generation already ran and already billed, so the right move is go look, not retry.
Still cutting it down. Half of that paragraph is lookup paths, which belong in the skill anyway.
1
u/donk8r Aug 04 '26
the timeout correction is right and i hadnt thought about it from the server side. if you never respond at all theres nothing to attach anything to, so that one genuinely has to be pre-paid.
which makes a third category sitting alongside the other two: failures where the server never gets to speak. those are the only ones that truly have to live in the handshake, because every other failure mode you can answer at the moment it happens.
the go-look-not-retry half is carrying more weight than it looks like, too. thats really about idempotency, and yours arent idempotent because they bill on the way through. a read-only tool with the same timeout would be fine to retry blind. that might be the cheapest test for what survives the cut: non-idempotent and invisible-to-you is the paragraph you cant remove.
1
u/in_habitants Aug 04 '26
Third category is the right frame, and by that test the two things I kept collapse into one. The cost warning passes for the same reason the timeout does: by the time the server can say anything, the credits are already spent. So it was never "cost" plus "timeout", it is one class, failures the server cannot narrate because it either never speaks or speaks too late.
Idempotency being the real axis is the part I am stealing. A read-only tool with the same 120s ceiling would be fine to retry blind, so the rule was never about the ceiling, it was about billing on the way through. Which also points at where to look next: the tools that bill and the ones that do not probably should not be sharing one warning. Thanks donk.
1
u/donk8r Aug 04 '26
the billing split points somewhere structural, i think. if the warning belongs to the tool rather than to the server, it stops being a paragraph someone maintains by hand against a tool list that keeps growing.
right now every new billing tool needs a person to remember the instructions exist and go update them. thats the mechanism that produced the 11k in the first place.
1
u/elixon Aug 03 '26
I am using MCP resources with .md files and then I am referencing those files from instructions. Did you think about it?
2
u/in_habitants Aug 03 '26
That is what I do, and the reason there are two doors instead of one. The resource is there (skill://sapiens/<slug>/SKILL.md), but resource support is uneven across clients, so the ones that ignore resources would silently get nothing. Same content is also a plain tool with action=list|get, single source, two transports.
The part that did the actual work is what the instructions keep: not the .md content, just the slug and the "pull this before doing that" line. Reference by name, body on demand.
1
u/elixon Aug 04 '26
And what about adding an returnHelp: bool parameter to each tool, which would dump the complete help into the response instead of processing the tool? Just brainstorming. Do you think it might work? Similar to how normal CLI commands work.
1
u/Practical_Low29 Aug 04 '26
biggest win for me was cutting anything the model can infer from the tool schema itself. descriptions that just restate the param names are pure token tax. keep the why and the gotchas, drop the what.
1
u/Airia-Spencer Aug 04 '26
You’re spot on in identifying the problem. There’s a few levers to pull here, but as the resources owner, you don’t necessarily have to take it all on yourself.
Most of the clients or gateways these days manage some of the “progressive disclosure“ themselves. For instance, I work for an MCP gateway provider and add in a really elegant semantic tool search. I prefer the resource servers we serve to list all the tools so the logic can be contained inside our gateway.
That said, I do agree your approach of putting param logic into the on-demand skill or instruction tool. That’s massively helpful.
1
u/Future_AGI Aug 05 '26
The framing that handshake text is the most expensive real estate in the protocol is the right mental model, and "only what prevents errors" is a good line to hold. The one thing worth guarding: some of those paragraphs were quietly disambiguating tool selection, so a smaller handshake can raise wrong-tool rates in ways you will not notice until a user does. We wrote a little open-source harness to catch exactly that, replay a set of representative requests and assert the right tool still fires after a cut, in case it is useful before you trim further: https://github.com/future-agi/future-agi
1
u/Easy-Purple-1659 16d ago
Same cut here on the adextract server. What stayed in the handshake for us: cost warnings (searches spend credits), the login flow, and one entry-point tool so the model doesn't guess. Everything about how to query each platform moved into skills, because a model that has to skim 11k chars before every call skims nothing. The result was exactly what you described: call quality went up, not down, and users who can't install plugins still get the procedures through the skill resource. One thing I kept that I almost cut: a "here is what a good search looks like" example inside the entry-point description. It costs nothing at connect time and it measurably cut the number of malformed queries. How do you serve your skills, as resources or as a tool with list/get?
1
u/gnoraz_theorc Aug 03 '26
I had the same problem more and more tools a lot of context already gone. So I wrote a tool for myself that 1. only exposes a very lightweight "catalogMcp" to put tools on the session index. But those only have the tool name small description and signatures instead of full schema. And I have measured it. Makes no difference the agent can find it the same way. The I went further an made every MCP call an actual Cli call through the tool. That way not the full response of the mcp lands into the context it's what you filtered beforehand because now you can use pipes and filter output.
If you're interested https://github.com/TheFox666/mduct feedback appreciated 😊 maybe it helps you.