MCP vs CLI vs Skills: What Each Costs in Context

OpenRouter ·

MCP vs CLI vs Skills: What Each Costs in Context

Every tool you give an agent sends its definition to the model on every request, and you pay for it as input tokens. A Model Context Protocol (MCP) server can add tens of thousands of tokens to the context window. The usual alternatives are a command-line tool (CLI) or a skill.

We measured it on one GitHub task with the GitHub MCP server, the gh CLI, and a gh skill, on Claude Haiku 5.5, Claude Sonnet 5.5, GPT-6 Luna, and Gemini 3.8 Flash. On Sonnet 5.5, all three answered correctly, but the input tokens they used were very different. The CLI used 1,421, the skill used 3,315, and the MCP server used 59,417 with its default tools loaded. Here’s where the tokens go and how to cut them with our Tool Search server tool.

TL;DR

  • The GitHub MCP server’s 46 default tools add 19,436 tokens to every request on Claude Sonnet 5.5.

  • On Sonnet 5.5, one tool result, GitHub’s release object, cost about 20,400 tokens, more than all 46 definitions.

  • Tool Search cut the up-front cost of 46 tools to under 600 tokens. Over the whole task, it halved the cost on Sonnet 5.5 and raised it on Claude Haiku 5.5.

What is the difference between MCP, a CLI, and a skill?

All three give a model a capability. They differ in what the model reads before it can use it.

What the model sees up frontWhat it sees when it acts
MCP serverEvery tool’s name, description, and JSON schema, on every requestThe tool’s result, in whatever shape the server returns
CLIA shell tool and a line saying the CLI existsThe command’s output, which you shape with flags such as —json
SkillA one-line description of each skillThe skill’s SKILL.md file, once, then the command output

A CLI relies on the model already knowing the tool. A skill teaches the model how to use a tool, and the model loads the full instructions only when a task needs them.

How many tokens does an MCP server add to every request?

The reliable way to count them is to ask the model. Each provider writes tool definitions into the prompt in its own format before counting tokens, which a local tokenizer misses. Send the same request with and without the tools, and the difference in usage.prompt_tokens is what the tools cost on every turn.

Save this as mcp_cost.py. It speaks the 2025-06-18 version of MCP, which the GitHub server accepts.

"""How many prompt tokens does an MCP server add to every request? Ask the model."""
import json
import os
import subprocess
import sys

import requests

API = "https://openrouter.ai/api/v1/chat/completions"
HEADERS = {"Authorization": f"Bearer {os.environ['OPENROUTER_API_KEY']}"}

def list_mcp_tools(command):
    """Start a stdio MCP server, run the handshake, and return its tools/list result."""
    proc = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
                            stderr=subprocess.DEVNULL, text=True)

    def rpc(method, params, id=None):
        msg = {"jsonrpc": "2.0", "method": method, "params": params}
        if id is not None:
            msg["id"] = id
        proc.stdin.write(json.dumps(msg) + "\n")
        proc.stdin.flush()
        while id is not None:
            reply = json.loads(proc.stdout.readline())
            if reply.get("id") == id:
                return reply["result"]

    rpc("initialize", {"protocolVersion": "2025-06-18", "capabilities": {},
                       "clientInfo": {"name": "mcp-cost", "version": "1"}}, id=1)
    rpc("notifications/initialized", {})
    tools, cursor, id = [], None, 2
    while True:  # follow nextCursor so servers that paginate tools/list are fully counted
        page = rpc("tools/list", {"cursor": cursor} if cursor else {}, id=id)
        tools += page["tools"]
        cursor, id = page.get("nextCursor"), id + 1
        if not cursor:
            break
    proc.kill()
    return tools

def prompt_tokens(model, tools=None):
    body = {"model": model, "max_tokens": 16, "messages": [{"role": "user", "content": "Say ok."}]}
    if tools:
        body["tools"] = tools
    if model.startswith("google/"):
        # Google Vertex and Google AI Studio count the same tools differently, so pin one.
        body["provider"] = {"only": ["google-vertex"]}
    r = requests.post(API, headers=HEADERS, json=body, timeout=120)
    r.raise_for_status()
    return r.json()["usage"]["prompt_tokens"]

