LangChain 接外部工具:MCP 和 CLI 到底该选哪个?

同一个需求两种做法,到底差在哪?这篇把 MCP 和 CLI Tool 两种方案从原理到代码到踩坑讲透,全程代码可复制。看完记得点赞收藏,你的支持是我更新最大的动力 🌟

前言:Agent 的"手脚"从哪来?

做过 LangChain Agent 的朋友都知道,Agent 要真正能干活,得有"工具"。查数据库、发邮件、读文件、调 API------这些都得靠外挂工具来完成。

问题是:工具怎么接进来?

目前摆在面前有两条主流的路:

路线 A:CLI Tool(传统做法)

gitcurlgh 这些命令行工具用 subprocess 包一下,套个 @tool 装饰器,就能给 Agent 用。简单直接,LangChain 从 0.0.x 版本就这么玩。

路线 B:MCP(Model Context Protocol)

Anthropic 在 2024 年底推出的开放协议,目标是做"Agent 界的 USB-C"------标准化 LLM 接入外部工具的方式。2025 年 LangChain 官方推出 langchain-mcp-adapters,现在 MCP Server 生态已经长出几百个。

两种路都能达到目的,但背后的取舍完全不同。这篇文章就是帮你搞清楚:

  • 两者的本质区别
  • 同一个需求两种做法的代码对比
  • 什么场景选什么
  • 生产环境踩过的坑

准备好了吗?我们开始。


一、先把两个概念说清楚

1.1 CLI Tool 方式:把命令行"翻译"给 Agent

核心思路一句话:任何能在命令行跑的东西,包一层都能给 Agent 用。

举个例子。我想让 Agent 能查 GitHub 某个仓库的 issues,最朴素的做法是:

python 复制代码
import subprocess
from langchain_core.tools import tool

@tool
def list_github_issues(repo: str) -> str:
    """
    列出指定 GitHub 仓库的开放 issues。
    repo 格式:owner/repo,比如 "langchain-ai/langchain"
    """
    result = subprocess.run(
        ["gh", "issue", "list", "--repo", repo, "--limit", "10"],
        capture_output=True,
        text=True,
        timeout=30,
    )
    if result.returncode != 0:
        return f"Error: {result.stderr}"
    return result.stdout

完事。只要本地装了 gh 命令,这个工具就能用。

优点 :直观、零协议开销、任何 CLI 都能用。 缺点:每个工具都要自己写 schema、自己处理输出、自己做错误处理。

1.2 MCP 方式:标准化的"能力中介"

MCP 引入了一个中间层------MCP Server

原本的架构是:

objectivec 复制代码
LangChain Agent → 直接调用 subprocess → CLI 工具

MCP 的架构是:

arduino 复制代码
LangChain Agent → MCP Client → MCP Server → 实际工具 / API / 数据库

看起来多了一层,但这一层带来了标准化。MCP Server 对外暴露:

  • Tools:可执行的操作(和 LangChain Tool 对应)
  • Resources:可读取的资源(文件、数据库记录等)
  • Prompts:预定义的 Prompt 模板

关键点同一个 MCP Server 可以被 Claude Desktop、Cursor、LangChain Agent、Cline 等多个客户端复用,不用每个框架都写一遍集成。

社区已经有几百个开源 MCP Server,覆盖 GitHub、GitLab、Slack、Postgres、Google Drive、Filesystem......基本上常用工具都有现成的。

1.3 两者到底差在哪?

我画个对比图你就懂了:

arduino 复制代码
【CLI Tool 方式】
  LangChain Agent 
        ↓ @tool + subprocess
  命令行程序(gh / git / curl)
  
  特点:点对点,一对一绑定


【MCP 方式】
  LangChain Agent ─┐
  Claude Desktop  ─┼──→ MCP Client 协议 ──→ MCP Server ──→ 底层能力
  Cursor          ─┘

  特点:标准化,一个 Server 多端复用

CLI 是"你自己造轮子 ",MCP 是"大家一起造一套轮子"。


二、实战 Round 1:CLI Tool 方式

还是 GitHub issues 查询的例子,做一个更完整的版本。

2.1 安装准备

bash 复制代码
# 装 gh 命令行
brew install gh            # macOS
# 或 Windows/Linux 去 cli.github.com 下载

