developer.chat
27 April 2025
category
模型上下文协议 (MCP):让AI真正连接现实世界的"USB-C接口"
是否曾幻想过你的AI聊天机器人能真正执行操作 —— 比如查询天气、访问数据库或操作本地文件,而无需编写繁琐的自定义集成代码?这正是模型上下文协议 (Model Context Protocol,简称MCP) 要解决的痛点。你可以把MCP理解为"AI领域的USB-C":一种标准化的AI模型与外部数据工具对接方式。
本文将通过真实代码示例,演示如何用最简单的方式构建和使用MCP服务器。
MCP的本质是什么?
MCP本质上是一种通信协议,为AI应用(如聊天机器人或IDE助手)定义了与数据源和服务交互的"通用语言"。开发者无需为每个集成对象(如Google Drive、Slack、内部数据库等)编写定制连接器,只需编写一个符合MCP标准的连接器,任何支持MCP的AI都能调用。
为什么这很重要?
- 告别重复造轮子:开发者无需为每个数据源重复编写集成代码
- 互操作性:为一个AI平台(如Claude、OpenAI)构建的工具,可在其他支持MCP的AI主机(如基于Python的聊天机器人)中复用
- 简洁与安全:明确分离数据访问与操作执行,简化审计与权限管理
- 可发现性:任何MCP客户端都能查询服务器提供的功能(工具/数据/提示词),消除调用猜测
MCP架构:客户端、服务器与主机
MCP采用客户端-服务器模式,包含以下角色:
- 主机 (Host):AI应用本身(如运行GPT的聊天机器人)
- 客户端 (Client):运行在主机内部,管理一个或多个MCP服务器的连接
- 服务器 (Server):通过MCP暴露特定能力(工具/数据)的独立程序(如天气API访问器)
- 数据源 (Data Sources):MCP服务器可读写的底层资源(本地文件/远程API等)
通信流程演示
- 主机启动MCP客户端
- 客户端连接MCP服务器(如weather.py)
- 客户端询问服务器能力(发现工具/资源)
- 用户向AI提问
- AI通过客户端调用工具(如get_forecast)
- 服务器获取天气数据并返回客户端
- 客户端将结果传回AI
- AI整合数据生成友好响应
就像通过USB-C接口连接不同外设,AI可以通过MCP接入任意服务器。
构建天气MCP服务器
以下是完整的weather.py服务器代码,演示如何从美国国家气象局(NWS)获取数据并通过MCP暴露。我们定义两个工具:
- get_alerts(state):返回指定美国州的活跃天气警报
- get_forecast(latitude, longitude):返回指定坐标的短期天气预报
import httpx # 用于向气象API发起请求
from typing import Any
from mcp.server.fastmcp import FastMCP # MCP服务器库
# 1. 初始化服务器
mcp = FastMCP("weather") # 服务器命名为"weather"
# 外部API常量
NWS_API_BASE = "https://api.weather.gov"
USER_AGENT = "weather-app/1.0"
async def make_nws_request(url: str) -> dict[str, Any] | None:
"""向NWS API发起GET请求并返回JSON,出错时返回None"""
headers = {
"User-Agent": USER_AGENT,
"Accept": "application/geo+json"
}
async with httpx.AsyncClient() as client:
try:
response = await client.get(url, headers=headers, timeout=30.0)
response.raise_for_status()
return response.json()
except Exception:
return None
def format_alert(feature: dict) -> str:
"""将天气警报特征转换为可读字符串"""
props = feature["properties"]
return (
f"事件: {props.get('event', '未知')}\n"
f"区域: {props.get('areaDesc', '未知')}\n"
f"严重性: {props.get('severity', '未知')}\n"
f"描述: {props.get('description', '无描述信息')}\n"
f"应对指南: {props.get('instruction', '无应对指南')}"
)
@mcp.tool()
async def get_alerts(state: str) -> str:
"""获取美国州级天气警报(使用两字母代码,如CA)"""
url = f"{NWS_API_BASE}/alerts/active/area/{state}"
data = await make_nws_request(url)
if not data or "features" not in data:
return "无法获取警报或未找到警报"
if not data["features"]:
return f"{state}当前无活跃警报"
alerts = [format_alert(f) for f in data["features"]]
return "\n---\n".join(alerts)
@mcp.tool()
async def get_forecast(latitude: float, longitude: float) -> str:
"""获取坐标位置短期天气预报"""
points_url = f"{NWS_API_BASE}/points/{latitude},{longitude}"
points_data = await make_nws_request(points_url)
if not points_data or "properties" not in points_data:
return "无法获取该位置气象数据"
forecast_url = points_data["properties"].get("forecast")
if not forecast_url:
return "未找到该位置预报URL"
forecast_data = await make_nws_request(forecast_url)
if not forecast_data or "properties" not in forecast_data:
return "无法获取详细预报"
periods = forecast_data["properties"].get("periods", [])
if not periods:
return "无可用预报时段"
lines = []
for period in periods[:5]: # 仅取前5个时段
lines.append(
f"{period['name']}:\n"
f" 温度: {period['temperature']}°{period['temperatureUnit']}\n"
f" 风力: {period['windSpeed']} {period['windDirection']}\n"
f" 预报: {period['detailedForecast']}"
)
return "\n\n".join(lines)
if __name__ == "__main__":
mcp.run(transport="stdio")
关键点:
mcp.tool()
装饰器:将get_alerts和get_forecast暴露给客户端- 异步处理:使用async/await实现网络请求,确保服务器响应性
mcp.run(transport="stdio")
:指定通过标准输入输出与客户端通信
运行python weather.py
时可能无输出,因为服务器正在等待客户端请求。
构建OpenAI版MCP客户端
接下来构建client.py,实现以下功能:
- 将weather.py作为子进程启动
- 通过MCP连接服务器
- 使用OpenAI函数调用API让GPT决定调用哪个工具
- 在服务器执行选定工具并将结果返回GPT生成最终答案
- 提供终端聊天界面方便测试
import os
import sys
import json
import asyncio
from typing import Optional
from dotenv import load_dotenv
load_dotenv() # 从.env加载OPENAI_API_KEY
from openai import AsyncOpenAI
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
from contextlib import AsyncExitStack
class MCPClient:
def __init__(self):
self.session: Optional[ClientSession] = None
self.exit_stack = AsyncExitStack()
# 使用异步OpenAI客户端
self.openai = AsyncOpenAI(api_key=os.getenv("OPENAI_API_KEY"))
self.model = "gpt-4o"
async def connect_to_server(self, server_script_path: str):
"""启动MCP服务器子进程并建立连接"""
if server_script_path.endswith(".py"):
command = "python"
elif server_script_path.endswith(".js"):
command = "node"
else:
raise ValueError("服务器脚本必须是.py或.js")
server_params = StdioServerParameters(command=command, args=[server_script_path], env=None)
stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params))
self.stdio, self.write = stdio_transport
self.session = await self.exit_stack.enter_async_context(ClientSession(self.stdio, self.write))
await self.session.initialize()
# 打印可用工具列表
tool_list = await self.session.list_tools()
print(f"已连接服务器。可用工具: {[t.name for t in tool_list.tools]}")
async def _get_openai_functions(self):
"""将MCP工具转换为OpenAI函数调用格式"""
tool_list = await self.session.list_tools()
functions = []
for tool in tool_list.tools:
fn_schema = {
"name": tool.name,
"description": tool.description or tool.name,
"parameters": {
"type": "object",
"properties": {}
},
"required": []
}
if tool.inputSchema and tool.inputSchema.get("type") == "object":
fn_schema["parameters"] = tool.inputSchema
functions.append(fn_schema)
return functions
async def process_user_message(self, user_message: str) -> str:
if not self.session:
raise RuntimeError("未找到MCP会话,请先调用connect_to_server")
functions = await self._get_openai_functions()
# 第一次GPT调用:提供用户消息+可用函数
response = await self.openai.chat.completions.create(
model=self.model,
messages=[{"role": "user", "content": user_message}],
functions=functions,
function_call="auto"
)
assistant_message = response.choices[0].message
if hasattr(assistant_message, "function_call") and assistant_message.function_call:
fn_name = assistant_message.function_call.name
fn_args_json = assistant_message.function_call.arguments
try:
fn_args = json.loads(fn_args_json) if fn_args_json else {}
except json.JSONDecodeError:
return "[错误] GPT生成非法JSON参数"
print(f"\n[工具请求] GPT希望调用{fn_name},参数:{fn_args}")
tool_result = await self.session.call_tool(fn_name, fn_args)
# 第二次GPT调用:提供工具执行结果
followup = await self.openai.chat.completions.create(
model=self.model,
messages=[
{"role": "user", "content": user_message},
assistant_message.to_dict(),
{"role": "function", "name": fn_name, "content": tool_result.content}
]
)
return followup.choices[0].message.content
else:
# GPT不需要调用函数
return assistant_message.content
async def chat_loop(self):
print("MCP + OpenAI 客户端。输入'quit'或'exit'退出")
while True:
user_inp = input("\n你: ")
if user_inp.strip().lower() in ("quit", "exit"):
break
answer = await self.process_user_message(user_inp)
print(f"\n助手: {answer}")
async def cleanup(self):
await self.exit_stack.aclose()
async def main():
if len(sys.argv) < 2:
print("用法: python client.py <服务器脚本路径>")
sys.exit(1)
server_path = sys.argv[1]
client = MCPClient()
try:
await client.connect_to_server(server_path)
await client.chat_loop()
finally:
await client.cleanup()
if __name__ == "__main__":
asyncio.run(main())
关键点:
- connect_to_server:启动服务器子进程并通过标准输入输出建立MCP会话
- 函数模式转换:_get_openai_functions将MCP工具模式转换为OpenAI函数调用需要的JSON格式
- process_user_message:
- 首次GPT调用:GPT根据用户消息和函数定义决定是否调用工具
- 执行工具:若需要调用工具,客户端通过
await self.session.call_tool(fn_name, fn_args)
与MCP服务器通信 - 二次GPT调用:将工具结果发送给GPT生成最终响应
运行示例
- 安装依赖:
pip install "mcp[cli]" openai httpx python-dotenv
- 创建.env文件存放OpenAI API密钥:
OPENAI_API_KEY=sk-12345...
- 运行客户端(自动启动服务器):
python client.py weather.py
- 开始对话:
已连接服务器。可用工具: ['get_alerts', 'get_forecast']
MCP + OpenAI 客户端。输入'quit'或'exit'退出
你: 怀俄明州有哪些警报?
[工具请求] GPT希望调用get_alerts,参数:{'state': 'WY'}
助手: 怀俄明州当前有以下活跃天气警报:
(此处显示格式化后的详细警报信息)
你: 北纬38.58西经121.49的天气预报是?
[工具请求] GPT希望调用get_forecast,参数:{'latitude': 38.58, 'longitude': -121.49}
助手: 该坐标位置未来天气预测如下:
(显示格式化后的天气预报)
GPT4o根据用户输入自动选择MCP工具,服务器从NWS获取数据后,OpenAI API生成简洁的天气摘要。
核心价值
- 标准化AI集成:定义服务器(如weather.py)后,任何MCP兼容客户端都能使用 —— 无需为不同AI平台重写代码
- 函数调用模式:结合OpenAI函数调用API,实现AI"智能体"的精准工具调用
- 安全的客户端-服务器分离:敏感数据和API操作由MCP服务器在受控环境中处理
- 无限扩展性:添加邮件发送、数据库查询、文件操作等工具,快速增强AI能力
通过本文示例,您可以快速搭建自己的MCP服务器和客户端,让GPT或其他AI安全高效地访问真实数据。快去构建一些令人惊叹的东西吧!
延伸思考:MCP正在重新定义AI的能力边界 —— 不仅让AI更"聪明",更能安全可靠地与现实世界交互。这种标准化接口模式,或将开启下一代AI应用开发的新范式。
- 登录 发表评论