if __name__ == "__main__":
    mcp_tools = list_mcp_tools(sys.argv[1:])
    # What an MCP client sends the model: each tool's name, description and input schema.
    tools = [{"type": "function", "function": {"name": t["name"], "description": t.get("description", ""),
                                               "parameters": t["inputSchema"]}} for t in mcp_tools]
    print(f"{len(tools)} tools")
    for model in ["anthropic/claude-haiku-5.5", "openai/gpt-6-luna", "google/gemini-3.8-flash"]:
        added = prompt_tokens(model, tools) - prompt_tokens(model)
        print(f"{model:30} +{added:,} prompt tokens on every request")

Pass the server’s start command as arguments, as here for the GitHub MCP server v2.0.2.

$ python mcp_cost.py github-mcp-server stdio --toolsets=default
46 tools
anthropic/claude-haiku-5.5     +19,436 prompt tokens on every request
openai/gpt-6-luna              +8,903 prompt tokens on every request
google/gemini-3.8-flash        +8,525 prompt tokens on every request

The cost grows with each tool you add. On Haiku 5.5, the issues toolset has 10 tools and adds 5,801 tokens. The users toolset has one tool and adds 619.

The model you use changes the count too. The same 46 tools cost 19,436 tokens on Haiku 5.5 but 8,903 on GPT-6 Luna, so measure on the model you plan to run.

Even the provider serving the model can change it. On Gemini 3.8 Flash, the 46 tools cost 8,525 tokens through Google Vertex and 13,010 through Google AI Studio. That’s why the script always sends Gemini requests to Google Vertex.

What does the same task cost with a CLI, a skill, and MCP?

We asked each model, “What is the latest release of the GitHub repository cli/cli, and what are the three most recently opened open issues in that repository with the label bug?” Each approach ran as a tool-calling loop on our Responses API, and we added up usage.input_tokens across requests.

  • CLI. One run_shell tool and the line “The GitHub CLI gh is installed and authenticated.”

  • Skill. The same tool, plus a one-line index pointing at a 1,492-character SKILL.md of gh recipes.

  • MCP. The server’s tools, scoped to the 2 the task needs, the default 46, or all 91.

We ran each combination three times on October 20, 2026, and show medians. Haiku 5.5 counts a prompt exactly as Sonnet 5.5 does, but it took extra turns in some runs, so we show Sonnet’s steadier tokens and billed cost.

ApproachSonnet 5.5 tokensSonnet 5.5 cost per taskGPT-6 Luna tokensGemini 3.8 Flash tokens
CLI1,421$0.00756275,292
Skill3,315$0.01181,5426,107
MCP, 2 tools23,228$0.055214,47917,267
MCP, 46 default tools59,417$0.1274116,28235,409
MCP, all 91 tools101,351$0.2113135,23853,289
MCP, 2 tools, results capped at 4,000 characters5,357$0.01912,7262,943

Every run in this table answered correctly. The failures came with Tool Search, covered below. On Sonnet 5.5, the default MCP server used 42 times the CLI’s input tokens and cost 17 times as much, $127 against $7.50 per 1,000 tasks. Output tokens cost five times as much as input on Sonnet 5.5, and the CLI run’s 463 output tokens were $0.0046 of its $0.0075.

Bar chart of input tokens for the whole task, split into tool definitions and instructions versus task, tool results and replies: CLI 1,421, Skill 3,315, MCP with 2 tools capped 5,357, MCP with 2 tools 23,228, MCP with 46 tools 59,417, MCP with 91 tools 101,351.

The skill cost more than the bare CLI because the file stays in the conversation once the model reads it. For a CLI tool the model knows well, a skill is optional. For your own CLI, the skill is how the model learns the commands.

Why do MCP tool results cost as much as the definitions?

get_latest_release returned GitHub’s full release object, 42,182 characters, and 87% of it described the 22 download files. The model needed the tag and date, which is all gh release view returned.

$ gh release view -R cli/cli --json tagName,publishedAt
{"publishedAt":"2026-09-30T02:40:02Z","tagName":"v2.102.0"}

