Skip to content

第 20 章 MCP 协议与 langchain-mcp-adapters ​

MCP(Model Context Protocol,模型上下文协议)是 Anthropic 于 2024 年 11 月开源的应用层协议,目标是统一 AI 模型访问外部工具、数据与提示词的接口标准,常被类比为"AI 世界的 USB-C"——任意 MCP 兼容的 Host(宿主应用)都能即插即用任意 MCP Server 暴露的能力,不需要为每个工具、每个数据源单独编写集成代码、也不需要针对每个工具定制 function calling schema 映射。LangChain 通过 langchain-mcp-adapters 官方适配库把 MCP Server 暴露的 Tools / Resources / Prompts 三类能力适配为 LangChain BaseTool / Runnable / PromptTemplate 实例,使 LangGraph 的 ToolNode、create_react_agent 等原生机制可以无差别消费 MCP 生态中成百上千的现成 Server(文件系统、GitHub、Postgres、Slack、Brave Search、Google Maps 等),也能让团队暴露的内部业务工具被任意 MCP 兼容客户端(Claude Desktop、IDE 插件、自建 Agent)使用。

20.1 MCP 架构与协议基础 ​

MCP 采用清晰的三角色架构:

  • Host(宿主):用户侧应用进程,如 Claude Desktop、Cursor IDE、VS Code 插件、自建 Agent 后端、Web 前端通过后端中转。Host 负责用户交互、LLM 调用编排、权限控制、多 Server 管理与用户同意流程(用户必须批准 Server 访问敏感资源)。一个 Host 可以同时接入多个 Server。
  • Client(客户端):Host 进程内的连接器对象,一个 Client 严格对应一个 Server(1:1 关系),保持与 Server 的双向通信通道,负责协议握手、消息序列化、能力调用与取消。Host 对多 Server 时持有多个 Client 实例,Client 之间相互独立、不直接通信。
  • Server(服务端):对外暴露 Tools/Resources/Prompts 能力的轻量进程或 HTTP 服务。Server 不直接调用 LLM(LLM 由 Host 调用),只负责提供数据读写、操作执行、提示词模板。Server 可以是本地进程(stdio)也可以是远程 HTTP 服务(Streamable HTTP)。

协议传输层在 2025 年新版本中支持两种主流方式:(1) stdio——Host 启动 Server 作为子进程,通过标准输入输出两个管道做双向 JSON-RPC 2.0 通信,进程退出即连接断开,适合本机单机部署、桌面应用、本地开发;(2) Streamable HTTP——Server 作为 HTTP 服务对外暴露一个端点,Client 通过 HTTP POST 发请求、通过 HTTP response stream(SSE 风格)接收服务器推送,支持断线重连、有状态会话与无状态请求混合模式,适合远程部署、多租户 SaaS、企业内网服务。旧版本的纯 SSE 传输(Client GET 一个 SSE 端点收推送、POST 另一个端点发请求)已在新协议中被 Streamable HTTP 取代,但生态中仍有大量存量 Server 支持,langchain-mcp-adapters 兼容三种传输方式。

握手流程按 JSON-RPC 2.0 规范固定为四步:

  1. Client 发送 initialize 请求,携带 protocolVersion、clientInfo(name/version)、capabilities(是否支持 sampling/roots 等高级特性);
  2. Server 响应 initialize result,告知协商后的协议版本、serverInfo、capabilities(是否支持 tools/resources/prompts/logging 等);
  3. Client 发送 notifications/initialized 单向通知(notification,无 id 不要求响应),握手完成,进入正常会话阶段;
  4. Client 通过 tools/list 获取工具清单(每个工具含 name、description、inputSchema——即描述参数的 JSON Schema),通过 prompts/list、resources/list 获取提示词模板与资源列表;之后按需调用 tools/call 执行工具、prompts/get 渲染模板、resources/read 读取资源内容。

