200 行代码实现 MCP 协议:让 AI "动手干活"
什么是 MCP?
MCP(Model Context Protocol,模型上下文协议)是由 Anthropic 推出的一种开放协议,旨在标准化 AI 模型与外部数据源、工具之间的交互方式。简单来说,它让大语言模型能够安全、可控地调用外部工具,就像给 AI 装上了"手脚"。
今天我们通过开源项目CatMCP,从0到1拆解MCP的Python实现,带你吃透客户端、服务器、工具调用、LLM集成的核心代码。
一、项目总览:CatMCP实现框架
CatMCP是基于Python的轻量级MCP示例,核心通过标准输入输出实现客户端与服务器通信,兼容OpenAI/通义千问等大模型,自带天气查询、新闻搜索工具,代码结构清晰,非常适合学习MCP原理。
项目文件结构:
catmcp/
├── main.py # 主入口,演示MCP客户端调用
├── stdio_client.py # MCP客户端(子进程启动服务器、LLM交互、工具调度)
├── stdio_server.py # MCP服务器(JSON-RPC 2.0、工具注册、请求处理)
├── requirements.txt # 依赖清单
└── .env# API密钥配置
核心技术栈:Python 3.12+、JSON-RPC 2.0、OpenAI SDK、python-dotenv。
二、核心代码逐行拆解
1. MCP服务器:stdio_server.py(工具提供方)
服务器是MCP的能力提供端,基于JSON-RPC 2.0协议,通过标准IO接收请求、执行工具、返回结果。
1.1 工具注册与定义
# 工具注册字典(核心扩展点)
TOOLS = {
"get_weather": {
"description": "获取指定地点的天气信息",
"inputSchema": {
"type": "object",
"properties": {"location": {"type": "string", "description": "城市名称"}},
"required": ["location"],
},
"handler": get_weather,
},
"search_news": {
"description": "根据关键词搜索新闻",
"inputSchema": {
"type": "object",
"properties": {"query": {"type": "string", "description": "搜索关键词"}},
"required": ["query"],
},
"handler": search_news,
},
}
- inputSchema:严格遵循JSON Schema,定义工具入参类型、必填项,LLM自动解析调用
- handler:工具执行函数,替换为真实API即可落地
1.2 工具实现函数
def get_weather(location: str) -> str:
# 模拟天气数据,实际替换为天气API
return f"{location}的天气是晴,温度是22摄氏度"
def search_news(query: str) -> str:
# 模拟新闻数据,实际替换为新闻搜索API
return f"搜索关键词:{query},搜索结果:国内油价24日将迎年内第六轮调整..."
1.3 JSON-RPC 2.0请求处理
服务器循环读取标准输入,解析RPC请求,分发到对应方法:
def handle_request(request: dict) -> dict:
method = request.get("method")
params = request.get("params", {})
request_id = request.get("id")
if method == "initialize":
# 初始化:返回协议版本、工具列表
return {"jsonrpc": "2.0", "result": {"protocolVersion": "0.1", "tools": list(TOOLS.keys())}, "id": request_id}
elif method == "tools/list":
# 返回所有工具详情(含Schema)
return {"jsonrpc": "2.0", "result": TOOLS, "id": request_id}
elif method == "tools/call":
# 执行工具调用
tool_name = params.get("name")
tool_params = params.get("parameters", {})
if tool_name not in TOOLS:
return {"jsonrpc": "2.0", "error": {"code": -32601, "message": "Method not found"}, "id": request_id}
result = TOOLS[tool_name]["handler"](**tool_params)
return {"jsonrpc": "2.0", "result": result, "id": request_id}
else:
return {"jsonrpc": "2.0", "error": {"code": -32601, "message": "Method not found"}, "id": request_id}
支持3个核心RPC方法:
initialize:初始化握手,返回协议版本tools/list:向客户端暴露所有工具tools/call:执行具体工具调用
1.4 服务器主循环
def run_server():
# 持续监听标准输入,处理请求
for line in sys.stdin:
line = line.strip()
if not line:
continue
request = json.loads(line)
response = handle_request(request)
print(json.dumps(response, ensure_ascii=False))
sys.stdout.flush()
- 基于标准IO通信,无网络依赖,本地直接运行
- 逐行读取JSON请求,处理后返回JSON响应
MCP服务器工作流程图:读取stdin→解析RPC→执行工具→返回stdout
2. MCP客户端:stdio_client.py(调度与LLM集成)
客户端是MCP的调用方,负责启动服务器子进程、与LLM对话、解析工具调用、转发请求到服务器。
2.1 启动服务器子进程
class MCPClient:
def __init__(self):
# 子进程启动MCP服务器,管道通信
self.server_process = subprocess.Popen(
[sys.executable, "stdio_server.py"],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8"
)
self.initialize()
def _send_request(self, method: str, params: dict = None) -> dict:
# 发送JSON-RPC请求到服务器
request_id = str(uuid.uuid4())
request = {"jsonrpc": "2.0", "method": method, "params": params or {}, "id": request_id}
self.server_process.stdin.write(json.dumps(request, ensure_ascii=False) + "\n")
self.server_process.stdin.flush()
# 读取服务器响应
response = self.server_process.stdout.readline()
return json.loads(response)
- 用
subprocess启动服务器,通过stdin/stdout双向通信 - 自动生成请求ID,保证请求-响应匹配
2.2 LLM集成与工具调用
def run_agent_with_mcp(prompt: str) -> str:
# 初始化LLM(通义千问/OpenAI兼容)
llm = OpenAI(
api_key=os.getenv("API_KEY"),
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
model="qwen-plus"
)
client = MCPClient()
# 获取服务器工具列表
tools = client._send_request("tools/list")["result"]
# 构造LLM支持的工具格式
llm_tools = [{"type": "function", "function": {"name": k, "description": v["description"], "parameters": v["inputSchema"]}} for k, v in tools.items()]
# LLM对话
messages = [{"role": "user", "content": prompt}]
response = llm.chat.completions.create(model="qwen-plus", messages=messages, tools=llm_tools)
# 处理工具调用
tool_call = response.choices[0].message.tool_calls[0]
tool_name = tool_call.function.name
tool_params = json.loads(tool_call.function.arguments)
# 调用MCP服务器
result = client._send_request("tools/call", {"name": tool_name, "parameters": tool_params})["result"]
return result
核心流程:
- 从服务器获取工具清单
- 转换为LLM兼容格式
- LLM判断是否调用工具、生成入参
- 客户端转发调用到服务器,返回结果给LLM
MCP客户端与LLM交互流程图:用户提问→LLM决策→调用工具→返回结果
3. 主入口:main.py(快速体验)
from stdio_client import run_agent_with_mcp
def main():
# 示例1:搜索新闻
news_result = run_agent_with_mcp("搜索今日新闻")
print(news_result)
# 示例2:查询天气
weather_result = run_agent_with_mcp("查询上海天气")
print(weather_result)
if __name__ == "__main__":
main()
运行命令:
pip install -r requirements.txt
# 配置.env文件API_KEY
python main.py
三、扩展开发:快速添加自定义工具
在stdio_server.py的TOOLS字典添加新工具,无需修改客户端代码:
# 新增工具函数
def calculate(expression: str) -> str:
try:
return f"计算结果:{eval(expression)}"
except:
return "表达式错误"
# 注册到工具列表
TOOLS["calculate"] = {
"description": "计算数学表达式",
"inputSchema": {
"type": "object",
"properties": {"expression": {"type": "string", "description": "数学表达式"}},
"required": ["expression"],
},
"handler": calculate,
}
重启程序,LLM即可自动识别并调用新工具。
四、核心原理总结
- MCP本质:标准化的模型-工具通信协议,基于JSON-RPC 2.0,降低集成复杂度
- 通信方式:CatMCP用标准IO,轻量无网络,适合本地工具;生产可改用WebSocket/HTTP
- 扩展能力:工具注册+Schema声明,LLM自动理解调用,支持动态扩展
- 架构优势:客户端与服务器解耦,LLM无需感知工具细节,专注推理
CatMCP用不到200行核心代码,完整实现MCP协议核心流程,是学习AI工具调用的绝佳案例。替换模拟数据为真实API、优化异常处理、添加日志,即可快速落地到生产项目。
关注公众号【dev派】,发送 "agent" 获取全部源码和模板