That object stays in the context window and is resent with every later request, and on Sonnet 5.5 it added about 20,400 tokens. Scoping the server to two tools cut the first request from 19,487 tokens to 1,406. The whole task still cost 23,228.

The table shows two fixes. First, expose only the tools the agent needs, with —tools=get_latest_release,list_issues or —toolsets=issues,repos. Second, shrink results in your client before the model sees them. A 4,000-character cap brought the scoped route to 5,357 tokens with every run correct, because GitHub puts the tag and date near the top.

The cap worked only because of where the fields happened to sit. Combined with Tool Search, the cap left Gemini 3.8 Flash and Haiku 5.5 with one correct answer in three runs each, and Gemini once hit our 12-request limit while retrying.

Keep whole fields instead, as —json does for the CLI. The GitHub server’s list_issues takes a fields argument, and asking for only number, title, and creation date cut its result from 8,336 characters to 714. Without it, the model decides the size. In four of six runs with 46 or 91 tools loaded, GPT-6 Luna asked for 100 issues with their full text and got 281,963 characters back.

Does Tool Search fix MCP’s context problem?

Tool Search removes most of the cost of tool definitions. You mark tools defer_loading: true and add {“type”: “openrouter:tool_search”}. We withhold the deferred definitions and send the search tool, plus any tools you left loaded. The model searches tool names and descriptions with a regular expression and calls a match on its next turn. It’s in beta and works with any model and provider on the Responses and Messages APIs.

With all 46 GitHub tools deferred, the cost before any search was 578 tokens on the Claude models, 144 on GPT-6 Luna, and 130 on Gemini 3.8 Flash.

This script sends one question with all 46 tools loaded, then deferred. Save it next to mcp_cost.py, since it reuses list_mcp_tools.

"""Send the same MCP tools with defer_loading and openrouter:tool_search, and compare prompt tokens."""
import os
import sys

import requests

from mcp_cost import list_mcp_tools

API = "https://openrouter.ai/api/v1/responses"
HEADERS = {"Authorization": f"Bearer {os.environ['OPENROUTER_API_KEY']}"}
QUESTION = "What is the latest release of the GitHub repository cli/cli?"

def ask(model, tools):
    r = requests.post(API, headers=HEADERS, timeout=300,
                      json={"model": model, "input": QUESTION, "tools": tools})
    r.raise_for_status()
    return r.json()

mcp_tools = list_mcp_tools(sys.argv[1:])
# The Responses API takes function tools flat: type, name, description, parameters.
tools = [{"type": "function", "name": t["name"], "description": t.get("description", ""),
          "parameters": t["inputSchema"]} for t in mcp_tools]
deferred = [{"type": "openrouter:tool_search"}] + [{**t, "defer_loading": True} for t in tools]

for model in ["anthropic/claude-haiku-5.5", "openai/gpt-6-luna"]:
    for label, tool_list in [("all loaded", tools), ("tool search", deferred)]:
        res = ask(model, tool_list)
        steps = [f"search {o['query']!r}" if o["type"] == "openrouter:tool_search" else f"call {o['name']}"
                 for o in res["output"] if o["type"] in ("openrouter:tool_search", "function_call")]
        print(f"{model:28} {label:12} {res['usage']['input_tokens']:>6,} input tokens  {' -> '.join(steps)}")
$ python tool_search.py github-mcp-server stdio --toolsets=default
anthropic/claude-haiku-5.5   all loaded   19,464 input tokens  call get_latest_release
anthropic/claude-haiku-5.5   tool search   7,069 input tokens  search 'github|release' -> search 'latest release|list_releases|get_latest_release' -> call get_latest_release
openai/gpt-6-luna            all loaded    8,923 input tokens  call get_latest_release
openai/gpt-6-luna            tool search   4,453 input tokens  search 'github repository latest release cli cli' -> search 'github' -> search 'search web internet browser' -> search 'release' -> call get_latest_release

To try it on your agent, mark your MCP tools defer_loading: true, add {“type”: “openrouter:tool_search”}, and compare usage.input_tokens before and after.

