1. 环境准备与核心概念扫盲

大家好,我是老张,在AI和智能硬件这行摸爬滚打了十几年。今天咱们不聊那些虚头巴脑的理论,直接上手干。如果你用过Cursor,肯定知道它的Agent模式很强大,但有时候想让它查个数据库、调个内部API,或者处理点本地文件,是不是感觉有点使不上劲?这时候,MCP就该登场了。

MCP,全称是模型上下文协议,你可以把它理解成Cursor的“插件系统”。它定义了一套标准,让任何外部工具或数据源都能以一种AI能理解的方式,接入到Cursor里。而SSE,全称是服务器推送事件,是一种基于HTTP的、允许服务器主动向客户端推送数据的技术。把这两者结合起来,我们就能构建一个可以通过网络访问的、功能强大的MCP服务。

我为什么推荐SSE模式而不是Stdio模式?简单说,Stdio模式就像是你给Cursor配了个本地小秘书,只能在你自己的电脑上用。而SSE模式,相当于你搭建了一个24小时在线的服务中心,任何有权限的Cursor(包括你同事的)都能通过网络来调用。这对于团队协作或者部署公共服务来说,是刚需。

在开始动手前,你需要准备好这几样东西:

  1. Python环境:我推荐用Python 3.10或以上版本,稳定性好。可以用condavenv创建一个干净的虚拟环境,避免包冲突。
  2. Cursor:确保你的Cursor版本比较新,最好在0.48以上,对MCP的支持更完善。
  3. 代码编辑器:VS Code、PyCharm都行,看你习惯。

接下来,我们先把必要的Python包装上。打开你的终端,在虚拟环境里执行下面这行命令:

pip install fastmcp==0.4.1 fastapi==0.115.12 uvicorn==0.34.0 starlette==0.46.1

这里简单解释下这几个包是干嘛的:

  • fastmcp:这是构建MCP服务器的核心SDK,它封装了MCP协议的细节,让我们能用很简单的装饰器就定义出工具。
  • fastapi & uvicorn:我们用它们来快速构建一个高性能的Web服务器,用于提供SSE端点。
  • starlette:FastAPI基于它,我们这里用它来把MCP的SSE应用和普通的API路由整合到同一个服务里。

包安装顺利的话,咱们的基础环境就算搭好了。记住,搞开发最怕环境问题,一步错步步错。如果你在这一步遇到任何网络超时或者版本冲突,别急着往下走,先搜搜错误信息,或者试试换用pip install -i https://pypi.tuna.tsinghua.edu.cn/simple 国内镜像源,把环境搞利索了是成功的第一步。

2. 手把手构建第一个SSE MCP服务

理论说再多不如一行代码。咱们的目标是创建一个天气查询的MCP服务。这个服务会做两件事:一是提供一个普通的HTTP API接口(/api/weather),方便其他程序调用;二是暴露一个MCP SSE端点(/sse),让Cursor的Agent能够发现并调用它内部的工具。

首先,在你喜欢的位置创建一个项目文件夹,比如D:\Mcp\test(Windows)或~/projects/mcp-weather(Mac/Linux)。在里面新建一个Python文件,我把它命名为weather_sse_server.py。接下来,我们把代码拆解成几块,一块块来看。

第一步,导入和初始化。这部分代码搭建了服务的骨架。

import uvicorn
from mcp.server.fastmcp import FastMCP
from fastapi import FastAPI, Response
from starlette.routing import Mount
from starlette.applications import Starlette

# 初始化FastMCP实例,这是我们的工具提供者
mcp = FastMCP("weather-sse")

# 初始化一个普通的FastAPI应用,可以用来提供额外的REST API
api = FastAPI(
    version="1.0.0",
    title="天气服务测试API",
    description="一个集成了MCP SSE的天气查询服务"
)

# 一个简单的根路径,用于服务健康检查
@api.get("/")
async def index():
    return Response(content="欢迎使用天气MCP服务", media_type="text/plain")

