Skip to main content
Glama
BobGod

WeChat Publisher MCP

by BobGod

wechat_publish_article

Publish articles to WeChat Official Accounts using Markdown format, converting content to HTML with image handling and preview options.

Instructions

将文章发布到微信公众号,支持Markdown格式

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
titleYes文章标题
contentYesMarkdown格式的文章内容
authorYes作者名称
appIdYes微信公众号AppID
appSecretYes微信公众号AppSecret
coverImagePathNo封面图片路径
previewModeNo是否为预览模式
previewOpenIdNo预览用户OpenID

Implementation Reference

  • Core handler function WeChatPublisher.publish() that implements the complete tool logic: parameter validation, Markdown to WeChat HTML conversion, automatic cover image generation/upload, article publishing or previewing via WeChatAPI.
    static async publish(params) { const startTime = Date.now(); try { // 详细记录调用参数(隐藏敏感信息) const logParams = { title: params.title, author: params.author, contentLength: params.content ? params.content.length : 0, contentPreview: params.content ? params.content.substring(0, 100) + '...' : '', appId: params.appId ? params.appId.substring(0, 8) + '***' : 'undefined', appSecret: params.appSecret ? '***已提供***' : 'undefined', coverImagePath: params.coverImagePath || 'undefined', previewMode: params.previewMode || false, previewOpenId: params.previewOpenId || 'undefined' }; logger.info('=== MCP调用开始 ==='); logger.info('调用参数详情', logParams); logger.info('开始发布流程', { title: params.title }); // 1. 参数验证 const validation = validatePublishParams(params); if (!validation.valid) { throw new Error(`参数验证失败: ${validation.errors.join(', ')}`); } const { title, content, author, appId, appSecret, coverImagePath, previewMode = false, previewOpenId } = params; // 2. 初始化微信API logger.debug('初始化微信API'); const wechatAPI = new WeChatAPI(appId, appSecret); // 3. 转换Markdown为微信HTML logger.debug('转换Markdown内容'); const htmlContent = MarkdownConverter.convertToWeChatHTML(content); logger.debug('Markdown转换完成', { originalLength: content.length, htmlLength: htmlContent.length }); // 4. 处理封面图 - 如果没有提供封面图,则自动生成 let thumbMediaId = null; let coverPath = coverImagePath; if (!coverPath) { // 自动生成封面图 logger.info('未提供封面图,正在根据文章内容自动生成封面图...'); coverPath = await WeChatPublisher.generateCoverImage(title, content); } if (coverPath) { try { logger.debug('开始上传封面图', { path: coverPath }); thumbMediaId = await wechatAPI.uploadCoverImage(coverPath); logger.info('封面图上传成功', { mediaId: thumbMediaId }); // 如果是自动生成的封面图,上传后删除临时文件 if (!coverImagePath && coverPath) { try { const fs = await import('fs/promises'); await fs.unlink(coverPath); logger.debug('临时封面图文件已清理', { coverPath }); } catch (cleanupError) { logger.warn('清理临时封面图文件失败', { error: cleanupError.message }); } } } catch (error) { logger.warn('封面图上传失败,将继续发布', { error: error.message }); // 不抛出错误,继续发布流程 } } // 5. 发布或预览文章 let result; if (previewMode) { if (!previewOpenId) { throw new Error('预览模式需要提供previewOpenId参数'); } logger.debug('开始预览文章', { previewOpenId }); result = await wechatAPI.previewArticle({ title, content: htmlContent, author, thumbMediaId, previewOpenId }); } else { logger.debug('开始正式发布文章'); result = await wechatAPI.publishArticle({ title, content: htmlContent, author, thumbMediaId }); } const executionTime = Date.now() - startTime; logger.info(`文章${previewMode ? '预览' : '发布'}成功`, { ...result, executionTime: `${executionTime}ms` }); // 6. 构建成功响应 const successMessage = this.buildSuccessMessage({ title, author, result, previewMode, executionTime, thumbMediaId }); return { content: [{ type: "text", text: successMessage }] }; } catch (error) { const executionTime = Date.now() - startTime; logger.error('发布流程失败', { error: error.message, executionTime: `${executionTime}ms`, stack: error.stack }); return { content: [{ type: "text", text: this.buildErrorMessage(error, params) }], isError: true }; } }
  • src/server.js:27-71 (registration)
    MCP tool registration for 'wechat_publish_article' using server.registerTool(), including input schema and async handler that invokes WeChatPublisher.publish().
    server.registerTool( "wechat_publish_article", { description: "将文章发布到微信公众号,支持Markdown格式", inputSchema: { title: z.string().describe("文章标题"), content: z.string().describe("Markdown格式的文章内容"), author: z.string().describe("作者名称"), appId: z.string().describe("微信公众号AppID"), appSecret: z.string().describe("微信公众号AppSecret"), coverImagePath: z.string().optional().describe("封面图片路径"), previewMode: z.boolean().default(false).describe("是否为预览模式"), previewOpenId: z.string().optional().describe("预览用户OpenID") } }, async (params) => { const { title, content, author, appId, appSecret, coverImagePath, previewMode, previewOpenId } = params; logger.info(`Publishing article: ${title}`); try { // 调用实际的发布逻辑 const result = await WeChatPublisher.publish({ title, content, author, appId, appSecret, coverImagePath, previewMode, previewOpenId }); return result; } catch (error) { logger.error(`发布失败: ${error.message}`); return { content: [{ type: "text", text: `❌ 发布失败: ${error.message}` }], isError: true }; } } );
  • Zod-based input schema defining parameters for the tool: title, content, author, appId, appSecret, coverImagePath, previewMode, previewOpenId.
    description: "将文章发布到微信公众号,支持Markdown格式", inputSchema: { title: z.string().describe("文章标题"), content: z.string().describe("Markdown格式的文章内容"), author: z.string().describe("作者名称"), appId: z.string().describe("微信公众号AppID"), appSecret: z.string().describe("微信公众号AppSecret"), coverImagePath: z.string().optional().describe("封面图片路径"), previewMode: z.boolean().default(false).describe("是否为预览模式"), previewOpenId: z.string().optional().describe("预览用户OpenID") }
  • Comprehensive parameter validation logic validatePublishParams() enforcing types, lengths, formats (e.g., AppID starts with 'wx', length 18), and business rules (e.g., previewMode requires previewOpenId).
    function validatePublishParams(params) { const errors = []; // 必需参数检查 if (!params.title || typeof params.title !== 'string' || params.title.trim() === '') { errors.push('title参数是必需的,且不能为空字符串'); } if (!params.content || typeof params.content !== 'string' || params.content.trim() === '') { errors.push('content参数是必需的,且不能为空字符串'); } if (!params.appId || typeof params.appId !== 'string' || params.appId.trim() === '') { errors.push('appId参数是必需的,且不能为空字符串'); } if (!params.appSecret || typeof params.appSecret !== 'string' || params.appSecret.trim() === '') { errors.push('appSecret参数是必需的,且不能为空字符串'); } // 可选参数类型检查 if (params.author && typeof params.author !== 'string') { errors.push('author参数必须是字符串类型'); } if (params.coverImagePath && typeof params.coverImagePath !== 'string') { errors.push('coverImagePath参数必须是字符串类型'); } if (params.previewMode !== undefined && typeof params.previewMode !== 'boolean') { errors.push('previewMode参数必须是布尔值类型'); } if (params.previewOpenId && typeof params.previewOpenId !== 'string') { errors.push('previewOpenId参数必须是字符串类型'); } // 业务规则验证 if (params.title && params.title.length > 64) { errors.push('标题长度不能超过64个字符'); } if (params.author && params.author.length > 8) { errors.push('作者名称长度不能超过8个字符'); } if (params.content && params.content.length > 200000) { errors.push('文章内容长度不能超过200,000个字符'); } // AppID格式验证 if (params.appId && !params.appId.startsWith('wx')) { errors.push('AppID格式错误,应该以"wx"开头'); } if (params.appId && params.appId.length !== 18) { errors.push('AppID长度应该为18个字符'); } // AppSecret格式验证 if (params.appSecret && params.appSecret.length !== 32) { errors.push('AppSecret长度应该为32个字符'); } // 预览模式验证 if (params.previewMode === true && !params.previewOpenId) { errors.push('预览模式下必须提供previewOpenId参数'); } // OpenID格式验证(如果提供了) if (params.previewOpenId && !isValidOpenId(params.previewOpenId)) { errors.push('previewOpenId格式不正确'); } return { valid: errors.length === 0, errors }; }
  • Helper function generateCoverImage() that automatically creates attractive cover images from title/content if none provided, supporting PNG via canvas or SVG fallback, with themed designs.
    static async generateCoverImage(title, content) { try { const path = await import('path'); const fs = await import('fs/promises'); // 提取文章关键信息 const cleanTitle = title.replace(/[#*`]/g, '').trim(); const shortTitle = cleanTitle.length > 20 ? cleanTitle.substring(0, 20) + '...' : cleanTitle; // 从内容中提取关键词或副标题 const contentLines = content.split('\n').filter(line => line.trim()); let subtitle = ''; for (const line of contentLines) { const cleanLine = line.replace(/[#*`<>]/g, '').trim(); if (cleanLine.length > 10 && cleanLine.length < 50 && !cleanLine.includes('http')) { subtitle = cleanLine; break; } } if (!subtitle) { subtitle = '精彩内容,值得一读'; } // 选择背景颜色(根据标题内容智能选择) const colors = [ { bg: '#3498db', text: '#ffffff', accent: '#2980b9' }, // 蓝色主题 { bg: '#e74c3c', text: '#ffffff', accent: '#c0392b' }, // 红色主题 { bg: '#2ecc71', text: '#ffffff', accent: '#27ae60' }, // 绿色主题 { bg: '#9b59b6', text: '#ffffff', accent: '#8e44ad' }, // 紫色主题 { bg: '#f39c12', text: '#ffffff', accent: '#e67e22' }, // 橙色主题 { bg: '#1abc9c', text: '#ffffff', accent: '#16a085' }, // 青色主题 ]; // 根据标题内容选择颜色 let colorIndex = 0; if (title.includes('AI') || title.includes('技术')) colorIndex = 0; else if (title.includes('重要') || title.includes('紧急')) colorIndex = 1; else if (title.includes('成功') || title.includes('增长')) colorIndex = 2; else if (title.includes('创新') || title.includes('未来')) colorIndex = 3; else if (title.includes('警告') || title.includes('注意')) colorIndex = 4; else colorIndex = Math.floor(Math.random() * colors.length); const theme = colors[colorIndex]; // 创建Canvas并生成PNG图片 const timestamp = Date.now(); const coverPath = path.default.join(process.cwd(), `auto-cover-${timestamp}.png`); // 使用Canvas API生成PNG图片 await WeChatPublisher.createPngCover({ title: shortTitle, subtitle: subtitle.substring(0, 30), theme, outputPath: coverPath }); // 检查文件大小 const stats = await fs.stat(coverPath); const fileSizeInMB = stats.size / (1024 * 1024); if (fileSizeInMB > 1) { logger.warn('生成的封面图超过1MB,尝试压缩', { size: `${fileSizeInMB.toFixed(2)}MB` }); // 如果文件过大,可以在这里添加压缩逻辑 } logger.info('自动生成封面图成功', { coverPath, title: shortTitle, size: `${fileSizeInMB.toFixed(2)}MB` }); return coverPath; } catch (error) { logger.error('自动生成封面图失败', { error: error.message }); throw new Error(`自动生成封面图失败: ${error.message}`); } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/BobGod/wechat-publisher-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server