Skip to main content
Glama
permanent-media-tool.ts10.2 kB
import { z } from 'zod'; import { WechatToolResult, McpTool } from '../types.js'; import { WechatApiClient } from '../../wechat/api-client.js'; import { logger } from '../../utils/logger.js'; import FormData from 'form-data'; // 永久素材工具参数Schema (暂未使用,保留用于未来扩展) // eslint-disable-next-line @typescript-eslint/no-unused-vars const permanentMediaToolSchema = z.object({ action: z.enum(['add', 'get', 'delete', 'list', 'count']), type: z.enum(['image', 'voice', 'video', 'thumb', 'news']).optional(), mediaId: z.string().optional(), filePath: z.string().optional(), fileData: z.string().optional(), fileName: z.string().optional(), title: z.string().optional(), introduction: z.string().optional(), offset: z.number().optional(), count: z.number().optional(), }); /** * 永久素材工具处理器 */ async function handlePermanentMediaTool(args: unknown, apiClient: WechatApiClient): Promise<WechatToolResult> { // MCP SDK已经验证了参数,直接使用 const { action } = args as any; try { switch (action) { case 'add': { const { type, filePath, fileData, fileName, title, introduction } = args as any; if (!type) { throw new Error('素材类型不能为空'); } if (!fileData && !filePath) { throw new Error('文件数据或文件路径不能为空'); } // 准备文件数据 let mediaBuffer: Buffer; let actualFileName: string; if (fileData) { // 如果提供了base64数据 mediaBuffer = Buffer.from(fileData, 'base64'); actualFileName = fileName || `media.${type === 'image' ? 'jpg' : type === 'voice' ? 'mp3' : type === 'video' ? 'mp4' : 'jpg'}`; } else if (filePath) { // 如果提供了文件路径 const fs = await import('fs'); mediaBuffer = fs.readFileSync(filePath); actualFileName = fileName || filePath.split('/').pop() || 'media'; } else { throw new Error('无效的文件数据'); } try { const result = await apiClient.post( `/cgi-bin/material/add_material?type=${type}`, (() => { const formData = new FormData(); formData.append('media', mediaBuffer, actualFileName); // 视频素材需要描述信息 if (type === 'video' && (title || introduction)) { const description = { title: title || '视频标题', introduction: introduction || '视频简介' }; formData.append('description', JSON.stringify(description)); } return formData; })() ) as any; return { content: [{ type: 'text', text: `永久素材上传成功!\n素材ID: ${result.media_id}${result.url ? `\n素材URL: ${result.url}` : ''}`, }], }; } catch (error) { throw new Error(`上传永久素材失败: ${error instanceof Error ? error.message : '未知错误'}`); } } case 'get': { const { mediaId } = args as any; if (!mediaId) { throw new Error('素材ID不能为空'); } try { const result = await apiClient.post('/cgi-bin/material/get_material', { media_id: mediaId }) as any; // 如果是图文素材,返回详细信息 if (result.news_item) { const articles = result.news_item.map((item: any, index: number) => `第${index + 1}篇:\n` + `标题: ${item.title}\n` + `作者: ${item.author || '未设置'}\n` + `摘要: ${item.digest || '无'}\n` + `链接: ${item.url}\n` + `封面图: ${item.thumb_url}\n` ).join('\n'); return { content: [{ type: 'text', text: `获取永久图文素材成功!\n\n${articles}`, }], }; } // 如果是其他类型素材,返回基本信息 return { content: [{ type: 'text', text: `获取永久素材成功!\n素材ID: ${mediaId}\n创建时间: ${new Date(result.create_time * 1000).toLocaleString()}${result.url ? `\n素材URL: ${result.url}` : ''}`, }], }; } catch (error) { throw new Error(`获取永久素材失败: ${error instanceof Error ? error.message : '未知错误'}`); } } case 'delete': { const { mediaId: deleteMediaId } = args as any; if (!deleteMediaId) { throw new Error('素材ID不能为空'); } try { await apiClient.post('/cgi-bin/material/del_material', { media_id: deleteMediaId }) as any; return { content: [{ type: 'text', text: `永久素材删除成功!\n素材ID: ${deleteMediaId}`, }], }; } catch (error) { throw new Error(`删除永久素材失败: ${error instanceof Error ? error.message : '未知错误'}`); } } case 'list': { const { type: listType, offset = 0, count = 20 } = args as any; if (!listType) { throw new Error('素材类型不能为空'); } try { const result = await apiClient.post('/cgi-bin/material/batchget_material', { type: listType, offset, count }) as any; if (listType === 'news') { // 图文素材列表 const newsList = result.item.map((item: any, index: number) => { const articles = item.content.news_item.map((article: any, articleIndex: number) => ` 第${articleIndex + 1}篇: ${article.title}` ).join('\n'); return `${offset + index + 1}. 素材ID: ${item.media_id}\n` + ` 更新时间: ${new Date(item.update_time * 1000).toLocaleString()}\n` + ` 文章列表:\n${articles}`; }).join('\n\n'); return { content: [{ type: 'text', text: `永久图文素材列表 (${offset + 1}-${offset + result.item.length}/${result.total_count}):\n\n${newsList}`, }], }; } else { // 其他类型素材列表 const mediaList = result.item.map((item: any, index: number) => `${offset + index + 1}. 素材ID: ${item.media_id}\n` + ` 文件名: ${item.name}\n` + ` 更新时间: ${new Date(item.update_time * 1000).toLocaleString()}${item.url ? `\n URL: ${item.url}` : ''}` ).join('\n\n'); return { content: [{ type: 'text', text: `永久${listType}素材列表 (${offset + 1}-${offset + result.item.length}/${result.total_count}):\n\n${mediaList}`, }], }; } } catch (error) { throw new Error(`获取永久素材列表失败: ${error instanceof Error ? error.message : '未知错误'}`); } } case 'count': { try { const result = await apiClient.get('/cgi-bin/material/get_materialcount') as any; return { content: [{ type: 'text', text: `永久素材统计信息:\n` + `图片素材: ${result.image_count} 个\n` + `语音素材: ${result.voice_count} 个\n` + `视频素材: ${result.video_count} 个\n` + `图文素材: ${result.news_count} 个`, }], }; } catch (error) { throw new Error(`获取永久素材统计失败: ${error instanceof Error ? error.message : '未知错误'}`); } } default: throw new Error(`Unknown action: ${action}`); } } catch (error) { logger.error('Permanent media tool error:', error); return { content: [{ type: 'text', text: `永久素材操作失败: ${error instanceof Error ? error.message : '未知错误'}`, }], isError: true, }; } } /** * 微信公众号永久素材工具 */ export const permanentMediaTool: McpTool = { name: 'wechat_permanent_media', description: '管理微信公众号永久素材,支持添加、获取、删除、列表和统计操作', inputSchema: { action: z.enum(['add', 'get', 'delete', 'list', 'count']).describe('操作类型:add-添加素材, get-获取素材, delete-删除素材, list-获取素材列表, count-获取素材总数'), type: z.enum(['image', 'voice', 'video', 'thumb']).optional().describe('素材类型:image-图片, voice-语音, video-视频, thumb-缩略图'), mediaId: z.string().optional().describe('媒体文件ID(get和delete操作必需)'), filePath: z.string().optional().describe('本地文件路径(add操作必需)'), fileData: z.string().optional().describe('Base64编码的文件数据(add操作可选,与filePath二选一)'), fileName: z.string().optional().describe('文件名(add操作可选)'), title: z.string().optional().describe('视频素材的标题(video类型add操作必需)'), introduction: z.string().optional().describe('视频素材的描述(video类型add操作必需)'), offset: z.number().optional().describe('从全部素材中的该偏移位置开始返回(list操作可选,默认0)'), count: z.number().optional().describe('返回素材的数量(list操作可选,默认20,最大20)') }, handler: handlePermanentMediaTool };

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/xwang152-jack/wechat-official-account-mcp'

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