简单入门 mcp server

怎么开发一个属于自己的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就来了,对于前端来讲,这个代码看起来还是没什么难度的。结果如下

问天气的就会先调用天气的

以上就是这样了

相关推荐
追逐时光者9 小时前
.NET 使用 CsvHelper 快速读取和写入 CSV 文件
后端·.net
_新一9 小时前
Go Context源码学习
后端·go
金銀銅鐵10 小时前
[Java] 浅析 Set.of(...) 方法
java·后端
Undoom10 小时前
当Python遇见高德:基于PyQt与JS API构建桌面三维地形图应用实战
javascript·后端
IT_陈寒10 小时前
Python性能优化:这5个隐藏技巧让我的代码提速300%!
前端·人工智能·后端
Rookie小强10 小时前
基于SpringBoot即刻出发畅游网
java·spring boot·后端
自珍JAVA11 小时前
【异步事件ApplicationEventPublisher及精细化事务处理TransactionTemplate 】
后端
Apifox11 小时前
理解和掌握 Apifox 中的变量(临时、环境、模块、全局变量等)
前端·后端·测试
前端世界11 小时前
ASP.NET 实战:用 SqlCommand 打造一个安全的用户注册功能
后端·安全·asp.net