MCP 与普通 REST API 的关键区别在于自描述性(Self-Describing):REST API 的接口 schema 存在于外部文档(OpenAPI 规范、Swagger 页面、公司 Wiki),客户端必须预先知道接口路径、方法、参数结构才能调用;MCP Server 在 initialize 握手完成后主动向 Client 汇报自己有哪些工具、每个工具需要什么参数(JSON Schema)、有哪些可订阅资源,Client(以及 Client 背后的 Agent)可以在运行时自动发现并使用工具,不需要硬编码接口文档。这让 MCP 非常适合"即插即用"式工具扩展场景——终端用户在 Claude Desktop 配置文件里加一段 server 配置、重启客户端后 Claude 就能自动发现并使用新工具;Agent 应用可以在运行时根据用户配置动态接入新的数据源而无需重新部署代码。

MCP 三类能力:

  • Tools(工具):可执行操作(运行命令、读文件、查数据库、发 Slack、提 GitHub PR、下单、查天气),有副作用,语义对应 LangChain 的 BaseTool。Agent 通过 function calling 机制主动选择调用;调用结果以 content 数组(文本/图片/音频/嵌入式资源)返回,带 isError 标志。
  • Resources(资源):可读数据(文件内容、数据库记录、API 响应、内存变量、日志条目),只读,URI 寻址(如 file:///home/xxx/report.md、postgres://host/db/table/123、memo:recent-conversations)。Host 可以把 Resource 作为上下文注入 LLM、订阅 Resource 变更通知、或让 Agent 通过 resources/read 按需读取。Resources 与 Tools 的区别是 Resources 无副作用。
  • Prompts(提示词模板):Server 提供的可复用提示词模板(如 review_pr 接受 pr_url 参数、sql_expert 接受 dialect 参数),含参数占位符与模板正文;Host 通过 prompts/get 渲染出完整 messages 后交给 LLM,有助于团队统一最佳实践 prompt。

20.2 langchain-mcp-adapters 库 ​

langchain-mcp-adapters 是 LangChain 官方维护的适配库(独立 PyPI 包),核心职责是把 MCP Server 暴露的 Tool 列表自动转换为 LangChain BaseTool 实例,使其可以直接传入 ToolNode、create_react_agent 或通过 llm.bind_tools() 绑定给模型。Resources/Prompts 适配通过独立辅助函数提供(load_mcp_resources、load_mcp_prompts)。

安装:pip install langchain-mcp-adapters mcp(mcp 是 Anthropic 官方 Python SDK,提供底层 ClientSession、Stdio/SSE/StreamableHTTP 传输实现)。

核心 API:

API说明
MultiServerMCPClient(server_configs)同时管理多个 MCP Server 连接的高级客户端,接受 server_name → config 映射;config 含 command/args(stdio)或 url(HTTP/SSE)、transport 类型、headers、env;通过 async context manager 使用,进入时自动启动所有 stdio 子进程/建立所有 HTTP 连接
await client.get_tools(server_name)从指定 server 加载全部工具,返回 list[BaseTool];会调用 tools/list 并逐个做 schema 转换
await client.get_prompts(server_name) / get_resources(server_name)加载 prompts/resources
load_mcp_tools(session)在一个已有的 MCP ClientSession 上批量加载工具,返回 list[BaseTool];适合需要自定义会话管理(共享 session、手动 initialize)的高级场景
convert_mcp_tool_to_langchain_tool(server_info, tool_def, client_session)单工具转换函数,精细控制时使用(如自定义 args_schema、自定义错误处理)

连接方式配置:(1) stdio:配置 {"command": "python", "args": ["my_server.py"], "transport": "stdio", "env": {"API_KEY": "..."}},启动子进程,Host 退出时子进程自动终止;(2) Streamable HTTP:{"url": "https://xxx.example.com/mcp", "transport": "streamable-http", "headers": {"Authorization": "Bearer ..."}};(3) SSE(旧协议):{"url": "https://xxx.example.com/sse", "transport": "sse"}。多 Server 集成时,Host 同时持有多个 Client(一个 Server 一个 Client);Server 之间不直接通信(MCP 协议不支持 Server-to-Server),跨 Server 的数据流转完全由 Agent 编排——Agent 先调用 A Server 的工具取数据,再把数据作为参数传给 B Server 的工具。

完整示例:连接本地 filesystem MCP Server,加载工具到 LangGraph ReAct Agent。

python
# langgraph==1.2.x, langchain-mcp-adapters>=0.0.1, mcp>=1.0
import asyncio
from langchain_mcp_adapters.client import MultiServerMCPClient
from langgraph.prebuilt import create_react_agent
from langchain_openai import ChatOpenAI

async def main():
    llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)

    async with MultiServerMCPClient({
        "filesystem": {
            "command": "npx",
            "args": [
                "-y",
                "@modelcontextprotocol/server-filesystem",
                "/tmp/workspace",  # 允许访问的根目录白名单
            ],
            "transport": "stdio",
        },
        # 示例:远程 Streamable HTTP Server
        # "internal_tools": {
        #     "url": "http://internal-tools.corp:8000/mcp",
        #     "transport": "streamable-http",
        #     "headers": {"Authorization": "Bearer svc-token"},
        # },
    }) as client:
        fs_tools = await client.get_tools("filesystem")
        # internal_tools = await client.get_tools("internal_tools")
        all_tools = fs_tools  # + internal_tools
        print("已加载 MCP 工具:", [t.name for t in all_tools])

        agent = create_react_agent(llm, all_tools)
        cfg = {"configurable": {"thread_id": "mcp-demo-1"}}

        async for evt in agent.astream_events(
            {"messages": [("user",
                "请列出 /tmp/workspace 目录下所有 .md 文件,并读取第一个文件的前 100 字。")]},
            config=cfg, version="v2",
        ):
            if evt["event"] == "on_chat_model_stream":
                chunk = evt["data"]["chunk"]
                if isinstance(chunk.content, str):
                    print(chunk.content, end="", flush=True)

