从 MCP 到 CLI:AI Agent 工具链的架构演进与实战抉择
1. 引言:Agent 工具链的十字路口
2025 年以来,AI Agent 的落地形态正在经历一场深刻的范式转移。早期,开发者习惯把一切能力封装成 MCP(Model Context Protocol)Server,让模型通过标准协议调用工具;而如今,越来越多的团队开始回归 CLI(Command Line Interface),把 Agent 的工具面收敛到终端命令。这并非简单的技术怀旧,而是一场关于「工具链架构」的理性再平衡。
本文将从架构演进、协议对比、实战代码三个维度,深入剖析 MCP 与 CLI 两种工具链形态的优劣与适用场景,并给出可落地的选型建议。
2. 背景:从 Function Calling 到 MCP 的演进
要理解 MCP 与 CLI 之争,必须先回顾 Agent 工具链的演进脉络。
2.1 第一阶段:Function Calling 的「硬编码」时代
在 GPT-4 时代,开发者通过 JSON Schema 描述函数签名,模型在推理时输出结构化调用参数。这种方式虽然直观,但存在明显痛点:
- 协议私有化:每个模型厂商的 Function Calling 格式不互通,切换模型需要重写工具层。
- 工具注册繁琐:每新增一个工具,都要在 Prompt 中追加 Schema,Token 消耗随工具数量线性增长。
- 无状态连接:工具调用是一次性的,无法维持长连接、流式推送或资源订阅。
2.2 第二阶段:MCP 的「标准化」尝试
Anthropic 于 2024 年底推出 MCP,试图用一套统一协议解决工具互联问题。MCP 的核心设计包括:
- Client-Server 架构:Host(如 Claude Desktop)通过 MCP Client 连接多个 MCP Server。
- 原语抽象:Tools(可执行操作)、Resources(可读数据)、Prompts(可复用提示词)。
- 传输层:支持 stdio(本地进程)和 Streamable HTTP(远程服务)。
// MCP Server 工具定义示例(JSON Schema)
{
"name": "get_weather",
"description": "查询指定城市的实时天气",
"inputSchema": {
"type": "object",
"properties": {
"city": { "type": "string", "description": "城市名称" }
},
"required": ["city"]
}
}
MCP 的出现在一定程度上统一了工具接入方式,但也引入了新的复杂度:协议解析、生命周期管理、鉴权模型、Server 部署成本等。
3. CLI 的回归:为什么终端重新成为 Agent 的工具面
就在 MCP 生态如火如荼时,以 Claude Code、OpenAI Codex CLI 为代表的终端 Agent 却选择了另一条路:直接调用 Shell 命令。这背后的逻辑值得深思。
3.1 CLI 的天然优势
- 零协议开销:CLI 是操作系统级标准,无需额外协议层,进程间通信直接通过 stdin/stdout 完成。
- 工具即命令:任何安装在本机的可执行文件都是潜在工具,无需为每个工具编写 Server 包装。
- 组合能力强:通过管道(Pipe)、重定向、Shell 脚本,Agent 可以像人类工程师一样自由组合命令。
- 调试直观:终端输出天然可读,错误信息、日志、退出码都是标准化的。
3.2 一个典型的 CLI Agent 工具调用
# Agent 通过 Shell 完成「查找并替换」任务
grep -rn "old_api" src/ | head -20
sed -i 's/old_api/new_api/g' src/utils/http.ts
npm run test -- --filter=http
这段命令序列不需要任何 MCP Server,Agent 直接通过 Bash 工具执行,成本几乎为零。
4. 架构对比:MCP 与 CLI 的深层差异
为了更清晰地理解两者的边界,我们从多个维度进行对比。
| 维度 | MCP | CLI |
|---|---|---|
| 协议层 | JSON-RPC 2.0 + 自定义原语 | 操作系统进程 + 标准 I/O |
| 工具发现 | Server 声明 Tools/Resources | PATH 环境变量 + 命令解析 |
| 鉴权模型 | OAuth / API Key / 自定义 | 系统用户权限 + sudo |
| 远程调用 | 原生支持 Streamable HTTP | 需借助 SSH / 远程 Shell |
| 流式输出 | 支持(JSON-RPC 通知) | 支持(stdout 实时管道) |
| 结构化返回 | 强类型 JSON Schema | 需自行解析文本/JSON |
| 部署成本 | 高(需维护 Server 进程) | 低(复用本机环境) |
| 生态成熟度 | 快速成长但碎片化 | 数十年沉淀,极其稳定 |
4.1 关键差异:结构化 vs 自由文本
MCP 的核心价值在于「结构化契约」:工具输入输出都有 JSON Schema 约束,模型可以精确理解参数含义。而 CLI 的输出是自由文本,Agent 需要借助自然语言理解或正则解析来提取信息。这决定了两种形态的适用场景:
- MCP 更适合:需要强类型校验、复杂参数对象、跨语言/跨平台共享工具的场景。
- CLI 更适合:本地开发、快速迭代、工具数量庞大且变化频繁的场景。
5. 实战:用 Python 构建一个 MCP Server
下面我们通过一个完整的 Python 示例,演示如何构建一个 MCP Server,并让 Agent 调用它。
5.1 环境准备
pip install mcp fastmcp httpx
5.2 定义 MCP Server
from fastmcp import FastMCP
import httpx
创建 MCP Server 实例
mcp = FastMCP("GitHub Helper")
@mcp.tool()
def get_repo_info(repo: str) -> dict:
"""获取 GitHub 仓库的 star 数和描述信息。
Args:
repo: 仓库路径,格式为 owner/repo,例如 "anthropics/anthropic-sdk-python"
"""
url = f"https://api.github.com/repos/{repo}"
resp = httpx.get(url, timeout=10)
resp.raise_for_status()
data = resp.json()
return {
"name": data["full_name"],
"stars": data["stargazers_count"],
"description": data["description"],
"language": data["language"],
}
@mcp.tool()
def search_issues(repo: str, keyword: str, limit: int = 5) -> list:
"""在指定仓库中搜索包含关键词的 Issue。
Args:
repo: 仓库路径,格式为 owner/repo
keyword: 搜索关键词
limit: 返回结果数量上限
"""
url = f"https://api.github.com/search/issues"
params = {"q": f"repo:{repo} {keyword}", "per_page": limit}
resp = httpx.get(url, params=params, timeout=10)
resp.raise_for_status()
items = resp.json().get("items", [])
return [
{"title": item["title"], "url": item["html_url"], "state": item["state"]}
for item in items
]
if name == "main":
mcp.run(transport="stdio")
5.3 在 Claude Desktop 中配置
{
"mcpServers": {
"github-helper": {
"command": "python",
"args": ["/path/to/github_helper.py"],
"env": {}
}
}
}
配置完成后,Claude Desktop 会自动发现并注册这两个工具,模型即可在对话中直接调用。
6. 实战:用 Python 构建一个 CLI Agent
接下来,我们实现一个轻量级 CLI Agent,它通过 Shell 命令与系统交互,完成文件操作、代码搜索等任务。
6.1 核心实现
import subprocess
import shlex
from typing import List, Dict
class ShellAgent:
"""一个基于 CLI 的轻量级 Agent,通过 Shell 命令执行任务。"""
def __init__(self, model_fn):
self.model_fn = model_fn # 模型调用函数,输入 prompt 输出决策
self.history: List[Dict] = []
def run_command(self, command: str) -> str:
"""执行 Shell 命令并返回输出。"""
try:
result = subprocess.run(
shlex.split(command),
capture_output=True,
text=True,
timeout=30,
shell=False
)
output = result.stdout
if result.stderr:
output += f"\n[stderr] {result.stderr}"
if result.returncode != 0:
output += f"\n[exit_code] {result.returncode}"
return output
except subprocess.TimeoutExpired:
return "[error] 命令执行超时"
except Exception as e:
return f"[error] {str(e)}"
def think_and_act(self, task: str, max_steps: int = 5) -> str:
"""Agent 主循环:模型决策 -> 执行命令 -> 观察结果。"""
prompt = f"""你是一个终端助手。请根据任务目标,输出要执行的 Shell 命令。
任务:{task}
规则:
只输出一条命令,不要解释。
如果任务已完成,输出 DONE。
如果命令执行失败,尝试其他方式。
历史记录:
{self._format_history()}
"""
for _ in range(max_steps):
decision = self.model_fn(prompt).strip()
if decision == "DONE":
return "任务完成"
output = self.run_command(decision)
self.history.append({"cmd": decision, "output": output[:500]})
prompt = prompt.replace("历史记录:", f"历史记录:\n$ {decision}\n{output[:500]}\n")
return "达到最大步数,任务可能未完成"
def _format_history(self) -> str:
return "\n".join(
f"$ {h['cmd']}\n{h['output']}" for h in self.history[-5:]
)
模拟模型调用(实际可接入 OpenAI / Claude API)
def mock_model(prompt: str) -> str:
if "查找" in prompt and "test" in prompt:
return "grep -rn 'test' src/ | head -10"
if "替换" in prompt:
return "sed -i 's/old/new/g' src/config.py"
return "DONE"
if name == "main":
agent = ShellAgent(model_fn=mock_model)
result = agent.think_and_act("在 src 目录下查找包含 test 的文件")
print(result)
6.2 运行效果
$ python shell_agent.py
$ grep -rn 'test' src/ | head -10
src/utils/test_http.py:12:def test_http_client():
src/utils/test_http.py:25: assert client.get("/ping") == 200
任务完成
可以看到,CLI Agent 的实现极其轻量,核心逻辑不到 50 行,却已经具备「决策-执行-观察」的完整闭环。
7. 混合架构:MCP + CLI 的协同实践
在实际生产环境中,MCP 与 CLI 并非二选一,而是可以形成互补的混合架构。
7.1 分层设计思路
- 核心业务工具:使用 MCP 封装,提供强类型、可远程调用的稳定接口。
- 本地开发工具:直接使用 CLI,降低维护成本,提升迭代速度。
- 桥接层:通过 MCP Server 包装 CLI 命令,让远程 Agent 也能调用本地 Shell。
7.2 用 MCP 包装 CLI 命令
from fastmcp import FastMCP
import subprocess
mcp = FastMCP("CLI Bridge")
@mcp.tool()
def run_shell(command: str, cwd: str = ".") -> str:
"""在指定目录执行 Shell 命令并返回输出。
Args:
command: 要执行的 Shell 命令
cwd: 工作目录
"""
result = subprocess.run(
command, shell=True, capture_output=True, text=True, cwd=cwd, timeout=30
)
output = result.stdout
if result.stderr:
output += f"\n[stderr] {result.stderr}"
return output
@mcp.tool()
def list_files(path: str = ".") -> list:
"""列出指定目录下的文件。
Args:
path: 目录路径
"""
result = subprocess.run(
["ls", "-la", path], capture_output=True, text=True, timeout=10
)
return result.stdout.splitlines()
if name == "main":
mcp.run(transport="streamable-http", host="0.0.0.0", port=8000)
通过这种方式,远程的 MCP Client 可以安全地调用本地 CLI 能力,实现「远程协议 + 本地执行」的混合架构。
8. 选型决策:何时用 MCP,何时用 CLI
综合以上分析,我们给出一个务实的选型框架。
8.1 优先选择 CLI 的场景
- 工具运行在本地,且数量多、变化快。
- 需要与现有 Shell 脚本、CI/CD 流程深度集成。
- 团队希望最小化基础设施成本,快速验证 Agent 能力。
- 工具输出以文本为主,无需强类型校验。
8.2 优先选择 MCP 的场景
- 工具需要被多个 Agent / 多个团队共享。
- 工具需要远程调用,且涉及鉴权、审计。
- 工具输入输出结构复杂,需要 Schema 约束。
- 需要流式推送、资源订阅等高级能力。
8.3 决策矩阵
| 评估项 | CLI 得分 | MCP 得分 |
|---|---|---|
| 开发速度 | ★★★★★ | ★★★☆☆ |
| 运维成本 | ★★★★★ | ★★☆☆☆ |
| 跨平台共享 | ★★☆☆☆ | ★★★★★ |
| 远程调用 | ★★☆☆☆ | ★★★★★ |
| 结构化契约 | ★★☆☆☆ | ★★★★★ |
| 生态成熟度 | ★★★★★ | ★★★☆☆ |
9. 未来趋势:工具链的收敛与融合
展望未来,MCP 与 CLI 的边界会进一步模糊。一方面,MCP 生态正在吸收 CLI 的轻量理念,例如「MCP 轻量模式」允许直接暴露 Shell 命令;另一方面,CLI Agent 也在借鉴 MCP 的结构化思想,例如通过 JSON 输出模式让命令结果更易解析。
可以预见,未来的 Agent 工具链将呈现「CLI 为底座、MCP 为桥梁」的格局:本地能力通过 CLI 高效执行,跨系统协作通过 MCP 标准化连接。开发者需要同时掌握两种范式,才能构建出既灵活又健壮的 Agent 系统。
10. 总结
从 MCP 到 CLI 的演进,本质上是 Agent 工具链在「标准化」与「轻量化」之间的钟摆运动。MCP 解决了工具互联的协议问题,却带来了部署复杂度;CLI 回归了工具的本质——命令即接口,却牺牲了结构化契约。理解两者的差异与互补,是构建生产级 Agent 的关键能力。
建议读者从自己的实际场景出发:如果追求快速迭代和低成本验证,优先拥抱 CLI;如果需要跨团队、跨系统的稳定集成,则认真评估 MCP。最好的架构,往往是两者的有机融合。
更多推荐

所有评论(0)