用 Python 从零写一个能跑起来的 MCP Server
MCP Server 不是一套需要先部署的中间件,而是一个遵循 Model Context Protocol 的普通进程:它通过标准输入输出(stdio)或 HTTP 与客户端交换 JSON-RPC 消息,向模型暴露工具、资源或提示词。
想跑通第一个 MCP Server,最短路径是用 Python 写一个只提供单个工具的 stdio 服务,然后让客户端连上它验证 tools/list 和 tools/call 是否正常返回。
下面的做法适合本地开发和学习协议,前提是本机已安装 Python 3.10 及以上版本,并且你能修改客户端的配置文件。
先弄清通信方式,再动手写代码
stdio 模式下,客户端启动你的脚本,双方通过管道通信。
每条消息是一行 JSON,包含 jsonrpc、id、method、params 等字段。
这意味着两点:
- 你的程序不能往标准输出打印日志或调试信息,否则会污染协议流,客户端直接解析失败。调试信息请写到
stderr。 - 服务端要循环读取输入,逐行解析并响应,不能启动后就退出。
一个最小可用的服务端至少要响应三个请求:initialize(握手并声明能力)、tools/list(返回工具清单)、tools/call(执行具体工具)。
客户端发来的通知类消息(没有 id 字段)只需处理,不必返回结果。
写一个只含单个工具的服务端
安装官方 Python SDK 可以省去手写协议细节:
pip install mcp
创建 server.py,注册一个把摄氏度换算为华氏度的工具:
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("demo-server")
@mcp.tool()
def c_to_f(celsius: float) -> float:
"""把摄氏度转换为华氏度"""
return celsius * 9 / 5 + 32
if __name__ == "__main__":
mcp.run()
FastMCP 默认使用 stdio 传输。
函数名、参数类型和 docstring 会被自动转换成工具描述,模型正是依靠这些信息判断何时调用工具。
参数类型尽量写明确,float 比不加注解更容易让模型传对值。
把服务端接入客户端
以 Claude Desktop 为例,
编辑其配置文件(macOS 通常在 ~/Library/Application Support/Claude/claude_desktop_config.json,
Windows 在 %APPDATA%\Claude\claude_desktop_config.json),
加入:
{
"mcpServers": {
"demo": {
"command": "python",
"args": ["/绝对路径/server.py"]
}
}
}
保存后完全退出并重启客户端。
路径必须写绝对路径,用相对路径时客户端的工作目录和你终端里的不同,会找不到脚本。
如果 Python 装在虚拟环境里,command 要指向该环境的解释器,例如 /path/to/venv/bin/python。
常见连不上的原因
服务端在客户端里显示为不可用,多数不是协议写错,而是下面几类问题:
- 脚本一启动就报错。先用
python server.py手动运行,确认没有语法错误或缺少依赖;stdio 服务启动后会静默等待输入,这是正常现象。 - 标准输出被日志污染。检查代码里有没有
print,第三方库也可能默认输出日志,必要时把日志重定向到stderr。 - 依赖不在客户端使用的环境里。客户端启动的是你配置的那个解释器,不是你当前 shell 的
python。用绝对路径的解释器执行一次pip show mcp确认。 - 改了配置没重启。多数客户端只在启动时读取 MCP 配置。
验证工具是否真的被调用
连接成功后,在对话中提出一个需要换算的问题,观察客户端是否弹出工具调用提示。
也可以直接测试协议层:向脚本的标准输入逐行写入请求并查看输出,握手请求大致如下:
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"0.1"}}}
注意协议版本会随规范更新,实际值应以你安装的 SDK 文档为准,不要照抄后当作所有版本的通用值。
若返回中包含 serverInfo 和 capabilities,说明握手正常;
再发 tools/list 应能看到 c_to_f 及其参数结构。
工具能列出但调用报错,优先检查参数名是否与函数签名一致、类型是否能被转换,以及工具内部是否有未捕获异常。
把异常信息写到 stderr,比在客户端界面上猜原因快得多。
从示例走向可用服务
示例跑通后,扩展方向通常是增加工具数量、接入真实数据源、改用 HTTP 传输供多客户端共享。
工具越多,越要注意每个工具的职责边界和参数校验:模型会按描述选择工具,描述含糊或功能重叠时容易调错。
涉及文件写入、外部请求等有副作用的操作,建议在工具内部做权限检查和输入限制,而不是完全依赖模型的判断。
对第一次开发 MCP Server 的人来说,判断是否成功的标准很简单:客户端能列出你的工具,模型能在合适的问题上选中它,调用结果能正确回到对话里。
这三点都通过,再考虑部署和扩展。