asyncio.run(main())

MCP 生态常用官方/社区 Server:

  • @modelcontextprotocol/server-filesystem:本地文件系统读写(可配置根目录白名单);
  • @modelcontextprotocol/server-github:GitHub PR/Issue/代码搜索/文件读取;
  • @modelcontextprotocol/server-postgres:Postgres 只读查询(内置安全模式强制只读);
  • @modelcontextprotocol/server-brave-search:Brave Search 联网搜索;
  • @modelcontextprotocol/server-google-maps:Google Maps 地理编码、路线、POI;
  • @modelcontextprotocol/server-slack:Slack 发消息、查频道历史、查用户;
  • @modelcontextprotocol/server-memory:基于本地 JSON 的持久化记忆存储;
  • FastMCP(官方 Python SDK 的 FastMCP 类):10 行代码即可写一个自定义 MCP Server(装饰器定义 tool/resource/prompt),适合暴露内部业务工具给 MCP 生态。

20.3 安全考虑与协议定位 ​

安全是 MCP 生产落地的最大关切。MCP Server 本质是可执行代码:stdio 模式下是本机启动的子进程(继承 Host 权限,可以读写文件、执行命令、发网络请求),HTTP 模式下是远程服务(可执行任意服务端逻辑),权限极大。历史上社区已出现过"恶意 MCP Server 通过 prompt 注入诱导 Agent 调用危险工具、泄露本地文件"的案例。生产环境必须实施多层安全控制:

  1. Server 来源管控:严格限定 Server 来源——内部 Server 通过私有镜像仓库分发并做签名校验;禁止终端用户任意配置第三方公共 Server(若开放必须经过沙箱化隔离);建立 Server 白名单与版本审计。
  2. 权限最小化:filesystem Server 必须配置最小根目录白名单(只允许访问项目目录,禁止访问 ~/.ssh、/etc、密钥目录);postgres Server 使用只读账号+行级安全策略+查询超时;shell/命令执行类 Server 默认禁用,启用时必须在容器/沙箱中运行。
  3. 工具白名单:即使 Server 暴露了 50 个工具,Agent 启动时也要按业务场景做工具白名单过滤(只允许安全的读操作工具),高风险工具(写文件、发消息、执行命令)必须经 HITL interrupt 人工审批后才调用。
  4. 调用审计:每次 tools/call 在 Agent 层记录审计日志——server 名、tool 名、参数摘要(敏感字段掩码)、调用方 user_id、request_id、返回状态、耗时;对批量导出、删除类操作实时告警。
  5. 远程 Server 认证:远程 Streamable HTTP Server 强制 HTTPS 与认证(API Key、OAuth2、mTLS),禁止裸 HTTP;服务端按 API Key 做租户隔离与限流。
  6. 用户同意流程:对新接入的 Server 与敏感工具,Host 层必须向终端用户展示授权确认弹窗("是否允许 xxx 访问您的本地文件?"),取得明确同意后才调用。

