开发一个MCP Server与Cursor集成,给Cursor插上翅膀!
这是本文介绍开发的调用AI画图功能的MCP Server工具,与AI集成使用效果很好,如果不想看本文整个开发过程,直接拿拿去用,开源地址:https://github.com/fengin/image-gen-server.git,里面有使用说明,觉得好用的话给我打个star,gitee上我也上传了。
在Cursor里面的使用效果:
生成的图片已经帮我放到对应的项目目录下了,而且有多张可以供我选择:
这个插件对接国内的即梦平台,需要注册平台账号,一天一个账号可以免费调用88次,足够用了
无缝与cursor集成,你提你的任务需求,cursor会自动根据你项目的上下文,帮你生成好提示词和调用工具生成,大大提升软件开发效率。
本文案例需要了解MCP协议,可以看AI全书这篇文章:一文看懂什么是MCP(大模型上下文),包括MCP的基本概念、功能价值、工作原理、能做什么以及怎么开发使用,文章最后附带一些相关的学习资料站,基本上学习MCP看这一文就足够了。
一、背景
发现自己慢慢地成成为Cursor的深度用户了,这周网上有看到个视频介绍在Cursor上集成MCP Server的能力,讲了三个MCP Server的功能,一个是思维链,一个是在线实时搜索,还有一个是图片生成。
相关的视频教程看Bilibili上,就是MCP官方推出的几个MCP Server例子
看了下,这些功能集成进来真的很强大的,难怪Cursor在最近的版本中就推出支持MCP Server插件了,不过我怀着好奇心按视频教程去增加这三个插件,结果一个没试用成功,研究了半天,了解到可能有以下几个原因:
1、会调用国外的接口,需要科学上网,但是一旦开启代理,好像Cursor调用又不行了;
2、Cursor刚集成进来,功能还不太成熟,问题不少,后面我也去看了下Cursor论坛,反馈问题确实不少;
3、MCP协议是24年11月底才推出来的,相关的实现源码也不是特别成熟,我后面的插件开发过程中就遇到有bug;
4、难得走通启动一个图片生成插件,发现竟然调的接口要收费的;
手头在独立做一个游戏产品开发,正好做到游戏图设计,本来到找AI平台工具去做的,如果能和Cursor集成,不管是效率还是效果上,会好很多,大致了解了下MCP 原理后,打算自己动手做一个能用的。
说行动就运行!
二、调用流程
既然开发的MCP Server要能生成图片,必须要调用生成图片的接口,当然,是要调AI的能力,由是去调研了下几个平台的接口,从网友们这了解到,目前生成图片效果比较好的是可灵 或者 即梦。
去两个平台看了下,可灵有生成图片的开放接口,但是要收费:
然后看了下即梦的产品,竟然连开放接口都没有!
正当我打算去通义千问看时,网友发给我一个即梦逆向接口开源地址,就是仿浏览器访问即梦产品,调用接口并生成图片:jimeng-free-api。
即梦每天可以送88个积分,一个积分调用可以生成一次图片(4张版本),正常来说也够我用了,不够可以再弄个账号。
试了下部署这个jimeng-free-api,很轻松地就跑起来这个接口了。
于是我的设计思路变成 Cursor—>image-gen-server(接下来要开发的MCP Server)—>jimeng-free-api—>即梦AI:
调用关系,该图也是由本文开发的imge-gen-server生成
后续可以把jimeng-free-api部署在家里nas上供调用,思路清晰后,准备开始开发image-gen-server。
三、初步尝试
其实刚开始试用了官方推荐的那三个MCP Server,我是比较懵的,还以为要装Claude 才能跑起来,由是去下载注册Claude,另外还要注册国外搜索接口什么的,用到虚拟手机号、虚拟银行卡各种拦路虎。
后来去了解了下MCP协议,大致知道这是个什么东西,但是让我自己写一个MCP Server,得先照着已有的例子跑一遍可能才会知道,可能比较慢。
我比较懒,想直接让Cursor帮我开发了。
于是我找了些文档资料,挑选了几个,确定采用python语言(Node.js我不熟悉)开发,看到有用Fastmcp框架比较简单,然后写了些提示词,丢给Cursor开发。
然而,过程并不顺利,使用Cursor有时候开发很顺手,但是做这个就是不太行,过程中遇到很多问题:
按Http Server来开发,可能受MCP Server里面的Server关键字影响;
调试过程中,启动后会自动关闭,多次打开会多个进程,然后它又是进程检查、又是生成本地进程pid,又是异步等等,搞得很复杂。
越到后面它理解越乱,甚至忘记最初的需求。
后来我总结了下,最主要的原因可能是MCP协议是2024年11月底才出来的,知识太新,虽然我提供相关的URL作为提示词给它,但是大模型对于MCP协议、框架具体使用方法规则、语法等在模型参数里都还是没有的,不能很好地生成代码内容。
后来我是让Cursor帮写一个方法,指定入参,提供它jimeng-free-api的使用文档,让它完成调用jimeng-free-api 生成图片返回URL后,再下载到本地。这个Cursor就很快地完成了。
四、完成第一个版本
接下来,我自己还是老老实实地参考fastmcp修改代码,fastmcp开源地址:https://github.com/jlowin/fastmcp
安装环境
Python,我试过3.11、3.12版本都可以
安装UV
#windows环境
powershell -c "irm https://astral.sh/uv/install.ps1 | iex"
#liunx
curl -LsSf https://astral.sh/uv/install.sh | sh
安装fastmcp模块(也可以放到项目requirement.txt里面)
pip install fastmcp
框架实现
按照框架例子,必须实现两个功能
注册MCP Server,并定义一个名字;
对你要实现的功能方法,加上对应的mcp的注解;
#其他代码
...
from fastmcp import FastMCP
# 创建注册FastMCP实例
mcp = FastMCP("image-gen-server")
#注册方法
@mcp.tool("generate_image")
async def generate_image(prompt: str, file_name: str, save_folder: str = None, sample_strength: float = 0.5, width: int = 1024, height: int = 1024) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
实现后,然后要配置到Cursor的MCP Servers里面:
这里填写有三项:
name:就是前面代码里面注册的image-gen-server
type:选择command
command:这里填啥???
刚开始不知道command填什么,然后看到fastmcp框架里介绍有安装mcp server到claude的命令:
fastmcp install server.py
这样可以去claude mcp server配置文件里找下是什么命令了,文件位置:
#windows下面
C:\Users\{用户名}\AppData\Roaming\Claude\claude_desktop_config.json
或者也可以从claude应用进入查看:Claude Desktop—>菜单—>Settings—>Developer—>Edit Config:
打开配置文件可以看到这样的内容:
{
"mcpServers": {
"image-gen-server": {
"command": "uv",
"args": [
"run",
"--with",
"fastmcp",
"fastmcp",
"run",
"D:\\code\\image-gen-server\\server.py"
]
}
}
}
然后看上面的内容,摘出来的command就是:uv run --with fastmcp fastmcp run D:\\code\\image-gen-server\\server.py
把这个填入Cursor增加MCP Server输入框的command框就可以了。
正常情况下,保存后就会弹出一个黑窗口(截目目前我还不知道怎么解决弹出黑窗口的问题,先不管)。
如果不弹出来,或者弹出来后立马自动消失,那说明在Cursor增加MCP Server的配置不对,或者你的MCP Server代码有问题,不能正常运行。
以下是第一版本的完整代码
requirements.txt
aiohttp>=3.8.1
aiofiles>=0.8.0
requests>=2.26.0
psutil>=5.8.0
fastmcp==0.4.1
mcp==1.2.1
server.py
#描述:基于即梦AI的图像生成服务,专门设计用于与Cursor IDE集成。它接收来自Cursor的文本描述,生成相应的图像,并提供图片下载和保存功能。
#作者:凌封 (微信fengin)
#GITHUB: https://github.com/fengin/image-gen-server.git
import os
import logging
from sys import stdin, stdout
import requests
import json
from fastmcp import FastMCP
import mcp.types as types
import base64
# API配置
JIMENG_API_URL = "http://192.168.1.20:8000" # jimeng-free-api 部署地址
JIMENG_API_TOKEN = "Bearer 057f7ad*****f5af**98d5386fe0" # 你登录即梦获得的session_id
IMG_SAVA_FOLDER = "D:/code/image-gen-server/images" # 图片默认保存路径
stdin.reconfigure(encoding='utf-8')
stdout.reconfigure(encoding='utf-8')
# 配置日志
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
# 创建FastMCP实例
mcp = FastMCP("image-gen-server")
@mcp.tool("generate_image")
async def generate_image(prompt: str, file_name: str, save_folder: str = None, sample_strength: float = 0.5, width: int = 1024, height: int = 1024) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
"""根据文本描述生成图片
Args:
prompt: 图片的文本prompt描述
file_name: 生成图片的文件名(不含路径,如果没有后缀则默认使用.jpg)
save_folder: 图片保存绝对地址目录(可选,默认使用IMG_SAVA_FOLDER)
sample_strength: 生成图片的精细度(可选,范围0-1,默认0.5)
width: 生成图片的宽度(可选,默认1024)
height: 生成图片的高度(可选,默认1024)
Returns:
List: 包含生成结果的JSON字符串
"""
logger.info(f"收到生成请求: {prompt}")
# 验证sample_strength参数范围
if not 0 <= sample_strength <= 1:
error_msg = f"sample_strength参数必须在0-1范围内,当前值: {sample_strength}"
logger.error(error_msg)
return [
types.TextContent(
type="text",
text=json.dumps({
"success": False,
"error": error_msg,
"images": []
}, ensure_ascii=False)
)
]
# 验证width和height参数
if width <= 0 or height <= 0:
error_msg = f"width和height必须大于0,当前值: width={width}, height={height}"
logger.error(error_msg)
return [
types.TextContent(
type="text",
text=json.dumps({
"success": False,
"error": error_msg,
"images": []
}, ensure_ascii=False)
)
]
# 检查并处理文件后缀
if not os.path.splitext(file_name)[1]:
file_name = f"{file_name}.jpg"
logger.info(f"文件名没有后缀,使用默认后缀: {file_name}")
# 如果未指定保存目录,使用默认目录
if not save_folder:
save_folder = IMG_SAVA_FOLDER
logger.info(f"使用默认保存目录: {save_folder}")
try:
headers = {
"Content-Type": "application/json",
"Authorization": JIMENG_API_TOKEN
}
payload = {
"model": "jimeng-2.1",
"prompt": prompt,
"negativePrompt": "",
"width": width,
"height": height,
"sample_strength": sample_strength
}
response = requests.post(
f"{JIMENG_API_URL}/v1/images/generations",
headers=headers,
json=payload,
timeout=300
)
if response.status_code != 200:
error_text = response.text
logger.error(f"API调用失败: {response.status_code} - {error_text}")
return [
types.TextContent(
type="text",
text=json.dumps({
"success": False,
"error": f"生成图片失败: {error_text}",
"images": []
}, ensure_ascii=False)
)
]
result = response.json()
image_urls = [img["url"] for img in result["data"]]
logger.info(f"获取到 {len(image_urls)} 张图片")
save_results = []
for i, url in enumerate(image_urls):
result = download_and_save_image(url, save_folder, file_name, i)
if result: # 只添加非None的结果
save_results.append(result)
return [
types.TextContent(
type="text",
text=json.dumps({
"success": True,
"message": "图片生成完成",
"images": save_results
}, ensure_ascii=False)
)
]
except requests.Timeout:
logger.error("请求超时")
return [
types.TextContent(
type="text",
text=json.dumps({
"success": False,
"error": "请求超时,请稍后重试",
"images": []
}, ensure_ascii=False)
)
]
except Exception as e:
logger.error(f"生成图片时出错: {str(e)}", exc_info=True)
return [
types.TextContent(
type="text",
text=json.dumps({
"success": False,
"error": f"生成图片出错: {str(e)}",
"images": []
}, ensure_ascii=False)
)
]
def download_and_save_image(image_url, save_folder, file_name, index):
"""下载并保存单张图片
Args:
image_url: 图片URL
save_folder: 保存目录路径
file_name: 文件名(不含路径,带后辍)
index: 图片索引
Returns:
str: 成功时返回图保存地址,失败时返回None
"""
try:
file_name_no_ext, ext = os.path.splitext(file_name)
indexed_file_name = f"{file_name_no_ext}_{index}{ext}"
save_path = os.path.join(save_folder, indexed_file_name)
response = requests.get(image_url, timeout=60)
if response.status_code != 200:
logger.error(f"下载图片失败: {response.status_code} - {image_url}")
return None
os.makedirs(save_folder, exist_ok=True)
with open(save_path, "wb") as f:
f.write(response.content)
logger.info(f"图片已保存: {save_path}")
return save_path
except requests.Timeout:
logger.error(f"下载图片超时: {image_url}")
return None
except Exception as e:
logger.error(f"保存图片时出错: {str(e)}")
return None
if __name__ == "__main__":
logger.info("启动图像生成服务...")
mcp.run()
如何调试
刚开始配置到Cursor上,根本用不起来,刚开始是MCP Servers列表里面加了但是显示工具状态不正常(状态点是红的),要么就是配置好保存一点闪一下黑窗口就不见了。
然后我用比较笨的办法,就网上找个最简单的demo例子,写个test方法,配置上Cursor然后让Cursor配合我调试:
后来还是在Fastmcp文档里看到的启动调试模式命令:
fastmcp dev server.py
这样在终端运行后,会启动调试模式,启动完成后,会有一个本地URL地址出来:
🔍 MCP Inspector is up and running at http://localhost:5173 🚀
点后就可以打开MCP Inspector工具进行调试:
有了这个工具调试就方便多了,使用也很简单,自己各个地方点一下差不多就知道了,不清楚的看上面的相关帮助文档。
中文问题
功能虽然做出来了,,我游戏项目里要生成图片,而我游戏是中国文化的,刚开始Cursor是用英文写了prompt,我就提示了下用中文,但是一调用发现异常了,工具都挂了。
开启MCP Inspector调试后,列表返回的工具说明是乱码的:
调用工具发现发到后台MCP Server接收消息也是失败了:
2025-02-06 23:30:44,322 - mcp.server.lowlevel.server - DEBUG - Received message: 1 validation error for JSONRPCMessage Input should be a valid string, unable to parse raw data as a unicode string [type=string_unicode, input_value='{"jsonrpc":"2.0","id":2,..._name":"美女"}}}\n', input_type=str] For further information visit https://errors.pydantic.dev/2.10/v/string_unicode
看样子是编码问题,我研究了两三个小时,还是解决不了这个问题。
后来想到曲线救国的办法,就是入参改成对中文进行Base64编码,工具收到后,再解码出来,测试方法什么得跑得通了。
拿到cursor里面一试,完蛋,走不通,提示cursor要把提示词转成base64,但是它生成的提示词在转base64就自觉地发挥AI伟大的创造性了,明明写得很好的提示词,但是一转成base64(应该也是调AI转的),再转回来看,完全是面目全非了,跟需求对应不上了。
第二天继续研究,找Fastmcp源码看,发现报错不在Fastmcp框架里,而是MCP的核心源码,然后到mcp源码的github里面想提issue,先看了下已有的issue,好家伙,果然好多人遇到同样的问题。
mcp源码还不够稳定,看上去用户提的这个问题在新版本解决了,但是我升级上来测试还是没用。
最后按照上面issue提到的建议,在代码开头加上编码约束解决了:
stdin.reconfigure(encoding='utf-8')
stdout.reconfigure(encoding='utf-8')
相关的issue地址:https://github.com/modelcontextprotocol/python-sdk/issues/107
还有种办法,就是设置环境变量:"PYTHONIOENCODING": "utf-8" ,不过我没研究在Cursor里面怎么配置,在MCP Inspector工具里面配置是有效的。
五、第二版本
第一版本完成后,功能是可以用了,但是使用起来还是比较麻烦,需要安装jimeng-free-api,我就想把它的功能直接合到image-gen-server里面,也不用启动一个http服务了。
jimeng-free-api好像是用TS写的node.js服务,这个我不擅长,本身就是一个逆向接口工程,和爬虫其实差不多的,大致能看个基本,但是让我自己用python写它,我感觉体力活不少,而且容易出错,调试很费时。
这时候Cursor又派上用场了,让它来仿写。
第一次尝试
把jimeng-free-api源代码下载来,放在项目目录下,先让它阅读一下readme.md理解下架构,然后提示下面提示词:
既然你已经熟悉了这个架构, 现在sever.py需要调192.168.1.20:8000这个jimeng-free-api的接口,而且这个接口要部署,使用起来比较麻烦,我有个想法,咱们直接在本项目下建一个jimeng的python模块,实现jimeng-free-api的功能,这样就不需另外再部署jimeng-free-api接口了,你帮我完成这个模块的开发,并且在server.py里面修改为调用这个模块,要求:
1、你要阅读了解jimeng-free-api接口实现的原理和细点;
2、本项目本目录下新建一个jimeng python模块,抄写jimeng-free-api里面的实现,完成jimeng接口的调用;
3、不需要jimeng-free-api里面一样启动http服务了,这里server.py可以直接调用了;
4、说清楚你的开发过程思路,让我了解你是怎么做的
真神速,一分钟就写完了。
但是,一运行发现不行,仔细检查了下,里面实现的即梦接口调用过程,跟TS版本的相差很大,好多参数都没有。
然后强调提示它,阅读完整的jimeng-free-api源码,再修改,它认认真真的一步步操作阅读相关文件,然后给出一系列计划,开始修改,好一阵子修改完了。
一调试,发现它又自己发挥了,修改了uri,参数名也修改了。
我再强调,然后又再开始修改,我python代码原来是一个MCP Server,它竟然引进来FastApi,开始做http服务了...
我告诉它不是做http服务,然后它又开始忘记前面的需求了
...
放弃了。
第二次尝试(利用Rules)
晚上回到家,思考了下,可能没有做项目约束,然后在项目下面增加了.cursor/rules/imitate.mdc,增加规则约束:
---
description: 每次进行修改任务都需要阅读确认规则
globs:
---
# Your rule content
- 回顾上下文,记住你要做的总体任务,是用python语言仿照jimeng-free-api里面功能实现,写一个python模块,供其他python模块调用,直接模块调用,不是http调用;
- 保证新模块开发调用即梦接口的实现机制、参数一定要和jimeng-free-api/src里面实现的完全一致,包括代码参数检查、动态参数获取机制、调用的逻辑、安全验证过程等等,用到的各种请求头、请求体、uri、url都要完全保持一致,包括不仅限于描述这些的,以jimeng-free-api/src里面实现的一致;
- 新模块只是提供其他python代码模块调用,不是一个http服务;
- 不要画蛇添足,增加不需要的功能。
发现它还是有点乱,会去查看结合已完成MCP服务sever.py进行修改,两三步修改又要开始乱了。
我再换下思路,把任务进行拆解:
任务一
仿jimeng-free-api,实现一个python模块,用于其他模块调用:
现在我们的任务是在proxy/jimeng目录下,仿jimeng-free-api功能实现一个调用即梦接口实现的模块,要求:
1、你先仔细阅读jimeng-free-api/src 下面的每行代码实现,完全了解这个jimeng-free-api项目的运行机制和原理,特别是每个方法实现细节(每个参数、参数效验、代码逻辑、安全参数生成、使用方式等等,包括不仅限我列出来的,具体以代码为准),这是个即梦接口逆向解析出来的实现,所以参数和实现机制要求一定要一致;
2、在proxy/jimeng目录下(proxy目录下后面可能会增加其他图片调用模块)用python语言实现一个仿照jimeng-free-api功能实现模块,后续用于其他模块调用;
3、python实现的模块是直接被其他模块调用,不再需要原jimeng-free-api里面的http服务实现,只是被简单的模型间调用,不是作为http服务供其他地方调用。
并且把jimeng-free-api的readme.md 和约束规则附给它。
接下来cursor就开始一步步阅读所有相关的文档和代码了,并给我一些有顺的反馈:
让我整理一下对源码的理解...
1. 核心实现机制:
- 项目是对即梦AI的API进行逆向工程实现
- 使用sessionid作为认证token
- 支持多账号token轮换使用
- 实现了与OpenAI兼容的接口格式
2. 关键模块:
a) 核心控制器(core.ts):
- 定义了基础常量(版本号、设备ID等)
- 实现了请求签名生成
- 处理token管理和请求封装
- 错误处理机制
b) 图像生成(images.ts):
- 支持多个模型版本
- 参数包括:宽度、高度、精细度、正反向提示词
- 实现了积分检查和领取
- 处理图像生成请求和结果获取
c) 对话补全(chat.ts):
- 支持同步和流式两种模式
- 将文本转为图像生成请求
- 处理多轮对话上下文
3. 安全机制:
- 请求签名生成
- 参数验证
- 错误重试
- 积分检查
4. 关键API调用流程:
a) 图像生成:
1. 参数验证
2. 积分检查
3. 生成请求ID和参数
4. 发送生成请求
5. 轮询获取结果
6. 处理返回URL
b) 对话补全:
1. 消息验证
2. 调用图像生成
3. 格式化返回结果
这些分析让我对如何实现Python模块有了清晰的思路。我们需要:
1. 完全复制这些核心参数和请求头
2. 实现相同的签名生成算法
3. 保持一致的参数验证
4. 实现相同的请求重试机制
5. 保持API调用流程的一致性
您觉得我的理解是否准确?我们是否可以开始规划Python模块的具体实现了?
后面就开始比较规范地写代码,让它创建了一个example.py,用于对prox.jimeng模块调用测试。
我测试不通,调试了下,发现还是和jiemg-free-api实现有差异,然后给它提示:
测试了下,发现不功能调不通,接口地址都不对,比如images.py里面获取credit的uri都不对,你一定要严格按照jimeng-free-api/src里面调用即梦所有的参数,你再检查下,所有调jimeng.jianying.com接口的uri和参数是不是都正确、完全一致!!!
接下来,Cursor突然变得很聪明,不停地对比实现和参数,然后自测,然后又去对比修改,大概有四五轮,像是在自言自语,我一点没参与,运行结束后,我测试已实现了对即梦接口调用。
任务二
第一步走通了,第二步就简单了,让它修改server.py里面的实现,原来是调用http服务的,改成调用proxy.jimeng模块
我现在我又在image-gen-server模块下面移过来一个server.py,这是一个mcp server代码,调用部署好的jimeng-free-api,已经实现的使用说明在image-moudle/readme.md 里面,我们现在已经实现proxy.jimeng平替了jimeng-free-api的功能,接下来把server.py里面对jimeng-free-api 的http请求调用,改成调用proxy.jimeng这个模块,server.py里面其他的功能不要修改。
然后它一次性就改完了
测试没问题
最后让它完善了下server.py的文档,以及proxy.jimeng模块的文档
至此,所有的开发过程结束,功能完成,相关源码见本文开头提供的开源地址。
总结
简单地总结一下
MCP是一个新出来的东西,未来潜在的价值比较大,但是目前的配套还不是特别成熟
网上有很多教程,有些还是比较粗的,学习过程中会有些弯路
完成第一次开发,后面就做其他MCP Server就顺手多了
当前做成的这个Cursor插件真的很好用,反正我用地很爽,开发中已经用起来了!