怎么开发一个属于自己的mcp server?
作为一个前端选手,有点头疼,于是只能看python,为啥看python,因为用它开发简单,下面开始开发吧(ps:反正我不会python )。
- python 项目工程怎么搭建?
- 怎么选取框架?
- 如何开发?
- 如何对接编辑器cursor?
- 如果使用deepseek 接入mcp?
python 项目工程怎么搭建?
我这边使用uv
进行初始化项目(ps:类似前端的pnpm)python>=3.12
,下面是安装uv
arduino
curl -LsSf https://astral.sh/uv/install.sh | sh
uv --version
验证是否安装成功,成功了下面开始创建项目了
bash
创建并激活虚拟环境(自动激活)
uv venv source
.venv/bin/activate # Linux/macOS
.venv\Scripts\activate # Windows
# 指定 Python 版本创建环境
uv venv --python 3.11
怎么选取框架?
初始化成功后开始下载我们的依赖 uv add fastapi-mcp httpx pydantic python-dotenv
- fastapi-mcp 一款专门为了mcp 打造的服务mcp 服务框架.
httpx
是可以在服务端请求的插件,前端就认为是axios就行了BaseModel
应该是定义类型python-dotenv
获取.env 环境变量的
如何开发
到这步,下面我们已经成功80%了,我不会python,我会ai,,,尴尬了,ai会,我不会,所以我只能使用ai ,这是我使用的 我需要一个python 天气 和 fastapi-mcp demo
提示词, 如果就给我如下代码
python
# -*- coding: utf-8 -*-
import os
import httpx
from fastapi import FastAPI, HTTPException
from fastapi_mcp import FastApiMCP
from pydantic import BaseModel
from dotenv import load_dotenv
# 加载环境变量
load_dotenv()
AMAP_API_KEY = os.getenv("AMAP_API_KEY") # 高德地图 API 密钥
# 数据模型定义
class WeatherRequest(BaseModel):
city: str # 城市名称,如"上海"
class WeatherResponse(BaseModel):
city: str
temperature: str # 温度
weather: str # 天气状况
wind_direction: str # 风向
wind_power: str # 风力
humidity: str # 湿度
report_time: str # 报告时间
# 初始化 FastAPI 应用
app = FastAPI(
title="天气查询 API",
description="基于高德地图 API 的天气查询服务,支持 MCP 协议"
)
# 天气查询接口
@app.post(
"/weather",
response_model=WeatherResponse,
tags=["weather"],
operation_id="get_weather" # MCP 工具名称
)
async def get_weather(request: WeatherRequest):
"""
查询指定城市的实时天气信息
使用高德地图开放平台天气 API
"""
if not AMAP_API_KEY:
raise HTTPException(status_code=500, detail="未配置高德地图 API 密钥")
# 高德地图天气 API
url = "https://restapi.amap.com/v3/weather/weatherInfo"
params = {
"key": AMAP_API_KEY,
"city": request.city,
"extensions": "base", # base: 实时天气, all: 预报天气
"output": "json"
}
try:
async with httpx.AsyncClient() as client:
response = await client.get(url, params=params)
data = response.json()
# 处理 API 响应
if data["status"] != "1":
raise HTTPException(status_code=400, detail=f"查询失败: {data.get('info', '未知错误')}")
if not data["lives"]:
raise HTTPException(status_code=404, detail="未找到该城市的天气信息")
weather_data = data["lives"][0]
return WeatherResponse(
city=weather_data["city"],
temperature=weather_data["temperature"],
weather=weather_data["weather"],
wind_direction=weather_data["winddirection"],
wind_power=weather_data["windpower"],
humidity=weather_data["humidity"],
report_time=weather_data["reporttime"]
)
except httpx.HTTPError as e:
raise HTTPException(status_code=500, detail=f"请求失败: {str(e)}")
except Exception as e:
raise HTTPException(status_code=500, detail=f"处理失败: {str(e)}")
# 集成 MCP 服务
mcp = FastApiMCP(
app,
name="天气查询 MCP 服务",
description="提供城市天气查询功能,基于高德地图 API",
describe_all_responses=True
)
mcp.mount_http() # 使用 HTTP 传输协议(推荐)
# 运行服务
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
没错就是这么简单,,,代码直接贴main文件就行,需要运行的话还需要配置一下.env, 这个在mcp 工具调用的时候也需要,我这边使用的是高德的 下面直接用编辑器启动,也可用命令uv run main.py
启动

就是这么简单
如何对接编辑器cursor ?
有种更简单的感觉,点击设置

json
{
"mcpServers": {
"weather-mcp-agent": {
"type": "http",
"url": "http://localhost:8000/mcp",
"env": {
"AMAP_API_KEY": "0720a57aba163a9ca3b633a70da55f02"
}
}
}
}
http://localhost:8000/mcp 这个事fastapi-mcp 自动生成的服务,无需开发,就是代码就行
python
# 集成 MCP 服务
mcp = FastApiMCP(
app,
name="天气查询 MCP 服务",
description="提供城市天气查询功能,基于高德地图 API",
describe_all_responses=True
)

