实现步骤参考的是MCP官方教程配合Cursor,原理分析部分使用cloudflare抓取大模型中间请求
一. 手动写一个MCP Server(windows为例)
1. 安装uv包管理器(其他python包管理器也可)
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
2. 初始化python项目
# Create a new directory for our project
uv init weather
cd weather
# Create virtual environment and activate it
uv venv
.venv\Scripts\activate
# Install dependencies
uv add mcp[cli] httpx
# Create our server file
new-item weather.py
3 构建服务器
在weather.py中添加如下代码:
from typing import Any
import httpx
from mcp.server.fastmcp import FastMCP
# 初始化FastMCP服务器,命名为"weather"
mcp = FastMCP("weather")
# 定义常量
NWS_API_BASE = "https://api.weather.gov" # 美国国家气象服务API的基础URL
USER_AGENT = "weather-app/1.0" # 用户代理字符串,用于标识请求来源
4. 定义辅助函数(Helper Function)
这段代码包含两个函数,分别用于:
-
make_nws_request
:向美国国家气象局(NWS)的 API 发送异步 HTTP 请求,并处理可能发生的错误。 -
format_alert
:将从 NWS 获取到的单个天气预警数据格式化为可读的字符串,方便用户阅读。
async def make_nws_request(url: str) -> dict[str, Any] | None:
"""Make a request to the NWS API with proper error handling."""
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:
"""Format an alert feature into a readable string."""
props = feature["properties"]
return f"""
Event: {props.get('event', 'Unknown')}
Area: {props.get('areaDesc', 'Unknown')}
Severity: {props.get('severity', 'Unknown')}
Description: {props.get('description', 'No description available')}
Instructions: {props.get('instruction', 'No specific instructions provided')}
"""
5. MCP工具函数定义
这段代码定义了两个 MCP 工具函数,用于从美国国家气象局(NWS)的 API 获取天气信息。这两个函数分别通过装饰器 @mcp.tool()
注册为 MCP 工具,供大型语言模型(如 Claude)调用。
@mcp.tool()
async def get_alerts(state: str) -> str:
"""Get weather alerts for a US state.
Args:
state: Two-letter US state code (e.g. CA, NY)
"""
url = f"{NWS_API_BASE}/alerts/active/area/{state}"
data = await make_nws_request(url)
if not data or "features" not in data:
return "Unable to fetch alerts or no alerts found."
if not data["features"]:
return "No active alerts for this state."
alerts = [format_alert(feature) for feature in data["features"]]
return "\n---\n".join(alerts)
@mcp.tool()
async def get_forecast(latitude: float, longitude: float) -> str:
"""Get weather forecast for a location.
Args:
latitude: Latitude of the location
longitude: Longitude of the location
"""
# First get the forecast grid endpoint
points_url = f"{NWS_API_BASE}/points/{latitude},{longitude}"
points_data = await make_nws_request(points_url)
if not points_data:
return "Unable to fetch forecast data for this location."
# Get the forecast URL from the points response
forecast_url = points_data["properties"]["forecast"]
forecast_data = await make_nws_request(forecast_url)
if not forecast_data:
return "Unable to fetch detailed forecast."
# Format the periods into a readable forecast
periods = forecast_data["properties"]["periods"]
forecasts = []
for period in periods[:5]: # Only show next 5 periods
forecast = f"""
{period['name']}:
Temperature: {period['temperature']}°{period['temperatureUnit']}
Wind: {period['windSpeed']} {period['windDirection']}
Forecast: {period['detailedForecast']}
"""
forecasts.append(forecast)
return "\n---\n".join(forecasts)
6. 最后设置入口函数
if __name__ == "__main__":
# Initialize and run the server
mcp.run(transport='stdio')
目前为止,一个最简单的MCP Server就完成了,接下来将把他注册到Cursor中
二. 在cursor中使用MCP
1. 确保Cursor更新至最新版本
2. 点击右上角Setting,然后选择MCP Server
3. 点击Add new global MCP Server,并添加如下json,注意,一定要填绝对路径
{
"mcpServers": {
"weather": {
"command": "uv",
"args": [
"--directory",
"/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather",
"run",
"weather.py"
]
}
}
}
4. 完成后应该能看到出现一个叫weather的server并且显示绿色,以及tools,那就大功告成
5.简单测试,在我的测试下Cursor得选择gpt4o,MCP server才能被正常调用
三. 原理解析
我翻看了B站及抖音一些大佬的分析(cloudflare AI Gateway截取prompt等).
我的理解是:MCP 的本质是通过客户端将用户的 Prompt 与可用工具列表一并发送给 LLM。LLM 在理解意图后,会返回需要调用的工具名称及其对应参数。随后,MCP 客户端通过定义好的统一接口调用对应工具,完成实际执行。
用户输入 Query
│
▼
MCP 客户端构造请求(Prompt + 工具列表)
│
▼
LLM
│
▼
解析 Prompt,返回工具名 + 参数
│
▼
MCP 客户端通过统一接口调用对应工具
│
▼
工具执行并返回结果
以官方的client 代码举例,通过self.session.list_tools()方法获取所有的tool,然后构建一个Claudi api call,让大语言模型去判断是否调用工具
"""Process a query using Claude and available tools"""
messages = [
{
"role": "user",
"content": query
}
]
response = await self.session.list_tools()
available_tools = [{
"name": tool.name,
"description": tool.description,
"input_schema": tool.inputSchema
} for tool in response.tools]
# Initial Claude API call
response = self.anthropic.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=1000,
messages=messages,
tools=available_tools
)
接下来如果返回内容中content的类型是tool_use,则获取tool的名称及入参,然后调用call_tool方法(mcp库提供),进行方法调用,并获得最终的结果
assistant_message_content = []
for content in response.content:
if content.type == 'text':
final_text.append(content.text)
assistant_message_content.append(content)
elif content.type == 'tool_use':
tool_name = content.name
tool_args = content.input
# Execute tool call
result = await self.session.call_tool(tool_name, tool_args)