这里创建了两个核心对象:mcpapimcp对象专门负责管理和暴露给Cursor的“工具”。api对象则是我们熟悉的Web API框架,可以用来做点别的。你可能会问,为什么不直接用mcp.run(transport="sse")?那样确实可以启动一个纯MCP SSE服务器,但今天我们想做得更“工程化”一点,把MCP服务和一些管理API放在一起,更贴近真实项目。

第二步,定义核心工具。这才是MCP的精华所在。我们通过一个装饰器,就把一个普通的Python函数变成了AI可以调用的“工具”。

# 关键!使用@mcp.tool()装饰器将函数注册为MCP工具
# 同时,我们也把它作为普通API暴露出来,一举两得
@mcp.tool()
@api.get("/api/weather")
def get_weather(location: str = "Beijing") -> str:
    """
    获取指定地点的天气预报。

    参数:
    location (str): 城市名,例如 'Beijing' 或 '上海'。

    返回:
    str: 格式化的天气信息字符串。
    """
    # 这里是模拟数据。真实场景中,你可以在这里调用中国天气网、和风天气等第三方API。
    # 重点:函数的文档字符串(Docstring)非常重要!AI会依靠它来理解工具的用途和参数。
    weather_info = {
        "temperature": 27,
        "condition": "晴",
        "humidity": 65,
        "wind": "东北风3-4级"
    }
    return f"{location}的天气:温度 {weather_info['temperature']}°C,{weather_info['condition']},湿度{weather_info['humidity']}%,{weather_info['wind']}"

# 你可以继续添加更多工具,比如获取天气预报、空气质量等
@mcp.tool()
def get_weather_forecast(location: str, days: int = 3) -> str:
    """
    获取多日天气预报。
    """
    # 模拟实现
    return f"{location}未来{days}天天气预报:晴、多云、小雨。"

我踩过一个坑:早期写工具时,文档字符串写得很随意,结果Cursor的Agent经常误解工具的用途,要么不调用,要么参数传错。后来我发现,必须像写API文档一样,清晰描述“这个工具是干嘛的”、“每个参数是什么”、“返回什么”。AI比我们想象的要依赖这段文字描述。

第三步,组装并启动应用。这是把MCP SSE和普通Web服务融合的关键步骤。

# 使用Starlette将FastAPI应用和MCP的SSE应用挂载到同一个服务下
app = Starlette(
    routes=[
        Mount('/api', app=api),          # 普通API访问路径
        Mount('/', app=mcp.sse_app()),   # MCP SSE端点,注意要挂在根路径或特定路径如/sse
    ]
)

if __name__ == '__main__':
    # 使用uvicorn启动服务器
    # host="0.0.0.0" 表示监听所有网络接口,方便局域网内其他设备访问
    # port=5000 是端口号,如果被占用可以改成5001、8080等
    uvicorn.run(app, host="0.0.0.0", port=5000, reload=False)
    # 如果你希望代码修改后自动重启,可以设置reload=True,但生产环境不建议。

这里mcp.sse_app()fastmcp库提供的魔法方法,它创建了一个兼容SSE协议的ASGI应用。我们把它挂载到根路径/,这意味着MCP SSE的端点就是http://你的IP:5000/。有些教程会专门挂载到/sse路径,都是可以的,只要后面Cursor配置的URL能对应上就行。

保存好文件,我们打开终端,进入脚本所在目录,用以下命令启动服务:

cd /你的/项目/路径
uvicorn weather_sse_server:app --host 0.0.0.0 --port 5000

如果一切顺利,你会看到类似Uvicorn running on http://0.0.0.0:5000的输出。现在,打开浏览器,访问http://127.0.0.1:5000/api/weather?location=广州,你应该能看到返回的模拟天气信息。这说明我们的普通API部分工作正常。MCP SSE部分暂时还看不到界面,别急,等会儿用Cursor来验证。

3. 在Cursor中配置与集成MCP服务