The Tool Search rows vary with how the model searches. In five separate runs of this script, Haiku 5.5 used 2,134 to 13,225 tokens, rising with each search. GPT-6 Luna wrote phrases instead of patterns, made three to six searches, and once gave up without calling the tool. Our docs suggest keeping your three to five most-used tools loaded.

Over the whole task, the result depended on the model.

ModelMCP, 46 tools loadedTool Search, 46 deferredTool Search, 91 deferred
Claude Sonnet 5.559,41730,33431,615
GPT-6 Luna116,28225,54457,975
Gemini 3.8 Flash35,40937,56838,445
Claude Haiku 5.599,319154,62331,781

GPT-6 Luna’s loaded figure includes the 100-issue request described above.

On Sonnet 5.5, 91 deferred tools cost 1,281 tokens more than 46, where loading all 91 cost about 42,000 more. Tool Search doesn’t shrink tool results, and the release object still filled most of Sonnet’s second request.

Haiku 5.5 cost more with 46 deferred tools than with all loaded. In all three runs it asked list_issues for a field the tool doesn’t offer, then retried three or four times with arguments of the wrong type, such as labels as a string instead of an array. With all tools loaded, Haiku made the same mistake in two of three runs and fixed it on its next call. Cap the requests in your loop, and check tool errors when a run’s cost jumps.

Tool Search reveals tools by widening tool_choice, so a request that also forces a tool call fails.

400 `tool_choice` conflicts with `openrouter:tool_search`. Deferred tools are revealed through `tool_choice`, so it must be omitted or set to `{“type”: “allowed_tools”, …}`. Remove `tool_choice`, or drop `defer_loading` from your tools to use it as-is.

Chat Completions doesn’t accept openrouter:tool_search and returns a 400, so move that agent to the Responses or Messages API first.

MCP vs CLI vs skills: which should you use?

Use a CLI when one exists and your agent can run commands. When it can’t, use MCP, scoped and with small results.

Your situationUseFrom our runs on Sonnet 5.5
A well-known CLI exists and the agent can run commandsThe CLI1,421 tokens per task
Your own CLI, or one the model uses inconsistentlyThe CLI plus a skill3,315 tokens, same commands every run
No CLI, no shell, or you need the server’s auth and permissionsMCP, scoped to the tools you need23,228 tokens with 2 tools, against 59,417 with 46
A large tool library across many serversMCP with Tool SearchOn Sonnet 5.5, 91 deferred tools cost about what 46 did
Any MCP routeTrim tool results to the fields you need23,228 down to 5,357 tokens

If your agent has no shell, our Shell server tool gives any model a sandboxed one. In our Agent SDK, MCP Tools connects remote MCP servers, and the skills loader example adds a skill’s instructions only after the model asks for it.

We tested one server on one task, so run mcp_cost.py on yours before you decide.

Frequently asked questions

What is a CLI in an AI agent context?

A CLI (command-line interface) is a program you run with typed commands. An agent gets a shell tool and writes the commands itself. In our tests, the gh CLI used 1,421 input tokens on a GitHub task, against 59,417 for the GitHub MCP server.

What is the difference between an MCP server and a connector?

An MCP server exposes tools over the Model Context Protocol. A connector is what an app such as Claude calls its link to a remote MCP server. Our Agent SDK can connect remote MCP servers through MCP Tools.

Are skills replacing MCP?

No. A skill is instructions, usually for a CLI, that load only when a task needs them. MCP gives typed tools with their own auth and works without a shell. On OpenRouter, Tool Search lets you keep MCP and load each tool only when needed.

Does prompt caching fix MCP’s token cost?

It lowers the price of those tokens but doesn’t remove them. As of October 2026, on OpenRouter, cached input costs a tenth of the normal rate on Claude Haiku 5.5 and GPT-6 Luna, per our live model prices, and still fills the context window.

How many MCP tools is too many?

Our Tool Search docs suggest deferring tools once definitions pass roughly 10,000 tokens or about 10 tools. The GitHub MCP server’s 46 default tools came to 19,436 tokens on Claude.

Next steps

By subscribing you agree to receive the OpenRouter newsletter: model usage data, product updates, and research reports, about one email a week. Unsubscribe anytime via the link in every email. See our Privacy Policy.