It’s 6pm. A test just went flaky in CI, and the one engineer who knows how to triage it - pull the last three runs, check the ownership tag, read the retry-policy doc before asking the model anything - is in a meeting. So you write your own version of that prompt. The agent gets a worse answer, and you quietly conclude it isn’t very good at triage.
Every team has a prompt like that. One person knows how to write it; everyone else writes a degraded copy under deadline pressure. Most teams also have the doc that was supposed to fix it - the PROMPTS.md entry, the wiki page - and here is the tension that doc creates instead of solving: writing the standard down feels like closing the problem, while every invocation still has to reassemble, live and through a contextless model, the exact context an expert already knew how to assemble once. The doc describes the procedure. It doesn’t attach the data, doesn’t run, and doesn’t stop anyone skipping a step at 6pm.
One boundary before the arithmetic, because “prompt” now sits in more than one title on this blog, and the nearest one is easy to mistake for this piece. Package the workflow, not the prompt operates one altitude up: it bundles subagents, hooks, commands, and servers into one installable plugin so a new teammate inherits the whole loop. This piece stays inside a single primitive - the MCP prompt - and prices what it costs to keep re-typing a procedure instead of shipping it.
Here is the claim, stated up front and re-derived below with arithmetic you can check with a pencil. Every hand-rolled invocation of the team’s flaky-test triage prompt costs the model 3 round trips: 600 tokens of plumbing and 7.2 seconds of turn-taking latency, before it has looked at the failure at all. A server-side prompt returns the same three pieces of context in one assembled turn. At ten triages a day, that is 30,000 tokens and 6 minutes a week back, from a function written once.
First, check whether your team has this
Section titled “First, check whether your team has this”Before any arithmetic below, one minute on your own team’s writing settles whether this piece is for you.
Open the doc your team keeps for the procedure you re-type most often - the PROMPTS.md entry, the wiki page, the pinned message with the triage steps or the review gates. It lists steps; that is what docs do. Now one question: has anyone, under deadline pressure, ever run a version of it that skipped a step the doc lists? Pulled the CI runs but not the ownership tag. Asked for a review without pasting the gates.
A yes means you have this problem in the exact shape this piece works on: the standard is written down, and it still degrades on every re-telling. The rest is worth your time.
No such doc exists anywhere, or the doc exists and nobody has ever quietly shortchanged it? Close the tab. Either the procedure isn’t recurring or your team already runs it faithfully, and the numbers below price a problem you don’t have.
A written-down standard that still gets skipped under deadline pressure is the exact problem this piece prices.
What one hand-rolled triage costs
Section titled “What one hand-rolled triage costs”Fix one concrete case and carry it through every section that follows: flaky test #4823. The team’s known-good triage needs three pieces of context in the window before anyone reasons about the failure - the last three CI runs, the ownership tag, and the retry-policy doc.
Watch the hand-rolled version assemble that, and count what each round trip through the model costs. These six constants are the only inputs this piece has. They are toy values, invented for traceability and sized to be faithful to the shape of a real round trip; every number downstream is arithmetic on them.
| What one round trip costs | Value |
|---|---|
| Tool-call emission - the model writing out the call | 45 tok |
| Inter-call reasoning - deciding what to call next | 155 tok |
| Plumbing per round trip | 200 tok |
| One model turn | 2.4 s |
| One tool execution | 1.2 s |
| Latency per round trip | 3.6 s |
The hand-rolled agent has the three fetches available as tools, so it calls them itself, one turn at a time. That is what exposing them as tools means: the model decides, emits, waits, decides again. Compute the total the obvious way, before any fix is named. Three round trips x 200 plumbing tokens is 600 tokens. Three round trips x 3.6 seconds is 10.8 seconds.
The content it was fetching all along - 780 tokens of run logs, a 25-token ownership tag, 1,150 tokens of retry policy, 1,955 in total - arrives on top of that, and keep an eye on that figure: it comes back at the honesty section.
Two costs the table cannot price, so they stay qualitative and this piece never invents numbers for them: each round trip is a fresh chance to fetch the wrong run, skip the tag, or never discover that the policy exists at all. The plumbing bill is the cost you can count. The skipped step is the cost you find out about later.
600 tokens and 10.8 seconds to reassemble a checklist the expert wrote down once - and the bug hasn’t been looked at yet.
A prompt you can ship
Section titled “A prompt you can ship”The fix is to stop treating the right prompt as tribal knowledge and start treating it as an artifact you deploy. An MCP server can expose a prompt: a parameterized template that takes arguments and returns a ready-to-run message when someone invokes it. Because the template is server-side code, it can embed resources - attach the data itself into the returned message - instead of leaving the model to go fetch them.
The declaration is small:
{ "name": "triage_flaky_test", "description": "Triage a flaky test using the team's standard procedure.", "arguments": [ { "name": "test_id", "description": "Test identifier", "required": true } ]}And the handler is a function:
@server.get_prompt()async def triage_flaky_test(test_id: str): runs = fetch_last_runs(test_id, n=3) owner_tag = fetch_owner_tag(test_id) return GetPromptResult( messages=[ PromptMessage(role="user", content=TextContent( text=f"Triage flaky test {test_id}. " f"Owner: {owner_tag}. Follow the retry policy below." )), # the data the model needs, embedded - no round trip PromptMessage(role="user", content=EmbeddedResource( resource=read_resource("policy://retry-policy") )), PromptMessage(role="user", content=EmbeddedResource( resource=TextResourceContents( uri=f"ci://runs/{test_id}", text=runs )) ), ] )The expert’s wording, the ownership tag, the last three runs, and the retry policy arrive in one message. The model starts its first turn already holding what the senior engineer would have handed it.
The assembly moves into a function the expert writes once, and every invocation inherits it.
One triage, two paths
Section titled “One triage, two paths”Now the walkthrough, both paths side by side. The same three fetches happen either way. The only difference is who does the assembling: the model, one turn at a time, or the server, with no model turn between its fetches.
| Path | Round trips | Plumbing tokens | Latency |
|---|---|---|---|
| Hand-rolled | 3 | 600 | 10.8 s |
| Server-side prompt | 1 | 0 | 3.6 s |
| Saved | 600 | 7.2 |
Check what the 7.2 is made of, because the honesty of the whole claim hangs on it. It is exactly 3 x 2.4 - the three model turns that no longer happen - and nothing else. The tool executions, 3 x 1.2 = 3.6 seconds, appear in both rows and cancel completely: the server still performs all three fetches, back to back, on every invocation. Nothing was rounded and nothing got faster. What disappeared is the model’s turn-taking between fetches, which made the hand-rolled path 3.0x slower to the same content.
Hold onto that pair, because the rest of the piece pays it off exactly twice: 600 tokens and 7.2 seconds, per invocation, from the first shipped line of server code.
The 7.2 also carries one assumption, and it belongs here rather than in a footnote: the hand-rolled agent makes its three calls one at a time, as traced above. A client or model that batches tool calls - issuing several in a single turn and running them together - keeps the 600-token plumbing saving but shrinks the latency saving toward roughly one eliminated model turn, about 2.4 seconds. The token number is robust to how your client schedules calls. The seconds number is not.
A server-side prompt eliminates the turn-taking, 600 tokens and 7.2 seconds of it. It never eliminates the fetch.
The prepaid prompt
Section titled “The prepaid prompt”Call the mechanism the prepaid prompt: the assembly cost is paid once, up front, in a function, and every later invocation cashes it in instead of paying the round-trip tax live.
The name is also the honest description. The server does not skip the three fetches - it still runs them, 3.6 seconds of them, on every call. What it prepays is the deciding: which runs, which tag, which doc, in which order, weighted how. That judgment was made once, by the person who knew. The 600 tokens and 7.2 seconds were the price of re-making that judgment through a contextless model, on every single invocation.
This is also why the doc never worked. A PROMPTS.md entry prepays nothing. It asks every future reader to re-pay the assembly themselves, out of their own patience, at 6pm. The function is the doc that runs.
The expert pays once; everyone else cashes it in.
The slash command is how it reaches everyone
Section titled “The slash command is how it reaches everyone”In clients that render the prompts primitive, a server prompt appears as a slash command, auto-discovered in the menu where your teammate is already typing. Claude Code namespaces them, so yours reads /mcp__testkit__triage_flaky_test and shows up unprompted. The teammate fills in the one argument that varies, and the senior engineer’s procedure arrives with its data attached. Nobody read a doc. The non-expert invoked the expert.
This is the part a doc can never buy: placement. A PROMPTS.md entry is skippable by definition - it sits behind a deliberate act of reading. A command in the autocomplete menu is the path of least resistance, and the path of least resistance is the only thing that survives contact with a deadline.
The spec defines one more layer: a completion API, where the client asks the server to autocomplete argument values as the user types. Open the test_id field and the menu fills with the tests that actually went red this week, not a blank box to paste into. It is young in practice - as of 2026 its main users are the TypeScript SDK and the MCP Inspector reference client - so treat it as a capability to grow into, not something your whole team gets today.
The standard stops being something you opt into and becomes something you trip over in the menu.
The same move, a harder prompt
Section titled “The same move, a harder prompt”Triage is the easy case: three fetches, one argument. The move scales up to the prompt nobody enjoys writing - the team’s PR-review pass.
Hand-rolled, it degrades the same way. An engineer pastes a diff and types “review this.” The model flags a missing null check and a typo, and says nothing about the three things the team actually gates on, because nothing told it they exist: the public API in billing/ has a backward-compatibility contract, schema changes need a migration runbook entry, and billing-path changes need a named second reviewer. Shipped as a prompt, the gates travel with the request:
@server.get_prompt()async def review_pr(pr_number: str): diff = fetch_pr_diff(pr_number) return GetPromptResult(messages=[ PromptMessage(role="user", content=TextContent( text=("Review this PR against our gates: (1) the public API in " "billing/ has a backward-compat contract - flag any breaking " "signature change; (2) schema changes need a migration runbook " "entry; (3) billing-path changes need a named second reviewer. " "Report each gate as pass/fail with the line that triggers it.") )), PromptMessage(role="user", content=EmbeddedResource( resource=TextResourceContents( uri=f"git://pr/{pr_number}.diff", text=diff) )), ])No new arithmetic here, deliberately. The PR-review case is the same prepaid mechanism one gate higher: what the author knew - the three gates, weighted as gates - is encoded once, and the reviewer who never internalized them now applies them, because they didn’t have to remember them.
A standard the model was never given is a standard it cannot enforce.
Two objections, answered where they form
Section titled “Two objections, answered where they form”“Just expose the fetches as tools and let the agent call them.” That is exactly how the 600 tokens got spent. Tool exposure gives the model the ability to gather the context; it does not give it the judgment about which context matters. The agent has to decide to call all three, in order, and to weight the retry policy as binding rather than advisory - and nothing in the request says the policy is load-bearing, so each round trip is a fresh chance to skip it. The division that falls out: embed the context the author already knows is needed; expose as tools only what genuinely depends on what the model finds along the way.
“Why stand up a server at all? A local command file does this.” A .claude/commands/*.md file, or a skill - no infrastructure, and it eliminates the same discovery round trips. Sometimes it is the right answer, and this blog has already made that case: the parameterized primer in deterministic-context is a local command that loads a fixed file set you can name in advance, with no server and no live fetch. The line between the two is what the template can contain. A local file can declare context that is fixed and known in advance. It cannot declare “the last three runs of this test” or “the diff of this PR,” because those exist only at call time and change with the argument. That is what the server is for: its fetch runs when the command runs, against the test_id that was just passed. A server prompt also reaches every teammate on any MCP client, not just the ones using the same tool’s command directory on one machine. Fixed file set, one client: the local file wins, and cheaper. Live content that varies per call, or a team on mixed clients: the server earns its keep.
Two boundary notes while we are here. If the thing you keep re-typing is a correction rather than a procedure - “don’t push yet” - it belongs in a standing instruction, and stop-re-typing-the-same-correction covers it; a rules file can carry a correction fine, but it cannot fetch this test’s runs at call time. And for the full sort of which primitive to reach for - resources, tools, prompts, and who pulls the trigger on each - see nouns-verbs-and-slash-commands; this piece deliberately stays inside one of the three.
A local file wins when the context is fixed; the server earns its keep when the context is live and varies per call.
The numbers, run out
Section titled “The numbers, run out”The running example’s cadence is a stated assumption, not a measurement: ten triages a day, a five-day week, fifty invocations. The planted pair scales without modification - no new inputs, just multiplication. 50 x 600 tokens is 30,000. 50 x 7.2 seconds is 360 seconds, which is 6.0 minutes.
| Invocations/day | Invocations/week | Tokens saved/week | Minutes saved/week |
|---|---|---|---|
| 2 | 10 | 6,000 | 1.2 |
| 5 | 25 | 15,000 | 3.0 |
| 10 | 50 | 30,000 | 6.0 |
| 20 | 100 | 60,000 | 12.0 |
| 40 | 200 | 120,000 | 24.0 |
Row 10 is the running example, and it is the spoiler from the top, reconciled exactly: the same 600 and 7.2 multiplied by fifty. The ratio never moves across the table - tokens and minutes both scale linearly with cadence, because every row is the same mechanism times a headcount of invocations. A team triaging 40 times a day gets 24 minutes back; a team at twice a day gets 1.2. Same function, same pair, different multiplier.
Every row is the same prepaid pair - 600 tokens, 7.2 seconds - times a cadence.
What this does not solve
Section titled “What this does not solve”The 1,955 content tokens never disappear. The prepaid prompt relocates the plumbing; the fetch itself still runs on every invocation. Embedding is also generous by default: a prompt that pre-loads a 4,000-line policy doc and a full diff on every call can spend more context than the hand-rolled version it replaced. When a resource is large and only a slice is usually relevant, embed a pointer and let the model fetch the slice.
Client support is uneven, and one gap is total. OpenAI’s ChatGPT MCP integration does not implement the prompts primitive at all as of 2026 (SurePrompts, “Model Context Protocol (MCP): The Complete 2026 Guide,” 2026), so a team living in ChatGPT gets none of this until that changes. The completion API is younger still: the TypeScript SDK and the MCP Inspector reference client are its main users as of 2026 (dev.to, “MCP Prompts and Resources: The Primitives You’re Not Using,” 2026).
An embedded resource is an injection surface. It is untrusted text spliced straight into the model’s context, and the MCP spec’s own security guidance names prompt injection as a live risk here. If the retry-policy resource is editable by anyone, or the CI output contains an attacker-influenced string, the prompt becomes a delivery channel - the model cannot tell your instructions from instructions that rode in on the data. Pin embedded resources to sources you control, and don’t embed free-text fields outsiders can write.
The skipped steps stay unpriced. This piece puts no number on how often a hand-rolled prompt quietly skips the ownership tag or never finds the policy, because no measured rate exists to cite and inventing one is the thing this site does not do. The 600 and the 7.2 cover the plumbing only. The completeness failure is real, probably larger, and qualitative here.
Know when not to ship one. A procedure still being figured out should not be frozen into a server function; let it stabilize as a habit first, then ship it once it stops changing. And a prompt that needs five or six varying arguments carries too many degrees of freedom for a single command. That is a workflow, and a subagent with its own tools and room to reason is the better home for it.
The arithmetic is the smaller payoff. Six minutes a week is honest, and modest. What the numbers cannot carry: the judgment about which context matters now travels to everyone on the team, the procedure arrives complete instead of degraded, and the standard sits in the menu instead of in a doc nobody opens. Those are the case. The tokens are a bonus.
Take the judgment transfer and the completeness; count the 6 minutes as interest.
Back to the table
Section titled “Back to the table”Row 10, one more time: 30,000 tokens and 6.0 minutes a week, the claim from the top reconciled against the table - the planted 600 and 7.2 times fifty invocations, nothing else added, the same linear mechanism every other row runs on.
What to do Monday fits in a sentence. Take the procedure one person on your team knows how to prompt - the triage, the incident summary, the review pass with your gates - and define it server-side: one argument for what genuinely varies, the rest embedded as resources the server assembles.
Which leaves the cost this fix itself created, and hands forward rather than solves. A prompt is frozen at the moment its server code ships. The runs it fetches are live, but the decision of which three pieces of context matter - and which doc is the current standard - was made once, by whoever wrote the function. When the team’s standard moves and the prompt doesn’t, it keeps shipping yesterday’s procedure with total confidence, on every invocation, to everyone. A doc nobody reads goes stale quietly; a command in everyone’s menu goes stale loudly.
The prepaid prompt buys back the plumbing. Who re-verifies the standard it froze - and how often - is the bill still outstanding.
About the numbers. The six per-round-trip constants (45 and 155 tokens, 2.4 and 1.2 seconds, and their 200-token and 3.6-second sums) and the three content sizes (780, 25, and 1,150 tokens) are toy values, invented for traceability and sized to be faithful to the shape of a real round trip - a model turn takes time, a fetch takes time, plumbing tokens sit on the meter - without claiming to be measured. The cadence (10 triages a day, a 5-day week) is a stated assumption, not an observation of any real team. Every other figure - the 600, the 10.8, the 3.6, the 7.2, the 1,955, the 30,000 tokens and 6.0 minutes, and every row of the master table - is exact arithmetic on those inputs, checked against an independent script before publishing. The client-support and completion-adoption claims are quoted and dated: SurePrompts’ 2026 MCP guide for ChatGPT’s missing prompts primitive, and dev.to’s “MCP Prompts and Resources: The Primitives You’re Not Using” (2026) for the completion API’s thin adoption.
For the per-tool mechanics, see MCP servers for exposing prompts and embedded resources, slash commands for how clients surface them, and Rules for the always-on context they complement.


