MCP协议实战:从零写个Agent工具

MCP协议实战:从零写个Agent工具

摘要:MCP 就是 AI 世界的 REST API------你写好服务,Claude 来调。前一篇聊了 AI Agent 的学习路线,这篇落到具体动作:用 Python 写一个 MCP Server,把自己的 API 包装成 Agent 能认识的标准工具。不讲协议规范,讲后端最关心的事------怎么让 AI 调你的服务。


每个大模型的工具调用方式都不一样。OpenAI 是 function calling,Claude 是 tool use,国产模型各有各的格式。做后端的人最烦这种事------同一个查天气的接口,接三个模型要写三套适配层。

举个例子。你用 Spring Boot 写了个天气查询接口,想让 AI 能调用。接 OpenAI,你得按它的 function calling 格式写 JSON Schema,字段名、参数类型全得按它的规范来。接 Claude,换一套 tool use 的定义格式,描述文案的写法也不一样。接国内的模型,又是一套。

最崩溃的不是第一次适配------是模型升级之后。OpenAI 改了个参数格式,你的适配层报错了。Claude 更新了 tool use 的限制,你之前能跑的工具现在调不起来了。三个模型三个迭代节奏,你一个人维护三套适配代码。

说实话,适配到想吐。

后来 Anthropic 搞了个 MCP(Model Context Protocol),把这事统一了。2024 年底提出,2025 年生态炸开,到 2026 年已经成了 Agent 工具调用的标配(来源:Anthropic 官方博客,2024年11月)。跟 HTTP 之于 Web 一样------大家都认这个协议,你写一次,所有支持 MCP 的模型都能调。

上篇聊了 AI Agent 的学习路线,这篇不聊概念,聊代码。


MCP 是什么,用后端的话说

你用 Spring Boot 写过一个 REST 接口。

bash 复制代码
GET /api/weather?city=北京
→ { "temp": 26, "humidity": 60 }

前端发起 HTTP 请求,你的 Controller 处理逻辑,返回 JSON------这条路你走了十年,闭着眼睛都能写。

MCP 干的事一模一样,只是调用方从浏览器换成了 AI。

arduino 复制代码
Claude 想查天气 → MCP 协议 → 你的 MCP Server → 返回天气数据

架构拆开来看:

arduino 复制代码
┌──────────────────┐                      ┌──────────────────┐
│   MCP Client     │   JSON-RPC 2.0       │   MCP Server     │
│                  │ ◄──────────────────► │                  │
│  Claude Desktop  │   stdio / HTTP/SSE   │  你写的 Python    │
│  Claude Code     │                      │   服务            │
│  Cursor / Cline  │                      │                  │
└──────────────────┘                      └─────────┬────────┘
                                                    │
                                                    ▼
                                          ┌─────────────────┐
                                          │   你的业务 API    │
                                          │                  │
                                          │  天气接口         │
                                          │  数据库查询       │
                                          │  搜索引擎         │
                                          └─────────────────┘

三个要点,用后端的类比就够了:

MCP Server = 你写的微服务。@server.tool() 装饰器注册工具函数,跟 Spring 的 @RestController 一样------声明这个方法对外暴露,MCP 框架自动处理协议细节。

MCP Client = 调用方。 Claude Desktop、Claude Code、Cursor 这些 AI 工具都内置了 MCP 客户端。你配好 Server 地址,它们自动发现你的工具、看懂工具描述、在需要的时候调用。

通信协议 = JSON-RPC 2.0。 跟 REST 的 HTTP + JSON 一样,MCP 用 JSON-RPC 做序列化,支持 stdio(本地进程通信)和 HTTP/SSE(远程部署)。本机调试用 stdio,部署到服务器换成 SSE。

一句话:以前你写 REST API 给浏览器调,现在写 MCP Server 给 AI 调。换的是调用方,不换的是"定义接口 → 处理请求 → 返回结果"这套逻辑。

为什么要用 MCP 而不是直接把 API 丢给 AI?两个原因。第一,MCP 是标准协议。AI 客户端不用知道你的 API 地址、认证方式、参数格式------它只认 MCP 协议,你的 Server 负责把内部 API 包装成标准工具。第二,工具发现是自动的。你加了新工具,AI 自动感知;你改了工具描述,AI 下次调用就用新的描述。不用通知任何一个客户端。


动手写:天气查询 MCP Server

理论够了,写代码。

环境准备

Python 3.10+(3.10 以下跑不了,后面踩坑会说),装一个包:

bash 复制代码
pip install mcp

就一个依赖。FastMCP 把协议细节全封好了,你只关心工具函数怎么写。

核心代码

