大模型能聊天,但聊不了你的数据库、内部 API 和本地文件——直到 MCP(Model Context Protocol)出现。MCP 是 Anthropic 掐出的开放协议,定义了 LLM 如何与外部数据源和工具对接。对 Python 开发者来说,写一个 MCP Server 比想象中简单:几行代码就能让 Claude 或其他兼容客户端直接调用你定义的函数、读取你暴露的资源。
下面拆解 MCP 的四个核心概念,再给一个可以跑起来的完整示例。
MCP 的四个角色
Server 是你写的服务端,负责向 LLM 暴露能力。一个 Server 可以同时提供三类东西:
- Tools(工具)——LLM 可以主动调用的函数,比如查库存、发邮件、跑脚本。
- Resources(资源)——LLM 可以读取的数据,比如一份配置文件、一条数据库记录。资源是被动拉取的,由客户端决定什么时候读。
- Prompts(提示模板)——预定义的对话模板,带可变参数,帮助用户快速进入特定场景。
Client 是 LLM 应用侧的连接器,负责发现 Server 提供的上述三类内容,并在对话中按需调用。Claude Desktop 就是一个内置了 MCP Client 的应用;你也可以用 SDK 自己写 Client。
一句话总结分工:Server 说"我能做什么",Client 说"我现在要用哪个"。
用 Python 写一个最小 MCP Server
官方提供了 mcp Python SDK,安装后就能快速起服务。以下示例暴露一个 Tool(查天气)和一个 Resource(城市列表),跑起来后 Claude Desktop 可以直接调用。
先装依赖:
pip install mcp
然后写 Server 代码,保存为 weather_server.py:
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("weather-demo")
# --- Tool: LLM 可主动调用的函数 ---
@mcp.tool()
def get_weather(city: str) -> str:
"""查询指定城市的当前天气(模拟数据)"""
# 实际项目中替换为真实 API 调用
mock_data = {
"北京": "晴,气温 28°C",
"上海": "多云,气温 25°C",
"深圳": "阵雨,气温 30°C",
}
return mock_data.get(city, f"暂无 {city} 的天气数据")
# --- Resource: LLM 可被动读取的数据 ---
@mcp.resource("cities://list")
def list_cities() -> str:
"""返回支持查询的城市列表"""
return "北京、上海、深圳"
# --- Prompt: 预定义的对话模板 ---
@mcp.prompt()
def weather_query(city: str) -> str:
"""生成天气查询的对话提示"""
return f"请帮我查询 {city} 的天气情况,并给出穿衣建议。"
if __name__ == "__main__":
mcp.run()
运行:
python weather_server.py
默认以 stdio 模式启动——Server 通过标准输入输出与 Client 通信,适合被 Claude Desktop 这类宿主进程拉起。
让 Claude Desktop 连上你的 Server
在 Claude Desktop 的配置文件中注册你的 MCP Server。配置文件路径:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
编辑配置:
{
"mcpServers": {
"weather-demo": {
"command": "python",
"args": ["weather_server.py"],
"cwd": "/absolute/path/to/your/project"
}
}
}
重启 Claude Desktop,在对话中试试:
- "帮我查一下北京的天气" → Claude 会调用
get_weatherTool - "你能查哪些城市?" → Claude 会读取
cities://listResource - 使用 Prompt 模板时,Claude 会在 Prompt 列表中看到
weather_query,填入城市参数后直接进入对话
几个容易踩的坑
Tool 和 Resource 别搞混。 Tool 是 LLM 主动触发的动作(有副作用,比如写数据库),Resource 是被动读取的数据(无副作用)。把纯查询写成 Tool 也能跑,但语义不对,会让 LLM 在不该调用时反复调用。
参数类型要显式标注。 FastMCP 通过函数签名和 docstring 推断 Tool 的参数 schema。如果参数是 int 却没标注,LLM 可能传字符串进来,服务端报错。建议每个参数都写类型注解,docstring 也别省。
stdio vs SSE。 上面的示例用的是 stdio 传输,适合本地进程间通信。如果你要把 Server 部署到远程机器,改用 SSE 模式:
mcp.run(transport="sse")
Client 配置也要相应改成 url 字段指向远程地址。注意 SSE 模式下要自己处理认证和 CORS。
错误处理要返回字符串,不要抛异常。 MCP Tool 的返回值最终会作为文本喂给 LLM。如果函数内部抛了未捕获异常,Client 只能看到一个笼统的错误提示。建议在 Tool 函数里 try-catch,把错误信息以可读字符串返回,LLM 能据此向用户解释或重试。
上手路线建议
- 先跑通最小示例——用上面的天气 Server 在 Claude Desktop 里验证连通性。
- 接真实数据源——把 mock 数据替换成你的内部 API 或数据库查询,注意加权限控制。
- 逐步加 Resource 和 Prompt——让 LLM 不仅能"做事",还能"看数据"和"按模板对话"。
- 考虑部署模式——本地开发用 stdio,生产环境用 SSE + 认证中间件。
- 写测试——用
mcpSDK 自带的 Client 类写集成测试,确保 Tool/Resource/Prompt 的 schema 和返回值符合预期,不要依赖手动在 Claude Desktop 里点。
MCP 的核心价值不是协议本身有多复杂,而是它给了一个标准化的接缝:你只管写 Server 暴露能力,任何兼容 Client 的 LLM 应用都能直接用。先把一个 Server 跑起来,比读完整个 spec 有用得多。