与 OpenAPI / Function Calling 的层次关系。技术栈上三个概念位于不同层次:

  • MCP 是工具暴露/发现/调用协议:定义工具如何被描述(JSON Schema)、如何被发现(tools/list)、如何被调用(tools/call)、如何通过 stdio/HTTP 传输,解决的是"工具从哪来、怎么描述、怎么连"的问题;
  • OpenAPI 是 REST API 描述规范,MCP Server 本身可以包装 OpenAPI 后端,也可以完全不使用 HTTP;两者不是替代关系——MCP 比 OpenAPI 多了双向会话、资源订阅、提示词模板、运行时发现等 Agent 原生能力;
  • Function Calling 是模型调用工具的接口规范:定义 LLM 如何在一次补全中结构化输出 tool_calls(参数、ID、name),解决的是"LLM 怎么决定调用工具"的问题。MCP Server 暴露的工具在 Host 侧被转换为 LangChain BaseTool 后,再通过 llm.bind_tools() 转成 Function Calling 格式递给模型。三者互补,LangGraph + langchain-mcp-adapters 把三层链路打通。

本章小结 ​

MCP 是 Anthropic 于 2024 年开源的模型上下文协议,以三角色架构(Host/Client/Server)通过 JSON-RPC 2.0 over stdio 或 Streamable HTTP 通信,核心流程为 initialize → tools/list → tools/call。协议自描述性强,Server 主动暴露 Tools/Resources/Prompts 三类能力的 JSON Schema,Client 在运行时自动发现,不需要硬编码接口文档。langchain-mcp-adapters 通过 MultiServerMCPClient 管理多 Server 连接,通过 get_tools/load_mcp_tools/convert_mcp_tool_to_langchain_tool 将 MCP Tools 转换为 LangChain BaseTool,直接对接 ToolNode / create_react_agent / bind_tools。stdio 适合本机、Streamable HTTP 适合远程、SSE 兼容旧版。生态已有 filesystem/github/postgres/brave-search/google-maps/slack 等现成 Server,FastMCP 支持快速开发自定义 Server。生产使用必须严控 Server 来源、配置最小权限白名单、工具调用审计、敏感工具 HITL 审批、远程连接强制认证。MCP 属于工具暴露层,Function Calling 属于模型接口层,两者互补不冲突。

