为什么需要 MCP?
大型语言模型(LLM)的能力再强,也受限于训练数据的时效性和封闭性------它不知道你的数据库里有什么,也没法直接调用外部 API。MCP(Model Context Protocol)就是为了解决这个问题而诞生的开放协议。
简单说,MCP 定义了一套标准接口:LLM 通过 MCP Client 发现工具 → Client 调用 Server 提供的工具 → Server 执行实际逻辑 → 结果返回 LLM。整个过程就像给 AI 装上了"万能插头"。
| 对比维度 | MCP | 传统 Function Calling |
|---|---|---|
| 协议标准 | 开放统一 | 各厂商自定 |
| 动态发现 | ✅ Server 自动声明工具 | ❌ 需预定义 Schema |
| 资源模型 | Tools + Resources + Prompts | 仅函数调用 |
| 多语言支持 | 任意语言实现 Server | SDK 绑定 |
| 远程调用 | 内置 Transport 层 | 需自己封装 |
环境准备
MCP 官方提供了 Python SDK,安装非常简单:
bash
pip install mcp
要求 Python 3.10+,建议在虚拟环境中操作。
实战:构建一个天气查询 Server
我们要实现一个 MCP Server,它提供两个工具:get_weather(查询指定城市的实时天气)和 get_forecast(查询未来 3 天天气预报)。
python
# weather_server.py
from mcp.server import Server, NotificationOptions
from mcp.server.models import InitializationOptions
import mcp.server.stdio
import httpx
import json
server = Server("weather-server")
@server.list_tools()
async def handle_list_tools():
return [
{
"name": "get_weather",
"description": "查询指定城市的实时天气",
"inputSchema": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名,如 北京、上海"
}
},
"required": ["city"]
}
},
{
"name": "get_forecast",
"description": "查询未来 3 天的天气预报",
"inputSchema": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名"}
},
"required": ["city"]
}
}
]
@server.call_tool()
async def handle_call_tool(name: str, arguments: dict):
city = arguments.get("city", "北京")
async with httpx.AsyncClient() as client:
if name == "get_weather":
resp = await client.get(
"https://api.openweathermap.org/data/2.5/weather",
params={"q": city, "appid": "YOUR_KEY", "units": "metric", "lang": "zh_cn"}
)
data = resp.json()
return {
"content": [{
"type": "text",
"text": json.dumps({
"城市": city,
"温度": f"{data['main']['temp']}°C",
"天气": data['weather'][0]['description'],
"湿度": f"{data['main']['humidity']}%",
"风速": f"{data['wind']['speed']}m/s"
}, ensure_ascii=False)
}]
}
elif name == "get_forecast":
resp = await client.get(
"https://api.openweathermap.org/data/2.5/forecast",
params={"q": city, "appid": "YOUR_KEY", "units": "metric", "lang": "zh_cn", "cnt": 3}
)
data = resp.json()
days = []
for item in data["list"]:
days.append({
"时间": item["dt_txt"],
"温度": f"{item['main']['temp']}°C",
"天气": item["weather"][0]["description"]
})
return {
"content": [{
"type": "text",
"text": json.dumps(days, ensure_ascii=False, indent=2)
}]
}
async def main():
async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
await server.run(
read_stream, write_stream,
InitializationOptions(server_name="weather-server")
)
if __name__ == "__main__":
import asyncio
asyncio.run(main())
核心逻辑就三步:声明工具列表(list_tools)→ 实现工具逻辑(call_tool)→ 启动 Server。
在 Claude Code 中配置
要让 Claude Code 使用这个 MCP Server,在项目根目录创建 .claude/settings.json:
json
{
"mcpServers": {
"weather": {
"command": "python",
"args": ["weather_server.py"]
}
}
}
重启 Claude Code 后输入"北京的天气怎么样",Claude 就会自动调用 get_weather 工具返回实时数据。
MCP 配置参数详解
| 参数 | 类型 | 说明 | 示例 |
|---|---|---|---|
| command | string | 启动命令 | python、node、uvx |
| args | string\[\] | 命令参数 | ["server.py"] |
| env | object | 环境变量(可选) | {"API_KEY": "xxx"} |
| transport | string | 传输方式 | stdio(默认) |
| disabled | boolean | 是否禁用 | false |
进阶:把任意 REST API 封装成工具
上面的模式可以泛化:任何 HTTP API 都可以用同样三步封装成 MCP 工具:
- 阅读 API 文档,提取输入参数和返回结构
- 定义 inputSchema,把 API 参数映射为 JSON Schema
- 用 httpx 发起请求,处理好错误和超时
- 返回格式化结果,让 LLM 能直接理解
这样做的好处是:团队的数据服务、内部 API、第三方平台统统可以通过 MCP 暴露给 AI,不需要为每个场景重复写集成代码。
踩坑记录
实战中几个容易翻车的地方:
- Tool name 不能重复:同一个 Server 里工具名必须唯一,否则启动报错
- 阻塞操作要加 timeout :
httpx默认不超时,网络慢会导致 LLM 等待太久 - 返回内容别太长 :单次
content建议控制在 5000 token 以内,太长影响 LLM 理解 - 环境变量别硬编码 :API Key 通过
env字段传入,不写死在代码里 - 路径兼容性 :Windows 用户注意
command要用完整路径或确保 python 在 PATH 中
下篇预告
本文实现了基础的 stdio 传输 MCP Server。下篇会介绍 SSE(Server-Sent Events)传输模式,让 MCP Server 可以远程部署、多个 Client 共享------真正把工具变成微服务。欢迎在评论区留下你想深入的方向,我会在后续文章中优先安排。
MCP 的工作原理
理解 MCP 的工作流程,对开发 Server 非常有帮助。一次典型的 MCP 调用分为三个阶段:
第一阶段:初始化(Initialization) Client 启动 Server 进程后,双方通过 JSON-RPC 交换能力信息。Server 声明自己支持哪些协议版本和功能特性,Client 告诉 Server 自己期望的配置。这一步是握手,确保双方在同一频道对话。
第二阶段:工具发现(List Tools) 初始化完成后,Client 调用 tools/list 请求,Server 返回所有可用工具的 JSON Schema 描述。这个描述包含了工具名称、说明和参数结构。Client 拿到这份"菜单"后,LLM 根据用户的问题自主判断该调用哪个工具。
第三阶段:工具调用(Call Tool) 当 LLM 决定使用某个工具时,Client 发起 tools/call 请求,传入工具名和参数。Server 执行实际逻辑后将结果返回,LLM 把结果整合进自己的回复中。
整个流程是被动驱动的------Server 只负责注册和实现工具,什么时候调用、传什么参数,完全由 LLM 根据用户输入动态决定。这种设计让 MCP Server 的开发者只需要关注业务逻辑本身,不需要操心 AI 调度。
协议层对比
选型时常见的几种 AI 工具集成方案:
| 方案 | 复杂度 | 灵活性 | 适用范围 |
|---|---|---|---|
| MCP | 中 | 高 | 跨平台、跨模型工具共享 |
| Function Calling | 低 | 中 | 单模型、固定工具集 |
| LangChain Tool | 高 | 高 | 多模型编排、复杂链路 |
| 自定义 HTTP API | 低 | 低 | 简单查询场景 |
MCP 的最大优势在于去厂商绑定。写一次 Server,Claude、Cursor、VS Code 的 GitHub Copilot 等支持 MCP 的客户端都能直接用。
JSON-RPC 消息格式
MCP 底层用 JSON-RPC 2.0 通信。查看 Server 和 Client 的交互日志,有助于调试问题。一个典型的 tools/call 请求如下:
json
// Client → Server
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_weather",
"arguments": {
"city": "北京"
}
}
}
// Server → Client
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"type": "text",
"text": "{"城市": "北京", "温度": "22°C", "天气": "晴", "湿度": "45%", "风速": "3m/s"}"
}
]
}
}
理解这个格式后,就算不用 SDK,自己用 Socket 也能实现一个 MCP Server。协议本身并不复杂,SDK 只是帮你省掉了 JSON-RPC 的解析样板代码。
实战经验分享
我在多个项目中接入了 MCP,总结几条实用经验:
第一,工具粒度要适中。一个工具如果做的事情太多,参数列表会很长,LLM 容易传错参数;如果太细,调用次数就会过多。好的实践是一个工具对应一个"业务操作",参数控制在 3-5 个。
第二,错误信息要友好 。当 API 调用失败时,不要只返回错误码。返回人类可读的中文错误描述,LLM 才能理解并尝试修复。例如 "城市名不能为空,请输入有效城市名" 比 "error: code 400" 有用得多。
第三,充分利用描述字段 。description 不只是给人看的------LLM 会用它来理解工具用途。描述写得越清楚,LLM 选对工具的概率越高。建议包含使用场景和参数格式说明。
第四,善用 Resources 能力 。MCP 除了 Tools(函数调用)还支持 Resources(资源读取)和 Prompts(提示模板)。比如可以让 Server 提供 weather://current/北京 这样的资源 URI,Client 直接读取而非调用函数。这个能力在很多教程里被忽视,但实际非常实用。
总结
MCP 正在快速成为 AI 工具集成的事实标准。Claude Code、Cursor、VS Code、JetBrains 等主流开发工具都已支持,生态越来越丰富。用 Python 构建一个 MCP Server 并不复杂------常规的 HTTP API 封装,一两个小时就能跑通。
关键收获:
- MCP 是开放协议,写一次 Server 多客户端通用
- Python SDK 封装了 JSON-RPC 细节,开发者只需要关注业务逻辑
- 工具粒度、错误信息、描述文档直接影响 LLM 调用成功率
- 从简单的天气查询开始,逐步扩展到内部数据服务、数据库查询、文件操作等
如果还在纠结"AI 怎么接入我的数据",MCP 就是目前最直接的答案。