服务跑起来了,现在得让Cursor知道它的存在。Cursor支持两种配置MCP的方式:全局配置项目级配置。我强烈建议使用项目级配置,因为这样配置只对当前项目生效,不会干扰其他工作区,也更利于团队协作(把配置文件放进版本控制)。

在你的项目根目录下(也就是你的代码文件夹),创建一个新的隐藏文件夹.cursor,然后在里面创建一个mcp.json文件。整个路径看起来应该是这样的:你的项目/.cursor/mcp.json

用文本编辑器打开这个mcp.json文件,输入以下配置内容:

{
  "mcpServers": {
    "my-weather-service": {
      "url": "http://127.0.0.1:5000/",
      "description": "我本地搭建的天气查询MCP服务"
    }
  }
}

我来解释一下这个配置:

  • "mcpServers" 是一个对象,里面可以配置多个服务。
  • "my-weather-service" 是你给这个服务起的名字,可以自定义,方便自己识别。
  • "url" 字段是最关键的,它必须指向你刚才启动的服务的SSE端点。因为我们把mcp.sse_app()挂载到了根路径,所以这里就是http://127.0.0.1:5000/。如果你挂载到了/sse,那这里就应该是http://127.0.0.1:5000/sse
  • "description" 是可选的,写点描述有助于以后管理。

保存这个JSON文件。现在,打开或重启你的Cursor(重要!新配置需要重启Cursor才能被加载)。重启后,点击Cursor界面左下角的设置图标(齿轮⚙️),在设置面板中找到 “Features”“MCP” 选项。

你应该能在MCP服务器列表里看到你刚配置的my-weather-service,旁边通常会有一个绿色的圆点或“Connected”状态,这表示Cursor已经成功连接上了你的MCP服务器,并获取到了可用的工具列表(就是我们用@mcp.tool()装饰的那些函数)。

如果这里显示红色或连接失败,别慌,按以下步骤排查:

  1. 检查服务是否运行:确认终端里uvicorn进程还在,没有报错退出。
  2. 检查端口和IP:确保mcp.json里的url和启动命令中的hostport完全一致。127.0.0.1是本地环回地址,如果你想让同局域网的其他电脑也能用,启动时要用0.0.0.0,但url里要填你本机在局域网的实际IP,比如http://192.168.1.100:5000/
  3. 检查防火墙:有时系统防火墙会阻止外部程序访问5000端口,暂时关闭防火墙试试,或者添加一个入站规则。

4. 在Agent聊天中实战调用与调试

配置成功,绿色的指示灯亮起,最激动人心的时刻到了——让Cursor的Agent使用我们自定义的工具。这里有个关键点:只有Cursor的 “Agent”模式(现在新版也叫“Composer”模式)才能调用MCP工具。普通的聊天模式是不行的。

在Cursor里新建一个聊天窗口,确保右上角或输入框旁的模式切换到了 “Agent”。然后,你就可以像和朋友说话一样,让Agent去干活了。比如,输入:

帮我查一下深圳的天气情况。

发送后,观察Cursor的反应。理想情况下,你会看到这样的流程:

  1. Agent会思考,并可能在消息中显示“正在调用工具 get_weather...”。
  2. 紧接着,它会弹出一个工具调用请求,询问你是否允许执行。这里会展示它要调用的工具名称(get_weather)和传入的参数(location: “深圳”)。
  3. 你点击“允许”或“同意”后,Agent就会去调用我们本地服务里的那个Python函数。
  4. 调用完成后,Agent会将函数返回的结果(“深圳的天气:温度 27°C,晴,湿度65%,东北风3-4级”)融入到它的回答中,给你一个完整的回复。