常见错误 ​

  1. MCP Server 进程未启动导致 Client 连接失败。现象:stdio 模式下 ClientSession initialize 超时(asyncio.TimeoutError)或连接被拒绝;日志显示 Server 立即退出。原因:command/args 配置错误(npx/node/python 未在 PATH 中)、依赖未安装(Node/npm 包未装、Python 包缺失)、Server 启动时立即崩溃(配置错误、鉴权失败、异常未捕获)。修复:先在命令行手动执行启动命令验证 Server 能正常运行且不退出;在配置中通过 env 字段传入必要环境变量(API Key、根目录);对常驻 Server 配 systemd/supervisor 进程守护自动重启。
  2. 工具 schema 转换时 Pydantic 字段类型不匹配。现象:BaseTool 调用时参数校验失败(Pydantic ValidationError),或复杂嵌套参数被转为 dict[str, Any] 后类型丢失、默认值错误。原因:MCP JSON Schema 中的 union(anyOf/oneOf)、format(date-time/email/uuid)、patternProperties、additionalProperties 等高级特性在自动转换为 Pydantic 模型时处理不完美。修复:对复杂参数工具改用 convert_mcp_tool_to_langchain_tool 手动指定 args_schema(Pydantic BaseModel);在 Agent system prompt 中明确参数格式与示例;对不兼容 Server 提交 issue 并在适配层用 wrapper Tool 做手工参数转换。
  3. 多 Server 工具重名导致 ToolNode / Agent 选错工具。现象:filesystem Server 和内部 Server 都有名为 read_file 的工具,Agent 调用时命中错误实现(读了本地文件而不是内部文档)。原因:工具名在同一 ToolNode 的工具列表中必须唯一,BaseTool 按 name 做索引,重复 name 会互相覆盖或引起 LLM 混淆。修复:加载工具后做重名检测,对冲突工具加命名空间前缀重命名(如 fs_read_file、wiki_read_file);在工具 description 前缀加 [filesystem]、[wiki] 标签帮助 LLM 区分。
  4. stdio 模式下 Server 崩溃无自动重启。现象:服务长时间运行后工具调用突然失败,错误显示连接已关闭(ConnectionResetError)或 session 已失效。原因:MCP Server 子进程因内存泄漏、未捕获异常、超时被杀等原因异常退出,langchain-mcp-adapters 1.x 版本默认不会自动重启 stdio 子进程。修复:生产中关键服务优先使用 Streamable HTTP 模式(Server 独立部署、前置负载均衡与健康检查、K8s 自动重启);stdio Server 必须配进程守护(systemd/supervisor/PM2);在 Host 侧监听连接断开异常后重建 MultiServerMCPClient 会话。
  5. 异步 Client 未正确关闭导致资源泄漏。现象:脚本进程无法正常退出(事件循环挂起)、文件句柄/子进程数持续增长、服务器运行一段时间后出现 "too many open files"。原因:未使用 async with 上下文管理器、手动创建 client/session 后未调用 aclose()、异常路径遗漏清理。修复:始终通过 async with MultiServerMCPClient(config) as client: 使用,确保退出时自动关连接并终止 stdio 子进程;长生命周期服务在关闭钩子(shutdown event)中显式 await client.session.aclose() 或 context aexit;CI 中加资源泄漏检测。

生产建议 ​

  • 自建 MCP Server 统一使用 FastMCP(Python)或官方 TypeScript SDK 开发,CI 中通过 mcp-inspector(官方调试工具)做 schema 兼容性与调用正确性测试;
  • 多 Server 加载后在启动阶段做工具名冲突检测(同名直接报错启动失败),并在工具 description 前统一加 [server_name] 命名空间前缀帮助 LLM 区分;
  • stdio Server 在容器内以非 root 用户运行,通过 seccomp/apparmor 配置限制系统调用(禁止 ptrace/exec 嵌套),文件系统目录以最小白名单挂载;
  • 远程 Server 部署在独立服务集群,前置 API 网关做 mTLS/OAuth2 认证、速率限制、请求大小限制;敏感 Server 放在内网不暴露公网;
  • 工具调用统一走审计中间件:自定义 ToolNode 包装器,在调用前后记录 server/tool/args_snippet(敏感字段掩码)/result_status/latency/token_usage 到审计日志与数据平台;
  • 高风险工具(写库、发消息、执行命令、文件删除)在 Agent 层强制加 interrupt_before 人工审批,不因为 MCP 自描述就放任 Agent 自动调用;
  • MCP Server 版本升级时先在影子/预发环境验证 schema 兼容性(启动后 tools/list 结果与基线做 diff),再切生产流量,避免新 schema 导致 BaseTool 转换失败或参数语义变更引发线上故障;
  • 对外部公共 MCP Server 配置请求速率限制、连接超时、响应大小上限(通过 HTTP Client 配置),防止慢速响应或超大响应拖垮 Agent 服务;
  • 长期运行的 Host 进程建立 Server 健康检查与自动重连机制(心跳 requests/list 周期性调用,失败则重建 session 并告警),避免因单个 Server 连接僵死导致整个 Agent 卡死。

第四篇 工程篇 ​