# 登录
gh auth login

# Python 依赖
pip install langchain langchain-openai langgraph

2.2 把 CLI 包成 Tool

python 复制代码
import subprocess
import json
from langchain_core.tools import tool

@tool
def list_github_issues(repo: str, limit: int = 10, state: str = "open") -> str:
    """
    列出指定 GitHub 仓库的 issues。
    
    Args:
        repo: 仓库名,格式为 "owner/repo"
        limit: 返回数量,默认 10
        state: issue 状态,可选 "open" / "closed" / "all"
    """
    try:
        result = subprocess.run(
            [
                "gh", "issue", "list",
                "--repo", repo,
                "--limit", str(limit),
                "--state", state,
                "--json", "number,title,author,createdAt",
            ],
            capture_output=True,
            text=True,
            timeout=30,
        )
        if result.returncode != 0:
            return f"Error: {result.stderr}"
        
        # gh --json 输出是 JSON,自己解析成更易读的格式
        issues = json.loads(result.stdout)
        if not issues:
            return "该仓库没有符合条件的 issue"
        
        lines = []
        for issue in issues:
            lines.append(
                f"#{issue['number']} {issue['title']} "
                f"(by {issue['author']['login']}, {issue['createdAt'][:10]})"
            )
        return "\n".join(lines)
    
    except subprocess.TimeoutExpired:
        return "Error: 命令超时"
    except Exception as e:
        return f"Error: {str(e)}"


@tool
def get_github_issue_detail(repo: str, issue_number: int) -> str:
    """获取 issue 的详细内容(标题、正文、评论数)。"""
    try:
        result = subprocess.run(
            [
                "gh", "issue", "view", str(issue_number),
                "--repo", repo,
                "--json", "title,body,comments",
            ],
            capture_output=True,
            text=True,
            timeout=30,
        )
        if result.returncode != 0:
            return f"Error: {result.stderr}"
        
        data = json.loads(result.stdout)
        return (
            f"Title: {data['title']}\n\n"
            f"Body: {data['body'][:500]}\n\n"
            f"Comments: {len(data['comments'])}"
        )
    except Exception as e:
        return f"Error: {str(e)}"

2.3 组装 Agent

python 复制代码
from langchain.agents import create_agent
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)

agent = create_agent(
    llm,
    tools=[list_github_issues, get_github_issue_detail],
)

# 跑起来
response = agent.invoke({
    "messages": [
        ("user", "看一下 langchain-ai/langchain 仓库最新的 5 个 issue,挑一个感兴趣的看详情")
    ]
})

for msg in response["messages"]:
    print(msg.content[:200] if msg.content else "", "\n---")

2.4 这个方案的问题

能跑通,但你会发现几个烦人的点:

  1. 每个工具的 schema 要手写:函数签名、docstring、参数说明,全靠你自己
  2. 输出格式不统一:有的 CLI 输出 JSON,有的输出表格,每个都要自己解析
  3. 错误处理重复造轮子:超时、returncode 非零、stderr 解析,每个工具都写一遍
  4. 没法被别的 Agent 框架复用 :换个 LlamaIndex、Autogen,这些 @tool 全废
  5. 权限控制要自己做:谁能调什么工具、读哪些目录,都得自己写一套

这些问题在单人小项目里不是问题,规模一大就全变成技术债


三、实战 Round 2:MCP 方式

同样的需求,用 MCP 怎么做。

3.1 安装准备

arduino 复制代码
pip install langchain-mcp-adapters langgraph "langchain[openai]"

3.2 方案 A:直接用社区现成的 MCP Server(推荐)

GitHub 官方已经维护了一个 MCP Server:github-mcp-server。装上后直接连就行,一行业务代码都不用写

ruby 复制代码
# 装官方的 GitHub MCP Server(用 Go 实现,也可以 npx 启动 npm 版)
brew install github-mcp-server
# 或用 npx:npx -y @modelcontextprotocol/server-github

LangChain 这边接入:

python 复制代码
import asyncio
import os
from langchain_mcp_adapters.client import MultiServerMCPClient
from langchain.agents import create_agent