python 复制代码
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("weather-server")


@mcp.tool()
async def get_weather(city: str) -> str:
    """查询指定城市的实时天气信息,返回温度、湿度、风向和天气概况"""
    # 这里接真实的天气 API,示例用模拟数据
    return f"{city}:晴,26°C,湿度 60%,北风 3 级"


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

就这么几行。拆一下:

  1. FastMCP("weather-server") ------ 创建一个 MCP Server 实例,名字随便起。
  2. @mcp.tool() ------ 装饰器注册工具,跟 Spring 的 @GetMapping 一个意思。
  3. 函数签名 (city: str) -> str ------ 参数类型和返回值 MCP 框架会自动转成 JSON Schema,AI 客户端用它来判断什么时候调用。
  4. 函数的 docstring ------ 这个不是注释,是 AI 看的东西。写什么、怎么写,直接决定 AI 调不调用你的工具。后面专门说。
  5. mcp.run(transport="stdio") ------ 启动服务。stdio 走标准输入输出,本机调试用;部署到服务器换成 transport="sse"

三十行不到,一个能跑的 MCP Server 就出来了。

再加一个工具:网页搜索

只有一个工具太单薄。再加一个搜索工具,让 AI 能查实时信息:

python 复制代码
import httpx


@mcp.tool()
async def search_web(query: str) -> str:
    """在互联网上搜索指定关键词,返回前 3 条搜索结果摘要和链接。

    适用于需要实时信息的问题,比如新闻、股价、最新动态。
    """
    async with httpx.AsyncClient() as client:
        resp = await client.get(
            "https://api.search.example.com/v1/search",
            params={"q": query, "limit": 3},
        )
        data = resp.json()
        return "\n".join(
            f"- {r['title']}: {r['snippet']} ({r['url']})" for r in data["results"]
        )

两个工具注册完,你的 MCP Server 就有了两样本事:查天气、搜网页。AI 拿到这两个工具,碰到"今天北京热不热"会调天气工具,碰到"最近 AI 圈有什么新闻"会调搜索工具。

先自己测一下

接 Claude 之前,先确认 Server 本身能跑。FastMCP 内置了调试模式:

bash 复制代码
python server.py --debug

或者在代码里用 mcp dev 命令启动一个本地测试界面,直接在浏览器里手动调工具、看返回结果。这个调试界面还显示了 AI 看到的工具 Schema------你会看到你的 docstring 是怎么被转成 JSON Schema 的,参数类型、返回值描述一目了然。这时候如果发现 Schema 不对,回去改 docstring 就行。

测通了再接 Claude,省得配完配置才发现 Server 起不来,两头排查更费劲。


接 Claude:让 AI 真正用起来

服务写好了,接下来让 Claude 认识它。

Claude Desktop 配置

macOS 上配置文件在这里:

javascript 复制代码
~/Library/Application Support/Claude/claude_desktop_config.json

打开加上这一段:

json 复制代码
{
  "mcpServers": {
    "weather-server": {
      "command": "python",
      "args": ["/Users/tangyuewei/mcp-server/server.py"]
    }
  }
}

command 是启动命令,args 是脚本路径。保存,重启 Claude Desktop。

重启后,Claude 对话框左下角会出现一个锤子图标------点开能看到你的两个工具:get_weathersearch_web,描述就是你写的 docstring。这时候你问 Claude "北京今天天气怎么样",它会自动调你的 get_weather

Claude Code 配置

如果你用的是 Claude Code(终端里的 Claude),配法更简单。在项目根目录加一个 .mcp.json

json 复制代码
{
  "mcpServers": {
    "weather-server": {
      "command": "python",
      "args": ["server.py"]
    }
  }
}

然后在 Claude Code 里 /mcp 就能看到连接状态。绿点代表连上了,可以调。

HTTP 部署

如果你想把服务部署到服务器上,让团队共用,把 stdio 换成 SSE:

python 复制代码
if __name__ == "__main__":
    mcp.run(transport="sse", host="0.0.0.0", port=8080)

客户端配置对应改成 URL:

json 复制代码
{
  "mcpServers": {
    "weather-server": {
      "url": "http://your-server:8080/sse"
    }
  }
}

跟 REST 服务的部署套路一样------本机开发用 stdio,上线换成 HTTP。


踩过的坑

写了几个 MCP Server 之后,我把踩过的坑说三个。

坑一:工具描述写模糊了,AI 不调用

这个是最大的坑。@mcp.tool() 的 docstring,AI 是当"工具说明书"来读的。你说得越清楚,AI 越知道什么时候调。