第一次调用成功的感觉非常棒,对吧?但这只是开始。在实际使用中,你可能会遇到Agent“不听话”的情况,比如:

  • 场景一:Agent不主动调用工具。你明明问了天气,它却自己编了一段。这时,你可以在提问时更明确地指示:“请使用MCP工具 get_weather 查询深圳的天气。” 或者,检查一下你工具的description文档字符串是否足够清晰,让AI能准确匹配。
  • 场景二:参数传递错误。比如它把“深圳”传成了sz。这通常是因为工具参数定义不够明确。确保你的函数参数有明确的类型注解(location: str)和清晰的文档说明。

为了提高效率,你可以开启Cursor的 “Yolo模式”(在设置里找)。开启后,对于已信任的MCP工具,Agent会自动调用而无需你每次手动批准,就像它执行终端命令一样流畅。但初期调试时,我建议先保持手动批准,方便观察每一次调用的细节。

除了在聊天中调用,你还可以直接通过浏览器测试SSE连接和工具列表。访问你配置的URL(如http://127.0.0.1:5000/),如果服务正常,通常会返回一些事件流信息。更专业的调试,可以使用MCP官方提供的Inspector工具:

npx @modelcontextprotocol/inspector

它会启动一个本地调试界面,让你可以像测试API一样,手动发送“列出工具”、“调用工具”等MCP协议请求,对于开发复杂的MCP服务器非常有用。

5. 进阶:构建更复杂的生产级MCP服务

我们成功实现了一个简单的天气服务,但真实世界的需求要复杂得多。你可能需要连接数据库、调用内部微服务、处理文件等等。下面,我以一个“智能电商助手”的场景,带你看看如何构建一个更实用的MCP服务。

假设我们需要为Cursor集成两个能力:查询产品库存创建用户订单。这些数据通常存在于公司的内部数据库或微服务中。我们的MCP服务将作为中间层,安全地连接Cursor和这些内部系统。

首先,设计工具。我们创建两个更健壮的工具函数:

import httpx
from typing import List, Optional
from pydantic import BaseModel

# 定义数据模型,让输入输出更规范
class OrderItem(BaseModel):
    product_id: str
    quantity: int

class CreateOrderRequest(BaseModel):
    customer_name: str
    items: List[OrderItem]

@mcp.tool()
async def check_inventory(product_ids: List[str]) -> dict:
    """
    批量查询指定产品的库存数量。

    参数:
    product_ids: 产品ID列表,例如 ["item_001", "item_002"]。

    返回:
    一个字典,键为产品ID,值为库存数量。如果产品不存在,数量为0。
    """
    # 这里模拟调用内部库存微服务
    async with httpx.AsyncClient(base_url="http://internal-inventory-service") as client:
        # 实际项目中,这里会是真实的API调用,可能涉及认证
        # response = await client.post("/batch-check", json={"ids": product_ids})
        # return response.json()
        return {pid: 100 for pid in product_ids}  # 模拟返回

@mcp.tool()
async def create_order(request: CreateOrderRequest) -> dict:
    """
    创建一个新的客户订单。
    此工具会验证库存,并在成功后扣减库存。

    参数:
    request: 包含客户名和订单项详情的对象。

    返回:
    包含订单ID、状态和总金额的字典。
    """
    # 1. 先检查库存
    product_ids = [item.product_id for item in request.items]
    inventory = await check_inventory(product_ids)
    for item in request.items:
        if inventory.get(item.product_id, 0) < item.quantity:
            return {"error": f"产品 {item.product_id} 库存不足"}

    # 2. 模拟调用订单服务创建订单(实际项目中是异步HTTP调用)
    # async with httpx.AsyncClient() as client:
    #     order_response = await client.post("http://internal-order-service/orders", json=request.dict())
    #     order_data = order_response.json()

    # 3. 模拟成功返回
    order_data = {
        "order_id": "ORD_" + str(hash(str(request.items)))[:8],
        "status": "created",
        "total_amount": 299.99,
        "customer_name": request.customer_name
    }
    return order_data

这个例子展示了几个进阶要点:

  1. 异步支持:使用async defhttpx.AsyncClient进行非阻塞的HTTP调用,这对于需要联网的工具至关重要,能避免阻塞整个MCP服务器。
  2. 结构化数据:使用Pydantic的BaseModel来定义复杂的输入参数。这不仅让代码更清晰,而且fastmcp能自动根据这些模型生成更精确的工具模式(Schema),帮助AI更好地理解如何调用。
  3. 工具组合create_order工具内部调用了check_inventory,展示了工具间的复用。
  4. 错误处理:在库存不足时返回明确的错误信息,AI能够理解并将其反馈给用户。

对于生产环境,你还需要考虑:

  • 认证与安全:你的内部服务肯定需要认证。不要在代码里硬编码密钥。可以通过mcp.json配置文件的env字段注入环境变量,或者在工具函数中从安全的配置中心读取令牌。
  • 日志与监控:在工具函数中添加详细的日志,记录调用参数和结果,方便排查问题。
  • 性能与超时:为外部HTTP调用设置合理的超时时间,并使用连接池。
  • 部署:你可以将这个FastAPI应用用Docker容器化,使用gunicornuvicorn配合多个工作进程,部署到云服务器上。这样,你团队的所有成员都可以在他们的Cursor中配置这个公共的URL来使用服务。

6. 常见问题与避坑指南

在开发和集成的路上,我踩过不少坑,这里总结一下,希望你能绕过去。

问题一:Cursor刷新后找不到MCP服务器,或者绿点变灰。

  • 原因:最常见的原因是本地MCP服务进程挂了(比如你关了终端),或者网络不通。
  • 解决:首先去终端确认uvicorn进程是否还在运行。然后,尝试在浏览器中直接访问你配置的url(如http://127.0.0.1:5000/),看是否有响应。如果浏览器一直在加载或报错,说明服务没起来。重启服务,并在Cursor的MCP设置页面点击“Refresh”按钮。

问题二:Agent列出了工具,但调用时失败,提示“调用被拒绝”或连接错误。

  • 原因:可能是MCP协议通信过程中出现了错误,比如工具函数抛出了未处理的异常,或者返回的数据格式不符合MCP协议。
  • 解决:仔细查看运行服务的终端输出,那里通常会有详细的错误堆栈信息。确保你的工具函数有完善的try...except,并返回AI能理解的错误信息。一个函数应该返回字符串、字典、列表等可序列化的Python基本类型,不要返回自定义的类实例。

问题三:工具被调用,但AI不理解返回的结果,回答得很奇怪。

  • 原因:AI(大模型)只接收到了工具返回的原始文本,它需要根据这个文本来组织回答。如果返回的数据是复杂的JSON,AI可能无法有效提取关键信息。
  • 解决:优化工具返回的内容。尽量返回自然语言格式的、信息完整的字符串。例如,与其返回一个JSON {"temp": 25, “condition”: “sunny”},不如直接返回“当前温度25度,天气晴朗”。这样AI几乎可以直接引用,回答更准确自然。

问题四:想同时运行多个本地MCP服务(比如一个管天气,一个管数据库)。

  • 解决:完全没问题。在.cursor/mcp.json文件里,在mcpServers对象下并列配置多个即可。注意给它们起不同的名字,并使用不同的端口号来启动对应的服务。
{
  "mcpServers": {
    "local-weather": {
      "url": "http://127.0.0.1:5000/"
    },
    "local-database": {
      "command": "python",
      "args": ["/path/to/your/db_server.py"]
    }
  }
}

注意,第二个服务我用了command方式,这是Stdio模式的配置,适用于那些通过标准输入输出通信的MCP服务器(通常也是用Python/Node.js写的)。这意味着你可以根据需求,混合使用SSE和Stdio两种模式。

最后,保持耐心和探索精神。MCP生态还在快速发展,不断有新的服务器实现和客户端支持出现。多看看官方文档和社区分享,把你觉得重复的工作都尝试封装成MCP工具,你会发现Cursor正在从一个代码助手,进化成你整个开发和工作流的智能中枢。

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