跳到正文
OpenRouter Announcements·· 9 小时前精选AI 评分62

OpenRouter 实测 MCP、CLI 与 Skill 的上下文开销

MCP vs CLI vs Skills: What Each Costs in Context

AI 导读

OpenRouter 在同一个 GitHub 任务上对比 MCP、CLI 和 Skill 三种工具接入方式的 token 开销:在 Claude Sonnet 5.5 上。

推荐理由

同一 GitHub 任务下三种工具接入方式的 token 与成本实测,给出了可复用的测量脚本和裁剪结果字段的做法。

正文 · AI 翻译

你给 agent 的每个工具都会在每次请求时把它的定义发送给模型,而你要为此支付输入 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 工具集有一个工具,增加 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 数
CLI1,421$0.00756275,292
Skill3,315$0.01181,5426,107
MCP,2 个工具23,228$0.055214,47917,267
MCP,46 个默认工具59,417$0.1274116,28235,409
MCP,全部 91 个工具101,351$0.2113135,23853,289
MCP,2 个工具,结果上限 4,000 字符5,357$0.01912,7262,943

此表中的每次运行都正确作答。失败出现在 Tool Search 上,详见下文。在 Sonnet 5.5 上,默认 MCP 服务器使用的输入 token 是 CLI 的 42 倍,成本是 17 倍,每 1,000 个任务为 $127 对 $7.50。在 Sonnet 5.5 上,输出 token 的成本是输入的五倍,CLI 运行的 463 个输出 token 占其 $0.0075 中的 $0.0046。

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.

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。

表中展示了两种修复方式。第一,只暴露智能体所需的工具,使用 —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

要在你的 agent 上试用,请将你的 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.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 的加载数值包含上文所述的 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,因此请先将该 agent 迁移到 Responses 或 Messages API。

MCP vs CLI vs skills:你应该使用哪一个?

当存在 CLI 且你的 agent 可以运行命令时,请使用 CLI。当它不能时,请使用 MCP,并限定范围且结果较小。

你的情况使用来自我们在 Sonnet 5.5 上的运行
存在一个众所周知的 CLI,且 agent 可以运行命令CLI每个任务 1,421 个 token
你自己的 CLI,或模型使用不一致的 CLICLI 加一个 skill3,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

如果你的 agent 没有 shell,我们的 Shell 服务器工具可为任何模型提供一个沙箱 shell。在我们的 Agent SDK 中,MCP Tools 连接远程 MCP 服务器,而 skills 加载器示例仅在模型请求后才添加 skill 的指令。

我们在一个任务上测试了一个服务器,因此在做决定之前,请在你的服务器上运行 mcp_cost.py。

常见问题

在 AI agent 上下文中,什么是 CLI?

CLI(命令行界面)是一种通过输入命令来运行的程序。智能体会获得一个 shell 工具,并自行编写命令。在我们的测试中,gh CLI 在一个 GitHub 任务上使用了 1,421 个输入 token,而 GitHub MCP 服务器则使用了 59,417 个。

MCP 服务器和连接器有什么区别?

MCP 服务器通过模型上下文协议(Model Context Protocol)对外提供工具。连接器则是 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。

后续步骤

来源:OpenRouter Announcements · openrouter.ai