md2wechat-mcp 0.1.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/markdown.js CHANGED
@@ -18,6 +18,10 @@ export function inlineFormat(text, theme) {
18
18
  escaped = escaped.replace(/`([^`]+)`/g, (_, code) => {
19
19
  return stash(`<code style=\"${theme.code_inline}\">${code}</code>`);
20
20
  });
21
+ // Images must be processed before links (pattern starts with `!`)
22
+ escaped = escaped.replace(/!\[([^\]]*)\]\((https?:\/\/[^\s)]+)\)/g, (_m, alt, src) => {
23
+ return stash(`<img src=\"${src}\" alt=\"${alt}\" style=\"max-width:100%;height:auto;display:block;margin:0.8em auto;\" />`);
24
+ });
21
25
  escaped = escaped.replace(/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g, (_m, label, href) => {
22
26
  return `<a href=\"${href}\" style=\"${theme.a}\">${label}</a>`;
23
27
  });
@@ -68,6 +72,8 @@ export function parseMarkdown(md, themeName = "default", title) {
68
72
  let codeLines = [];
69
73
  let listType;
70
74
  let listItems = [];
75
+ let olStart = 1;
76
+ let olNextExpected;
71
77
  let paragraphBuffer = [];
72
78
  const flushParagraph = () => {
73
79
  if (paragraphBuffer.length > 0) {
@@ -81,7 +87,15 @@ export function parseMarkdown(md, themeName = "default", title) {
81
87
  const flushList = () => {
82
88
  if (listType && listItems.length > 0) {
83
89
  const renderedItems = listItems.map((item) => `<li style=\"${theme.li}\">${item}</li>`).join("");
84
- out.push(`<${listType} style=\"margin: 0.6em 0 0.9em 1.2em; padding: 0;\">${renderedItems}</${listType}>`);
90
+ if (listType === "ol") {
91
+ const startAttr = olStart !== 1 ? ` start=\"${olStart}\"` : "";
92
+ out.push(`<ol${startAttr} style=\"margin: 0.6em 0 0.9em 1.2em; padding: 0;\">${renderedItems}</ol>`);
93
+ olNextExpected = olStart + listItems.length;
94
+ }
95
+ else {
96
+ out.push(`<ul style=\"margin: 0.6em 0 0.9em 1.2em; padding: 0;\">${renderedItems}</ul>`);
97
+ olNextExpected = undefined;
98
+ }
85
99
  }
86
100
  listType = undefined;
87
101
  listItems = [];
@@ -209,14 +223,25 @@ export function parseMarkdown(md, themeName = "default", title) {
209
223
  listItems.push(inlineFormat(ul[1], theme));
210
224
  continue;
211
225
  }
212
- const ol = line.match(/^\s*\d+\.\s+(.+)$/u);
226
+ const ol = line.match(/^\s*(\d+)\.\s+(.+)$/u);
213
227
  if (ol) {
214
228
  flushParagraph();
229
+ const itemNum = parseInt(ol[1], 10);
215
230
  if (listType && listType !== "ol") {
216
231
  flushList();
217
232
  }
233
+ if (listType !== "ol") {
234
+ // Starting a new ol group: check if this continues a previous flushed group
235
+ if (olNextExpected !== undefined && itemNum === olNextExpected) {
236
+ olStart = itemNum;
237
+ }
238
+ else {
239
+ olStart = itemNum;
240
+ olNextExpected = undefined;
241
+ }
242
+ }
218
243
  listType = "ol";
219
- listItems.push(inlineFormat(ol[1], theme));
244
+ listItems.push(inlineFormat(ol[2], theme));
220
245
  continue;
221
246
  }
222
247
  flushList();
package/dist/server.js CHANGED
@@ -8,15 +8,46 @@ import pkg from "../package.json" with { type: "json" };
8
8
  const SERVER_NAME = pkg.name;
9
9
  const SERVER_VERSION = pkg.version;
10
10
  const themeSchema = z.enum(["default", "tech", "warm", "apple", "wechat-native"]);
11
+ const SERVER_INSTRUCTIONS = `
12
+ ## 发布 Markdown 图文到微信公众号草稿箱
13
+
14
+ 前置条件:MCP server 配置环境变量 WECHAT_APPID 和 WECHAT_APPSECRET。
15
+
16
+ 完整工作流:
17
+
18
+ Step 1 — 获取 access_token
19
+ wechat_get_access_token() → { access_token, expires_in }
20
+ 同一次任务保存复用,无需重复获取(有效期 7200 秒)。
21
+
22
+ Step 2 — 转换 Markdown 并自动上传本地图片
23
+ convert_markdown_to_wechat_html(markdown_path, access_token, theme?, title?)
24
+ 传入 access_token 后,![alt](./local/path) 形式的本地图片自动上传并替换为微信 CDN URL。
25
+
26
+ Step 3(可选)— 上传文章内嵌图片
27
+ wechat_upload_image(access_token, file_path) → { url }
28
+ 返回的 url 可用于文章 HTML 中的 <img src>,但不能用作封面 thumb_media_id。
29
+
30
+ Step 4 — 新增草稿
31
+ wechat_draft_add(access_token, articles: [{ title, content, author?, digest?, thumb_media_id?, ... }])
32
+ content 使用 Step 2 返回的 HTML。返回 media_id 可用于后续更新或删除。
33
+
34
+ 其他工具:
35
+ wechat_draft_batchget — 分页查询草稿列表
36
+ wechat_draft_update — 按 media_id 更新草稿
37
+ wechat_draft_delete — 按 media_id 删除草稿
38
+
39
+ 常见错误:40005=图片格式不支持,40009=图片超1MB,access_token过期重新调用Step1。
40
+ `.trim();
11
41
  export function createServer() {
12
- const server = new McpServer({ name: SERVER_NAME, version: SERVER_VERSION });
42
+ const server = new McpServer({ name: SERVER_NAME, version: SERVER_VERSION }, { instructions: SERVER_INSTRUCTIONS });
13
43
  server.registerTool("convert_markdown_to_wechat_html", {
14
44
  description: "Convert Markdown to WeChat-friendly HTML with inline styles for copy/paste publishing workflows.",
15
45
  inputSchema: {
16
46
  markdown: z.string().optional().describe("Source markdown text. If both markdown and markdown_path are provided, markdown is used."),
17
47
  markdown_path: z.string().optional().describe("Local markdown file path. Use this to avoid sending full content through tokens."),
18
48
  theme: themeSchema.optional().default("default").describe("Theme name: default | tech | warm | apple | wechat-native"),
19
- title: z.string().optional().describe("Optional article title rendered as h1")
49
+ title: z.string().optional().describe("Optional article title rendered as h1"),
50
+ access_token: z.string().optional().describe("WeChat API access token. When provided, local images referenced as ![alt](./path) are automatically uploaded to WeChat CDN and replaced with permanent URLs.")
20
51
  }
21
52
  }, async (args) => handleToolCall("convert_markdown_to_wechat_html", args));
22
53
  server.registerTool("list_wechat_themes", {
@@ -29,6 +60,60 @@ export function createServer() {
29
60
  cacheHtmlPath: z.string().describe("Existing cached HTML file path to open directly")
30
61
  }
31
62
  }, async (args) => handleToolCall("open_wechat_html_in_browser", args));
63
+ const articleSchema = z.object({
64
+ article_type: z.enum(["news", "newspic"]).optional().describe("Article type: news (text+image) or newspic (pure image). Defaults to news."),
65
+ title: z.string().describe("Article title (max 32 chars)"),
66
+ author: z.string().optional().describe("Author name (max 16 chars)"),
67
+ digest: z.string().optional().describe("Article summary shown in list view (max 128 chars)"),
68
+ content: z.string().describe("Article HTML content (max 20000 chars). Use convert_markdown_to_wechat_html to generate this."),
69
+ content_source_url: z.string().optional().describe("Original article URL (max 1024 chars)"),
70
+ thumb_media_id: z.string().optional().describe("Cover image media ID obtained from WeChat material upload API"),
71
+ need_open_comment: z.union([z.literal(0), z.literal(1)]).optional().describe("Enable comments: 1 = yes, 0 = no"),
72
+ only_fans_can_comment: z.union([z.literal(0), z.literal(1)]).optional().describe("Only followers can comment: 1 = yes, 0 = no")
73
+ });
74
+ server.registerTool("wechat_get_access_token", {
75
+ description: "Get WeChat API access_token. Reads AppID and AppSecret from environment variables WECHAT_APPID and WECHAT_APPSECRET. The token is valid for 7200 seconds.",
76
+ inputSchema: {}
77
+ }, async () => handleToolCall("wechat_get_access_token", {}));
78
+ server.registerTool("wechat_draft_add", {
79
+ description: "Add a new draft to WeChat Official Account draft box. Returns media_id for the created draft.",
80
+ inputSchema: {
81
+ access_token: z.string().describe("WeChat API access token from wechat_get_access_token"),
82
+ articles: z.array(articleSchema).min(1).describe("Array of articles to include in the draft (usually 1)")
83
+ }
84
+ }, async (args) => handleToolCall("wechat_draft_add", args));
85
+ server.registerTool("wechat_draft_update", {
86
+ description: "Update an existing draft in WeChat Official Account draft box by media_id.",
87
+ inputSchema: {
88
+ access_token: z.string().describe("WeChat API access token from wechat_get_access_token"),
89
+ media_id: z.string().describe("Draft media_id returned from wechat_draft_add"),
90
+ index: z.number().int().min(0).default(0).describe("0-based index of article to update within the draft"),
91
+ article: articleSchema.describe("Updated article content")
92
+ }
93
+ }, async (args) => handleToolCall("wechat_draft_update", args));
94
+ server.registerTool("wechat_upload_image", {
95
+ description: "Upload a local JPG/PNG image (max 1MB) to WeChat for use in article content. Returns a permanent image URL. Does not count toward the 100,000 material limit.",
96
+ inputSchema: {
97
+ access_token: z.string().describe("WeChat API access token from wechat_get_access_token"),
98
+ file_path: z.string().describe("Absolute local path to the image file (JPG or PNG, max 1MB)")
99
+ }
100
+ }, async (args) => handleToolCall("wechat_upload_image", args));
101
+ server.registerTool("wechat_draft_batchget", {
102
+ description: "Query draft list from WeChat Official Account draft box with pagination.",
103
+ inputSchema: {
104
+ access_token: z.string().describe("WeChat API access token from wechat_get_access_token"),
105
+ offset: z.number().int().min(0).default(0).describe("Starting offset (0 = first item)"),
106
+ count: z.number().int().min(1).max(20).default(10).describe("Number of drafts to return (1-20)"),
107
+ no_content: z.union([z.literal(0), z.literal(1)]).optional().default(0).describe("1 = exclude article content field to reduce response size, 0 = include (default)")
108
+ }
109
+ }, async (args) => handleToolCall("wechat_draft_batchget", args));
110
+ server.registerTool("wechat_draft_delete", {
111
+ description: "Delete a draft from WeChat Official Account draft box by media_id. This action is irreversible.",
112
+ inputSchema: {
113
+ access_token: z.string().describe("WeChat API access token from wechat_get_access_token"),
114
+ media_id: z.string().describe("Draft media_id to delete")
115
+ }
116
+ }, async (args) => handleToolCall("wechat_draft_delete", args));
32
117
  return server;
33
118
  }
34
119
  export async function main() {
package/dist/tools.js CHANGED
@@ -3,6 +3,7 @@ import { saveHtmlCache } from "./cache.js";
3
3
  import { openFileInBrowser } from "./browser.js";
4
4
  import { THEME_NAMES, THEMES } from "./themes.js";
5
5
  import { readFile } from "node:fs/promises";
6
+ import { getAccessToken, draftAdd, draftUpdate, draftDelete, draftBatchGet, uploadImage } from "./wechat-api.js";
6
7
  export function listThemesPayload() {
7
8
  return {
8
9
  themes: [
@@ -76,6 +77,37 @@ export async function handleToolCall(name, args) {
76
77
  return invalidThemeResult(theme);
77
78
  }
78
79
  const title = typeof args.title === "string" ? args.title : undefined;
80
+ // Upload local images if access_token is provided
81
+ const accessToken = typeof args.access_token === "string" ? args.access_token.trim() : undefined;
82
+ if (accessToken) {
83
+ const baseDir = typeof markdownPath === "string" ? markdownPath.replace(/[^/\\]+$/, "") : "";
84
+ const localImagePattern = /!\[([^\]]*)\]\((?!https?:\/\/)([^)]+)\)/g;
85
+ const uploadErrors = [];
86
+ const uploadCache = new Map();
87
+ const matches = [...markdown.matchAll(localImagePattern)];
88
+ for (const match of matches) {
89
+ const rawPath = match[2];
90
+ if (!rawPath || uploadCache.has(rawPath))
91
+ continue;
92
+ const absPath = rawPath.startsWith("/") ? rawPath : `${baseDir}${rawPath}`;
93
+ try {
94
+ const result = await uploadImage(accessToken, absPath);
95
+ uploadCache.set(rawPath, result.url);
96
+ }
97
+ catch (error) {
98
+ uploadErrors.push(`${rawPath}: ${error.message}`);
99
+ }
100
+ }
101
+ for (const [localPath, cdnUrl] of uploadCache) {
102
+ markdown = markdown.replaceAll(`](${localPath})`, `](${cdnUrl})`);
103
+ }
104
+ if (uploadErrors.length > 0) {
105
+ return {
106
+ content: [{ type: "text", text: `Image upload failed:\n${uploadErrors.join("\n")}` }],
107
+ isError: true
108
+ };
109
+ }
110
+ }
79
111
  const html = parseMarkdown(markdown, theme, title);
80
112
  const savedPath = saveHtmlCache(html);
81
113
  return {
@@ -112,6 +144,125 @@ export async function handleToolCall(name, args) {
112
144
  meta: { cacheHtmlPath: openedPath }
113
145
  };
114
146
  }
147
+ if (name === "wechat_get_access_token") {
148
+ const appid = process.env.WECHAT_APPID;
149
+ const secret = process.env.WECHAT_APPSECRET;
150
+ if (!appid) {
151
+ return { content: [{ type: "text", text: "Environment variable WECHAT_APPID is not set." }], isError: true };
152
+ }
153
+ if (!secret) {
154
+ return { content: [{ type: "text", text: "Environment variable WECHAT_APPSECRET is not set." }], isError: true };
155
+ }
156
+ try {
157
+ const result = await getAccessToken(appid, secret);
158
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
159
+ }
160
+ catch (error) {
161
+ return { content: [{ type: "text", text: error.message }], isError: true };
162
+ }
163
+ }
164
+ if (name === "wechat_draft_add") {
165
+ const accessToken = args.access_token;
166
+ const articles = args.articles;
167
+ if (typeof accessToken !== "string" || !accessToken.trim()) {
168
+ return { content: [{ type: "text", text: "access_token is required." }], isError: true };
169
+ }
170
+ if (!Array.isArray(articles) || articles.length === 0) {
171
+ return { content: [{ type: "text", text: "articles must be a non-empty array." }], isError: true };
172
+ }
173
+ try {
174
+ const result = await draftAdd(accessToken, articles);
175
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
176
+ }
177
+ catch (error) {
178
+ return { content: [{ type: "text", text: error.message }], isError: true };
179
+ }
180
+ }
181
+ if (name === "wechat_draft_update") {
182
+ const accessToken = args.access_token;
183
+ const mediaId = args.media_id;
184
+ const index = args.index ?? 0;
185
+ const article = args.article;
186
+ if (typeof accessToken !== "string" || !accessToken.trim()) {
187
+ return { content: [{ type: "text", text: "access_token is required." }], isError: true };
188
+ }
189
+ if (typeof mediaId !== "string" || !mediaId.trim()) {
190
+ return { content: [{ type: "text", text: "media_id is required." }], isError: true };
191
+ }
192
+ if (typeof index !== "number") {
193
+ return { content: [{ type: "text", text: "index must be a number." }], isError: true };
194
+ }
195
+ if (typeof article !== "object" || article === null) {
196
+ return { content: [{ type: "text", text: "article is required." }], isError: true };
197
+ }
198
+ try {
199
+ const result = await draftUpdate(accessToken, mediaId, index, article);
200
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
201
+ }
202
+ catch (error) {
203
+ return { content: [{ type: "text", text: error.message }], isError: true };
204
+ }
205
+ }
206
+ if (name === "wechat_upload_image") {
207
+ const accessToken = args.access_token;
208
+ const filePath = args.file_path;
209
+ if (typeof accessToken !== "string" || !accessToken.trim()) {
210
+ return { content: [{ type: "text", text: "access_token is required." }], isError: true };
211
+ }
212
+ if (typeof filePath !== "string" || !filePath.trim()) {
213
+ return { content: [{ type: "text", text: "file_path is required." }], isError: true };
214
+ }
215
+ const ext = filePath.toLowerCase().split(".").pop();
216
+ if (ext !== "jpg" && ext !== "jpeg" && ext !== "png") {
217
+ return { content: [{ type: "text", text: "Only JPG and PNG files are supported." }], isError: true };
218
+ }
219
+ try {
220
+ const result = await uploadImage(accessToken, filePath);
221
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
222
+ }
223
+ catch (error) {
224
+ return { content: [{ type: "text", text: error.message }], isError: true };
225
+ }
226
+ }
227
+ if (name === "wechat_draft_batchget") {
228
+ const accessToken = args.access_token;
229
+ const offset = args.offset ?? 0;
230
+ const count = args.count ?? 10;
231
+ const noContent = args.no_content ?? 0;
232
+ if (typeof accessToken !== "string" || !accessToken.trim()) {
233
+ return { content: [{ type: "text", text: "access_token is required." }], isError: true };
234
+ }
235
+ if (typeof offset !== "number" || typeof count !== "number") {
236
+ return { content: [{ type: "text", text: "offset and count must be numbers." }], isError: true };
237
+ }
238
+ if (count < 1 || count > 20) {
239
+ return { content: [{ type: "text", text: "count must be between 1 and 20." }], isError: true };
240
+ }
241
+ try {
242
+ const result = await draftBatchGet(accessToken, offset, count, noContent);
243
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
244
+ }
245
+ catch (error) {
246
+ return { content: [{ type: "text", text: error.message }], isError: true };
247
+ }
248
+ }
249
+ if (name === "wechat_draft_delete") {
250
+ const accessToken = args.access_token;
251
+ const mediaId = args.media_id;
252
+ if (typeof accessToken !== "string" || !accessToken.trim()) {
253
+ return { content: [{ type: "text", text: "access_token is required." }], isError: true };
254
+ }
255
+ if (typeof mediaId !== "string" || !mediaId.trim()) {
256
+ return { content: [{ type: "text", text: "media_id is required." }], isError: true };
257
+ }
258
+ try {
259
+ const result = await draftDelete(accessToken, mediaId);
260
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
261
+ }
262
+ catch (error) {
263
+ return { content: [{ type: "text", text: error.message }], isError: true };
264
+ }
265
+ }
115
266
  return {
116
267
  content: [{ type: "text", text: `Unknown tool: ${name}` }],
117
268
  isError: true
@@ -0,0 +1,54 @@
1
+ export interface WechatArticle {
2
+ article_type?: "news" | "newspic";
3
+ title: string;
4
+ author?: string;
5
+ digest?: string;
6
+ content: string;
7
+ content_source_url?: string;
8
+ thumb_media_id?: string;
9
+ need_open_comment?: 0 | 1;
10
+ only_fans_can_comment?: 0 | 1;
11
+ }
12
+ export declare function getAccessToken(appid: string, secret: string): Promise<{
13
+ access_token: string;
14
+ expires_in: number;
15
+ }>;
16
+ export declare function draftAdd(accessToken: string, articles: WechatArticle[]): Promise<{
17
+ media_id: string;
18
+ }>;
19
+ export declare function draftUpdate(accessToken: string, mediaId: string, index: number, article: WechatArticle): Promise<{
20
+ errcode: number;
21
+ errmsg: string;
22
+ }>;
23
+ export interface DraftNewsItem {
24
+ article_type: "news" | "newspic";
25
+ title: string;
26
+ author: string;
27
+ digest: string;
28
+ content: string;
29
+ content_source_url: string;
30
+ thumb_media_id: string;
31
+ need_open_comment: number;
32
+ only_fans_can_comment: number;
33
+ url: string;
34
+ }
35
+ export interface DraftItem {
36
+ media_id: string;
37
+ content: {
38
+ news_item: DraftNewsItem[];
39
+ };
40
+ update_time: number;
41
+ }
42
+ export interface DraftBatchGetResult {
43
+ total_count: number;
44
+ item_count: number;
45
+ item: DraftItem[];
46
+ }
47
+ export declare function draftBatchGet(accessToken: string, offset: number, count: number, noContent?: 0 | 1): Promise<DraftBatchGetResult>;
48
+ export declare function uploadImage(accessToken: string, filePath: string): Promise<{
49
+ url: string;
50
+ }>;
51
+ export declare function draftDelete(accessToken: string, mediaId: string): Promise<{
52
+ errcode: number;
53
+ errmsg: string;
54
+ }>;
@@ -0,0 +1,59 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { basename, extname } from "node:path";
3
+ const WECHAT_BASE = "https://api.weixin.qq.com";
4
+ async function wechatFetch(url, options) {
5
+ const res = await fetch(url, options);
6
+ if (!res.ok) {
7
+ throw new Error(`HTTP ${res.status}: ${res.statusText}`);
8
+ }
9
+ const data = (await res.json());
10
+ if (typeof data.errcode === "number" && data.errcode !== 0) {
11
+ throw new Error(`WeChat API error ${data.errcode}: ${data.errmsg ?? "unknown"}`);
12
+ }
13
+ return data;
14
+ }
15
+ export async function getAccessToken(appid, secret) {
16
+ const url = `${WECHAT_BASE}/cgi-bin/token?grant_type=client_credential&appid=${encodeURIComponent(appid)}&secret=${encodeURIComponent(secret)}`;
17
+ return wechatFetch(url);
18
+ }
19
+ export async function draftAdd(accessToken, articles) {
20
+ const url = `${WECHAT_BASE}/cgi-bin/draft/add?access_token=${encodeURIComponent(accessToken)}`;
21
+ return wechatFetch(url, {
22
+ method: "POST",
23
+ headers: { "Content-Type": "application/json; charset=utf-8" },
24
+ body: JSON.stringify({ articles })
25
+ });
26
+ }
27
+ export async function draftUpdate(accessToken, mediaId, index, article) {
28
+ const url = `${WECHAT_BASE}/cgi-bin/draft/update?access_token=${encodeURIComponent(accessToken)}`;
29
+ return wechatFetch(url, {
30
+ method: "POST",
31
+ headers: { "Content-Type": "application/json; charset=utf-8" },
32
+ body: JSON.stringify({ media_id: mediaId, index, articles: article })
33
+ });
34
+ }
35
+ export async function draftBatchGet(accessToken, offset, count, noContent) {
36
+ const url = `${WECHAT_BASE}/cgi-bin/draft/batchget?access_token=${encodeURIComponent(accessToken)}`;
37
+ return wechatFetch(url, {
38
+ method: "POST",
39
+ headers: { "Content-Type": "application/json; charset=utf-8" },
40
+ body: JSON.stringify({ offset, count, no_content: noContent ?? 0 })
41
+ });
42
+ }
43
+ export async function uploadImage(accessToken, filePath) {
44
+ const url = `${WECHAT_BASE}/cgi-bin/media/uploadimg?access_token=${encodeURIComponent(accessToken)}`;
45
+ const fileBuffer = await readFile(filePath);
46
+ const ext = extname(filePath).toLowerCase();
47
+ const mimeType = ext === ".png" ? "image/png" : "image/jpeg";
48
+ const form = new FormData();
49
+ form.append("media", new Blob([fileBuffer], { type: mimeType }), basename(filePath));
50
+ return wechatFetch(url, { method: "POST", body: form });
51
+ }
52
+ export async function draftDelete(accessToken, mediaId) {
53
+ const url = `${WECHAT_BASE}/cgi-bin/draft/delete?access_token=${encodeURIComponent(accessToken)}`;
54
+ return wechatFetch(url, {
55
+ method: "POST",
56
+ headers: { "Content-Type": "application/json; charset=utf-8" },
57
+ body: JSON.stringify({ media_id: mediaId })
58
+ });
59
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "md2wechat-mcp",
3
- "version": "0.1.3",
3
+ "version": "0.2.0",
4
4
  "directories": {
5
5
  "test": "tests"
6
6
  },