đź’° Skills vs MCP: What the Token Bill Actually Measures
đź“… Monday, Jul 20, 2026
⏰ 23:33:10+05:30
🏷️
Move deterministic business rules out of the model and into code. For the one workload I benchmarked, that single decision cut hosted-model cost by 32% on Claude Opus 4.8 and 76% on GPT-5, ran two to five times faster, and eliminated a silent age-calculation error because an authoritative clock replaced the model’s guess. That is the conclusion. Everything below is how I measured it, why the popular “Skills versus MCP” framing is the wrong dividing line, and where the result stops being defensible.
The question that sent me down this path came after a conference talk I gave about building MCP servers. Another engineer asked me something blunt: Why are you still talking about MCP when Skills can do all of this?
My first reaction was equally blunt. If I worked for a model provider, I might prefer the design that keeps more work inside the model. I work for the organization paying the bill, so I want deterministic business rules to run in ordinary code whenever that is practical.
That is my opinion, not evidence of a vendor conspiracy. Providers have good reasons to make models more capable, and customers are free to choose the architecture. Still, the incentive is worth noticing: when a model does more work, customers usually buy more inference. The two hosted models in this benchmark also price output tokens at five to eight times their input-token rate.
So I built a small benchmark to see how much that choice mattered. The result supported my architectural instinct, but it also exposed a problem with the original framing of this article. “Skills versus MCP” is not the real dividing line. The real one is model-executed rules versus code-executed rules.
What I actually compared
A Skill is a package, not an execution environment. The open Agent Skills specification allows a Skill to contain instructions, reference material, assets, and executable scripts. OpenAI’s description of Skills likewise says a Skill can include code.
MCP is a protocol through which a model-facing client can discover and call tools. An MCP server can contain business logic, but it can just as easily wrap a database, an API, a clock, or a bad nondeterministic service. The protocol itself does not make the result correct.
The benchmark therefore compared these specific implementations:
- Instruction-only Skill: the Skill contains the rules and catalog in Markdown. The model performs the calculations.
- MCP-backed tool: a Rust server performs the calculations and returns structured data. The model calls it and formats the response.
- Prompt-only baseline: the same rules are placed directly in the system prompt and the model performs the calculations.
That is a narrower and more useful comparison: model-executed rules versus code-executed rules.
An executable Skill would make a legitimate fourth configuration, but “the Skill contains code” is not enough information. There are two very different ways an agent can use that code. It can run the script in a real software runtime, or it can read the source and attempt to follow the logic itself. The second option is still model-executed business logic. In fact, it may be worse than giving the model concise rules: the source consumes more context, while the model remains free to miss a branch, mishandle a boundary, or improvise around the implementation.
Running the script is different. Once ordinary software receives the same validated inputs, it can produce the same authoritative output as the Rust server. The remaining uncertainty sits in the orchestration around it. Will the model load the Skill, notice the script, invoke it instead of doing the calculation itself, construct the command and arguments correctly, and present the returned value without “correcting” it? If script execution is merely suggested in prose, the architecture still depends on the model choosing the deterministic path on every request.
If the agent runtime exposes the bundled script as a required, typed capability and reliably routes the request through it, I would expect the token and latency profile to resemble the MCP-backed path. At that point, however, the important win comes from executable code being treated as authoritative, not from the Skill label. If the model is expected to read and mentally execute the bundled source, I would expect more tokens without gaining the guarantee that made the code worth writing.
I have not tested either version of that fourth configuration yet. A useful follow-up would measure them separately: Skill plus enforced script execution and Skill plus source code for the model to interpret. Combining those into one “executable Skill” result would hide the architectural difference, but both versions still hit the core economic question: how much material must the model read, reason about, and generate before the code runs? Loading Skill instructions or source, deciding to invoke a script, constructing the command and arguments, and then consuming the result all use tokens. Executable code makes the calculation authoritative; it does not make the orchestration free. My expectation is that asking the model to interpret source will be the most expensive version, while enforced script execution should be closer to MCP. Whether it is cheaper or more expensive than a compact, typed MCP tool call is something the fourth benchmark must measure rather than assume.
The workload
The customer record contains a birth date, ZIP code, ordered interests, and a maximum budget. The system must calculate age, classify the customer, filter a small activity catalog, apply discounts, rank eligible activities, and return the best matches.
The harness ran all three configurations 15 times on each of three models:
- Claude Opus 4.8
- GPT-5
qwen3:30b, served locally through Ollama
That produced 135 responses. The harness recorded provider-reported input, output, and total tokens; model and tool calls; end-to-end latency; estimated API cost; and the final text. The Rust server, Skill, prompts, harness, tests, and raw JSON are in the public benchmark repository .
There is one important wrinkle. The customer request did not include the current date. The MCP server read the host’s clock, while the instruction-only configurations had no equivalent clock tool. An earlier draft of this article said the date was part of the scenario. It was not.
That means the cost and latency comparison uses the same user request, but the later age comparison is not a controlled test of code versus model arithmetic. It also tests access to current state. I discuss those results separately rather than pretending the difference does not exist.
Results
Every number below is the mean of 15 successful runs. Prices are standard, non-cached API prices at the time of the test. GPT-5 was $1.25 per million input tokens and $10 per million output tokens . Claude Opus 4.8 was $5 and $25 , respectively.
The “text variation” column is the mean pairwise character-sequence difference among the 15 final answers, calculated with Python’s SequenceMatcher. It measures how different the rendered responses were. It does not measure semantic correctness, business-rule determinism, or auditability.
Claude Opus 4.8
| Config | Input tokens | Output tokens | Total tokens | Model calls | Input $ | Output $ | Total $ | Latency (s) | Text variation |
|---|---|---|---|---|---|---|---|---|---|
| Instruction-only Skill | 1,210 | 1,193 | 2,403 | 1 | $0.00605 | $0.02983 | $0.03588 | 12.5 | 48.5% |
| MCP-backed tool | 2,071 | 561 | 2,632 | 2 | $0.01036 | $0.01403 | $0.02438 | 9.0 | 38.8% |
| Prompt only | 1,131 | 1,231 | 2,362 | 1 | $0.00566 | $0.03078 | $0.03643 | 12.9 | 61.9% |
GPT-5
| Config | Input tokens | Output tokens | Total tokens | Model calls | Input $ | Output $ | Total $ | Latency (s) | Text variation |
|---|---|---|---|---|---|---|---|---|---|
| Instruction-only Skill | 802 | 2,089 | 2,891 | 1 | $0.00100 | $0.02089 | $0.02189 | 16.1 | 76.8% |
| MCP-backed tool | 963 | 408 | 1,371 | 2 | $0.00120 | $0.00408 | $0.00529 | 4.8 | 38.7% |
| Prompt only | 783 | 2,320 | 3,103 | 1 | $0.00098 | $0.02320 | $0.02418 | 20.8 | 67.8% |
qwen3:30b (local via Ollama on an Apple M4 Max with 128 GB RAM)
| Config | Input tokens | Output tokens | Total tokens | Model calls | Input $ | Output $ | Total $ | Latency (s) | Text variation |
|---|---|---|---|---|---|---|---|---|---|
| Instruction-only Skill | 898 | 4,192 | 5,090 | 1 | $0.00 | $0.00 | $0.00 | 43.4 | 76.7% |
| MCP-backed tool | 1,480 | 1,086 | 2,565 | 2 | $0.00 | $0.00 | $0.00 | 10.8 | 58.9% |
| Prompt only | 882 | 5,112 | 5,994 | 1 | $0.00 | $0.00 | $0.00 | 54.4 | 73.2% |
Cost per 1,000 requests
| Model | MCP-backed tool | Instruction-only Skill | Prompt only | Tool vs Skill | Tool vs Prompt |
|---|---|---|---|---|---|
| Claude Opus 4.8 | $24.38 | $35.88 | $36.43 | 32% less | 33% less |
| GPT-5 | $5.29 | $21.89 | $24.18 | 76% less | 78% less |
What those numbers support
The tool-backed path sent more input because the request included a tool schema and required a second model call. It still cost less because it used far fewer completion tokens. On these models, for this task, the price difference between input and output was large enough to overwhelm the extra round trip.
The same implementation was also faster in this sweep. It reduced mean latency from 12.5 to 9.0 seconds against the instruction-only Skill on Opus, from 16.1 to 4.8 seconds on GPT-5, and from 43.4 to 10.8 seconds on the local model. The local comparison matters because there is no per-token invoice to blame. Generating fewer tokens still consumed less wall-clock time on my hardware.
The provider APIs reported completion-token usage; those counts are what I priced. Depending on the provider and model, that usage can include billed reasoning tokens that never appear in the visible answer. It would be inaccurate to say every calculation was literally printed to the user.
The text-variation result is interesting but weaker. Tool-backed answers were more alike in all three sets, which makes sense because the model received the same structured result each time. A character-level similarity score is sensitive to headings, wording, and answer length, though. I would not use it as evidence that the business logic is deterministic. For that, I would test the function outputs directly.
Most important, this is one synthetic workload with one customer and one catalog. It shows that moving this set of rules into code reduced tokens, latency, and hosted-model cost. It does not prove that an MCP call is always cheaper. A large tool schema, a chatty tool response, network latency, retries, or a tiny calculation could reverse the result.
The clock result: useful, but not a fair arithmetic contest
All configurations returned the same three activities in the same order:
- Guided Nature Walk — $20
- Mountain Hiking Tour — $45
- Jazz Club Evening — $60
The reported age differed:
| Config | Claude Opus 4.8 | GPT-5 | qwen3:30b |
|---|---|---|---|
| MCP-backed tool | 36 (15/15) | 36 (15/15) | 36 (15/15) |
| Instruction-only Skill | 35 (14/15) | 36 (15/15) | 33 (12/15) |
| Prompt only | 35 (15/15) | 36 (15/15) | 33 (14/15) |
The benchmark ran on July 19, 2026, and the customer was born on May 15, 1990, so 36 was correct. The Rust service read that date from the system clock. Opus often behaved as if the year were 2025, and qwen often behaved as if it were 2023. GPT-5 returned the exact benchmark date even though the user request and harness system prompt did not supply it. The data does not tell me whether that date came from model behavior or provider-side context, so calling it a “guess” would go beyond the evidence.
This result demonstrates a real production lesson: if an answer depends on current state, give the system an authoritative source for that state. It does not demonstrate that MCP is uniquely able to provide one. A bundled Skill script, a local command, a conventional API, or an MCP server could all read a clock.
It also explains why the bad ages did not change the recommendations. Ages 33, 35, and 36 fall into the same age band, generation, and discount tier in this catalog. Near a boundary, the error could matter. A 65-year-old calculated as 62 would miss the senior discount; an 18-year-old calculated as 17 could cross discount and eligibility rules. Those are examples of what the defect could cause, not outcomes observed in this run.
For repeatable testing, the server already has a FixedClock implementation. The benchmark should use it, or pass the same explicit date to all three configurations. I plan to add that controlled case before making broader correctness claims.
Where I draw the boundary
This experiment reminded me of reading a SQL execution plan. Returning the right rows is necessary, but it is not the end of the engineering work. At scale, I also care about the cost of getting those rows, the behavior under failure, and whether I can test the logic without asking a probabilistic model to repeat it.
My rule of thumb is now:
- Use Skill instructions for judgment, workflow, and reusable operating procedures.
- Use executable code for calculations, eligibility, prices, policy rules, and access to authoritative state.
- Use MCP when that executable capability should be a shared, discoverable tool with a stable interface. A bundled Skill script or ordinary service may be simpler when it should not.
That is also what I look for when interviewing engineers who use AI. This is a personal hiring preference, not a universal standard. I care less about whether a candidate can make a model produce working code and more about whether they can explain which parts belong in model reasoning, which parts need a deterministic boundary, and what the choice costs at production volume.
The benchmark changed my wording, not my conclusion. “MCP beats Skills” is too broad. The claim I can defend is this: for this workload, code-executed business rules beat model-executed rules on cost and latency, and an authoritative clock prevented a silent data error.
That is enough to influence an architecture. It is not enough to declare a universal winner.
Reproduce the experiment or challenge it in the skill-vs-mcp repository . The raw 15-run result set used here is included.