就这样成功了
如果使用deepseek接入mcp?
代码我也不会,继续让ai写, 提示词fastapi-mcp 如何和deepseek 对接
python
# -*- coding: utf-8 -*-
import os
import httpx
import asyncio
import json
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import Optional
from dotenv import load_dotenv
import uvicorn
# 加载环境变量
load_dotenv()
DEEPSEEK_API_KEY = os.getenv("DEEPSEEK_API_KEY")
if not DEEPSEEK_API_KEY:
raise ValueError("请在 .env 中配置 DEEPSEEK_API_KEY")
# 配置地址
DEEPSEEK_API_URL = "https://api.deepseek.com/v1/chat/completions"
MCP_SERVICE_URL = "http://localhost:8000/weather"
# 定义工具函数描述
tools = [
{
"type": "function",
"function": {
"name": "query_weather",
"description": "查询指定城市的实时天气信息,包括温度、天气状况、风向等",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称,例如:北京、上海、广州"
}
},
"required": ["city"]
}
}
}
]
# 初始化 FastAPI 应用
app = FastAPI(
title="DeepSeek 智能天气问答 API",
description="基于 DeepSeek 的智能天气问答服务,支持自然语言交互"
)
# 请求模型
class ChatRequest(BaseModel):
message: str
model: str = "deepseek-chat"
# 天气数据模型
class WeatherData(BaseModel):
city: str
temperature: str
weather: str
wind_direction: str
wind_power: str
humidity: str
report_time: str
# 响应模型
class ChatResponse(BaseModel):
message: str
response_type: str # "weather" | "general" | "error"
weather_data: Optional[WeatherData] = None
tool_called: bool = False
success: bool = True
error_message: Optional[str] = None
async def query_weather(city: str):
"""调用MCP服务查询天气"""
try:
async with httpx.AsyncClient() as client:
response = await client.post(
MCP_SERVICE_URL,
json={"city": city},
headers={"Content-Type": "application/json"},
timeout=10.0
)
response.raise_for_status()
return response.json()
except Exception as e:
return {"error": f"查询天气失败: {str(e)}"}
async def call_deepseek_with_mcp(user_query: str, model: str = "deepseek-chat"):
"""调用DeepSeek进行智能天气问答"""
async with httpx.AsyncClient(timeout=30.0) as client:
payload = {
"model": model,
"messages": [{"role": "user", "content": user_query}],
"tools": tools,
"tool_choice": "auto"
}
try:
# 第一次调用 - 检查是否需要工具
response = await client.post(
DEEPSEEK_API_URL,
headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {DEEPSEEK_API_KEY}"
},
json=payload
)
response.raise_for_status()
response_data = response.json()
message = response_data["choices"][0]["message"]
weather_data = None
tool_called = False
response_type = "general"
if "tool_calls" in message:
tool_called = True
response_type = "weather"
# 处理工具调用
tool_results = []
for tool_call in message["tool_calls"]:
if tool_call["function"]["name"] == "query_weather":
args = json.loads(tool_call["function"]["arguments"])
city = args.get("city", "")
# 调用天气查询函数
weather_result = await query_weather(city)
# 检查是否有错误
if "error" in weather_result:
return weather_result["error"], None, False, "error", False, weather_result["error"]
weather_data = weather_result
tool_results.append({
"tool_call_id": tool_call["id"],
"role": "tool",
"name": "query_weather",
"content": json.dumps(weather_result, ensure_ascii=False)
})
# 第二次调用 - 获取最终回答
messages = [
{"role": "user", "content": user_query},
message,
*tool_results
]
final_payload = {
"model": model,
"messages": messages
}
final_response = await client.post(
DEEPSEEK_API_URL,
headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {DEEPSEEK_API_KEY}"
},
json=final_payload
)
final_response.raise_for_status()
final_data = final_response.json()
return final_data["choices"][0]["message"]["content"], weather_data, tool_called, response_type, True, None
else:
# 不需要调用工具,直接返回回答
return message.get("content", "未获取到回答内容"), weather_data, tool_called, response_type, True, None
except httpx.HTTPError as e:
return f"API 调用失败: {str(e)}", None, False, "error", False, str(e)
except Exception as e:
return f"处理失败: {str(e)}", None, False, "error", False, str(e)
@app.post("/chat", response_model=ChatResponse)
async def chat_with_weather(request: ChatRequest):
"""
智能天气问答接口
支持自然语言交互,AI会自动判断是否需要查询天气
"""
try:
message, weather_data, tool_called, response_type, success, error_message = await call_deepseek_with_mcp(
request.message,
request.model
)
# 构建天气数据对象
weather_obj = None
if weather_data and "error" not in weather_data:
weather_obj = WeatherData(
city=weather_data["city"],
temperature=weather_data["temperature"],
weather=weather_data["weather"],
wind_direction=weather_data["wind_direction"],
wind_power=weather_data["wind_power"],
humidity=weather_data["humidity"],
report_time=weather_data["report_time"]
)
return ChatResponse(
message=message,
response_type=response_type,
weather_data=weather_obj,
tool_called=tool_called,
success=success,
error_message=error_message
)
except Exception as e:
return ChatResponse(
message="系统错误",
response_type="error",
weather_data=None,
tool_called=False,
success=False,
error_message=str(e)
)
@app.get("/health")
async def health_check():
"""健康检查接口"""
return {"status": "healthy", "service": "DeepSeek Weather Chat API"}
@app.get("/")
async def root():
"""根路径"""
return {
"message": "DeepSeek 智能天气问答 API",
"docs": "/docs",
"chat_endpoint": "/chat",
"health_check": "/health"
}
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8001)
为什么是sse的,我中间补了一个需要sse的方式,ai就来了,对于前端来讲,这个代码看起来还是没什么难度的。结果如下
问天气的就会先调用天气的
以上就是这样了