async def main():
    # 配置 MCP Server
    client = MultiServerMCPClient({
        "github": {
            "command": "github-mcp-server",
            "args": ["stdio"],
            "transport": "stdio",
            "env": {
                "GITHUB_PERSONAL_ACCESS_TOKEN": os.environ["GITHUB_TOKEN"],
            },
        },
    })
    
    # 自动发现 Server 提供的所有工具
    tools = await client.get_tools()
    print(f"从 MCP Server 加载了 {len(tools)} 个工具:")
    for t in tools:
        print(f"  - {t.name}: {t.description[:60]}")
    
    # 组装 Agent
    agent = create_agent("openai:gpt-4o-mini", tools=tools)
    
    # 跑同样的任务
    response = await agent.ainvoke({
        "messages": [
            ("user", "看一下 langchain-ai/langchain 仓库最新的 5 个 issue,挑一个感兴趣的看详情")
        ]
    })
    
    for msg in response["messages"]:
        print(msg.content[:200] if msg.content else "", "\n---")


asyncio.run(main())

注意两个关键差异

  • 工具是 自动发现 的(client.get_tools()),不用一个个手写
  • Server 由 GitHub 官方维护,功能远比我们手写的 2 个函数全------issue、PR、release、workflow 都能查

3.3 方案 B:自己写一个 MCP Server

如果你的工具是内部系统(比如公司自建的工单平台),社区没有现成的 MCP Server,就得自己写。好消息是 FastMCP 让这件事特别简单。

先写 Server:

python 复制代码
# my_github_server.py
from mcp.server.fastmcp import FastMCP
import subprocess
import json

mcp = FastMCP("GitHub Tools")


@mcp.tool()
def list_issues(repo: str, limit: int = 10, state: str = "open") -> str:
    """列出 GitHub 仓库 issues"""
    result = subprocess.run(
        ["gh", "issue", "list", "--repo", repo,
         "--limit", str(limit), "--state", state,
         "--json", "number,title,author,createdAt"],
        capture_output=True, text=True, timeout=30,
    )
    if result.returncode != 0:
        return f"Error: {result.stderr}"
    
    issues = json.loads(result.stdout)
    return "\n".join(
        f"#{i['number']} {i['title']} (by {i['author']['login']})"
        for i in issues
    ) or "无 issue"


@mcp.tool()
def get_issue_detail(repo: str, issue_number: int) -> str:
    """获取 issue 详情"""
    result = subprocess.run(
        ["gh", "issue", "view", str(issue_number), "--repo", repo,
         "--json", "title,body,comments"],
        capture_output=True, text=True, timeout=30,
    )
    if result.returncode != 0:
        return f"Error: {result.stderr}"
    
    data = json.loads(result.stdout)
    return f"Title: {data['title']}\n\nBody: {data['body'][:500]}"


if __name__ == "__main__":
    mcp.run(transport="stdio")

然后在 LangChain 端接入:

python 复制代码
import asyncio
from langchain_mcp_adapters.client import MultiServerMCPClient
from langchain.agents import create_agent


async def main():
    client = MultiServerMCPClient({
        "github": {
            "command": "python",
            "args": ["/absolute/path/to/my_github_server.py"],
            "transport": "stdio",
        },
    })
    tools = await client.get_tools()
    
    agent = create_agent("openai:gpt-4o-mini", tools=tools)
    response = await agent.ainvoke({
        "messages": [("user", "看下 langchain-ai/langchain 的最新 issues")]
    })
    print(response["messages"][-1].content)


asyncio.run(main())

代码量和 CLI 方案差不多,但这个 Server 现在同时可以被 Claude Desktop、Cursor 用 ------把 Server 配置丢进这些客户端的 config 就完事了。一次写,多端用。

3.4 多 Server 混合

MCP 真正的威力在 组合MultiServerMCPClient 可以一次连多个 Server:

makefile 复制代码
client = MultiServerMCPClient({
    "github": {
        "command": "github-mcp-server",
        "args": ["stdio"],
        "transport": "stdio",
        "env": {"GITHUB_PERSONAL_ACCESS_TOKEN": os.environ["GITHUB_TOKEN"]},
    },
    "filesystem": {
        "command": "npx",
        "args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/me/projects"],
        "transport": "stdio",
    },
    "postgres": {
        "url": "http://localhost:8001/mcp",
        "transport": "http",
    },
})

tools = await client.get_tools()
# 现在 agent 同时拥有 GitHub、文件系统、Postgres 三类能力

