自媒体创作场景实践|通义千问3 + MCP=一切皆有可能
代码
1. 本server实现代码中,使用了固定的workflow,可以按需替换成自己需要的workflow;
2. 只针对文生图场景的实现,其他场景可以按需自己添加mcp server tool;
import urllibimport uuidfrom typing import Dict, Any, Listimport httpximport loggingimport websocketsfrom PIL import Imageimport iofrom fastmcp import FastMCPfrom fastmcp.prompts import UserMessageimport jsonfrom time import sleep# Configure logginglogging.basicConfig(level=logging.INFO)logger = logging.getLogger("comfy-image-mcp-server")COMFY_SERVER = "8.147.113.150:8000"CLIENT_ID = str(uuid.uuid4())# Create a basic server instancemcp = FastMCP(name="CompyImageServer")# You can also add instructions for how to interact with the servermcp_with_instructions = FastMCP(name="HelpfulAssistant",instructions="这个服务是用来通过comfyui生成图片的.""调用generate_image_async()来异步地生成需要的图片.")def queue_prompt(prompt: Dict[str, Any] = None) -> Dict[str, Any]:url = f"http://{COMFY_SERVER}/api/prompt"json = {"prompt": prompt,"client_id": CLIENT_ID}try:response = httpx.post(url=url, json=json, verify=False, trust_env=False, timeout=60.0)if response.status_code != 200:raise RuntimeError(f"Failed to queue prompt: {response.status_code} - {response.text}")return response.json()except httpx.RequestError as e:# logger.info(traceback.format_exc())raise RuntimeError(f"HTTP request failed: {e}")async def download_image(prompt_id: str) -> bytes:uri = f"ws://{COMFY_SERVER}/ws?clientId={CLIENT_ID}"logger.info(f"Connecting to websocket at {uri}")async with websockets.connect(uri) as websocket:while True:try:message = await websocket.recv()if isinstance(message, str):try:data = json.loads(message)logger.info(f"Received text message: {data}")if data.get("type") == "executing":exec_data = data.get("data", {})if exec_data.get("prompt_id") == prompt_id:node = exec_data.get("node")logger.info(f"Processing node: {node}")if node is None:logger.info("Generation complete signal received")breakexcept:passelse:logger.info(f"Received binary message of length: {len(message)}")if len(message) > 8: # Check if we have actual image datareturn message[8:] # Remove binary headerelse:logger.warning(f"Received short binary message: {message}")except websockets.exceptions.ConnectionClosed as e:logger.error(f"WebSocket connection closed: {e}")breakexcept Exception as e:logger.error(f"Error processing message: {e}")continuedef get_image(filename, subfolder, folder_type):data = {"filename": filename, "subfolder": subfolder, "type": folder_type}url_values = urllib.parse.urlencode(data)with urllib.request.urlopen("http://{}/view?{}".format(COMFY_SERVER, url_values)) as response:return response.read()def get_history(prompt_id):with urllib.request.urlopen("http://{}/history/{}".format(COMFY_SERVER, prompt_id)) as response:return json.loads(response.read())def get_image_and_download(prompt_id, path):output_images = []while True:history = get_history(prompt_id)[prompt_id]if history['status']['status_str'] == 'success':for node_id in history['outputs']:node_output = history['outputs'][node_id]images_output = []if'images' in node_output:for image in node_output['images']:image_data = get_image(image['filename'], image['subfolder'], image['type'])image_bytes = Image.open(io.BytesIO(image_data))file_path = ""if path.endswith("/"):file_path = path + image['filename']else:file_path = path + "/" + image['filename']image_bytes.save(file_path)output_images.append(file_path)# images_output.append(image_data)# output_images[node_id] = images_output# output_images_names[node_id] =return output_imageselse:logger.info(f"promot {prompt_id} unfinished meta: {history}")sleep(1)continue@mcp.prompt()def generate_image_request(prompt: str, style: str = "动漫风格") -> UserMessage:"""Generates a user message requesting image generation"""content = f"生成一个comfyui的英文prompt,要求包含下面的内容: {prompt} 并且要求生成的图片需要具有很高的质量,风格是{style}"return UserMessage(content=content)@mcp.tool()def generate_image_async(prompt: str = "a cat with yellow hat", width=512, height=512,seed=4787458) -> Dict[str, Any]:workflow = {"6": {"inputs": {"text": prompt,"clip": ["30",1]},"class_type": "CLIPTextEncode","_meta": {"title": "CLIP Text Encode (Positive Prompt)"}},"8": {"inputs": {"samples": ["31",0],"vae": ["30",2]},"class_type": "VAEDecode","_meta": {"title": "VAE解码"}},"9": {"inputs": {"filename_prefix": "ComfyUI","images": ["8",0]},"class_type": "SaveImage","_meta": {"title": "保存图像"}},"27": {"inputs": {"width": width,"height": height,"batch_size": 1},"class_type": "EmptySD3LatentImage","_meta": {"title": "空Latent图像(SD3)"}},"30": {"inputs": {"ckpt_name": "flux1-dev-fp8.safetensors"},"class_type": "CheckpointLoaderSimple","_meta": {"title": "Checkpoint加载器(简易)"}},"31": {"inputs": {"seed": seed,"steps": 20,"cfg": 1,"sampler_name": "euler","scheduler": "simple","denoise": 1,"model": ["30",0],"positive": ["35",0],"negative": ["33",0],"latent_image": ["27",0]},"class_type": "KSampler","_meta": {"title": "K采样器"}},"33": {"inputs": {"text": "","clip": ["30",1]},"class_type": "CLIPTextEncode","_meta": {"title": "CLIP Text Encode (Negative Prompt)"}},"35": {"inputs": {"guidance": 3.5,"conditioning": ["6",0]},"class_type": "FluxGuidance","_meta": {"title": "Flux引导"}}}return queue_prompt(workflow)@mcp.tool()def get_image_status_and_download_to_local(prompt_id:str, absolute_path: str = "/Users/wangrupeng/Documents/work/files/images") -> List[str]:"""get image generate status and download to local when it's generating success"""images = get_image_and_download(prompt_id, absolute_path)return imagesif __name__ == "__main__":mcp.run(transport="sse",host="127.0.0.1", port=9000)# create_simple_note()# print(generate_image_async("a cute girl with red hat standing on the green land"))# images = download_image_to_local("03e6da53-9779-4af8-b7a9-564f90eeea36")# print(images)
启动
此处记住端口
fastmcp run server.py:mcp --transport sse --host 127.0.0.1 --port 9000第三方媒体Server
1. 参考Github项目 social-auto-upload:https://github.com/dreammis/social-auto-upload,支持抖音、B站、小红书等社交媒体;
2. 自己实现的server功能比较简单,仅为Demo展示使用;
代码
import jsonimport loggingimport httpxfrom fastmcp import FastMCPfrom time import sleepfrom playwright.sync_api import sync_playwrightfrom xhs import XhsClientcookie = "a1=xxxx;"XIAOHONGSHU_SERVER = "127.0.0.1:5005"# social-auto-load服务def sign(uri, data=None, a1="", web_session=""):for _ in range(10):try:with sync_playwright() as playwright:stealth_js_path = "/Users/wangrupeng/Documents/dev/github/stealth_min/stealth.min.js"chromium = playwright.chromium# 如果一直失败可尝试设置成 False 让其打开浏览器,适当添加 sleep 可查看浏览器状态browser = chromium.launch(headless=True)browser_context = browser.new_context()browser_context.add_init_script(path=stealth_js_path)context_page = browser_context.new_page()context_page.goto("https://www.xiaohongshu.com")browser_context.add_cookies([{'name': 'a1', 'value': a1, 'domain': ".xiaohongshu.com", 'path': "/"}])context_page.reload()# 这个地方设置完浏览器 cookie 之后,如果这儿不 sleep 一下签名获取就失败了,如果经常失败请设置长一点试试sleep(1)encrypt_params = context_page.evaluate("([url, data]) => window._webmsxyw(url, data)", [uri, data])return {"x-s": encrypt_params["X-s"],"x-t": str(encrypt_params["X-t"])}except Exception as e:# 这儿有时会出现 window._webmsxyw is not a function 或未知跳转错误,因此加一个失败重试趴logger.warning(f"failed : {e}")passraise Exception("重试了这么多次还是无法签名成功")xhs_client = XhsClient(cookie, sign=sign)# Configure logginglogging.basicConfig(level=logging.INFO)logger = logging.getLogger("comfy-image-mcp-server")# Create a basic server instancemcp = FastMCP(name="XiaoHongShuServer")# You can also add instructions for how to interact with the servermcp_with_instructions = FastMCP(name="HelpfulAssistant",instructions="这个服务是用来发布管理和查看小红书笔记的")def create_simple_note(title: str, desc: str, images: []) -> str:note = xhs_client.create_image_note(title, desc, images, is_private=False)return json.dumps(note, ensure_ascii=False, indent=2)@mcp.tool()def publish_xiaohongshu_note(title: str = "", desc = "a cat", images: [] = ["/path/to/local/demo.jpg",]) -> str:"""publish a xiaohongshu note"""try:url = f"http://{XIAOHONGSHU_SERVER}/create"json = {"title": title,"desc": desc,"images": images}response = httpx.post(url=url, json=json, verify=False, trust_env=False, timeout=60.0)return response.json()except Exception as e:return f"error {e}"if __name__ == "__main__":mcp.run(transport="sse",host="127.0.0.1", port=9001)
CLINE配置MCP Server
CLINE中右上角点击MCP Servers按钮
更新cline_mcp_settings.json配置文件
{"mcpServers": {"filesystem": {"autoApprove": ["read_file","list_allowed_directories","read_multiple_files","create_directory","list_directory","directory_tree","search_files","get_file_info"],"disabled": false,"timeout": 60,"command": "npx","args": ["-y","@modelcontextprotocol/server-filesystem","/Users/wangrupeng/Documents/Cline/MCP/filesystem-server"],"transportType": "stdio"},"comfyui": {"autoApprove": ["generate_image_async","get_image_status","get_image_status_and_download_to_local"],"disabled": false,"timeout": 60,"url": "http://127.0.0.1:9001/sse","transportType": "sse"},"xiaohongshu": {"disabled": false,"timeout": 60,"url": "http://127.0.0.1:9002/sse","transportType": "sse","autoApprove": ["publish_xiaohongshu_note"]}}}
感悟和思考
不管是大数据还是AI,核心其实都是数据,任何只要有IO的系统(不管是生理系统,还是计算机系统),IO过程都可以抽象为下面的模型:
我们日常生活中实际上也是在各种输入和输出的场景中切换,MCP实际上是借助大模型打通了人在各种场景的上下文,打通了大模型和物理世界的交互联系。
商业化场景的思
1. 售卖MCP Server API
对一些比较重的复杂的操作,比如商业非开源在线的产品,供应商可以封装内部API,对客户通MCP Server的调用;
2. 订单自动化分析管理
电商等平台订单系统API接入MCP Server,可以做到大模型自动帮客户分析订单,制作报表或者商家商品,灵活调整售价等等;
3. 脑机、残疾人机械臂接入大模型
有了MCP之后,可以为残障任务定制化场景,让他们能够通过MCP + 大模型完成任务,游戏、工作等;
4. 具身智能机器人场景
大模型驱动的移动机器人覆盖路径规划 ;
大模型赋能的手术机器人能够理解复杂的手术环境和任务要求 ;
智能家居机器人能够理解并执行各种家庭任务;
总之只要有API接口,MCP理论上都能够接入,从这里看能限制MCP应用的只有想象力了。不过这里说的MCP能做,离做好还是有差距的,请看下面的不足分析。
不足之处
1. 大模型还不够智能
demo制作过程中尝试过多款模型,最好的大模型依然会有失误的情况,而且一步错可能步步错;
精细化的任务很难处理,比如给大模型下一个通过blender建模的工作,生成的blender指令经常是错误的,这个也和大模型掌握的blender版本知识库过旧有关系;
2. MCP Server有一定开发难度
基于FastMCP2.0开发的代码量不大,但是中间有很多网络、依赖冲突等方面的坑,目前很多都需要自己解决,网上资料很少;
3. 安全风险
身份验证机制还不完善;
可在本地执行有风险的操作,比如Filesystem server可能会误修改本地文件;
经过大模型“翻译”之后,下达给MCP的指令可能是错的,甚至是有风险的;
MCP的操作很多不是“原子性”的,比如预定机票,万一定错了可能无法退订,会有资金损失的风险;
本地Secret数据可能被第三方mcp server服务器收集,存在泄露风险。
当前MCP Server的开发技巧和建议
建议使用FastMCP框架:https://github.com/jlowin/fastmcp,其在MCP官方SDK的基础上做了更好的封装,开发更简洁,而且FastMCP已经被官方采纳,官网Demo就是用FastMCP实现的。
1. tool定义的函数和参数命名尽可能的明确 ;
2. 函数内部加tool功能的prompt注解和注释 ;
3. 参数尽量附带默认值,防止调用的时候漏掉参数等 ;
这样mcp list tool和选择tool的时候会对每个tool的功能有更明确的理解。
未来展望
1. 让大模型更智能,通义千问3开源之后我们又往前更近了一步;
2. 应用场景上会有越来越多的MCP应用场景出现,也会有越来越多的server出现;
3. 亟待解决安全类问题,不然商业场景上会存在很大不可靠性。
参考链接:
[1]https://bailian.console.aliyun.com/
[2]https://bailian.console.aliyun.com/?spm=5176.21213303.J_v8LsmxMG6alneH-O7TCPa.3.3e532f3dk98C98&scm=20140722.S_card%40%40%E4%BA%A7%E5%93%81%40%402983180._.ID_card%40%40%E4%BA%A7%E5%93%81%40%402983180-RL_%E7%99%BE%E7%82%BC-LOC_2024SPSearchCard-OR_ser-PAR1_213e364317425218525852943e3017-V_4-RE_new6-P0_0-P1_0&tab=mcp#/mcp-market/detail/Filesystem
轻松上手 Qwen3:最新全球开源冠军
截至目前,Qwen3 已斩获LiveBench、LiveCodeBench、SuperClue、Artificial Analysis等榜单的全球开源冠军、国产模型冠军。在阿里云,您可以快速使用 Qwen3,最快 10 分钟,最低 0 元。
点击阅读原文查看详情。