1. 从一次 Cursor 里 MCP 红灯说起
如果你正在用 Cursor IDE 写代码,又想让 LLM Agent 直接调用你本地的业务接口,大概率会碰到 MCP Server 连不上的问题。MCP 全称 Model Context Protocol,简单说就是给大模型挂一个"工具卡片",让它在对话时能主动调用你后端暴露的函数。MCP Server 负责把这些函数按协议暴露出来,MCP Client 负责连接并转发请求,Cursor IDE 就是最常见的 Client 之一。
这篇聚焦一个具体场景:用 fastapi_mcp 在已有的 FastAPI 服务上加一层 MCP 能力,通过 SSE 协议把端点暴露出去,再在 Cursor IDE 的 settings.json 里配置好连接,让整条链路一次跑通。适合已经有一个 FastAPI 后端、想让 Cursor 里的 Agent 直接调用这些接口的开发者。核心检索词就三个:MCP Server、SSE 协议、Cursor IDE 配置。下面按"服务端骨架 → SSE 端点 → Cursor 配置 → 验证请求 → 排障"的顺序走一遍,每一步都给可复制的代码和命令。
我试过把公司内部一个订单查询服务接进 Cursor,中间踩了几个坑,后面会逐个说清楚。你跟着做,基本能一次点亮绿灯。
2. TaoToken 前置:统一 Key 与 API 通道
在动手写 MCP Server 之前,先把模型侧的通道准备好。MCP 只是"工具调用协议",真正干活的还是背后的 LLM。如果你在 Cursor 里同时用多个模型,或者团队里多人共用一套 Key,建议先把 API 通道统一到 TaoToken,这样 MCP Server 里调模型、Cursor 里调模型,走的是同一个入口,排查问题时不用来回切换。
TaoToken 提供的是兼容 OpenAI 风格的 API 通道,官网入口在 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 基址是 https://taotoken.net/api 。你需要先在控制台创建一个 API Key,然后把它填到 Cursor 的模型配置里。这一步不复杂,但顺序别搞反:先有 Key,再配 Cursor,最后才启动 MCP Server,否则 Cursor 那边连模型都调不通,MCP 绿灯也没意义。
具体动作:打开控制台 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,在 API Keys 页面新建一个 Key,复制出来备用。这个 Key 后面会出现在 Cursor 的 settings.json 里,也会出现在你本地测试 MCP Server 的 curl 命令里。如果你打算长期在 Cursor 里跑编码 Agent,可以顺手看一下 Coding Plan https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,它针对的就是这种持续编码场景。
注意:Key 只创建一次就够,不要在每个 MCP Server 里重复建。统一通道的意义就在于"一个 Key 走天下",后面排障时你只需要怀疑一处。
3. 可复制配置:fastapi_mcp 服务端骨架
现在进入正题。假设你已经有一个 FastAPI 服务,比如一个订单查询接口。我们要做的是在这个服务上加一层 MCP,让它对 Cursor 可见。先装依赖:
pip install fastapi uvicorn fastapi_mcp mcp然后写服务端骨架。核心思路是:先定义正常的 FastAPI 路由,再用 FastApiMCP 把这些路由包装成 MCP 工具,最后 mount 上去。
from fastapi import FastAPI from fastapi_mcp import FastApiMCP app = FastAPI(title="order-mcp-server") @app.get("/health") async def health(): return {"status": "ok"} @app.get("/orders/{order_id}") async def get_order(order_id: str): # 这里替换成你真实的业务查询 return {"order_id": order_id, "status": "paid", "amount": 199.0} mcp = FastApiMCP( app, name="Order API MCP", description="MCP server for order query", base_url="http://127.0.0.1:8000", ) mcp.mount() mcp.setup_server()几个参数要留意。base_url必须和你实际监听的地址一致,Cursor 那边连的就是这个地址。name和description会出现在 Cursor 的工具列表里,写清楚一点,Agent 才知道什么时候该调它。mcp.mount()负责把 MCP 端点挂到 FastAPI 上,mcp.setup_server()做最后的初始化,顺序不能反。
启动服务:
uvicorn main:app --host 0.0.0.0 --port 8000看到Uvicorn running on http://0.0.0.0:8000就说明服务起来了。此时访问http://127.0.0.1:8000/health应该返回{"status":"ok"}。这一步先确认基础服务没问题,再去看 MCP 端点。
4. SSE 端点配置与协议细节
fastapi_mcp 默认会帮你挂好 SSE 端点,但如果你要自己控制,或者想理解底层发生了什么,就得知道 SSE 是怎么接的。MCP 的 SSE 传输本质上是两条路:一条 GET/sse用来建立长连接、接收服务端推送;一条 POST/messages/用来发送客户端消息。
底层用 Starlette 手写的话,核心代码长这样:
from starlette.applications import Starlette from starlette.routing import Route, Mount from mcp.server.sse import SseServerTransport from mcp.server import Server def create_starlette_app(mcp_server: Server, *, debug: bool = False) -> Starlette: sse = SseServerTransport("/messages/") async def handle_sse(request): async with sse.connect_sse( request.scope, request.receive, request._send, ) as (read_stream, write_stream): await mcp_server.run( read_stream, write_stream, mcp_server.create_initialization_options(), ) return Starlette( debug=debug, routes=[ Route("/sse", endpoint=handle_sse), Mount("/messages/", app=sse.handle_post_message), ], )这段代码的关键点:SseServerTransport("/messages/")里的路径必须和Mount的路径一致,否则客户端发消息会 404。handle_sse里拿到读写流之后,交给mcp_server.run进入事件循环,连接会一直挂着,直到客户端断开。
用 fastapi_mcp 的话,这些它都帮你做了,你只需要确认挂载后的实际路径。启动服务后,用 curl 探一下 SSE 端点是否活着:
curl -N http://127.0.0.1:8000/sse-N是关闭缓冲,你会看到连接保持住并陆续吐出event: endpoint之类的数据。如果立刻断开或者返回 404,说明端点没挂上,回去检查mcp.mount()是否在setup_server()之前调用。
提示:SSE 是长连接,用 curl 测的时候会一直挂着,按 Ctrl+C 退出即可,这是正常现象,不是卡死。
5. Cursor IDE 配置:settings.json 可复制片段
服务端跑起来后,到 Cursor 这边配置 Client。Cursor 的 MCP 配置有两种方式:图形界面添加,或者直接改 settings.json。推荐后者,方便版本管理和复制。
打开 Cursor,按Cmd/Ctrl + Shift + P,输入Preferences: Open User Settings (JSON),在打开的 settings.json 里加入:
{ "mcpServers": { "order-mcp": { "url": "http://127.0.0.1:8000/sse" } } }如果你用的是需要命令启动的 stdio 方式,配置会长这样:
{ "mcpServers": { "order-mcp": { "command": "python3", "args": ["-m", "mcp_proxy", "http://127.0.0.1:8000/sse"] } } }两种方式的区别:url方式直接连 SSE 端点,适合服务已经常驻运行的场景;command方式由 Cursor 拉起一个代理进程,适合服务需要按需启动的场景。我一般用第一种,因为服务本来就要跑着。
保存 settings.json 后,回到 Cursor 的 MCP 面板,应该能看到order-mcp这一项,状态灯变绿就说明连上了。如果显示红色或黄色,先别急着改代码,按下一节的顺序排查。
另外,模型通道别忘了配。在 Cursor 的模型设置里,把 API Base 填成https://taotoken.net/api,Key 填你在控制台创建的那个。这样 Cursor 里的 Agent 既能调模型,又能通过 MCP 调你的本地接口。
6. 验证请求与成功结果
绿灯只是第一步,真正要验证的是"请求能不能转发到业务接口"。在 Cursor 的对话里输入一句自然语言,比如"帮我查一下订单 12345 的状态"。如果 Agent 识别到需要调用工具,它会自动触发get_order,你会在对话里看到工具调用记录,返回{"order_id":"12345","status":"paid","amount":199.0}。
如果 Agent 没调工具,先确认两件事:一是 MCP 面板里工具列表是否显示了get_order;二是模型是否支持 function calling。前者说明 MCP 注册成功,后者说明模型侧通道没问题。
也可以绕过 Cursor,直接用 curl 验证 SSE 链路:
curl -N http://127.0.0.1:8000/sse连接建立后,另开一个终端发消息:
curl -X POST http://127.0.0.1:8000/messages/ \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'正常会返回工具列表,里面包含get_order。这一步能过,说明 SSE 的收发两条路都通了,剩下的就是 Cursor 侧的配置问题。
实测下来,最容易出问题的不是代码,而是地址和端口。Cursor 里填127.0.0.1还是localhost,服务监听0.0.0.0还是127.0.0.1,这几个组合要一致。我踩过的坑就是服务监听127.0.0.1,Cursor 里填了本机局域网 IP,结果连不上,改成127.0.0.1立刻绿灯。
7. 本篇常见错排查
红灯一:SSE 端点 404。检查mcp.mount()和mcp.setup_server()的调用顺序,mount 必须在 setup 之前。另外确认 FastAPI 的版本,太老的版本可能不兼容 fastapi_mcp 的挂载方式。
红灯二:Cursor 显示黄色,提示连接超时。九成是地址问题。服务监听0.0.0.0:8000,Cursor 里就填http://127.0.0.1:8000/sse;如果服务在容器里,要确认端口映射。别用localhost和127.0.0.1混着填,有些环境解析不一样。
红灯三:工具列表为空。说明 MCP 连上了,但没注册到工具。检查你的 FastAPI 路由是否在FastApiMCP初始化之前定义。fastapi_mcp 是在初始化时扫描已有路由的,后加的路由不会被自动收录,需要重新setup_server()。
红灯四:conda 虚拟环境里命令找不到。如果你用 stdio 方式配置,command要写绝对路径。在虚拟环境里执行which python3拿到绝对路径,填进去。同理mcp-proxy也要用which mcp-proxy找到绝对位置。
红灯五:请求发出去了但业务接口没反应。看服务端日志,确认 POST/messages/有没有进来。如果进来了但没触发业务函数,多半是工具名或参数对不上,检查get_order的参数名是否和 Agent 传的一致。
排障时如果怀疑是 Key 或通道问题,直接去 API Keys 页面 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 重新生成一个 Key 换上,排除掉鉴权因素。接入细节可以对照文档 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 里的示例。
8. 下一步:把链路固定下来
整条链路跑通后,建议做两件事让它稳定下来。第一,把 MCP Server 写成 systemd 服务或者用 supervisor 托管,避免每次手动启动。第二,把 Cursor 的 settings.json 纳入 dotfiles 管理,换机器时直接同步。
如果你还想验证模型侧是否正常,可以打开模型对话 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 发一条测试消息,确认通道通畅。长期在 Cursor 里跑编码 Agent 的话,Coding Plan https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 会更合适,省得每次单独配 Key。
最后留一个实用技巧:MCP Server 的description字段别偷懒,写清楚这个工具能干什么、参数是什么格式。Agent 判断要不要调工具,全靠这段描述。描述写得模糊,Agent 就会该调的时候不调,不该调的时候乱调。这是我在多个项目里验证过的经验,比调模型参数管用。