你可以让 Agent 完成这样的任务:

"从 Postgres 查最近一周的错误日志,到 GitHub 对应仓库搜一下有没有相关 issue,没有就创建一个,把日志摘要写进去,然后把分析结果存到本地 ~/projects/error_report.md。"

这个任务在 CLI 方案下要自己封装三套 subprocess 调用;在 MCP 下就是把三个现成的 Server 配上去,剩下全自动


四、多维度对比

把两种方案放到同一张表里对比:

维度 CLI Tool MCP
接入成本 低(有 CLI 就能包) 中(需要 Server,但常用场景有现成的)
工具描述 手写 docstring + schema 自动发现
输出格式 原始字符串,自己解析 结构化内容块(content blocks)
可复用性 强绑定当前 Agent 框架 跨框架复用(Claude/Cursor/LangChain 通吃)
错误处理 自己做 协议标准化
权限控制 自己做 Server 端统一管理
调试链路 短(一层 subprocess) 长(跨进程通信)
传输协议 / stdio / HTTP / SSE 可选
生态成熟度 高(任何 CLI 都能包) 成长中但发展极快
运行开销 多一个进程,略高

五、选型建议:什么场景选什么

5.1 选 CLI Tool 的场景

快速原型验证 :想法阶段,用最短路径跑通就行 ✅ 工具数量少且单一 :只需要 2-3 个简单工具,写个 @tool 就完事,不需要 Server 那套 ✅ 纯内部脚本 :一次性任务或临时自动化,不考虑长期维护 ✅ 对延迟极度敏感:跨进程通信终究有开销

5.2 选 MCP 的场景

多 Agent / 多客户端复用 :同一套工具既给 LangChain Agent 用,又给 Claude Desktop 用 ✅ 接入热门外部服务 :GitHub、Slack、Notion、Postgres 都有现成 Server,白嫖即可 ✅ 多人协作的长期项目 :Server 有版本管理、权限隔离、统一鉴权,比散落各处的 @tool 好维护 ✅ 需要能力热插拔:不改 Agent 代码,通过改 Server 配置就能增减工具

5.3 混合方案(生产环境推荐)

真实项目往往是两者混用

  • 底层能力用 MCP:Server 化封装外部系统(数据库、内部 API、第三方服务)
  • 胶水逻辑用 @tool:一些简单的本地函数、格式转换、业务特定计算,没必要上 MCP

举个例子------一个内部知识库 Agent:

python 复制代码
# MCP 提供外部能力
client = MultiServerMCPClient({
    "company_wiki": {"url": "http://internal-mcp/wiki", "transport": "http"},
    "jira": {"url": "http://internal-mcp/jira", "transport": "http"},
})
mcp_tools = await client.get_tools()

# @tool 提供业务特定函数
@tool
def format_report(title: str, content: str) -> str:
    """按公司模板格式化一份报告"""
    return f"# {title}\n\n发布日期:{datetime.now():%Y-%m-%d}\n\n{content}"

# 混合给 Agent
all_tools = mcp_tools + [format_report]
agent = create_agent(llm, tools=all_tools)

这才是真实项目的姿势------该抽象的抽象,该务实的务实

5.4 一句话决策

工具要被"你一个人、一次性"用 → CLI Tool。 工具要被"多人、多端、长期"用 → MCP。


六、踩坑记录

最后分享一些实际用下来的踩坑经验。

6.1 CLI Tool 的坑

坑 1:子进程编码问题

中文系统 + Windows 下,subprocess 输出经常乱码。一定要显式指定编码:

ini 复制代码
subprocess.run([...], capture_output=True, text=True, encoding="utf-8")

坑 2:忘了设超时

Agent 调 CLI 时如果某个命令卡住,整个 Agent 会一直等。永远加 timeout 参数

ini 复制代码
subprocess.run([...], timeout=30)

坑 3:shell=True 的安全风险

千万别为了方便用 shell=True + 字符串拼接,LLM 生成的参数可能带恶意内容。用 list 形式传参

python 复制代码
# ❌ 危险
subprocess.run(f"gh issue list --repo {repo}", shell=True)

# ✅ 安全
subprocess.run(["gh", "issue", "list", "--repo", repo])

6.2 MCP 的坑

坑 1:stdio vs HTTP 搞混