我做过对比。描述写"查询天气信息"------调用率 70% 左右。改成了"查询指定城市的实时天气信息,返回温度、湿度、风向和天气概况"------调用率跳到 95% 以上。加了"适用于查询天气相关问题"这句使用场景描述后,误调用也少了。

为什么差这么多?因为 LLM 靠语义匹配决定调哪个工具。你写"查询天气",它只在用户明确说"天气"的时候调;你写了"温度、湿度、风向",用户说"今天热不热"它也能匹配上。

经验:docstring 写三行------第一行说功能,第二行说输入输出,第三行说适用场景。别省这个功夫,这是你跟 AI 之间唯一的接口文档。

还有一个容易被忽略的点:工具名也是有语义的。get_weatherfetch_wx 好,因为 AI 能直接从名字推断功能。如果你用缩写或项目内部代号当工具名,AI 看不懂,调用率就低。工具名 + 描述一起决定了 AI 的调用意愿。

坑二:Python 版本

MCP 官方依赖要求 Python 3.10+。macOS 自带的 Python 3.9 跑不了。我第一次装了之后启动报 TypeError,查了半小时才反应过来是版本问题。

装个 pyenv 切到 3.12 解决:

bash 复制代码
pyenv install 3.12
pyenv local 3.12
pip install mcp

坑三:配置文件路径

Claude Desktop 的配置路径 macOS 和 Windows 不一样。macOS 是 ~/Library/Application Support/Claude/claude_desktop_config.json,Windows 在 %APPDATA%\Claude\ 下面。网上很多教程只贴 mac 路径,用 Windows 的同事照着找半天找不到。

还有一个点:配置文件里的 args 必须是绝对路径,写相对路径 Claude Desktop 找不到。用 pwd 看完整路径再填进去。


后端看 MCP

写了一圈,回到一个朴素的问题:MCP 对后端工程师到底意味着什么。

它不是什么新东西。本质上跟你写了十年的 REST API 一样------定义接口、处理请求、返回结果。MCP 不过是把请求方从浏览器换成了 AI。

但换个角度想,这意味着你的代码能力直接延伸到了 AI 的世界。以前你写的接口只有前端能调------用户点按钮 → 前端发请求 → 后端处理。现在你写的接口 AI 也能调------用户说句话 → AI 理解意图 → AI 调你的工具 → 返回结果。这条链路上,你的代码是最后一公里------它连接着 AI 的理解能力和真实世界的业务数据。

回头看那个类比:MCP 就是 AI 世界的 REST API。 十年前后端学 REST,现在后端学 MCP------逻辑没变,能力范围变了。

有一件事值得多想一步。REST API 的时代,你的接口能力边界是前端决定的------前端能调什么,你的系统就能做什么。MCP 时代,你的接口能力边界是 AI 的理解能力决定的------AI 能理解用户的意图、能组合调用你的工具、能在多个工具之间做决策。同样是你的后端代码,在 REST 时代是被动等调用,在 MCP 时代是主动参与 AI 的推理链。

这不只是"换个调用方"的事。它意味着你写的每一个工具,都可能被 AI 以一种你没想到的方式组合使用------今天它拿天气工具查天气,明天可能用天气工具 + 搜索工具 + 数据库工具自动生成一份出差报告。工具的排列组合效应,是 MCP 区别于 REST 最本质的地方。

上篇说关注 MCP 和 A2A 两个协议,这篇算是把 MCP 从概念到代码走了一遍。A2A(Agent-to-Agent)是 Agent 之间的通信协议,场景不一样,后面再写。


tangyuewei,从后端出发,用 AI 拓展到全栈的工程师。

相关推荐
counterxing2 小时前
最近发现一个 Mac 工具,有点像把 Raycast、语音输入法、截图和录屏塞到了一起
macos·ai编程·claude
薛定喵的谔2 小时前
Term Proxy — 用 Tauri 2 打造跨平台终端配置管理工具
electron·ai编程·全栈
小溪彼岸3 小时前
CC Switch可视化管理Skill、提示词、会话
aigc·ai编程
aqi006 小时前
15天学会AI应用开发(九)利用Chroma持久化向量数据
人工智能·python·大模型·ai编程·ai应用
老实人阿三7 小时前
用 VS Code 和 Suno MCP 轻松生成背景音乐
mcp
kfaino7 小时前
你好,我叫 Prompt——其实,你一直在给 AI 写程序
后端·openai·ai编程
kfaino16 小时前
你好,我叫Token——AI世界里最忙的搬砖工
aigc·openai·ai编程
程序员老刘18 小时前
Flutter版本选择指南:3.44系列继续观望 | 2026年6月
flutter·ai编程·客户端