OpenRouter 实测 MCP vs CLI vs Skills 的上下文 token 成本
MCP vs CLI vs Skills: What Each Costs in Context
OpenRouter 实测同一 GitHub 任务下 MCP、CLI 与 skill 的 token 成本:在 Claude Sonnet 5.5 上 CLI 用 1。
原文实测同一 GitHub 任务下 MCP、CLI 与 skill 的 token 开销,给出缩窄工具和 Tool Search 的降本方法,可直接迁移到自己的智能体。
你给智能体提供的每个工具,都会在每次请求时把它的定义发给模型,而这些定义会作为输入 token 计费。一个 Model Context Protocol(MCP)服务器可能给上下文窗口增加数万个 token。常见的替代方案是命令行工具(CLI)或 skill。
我们在一个 GitHub 任务上做了实测,用到 GitHub MCP server、gh CLI 和一个 gh skill,测试模型包括 Claude Haiku 5.5、Claude Sonnet 5.5、GPT-6 Luna 和 Gemini 3.8 Flash。在 Sonnet 5.5 上,三者都答对了,但消耗的输入 token 差别很大:CLI 用了 1,421 个,skill 用了 3,315 个,而 MCP 服务器在加载默认工具的情况下用了 59,417 个。下面说说这些 token 花在了哪里,以及如何用我们的 Tool Search 服务器工具把它们降下来。
TL;DR
-
在 Claude Sonnet 5.5 上,GitHub MCP 服务器的 46 个默认工具会给每次请求增加 19,436 个 token。
-
在 Sonnet 5.5 上,单条工具结果——GitHub 的 release 对象——就花了约 20,400 个 token,比全部 46 个工具定义还多。
-
Tool Search 把 46 个工具的前置开销降到了 600 个 token 以内。从整个任务来看,它让 Sonnet 5.5 的成本减半,却让 Claude Haiku 5.5 的成本上升。
MCP、CLI 和 skill 有什么区别?
三者都能给模型一项能力,区别在于模型在使用这项能力之前要读什么。
| 模型一开始看到什么 | 模型执行时看到什么 | |
|---|---|---|
| MCP 服务器 | 每次请求都带上每个工具的名称、描述和 JSON schema | 工具返回的结果,格式由服务器决定 |
| CLI | 一个 shell 工具,外加一行说明该 CLI 存在 | 命令的输出,你可以用 —json 之类的参数来控制格式 |
| Skill | 每个 skill 的一行描述 | 先读一次该 skill 的 SKILL.md 文件,然后是命令输出 |
CLI 依赖模型本来就懂这个工具。skill 则是教模型怎么用某个工具,模型只在任务需要时才加载完整说明。
MCP 服务器会给每次请求增加多少 token?
可靠的计数方式是直接问模型。各家服务商在计算 token 之前,会先按自己的格式把工具定义写进提示词,本地分词器会漏掉这部分。把同一个请求分别带工具和不带工具各发一次,usage.prompt_tokens 的差值就是这些工具每一轮的开销。
把这段代码保存为 mcp_cost.py。它使用 MCP 的 2025-06-18 版本,GitHub 服务器接受这个版本。
"""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")把服务器的启动命令作为参数传入,下面以 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每增加一个工具,开销就会增长。在 Haiku 5.5 上,issues 工具集有 10 个工具,增加 5,801 个 token;users 工具集只有 1 个工具,增加 619 个。
所用模型也会改变计数。同样的 46 个工具,在 Haiku 5.5 上要 19,436 个 token,在 GPT-6 Luna 上只要 8,903 个,所以要按你打算实际运行的模型来测量。
就连提供模型的服务商也会影响结果。在 Gemini 3.8 Flash 上,这 46 个工具通过 Google Vertex 要 8,525 个 token,通过 Google AI Studio 则要 13,010 个。所以脚本总是把 Gemini 请求发往 Google Vertex。
同一个任务用 CLI、skill 和 MCP 分别要花多少?
我们问每个模型:“GitHub 仓库 cli/cli 的最新 release 是哪个?该仓库中带 bug 标签、最近打开的三个 open issue 是什么?”每种方式都在我们的 Responses API 上以工具调用循环运行,我们把各次请求的 usage.input_tokens 累加起来。
-
CLI。一个 run_shell 工具,外加一句“GitHub CLI gh 已安装并完成认证”。
-
Skill。同一个工具,再加一行索引,指向一份 1,492 字符的 SKILL.md,里面是 gh 的常用命令。
-
MCP。服务器提供的工具,范围可选任务所需的 2 个、默认的 46 个,或全部 91 个。
我们在 2026 年 10 月 20 日把每种组合各跑了三次,表中显示的是中位数。Haiku 5.5 的提示词计数方式与 Sonnet 5.5 完全相同,但它在部分运行中多走了几轮,因此我们展示的是 Sonnet 更稳定的 token 数和计费成本。
| 方案 | Sonnet 5.5 token 数 | Sonnet 5.5 每任务成本 | GPT-6 Luna token 数 | Gemini 3.8 Flash token 数 |
|---|---|---|---|---|
| CLI | 1,421 | $0.0075 | 627 | 5,292 |
| Skill | 3,315 | $0.0118 | 1,542 | 6,107 |
| MCP,2 个工具 | 23,228 | $0.0552 | 14,479 | 17,267 |
| MCP,46 个默认工具 | 59,417 | $0.1274 | 116,282 | 35,409 |
| MCP,全部 91 个工具 | 101,351 | $0.2113 | 135,238 | 53,289 |
| MCP,2 个工具,结果截断至 4,000 字符 | 5,357 | $0.0191 | 2,726 | 2,943 |
表中每一次运行都答对了。失败案例出现在 Tool Search 上,详见下文。在 Sonnet 5.5 上,默认 MCP 服务器的输入 token 是 CLI 的 42 倍,成本是 17 倍,每 1,000 个任务为 127 美元对 7.50 美元。Sonnet 5.5 的输出 token 单价是输入的 5 倍,CLI 那次运行的 463 个输出 token 占其 0.0075 美元成本中的 0.0046 美元。

Skill 比裸 CLI 更贵,因为文件一旦被模型读取,就会一直留在对话里。对于模型已经很熟悉的 CLI 工具,Skill 是可选项。而对于你自己的 CLI,Skill 正是模型学习命令的途径。
为什么 MCP 工具结果的成本和工具定义一样高?
get_latest_release 返回了 GitHub 的完整 release 对象,共 42,182 字符,其中 87% 都在描述那 22 个下载文件。模型需要的只是 tag 和日期,而这正是 gh release view 返回的全部内容。
$ gh release view -R cli/cli --json tagName,publishedAt
{"publishedAt":"2026-09-30T02:40:02Z","tagName":"v2.102.0"}这个对象会留在上下文窗口中,并在之后的每次请求中重新发送,在 Sonnet 5.5 上它增加了约 20,400 个 token。把服务器限定为两个工具后,首次请求从 19,487 个 token 降到 1,406 个。整个任务仍然花了 23,228 个 token。
表中展示了两种解决办法。第一,只暴露智能体需要的工具,用 —tools=get_latest_release,list_issues 或 —toolsets=issues,repos。第二,在模型看到结果之前,先在客户端里精简结果。4,000 字符的上限让限定范围的方案降到 5,357 个 token,且每次运行都正确,因为 GitHub 把 tag 和日期放在靠前的位置。
这个上限之所以有效,只是因为相关字段恰好排在前面。与 Tool Search 结合后,该上限让 Gemini 3.8 Flash 和 Haiku 5.5 各自三次运行中只有一次答对,Gemini 还有一次在重试时触发了我们 12 次请求的上限。
更好的做法是保留完整字段,就像 CLI 的 —json 那样。GitHub 服务器的 list_issues 接受 fields 参数,只请求 number、title 和 creation date 后,其结果从 8,336 字符降到 714 字符。不加这个参数,结果大小就由模型决定。在加载 46 或 91 个工具的六次运行中,有四次 GPT-6 Luna 请求了 100 个 issue 及其全文,拿回了 281,963 字符。
Tool Search 能解决 MCP 的上下文问题吗?
Tool Search 消除了工具定义的大部分成本。你把工具标记为 defer_loading: true,并加上 {“type”: “openrouter:tool_search”}。我们会暂不发送被延迟的定义,只发送搜索工具,以及你保留加载的工具。模型用正则表达式搜索工具名称和描述,并在下一轮调用匹配到的工具。该功能处于 beta 阶段,可在 Responses 和 Messages API 上配合任意模型和提供商使用。
把全部 46 个 GitHub 工具都延迟加载后,任何搜索之前的成本在 Claude 模型上是 578 个 token,GPT-6 Luna 上是 144 个,Gemini 3.8 Flash 上是 130 个。
这个脚本会先加载全部 46 个工具发送一个问题,然后再改为延迟加载。把它保存到 mcp_cost.py 旁边,因为它复用了 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要在你的智能体上试用,请把你的 MCP 工具标记为 defer_loading: true,添加 {"type": "openrouter:tool_search"},然后对比前后的 usage.input_tokens。
Tool Search 各行数据会随模型的搜索方式而变化。在这个脚本的五次独立运行中,Haiku 5.5 用了 2,134 到 13,225 个 token,且每次搜索都会增加。GPT-6 Luna 写的是短语而不是模式,进行了三到六次搜索,还有一次直接放弃,没有调用该工具。我们的文档建议保持你最常用的三到五个工具处于加载状态。
在整个任务中,结果取决于模型。
| 模型 | MCP,加载 46 个工具 | Tool Search,延迟 46 个 | Tool Search,延迟 91 个 |
|---|---|---|---|
| Claude Sonnet 5.5 | 59,417 | 30,334 | 31,615 |
| GPT-6 Luna | 116,282 | 25,544 | 57,975 |
| Gemini 3.8 Flash | 35,409 | 37,568 | 38,445 |
| Claude Haiku 5.5 | 99,319 | 154,623 | 31,781 |
GPT-6 Luna 的加载数值包含了上文所述的 100 个 issue 请求。
在 Sonnet 5.5 上,延迟 91 个工具比延迟 46 个多花 1,281 个 token,而全部加载 91 个则多花约 42,000 个。Tool Search 不会缩小工具结果,而且 release 对象仍然占据了 Sonnet 第二次请求的大部分。
Haiku 5.5 在延迟 46 个工具时比全部加载时花费更高。在全部三次运行中,它都向 list_issues 请求了一个该工具并不提供的字段,然后用错误类型的参数重试了三到四次,例如把 labels 写成字符串而不是数组。在全部工具加载的情况下,Haiku 在三次运行中有两次犯了同样的错误,并在下一次调用时修正了。请限制循环中的请求数量,并在某次运行成本激增时检查工具错误。
Tool Search 通过放宽 tool_choice 来揭示工具,因此同时强制进行工具调用的请求会失败。
400 `tool_choice` 与 `openrouter:tool_search` 冲突。延迟工具是通过 `tool_choice` 揭示的,因此必须省略它或将其设置为 `{"type": "allowed_tools", …}`。移除 `tool_choice`,或者从你的工具中去掉 `defer_loading` 以按原样使用。
Chat Completions 不接受 openrouter:tool_search 并返回 400,因此请先将该智能体迁移到 Responses 或 Messages API。
MCP vs CLI vs 技能:你应该用哪个?
当存在一个 CLI 且你的智能体能运行命令时,就用 CLI。当它不能时,就用 MCP,并限定范围、保持结果精简。
| 你的情况 | 使用 | 来自我们在 Sonnet 5.5 上的运行 |
|---|---|---|
| 存在一个众所周知的 CLI,且智能体能运行命令 | CLI | 每个任务 1,421 个 token |
| 你自己的 CLI,或者模型使用不稳定的 CLI | CLI 加一个技能 | 3,315 个 token,每次运行命令相同 |
| 没有 CLI、没有 shell,或者你需要服务器的认证和权限 | MCP,限定为你需要的工具 | 2 个工具时 23,228 个 token,而 46 个工具时为 59,417 个 |
| 跨多个服务器的大型工具库 | 带 Tool Search 的 MCP | 在 Sonnet 5.5 上,延迟 91 个工具的成本与 46 个大致相同 |
| 任何 MCP 路线 | 将工具结果裁剪为你需要的字段 | 从 23,228 降至 5,357 个 token |
如果你的智能体没有 shell,我们的 Shell 服务器工具可以为任何模型提供一个沙箱化的 shell。在我们的 Agent SDK 中,MCP Tools 可连接远程 MCP 服务器,而技能加载器示例仅在模型请求后才添加技能的指令。
我们只在一个任务上测试了一个服务器,所以在做决定之前,请在你的服务器上运行 mcp_cost.py。
常见问题
在 AI 智能体语境中,什么是 CLI?
CLI(命令行界面)是用输入命令来运行的程序。智能体拿到一个 shell 工具,自己写出这些命令。在我们的测试中,gh CLI 在一个 GitHub 任务上用了 1,421 个输入 token,而 GitHub MCP 服务器用了 59,417 个。
MCP 服务器和连接器有什么区别?
MCP 服务器通过模型上下文协议对外提供工具。连接器则是 Claude 这类应用对其连接远程 MCP 服务器的链路的称呼。我们的 Agent SDK 可以通过 MCP Tools 连接远程 MCP 服务器。
技能会取代 MCP 吗?
不会。技能是通常面向 CLI 的指令,只在任务需要时才加载。MCP 提供带自身鉴权的类型化工具,无需 shell 即可工作。在 OpenRouter 上,Tool Search 让你可以保留 MCP,只在需要时加载每个工具。
提示词缓存能解决 MCP 的 token 成本问题吗?
它能降低这些 token 的价格,但并不能消除它们。截至 2026 年 10 月,在 OpenRouter 上,根据我们的实时模型价格,在 Claude Haiku 5.5 和 GPT-6 Luna 上缓存输入的价格是正常费率的十分之一,而且它仍然会占用上下文窗口。
多少个 MCP 工具算太多?
我们的 Tool Search 文档建议,一旦工具定义超过约 10,000 个 token 或大约 10 个工具,就推迟加载工具。GitHub MCP 服务器默认的 46 个工具在 Claude 上合计 19,436 个 token。
后续步骤
-
Tool Search 涵盖所有选项,包括 max_results。
-
构建可靠的工具调用智能体循环展示了我们各次运行所用的循环类型,并带有迭代上限。
-
OpenRouter 提示词缓存说明了缓存 token 的费用。
来源:OpenRouter:Announcements · openrouter.ai