官方文档有句重要提示:stdio 主要是为本地单用户应用设计的 。如果你在 Web Server 里跑 Agent,多用户共用一个 MCP Server,务必用 HTTP / streamable_http,不要用 stdio------stdio 模式下每个用户都要起一个子进程,很快就爆资源。

makefile 复制代码
# Web 服务里的正确姿势
client = MultiServerMCPClient({
    "github": {
        "url": "http://mcp-server:8080/mcp",
        "transport": "streamable_http",
        "headers": {"Authorization": f"Bearer {user_token}"},  # 用户级鉴权
    },
})

坑 2:异步上下文管理

MultiServerMCPClient 是异步的。新手经常忘了 await,或者在同步函数里调:

csharp 复制代码
# ❌ 错误
def my_func():
    tools = client.get_tools()  # 返回的是 coroutine

# ✅ 正确
async def my_func():
    tools = await client.get_tools()

在 Jupyter 里可以直接 await,在普通脚本里要用 asyncio.run() 包一下。

坑 3:Server 起不来但没报错

stdio 模式下,如果 Server 脚本本身有 import 错误,你可能看到的是"工具列表为空"而不是明确的错误。调试的时候先手动跑一下 Server

bash 复制代码
python my_github_server.py
# 看看有没有报错,或者直接退出

坑 4:Token / 权限不要硬编码

最容易犯的错是把 GitHub Token 写死在 Server 代码里。正确做法是通过 env 传:

lua 复制代码
client = MultiServerMCPClient({
    "github": {
        "command": "github-mcp-server",
        "args": ["stdio"],
        "env": {"GITHUB_PERSONAL_ACCESS_TOKEN": os.environ["GITHUB_TOKEN"]},
    },
})

多租户场景下更要注意------每个用户的 token 不同,要用 HTTP transport + 请求头传,不能塞在启动环境里。

坑 5:协议还在演进

MCP 这个协议 2024 年底才发布,API 和库版本更新很快。langchain-mcp-adapters 从 0.1 到 0.2 就有 breaking change(比如 transport 参数名)。建议锁版本

ini 复制代码
langchain-mcp-adapters==0.2.x

升级前看一眼 changelog。


写在最后

回到开头的问题:MCP 和 CLI,到底该选哪个?

这篇文章写下来我的答案是------没有谁取代谁,是工具栈的演进

  • CLI Tool 是短路径:适合快速、内部、一次性
  • MCP 是长期路径:适合长期、多端、团队协作

有意思的是,LangChain 官方文档里专门有句话提醒大家:

"Before using stdio in a web server context, evaluate whether there's a more appropriate solution. For example, do you actually need MCP? or can you get away with a simple @tool?"

翻译一下:别为了用 MCP 而用 MCP 。如果 @tool 就能解决,那就 @tool

技术选型最怕跟风,以终为始才靠谱------你的工具未来会被谁用、用多久、用多频繁,决定了你现在该怎么接。

相关推荐
liu****3 小时前
LangGraph-AI应用开发框架(五)
python·langchain·大模型·langgraph
花千树-0103 小时前
三个 Agent 并行调研:用 concurrent 节点构建并发-汇聚式旅游规划助手
java·langchain·agent·function call·multi agent·mcp·harness
是小蟹呀^4 小时前
【整理】Agent中的ReAct架构
langchain·agent·react
舒一笑13 小时前
大模型根本不是“学会了”,它只是会“看例子”:一文讲透 In-context Learning(ICL)
langchain·llm·openai
Java后端的Ai之路16 小时前
LangChain ReAct Agent 核心技术问答
前端·react.js·langchain
Java后端的Ai之路20 小时前
LangChain 面试问答指南2
面试·职场和发展·langchain
Java后端的Ai之路1 天前
当大模型开始“水土不服“:从通才到专才的进化论——Fine-tuning 企业级实战全攻略
人工智能·python·langchain·rag·lcel
Trouvaille ~1 天前
零基础入门 LangChain 与 LangGraph(五):核心组件上篇——消息、提示词模板、少样本与输出解析
人工智能·算法·langchain·prompt·输入输出·ai应用·langgraph
猫头虎1 天前
一个插件,国内直接用Claude Opus 4.7
人工智能·langchain·开源·prompt·aigc·ai编程·agi