跳到主内容

MCP 协议实战避坑实录:为 Agent 接入自定义工具服务器的完整流程 ​

给 AI Agent 接工具这件事,很多团队都经历过同一种折腾:先为某一个客户端写了一套插件,切到另一个 Agent 框架又要把调用层重写一遍;工具还没接几个,各处的胶水代码已经堆了上千行,改一个接口签名要同步三四个仓库。MCP(Model Context Protocol)的出现正是为了终结这种重复劳动——它是 Anthropic 在 2024 年底开源的模型上下文协议,用统一的方式把工具、资源、提示词暴露给任意支持该协议的客户端,2025 年以来已被 Claude Desktop、Cursor、Trae 等主流客户端和众多 Agent 框架原生支持。

协议上手并不难,官方 SDK 几十行代码就能跑通一个 demo。但从单机 demo 到生产可用,中间的坑一个不少:工具写了模型却不调用,返回内容太大撑爆上下文,远程部署时被随意扫描探测……这篇文章以一个「内部订单查询」工具服务器的完整接入过程为例,把流程与踩坑一次讲清。

方案对比:为什么要用 MCP 而不是自己封装 ​

在 MCP 普及之前,团队通常有三条路。第一条是为每个 Agent 客户端定制插件:平台各自定义插件格式,同一能力要写 N 遍。第二条是自建函数调用网关:把内部 REST API 包一层函数描述,交给模型的 function calling 直接调用,灵活但全是自维护代码。第三条是 MCP 工具服务器:能力实现一次,任何支持 MCP 的客户端都能挂载。

维度各客户端定制插件自建函数调用网关MCP 工具服务器
开发成本高,每个平台重写一遍中,需自维护调用协议低,SDK 封装了协议细节
跨客户端复用差,格式互不相通一般,需各端适配好,协议统一即插即用
部署与权限控制分散在各平台集中但要自研集中,可独立部署与鉴权
生态兼容性依赖特定平台生死纯自维护主流客户端原生支持

结论很直接:内部能力超过两三个、预期会被多个 Agent 客户端消费时,MCP 是唯一不用反复缴「适配税」的方案。但如果只是单个客户端用一次的临时脚本,直接写插件反而更快,不必为了协议而协议。

还有一条容易被忽略的隐性收益:MCP 协议内置了工具发现机制,客户端连接后能自动列出服务器提供的全部工具与参数签名,新增工具不需要通知下游改造。对比自建网关那种「每加一个接口就要同步一份函数描述文档」的模式,这份免维护的自动化同步在工具快速迭代期尤其值钱——团队第一年往往就因此省掉一轮文档腐化带来的联调成本。

实操第一步:用 FastMCP 写一个工具服务器 ​

官方 Python SDK 内置了 FastMCP 这层封装,用装饰器就能把普通函数变成 MCP 工具。下面是一个查询订单的最小实现,走 stdio 传输(客户端在本机拉起子进程通信):

python
# server.py:订单查询 MCP 工具服务器(stdio 传输)
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("order-service")

@mcp.tool()
def get_order(order_id: str) -> str:
    """按订单号查询订单状态、金额与物流信息。

    Args:
        order_id: 平台订单号,以 SO 开头的字母数字串
    """
    # 内部逻辑:查库并拼装精简摘要,只返回模型需要的字段
    order = query_order_from_db(order_id)
    if order is None:
        return f"未找到订单 {order_id},请提示用户核对订单号"
    return (f"订单 {order.order_id}:状态={order.status},"
            f"金额={order.amount}元,物流={order.logistics_summary}")

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

注意 docstring 不是给人看的注释,而是模型决定「什么时候调、怎么调」的唯一依据。函数名、参数说明、返回格式都要写得像一段指令:什么时候该用这个工具、参数长什么样、查不到会返回什么。写得越具体,模型的调用准确率越高。

实操第二步:把服务器注册给客户端 ​

以 Claude Desktop 这类桌面客户端为例,在配置文件里声明 mcpServers 即可,客户端会在启动时自动拉起子进程并完成握手:

json
{
  "mcpServers": {
    "order-service": {
      "command": "python",
      "args": ["D:/mcp/order-service/server.py"],
      "env": {
        "ORDER_DB_URL": "postgresql://readonly:***@10.0.0.8:5432/orders"
      }
    }
  }
}

三个细节值得强调:依赖环境建议用独立的虚拟环境并在 args 里指定其 python 路径,避免全局环境污染;数据库这类敏感配置通过 env 注入,绝不硬编码进代码;给 Agent 用的账号只授只读权限,把「模型出错」的爆炸半径限制在能查、不能改。

实操第三步:远程部署与传输升级 ​

MCP 工具服务器远程部署架构团队成员 AAI Agent 客户端团队成员 BAI Agent 客户端网关鉴权层API Key / Token访问控制与审计集中部署服务器FastMCP Tool Server订单查询工具独立虚拟环境环境变量注入密钥订单库只读账号HTTPSStreamable HTTP 传输:适合跨机器、团队共享能查不能改

stdio 只适合客户端与服务器在同一台机器的场景。当工具服务器要集中部署给整个团队共用时,应改走远程传输。MCP 在 2025-03-26 版规范中引入了 Streamable HTTP,取代早期的 HTTP+SSE 组合,成为远程连接的推荐方式,SDK 层只需改一行:

python
# 远程模式:以 HTTP 服务方式运行,客户端通过 URL 连接
if __name__ == "__main__":
    mcp.run(transport="streamable-http")

远程化之后,安全边界彻底变了:stdio 模式下只有本机客户端能连,而 HTTP 服务暴露在内网甚至公网端口上。此时必须在网关层做鉴权(如 OAuth 或内部网关的 token 校验)、按客户端来源做最小授权,并记录审计日志。凡是「跑起来能通就直接暴露」的 MCP 服务,都等于把内部数据库的查询入口挂在了墙上。

避坑总结 ​

  1. 工具描述是写给模型的提示词。模型靠名称和 docstring 决定调用行为,描述含糊会导致不调用或乱调用;写清使用场景、参数格式、异常返回,并随线上 badcase 持续修订。
  2. 返回体量必须主动控制。工具返回几百条记录会瞬间撑爆上下文,还会稀释后续推理质量;默认分页、限制条数、只返回关键字段,把「原始数据」换成「可直接使用的摘要」。
  3. 工具数量按业务域拆分服务器。单个服务器挂几十个工具,模型的选择准确率会明显下降;按订单、库存、工单等业务域拆成多个 MCP 服务器,按需挂载。
  4. 远程部署必须过鉴权和审计。Streamable HTTP 上线前先回答三个问题:谁能连、能调哪些工具、调用记录留在哪;三者缺一项都不该上生产。
  5. 协议在演进,别手写协议层。MCP 规范仍在持续更新(如 Streamable HTTP 取代 HTTP+SSE),始终通过官方 SDK 升级来跟进,把兼容性风险收敛在依赖版本上。

写在最后 ​

MCP 把「给 Agent 接工具」从一次性集成工作变成了可复用的基础设施:能力实现一次,协议层到处可用。真正决定这套体系质量的,不是 SDK 用得多熟,而是工程细节——工具描述的打磨、返回体量的克制、安全边界的严密。把这三件事做扎实,工具服务器才配得上「基础设施」这个定位。

免责声明

本工具与文章内容仅用于学习测试场景,优化建议仅供参考,请勿直接用于生产环境,由此产生的风险自行承担。