开发一个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有时候开发很顺手,但是做这个就是不太行,过程中遇到很多问题:

  1. 按Http Server来开发,可能受MCP Server里面的Server关键字影响;

  2. 调试过程中,启动后会自动关闭,多次打开会多个进程,然后它又是进程检查、又是生成本地进程pid,又是异步等等,搞得很复杂。

  3. 越到后面它理解越乱,甚至忘记最初的需求。

后来我总结了下,最主要的原因可能是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

框架实现

按照框架例子,必须实现两个功能

  1. 注册MCP Server,并定义一个名字;

  2. 对你要实现的功能方法,加上对应的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里面:

这里填写有三项:

  1. name:就是前面代码里面注册的image-gen-server

  2. type:选择command

  3. 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:

如果没有安装claude话,可以在对应的地方建文件夹和文件,名字保持上面路径里面的名字一致

打开配置文件可以看到这样的内容:

{
  "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插件真的很好用,反正我用地很爽,开发中已经用起来了!

License:  CC BY 4.0

©2025 AI全书. Some rights reserved.

    备案号: 浙ICP备06043869号-8