@wenyan-md/mcp 1.0.8 → 1.0.10

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/README.md CHANGED
@@ -29,7 +29,7 @@
29
29
 
30
30
  - [macOS App Store 版](https://github.com/caol64/wenyan) - MAC 桌面应用
31
31
  - [跨平台桌面版](https://github.com/caol64/wenyan-pc) - Windows/Linux
32
- - [CLI 版本](https://github.com/caol64/wenyan-cli) - 命令行工具
32
+ - [CLI 版本](https://github.com/caol64/wenyan-cli) - 命令行 / CI 自动化发布
33
33
  - 👉 [MCP 版本](https://github.com/caol64/wenyan-mcp) - 本项目
34
34
  - [核心库](https://github.com/caol64/wenyan-core) - 嵌入 Node / Web 项目
35
35
 
@@ -39,7 +39,13 @@
39
39
  - 使用内置主题对 Markdown 内容排版
40
40
  - 自动处理并上传图片(本地 / 网络)
41
41
  - 一键发布文章到微信公众号草稿箱
42
- - **与 AI 深度集成**:让 AI 帮你管理公众号的排版和发布
42
+ - **与 AI 深度集成**:[让 AI 帮你管理公众号的排版和发布](https://babyno.top/posts/2025/06/let-ai-help-you-manage-your-gzh-layout-and-publishing/)
43
+
44
+ <video src="https://github.com/user-attachments/assets/2c355f76-f313-48a7-9c31-f0f69e5ec207"></video>
45
+
46
+ > [!TIP]
47
+ >
48
+ > 如果与 AI 集成遇到问题,可以参考 [test/list.js](./test/list.js) 和 [test/publish.js](./test/publish.js) 中的完整调用示例。
43
49
 
44
50
  ## 主题效果预览
45
51
 
@@ -160,7 +166,7 @@ cover: /Users/xxx/image.jpg
160
166
 
161
167
  ## 微信公众号 IP 白名单
162
168
 
163
- > ⚠️ 重要
169
+ > [!IMPORTANT]
164
170
  >
165
171
  > 请确保运行文颜 MCP Server 的机器 IP 已加入微信公众号后台的 IP 白名单,否则上传接口将调用失败。
166
172
 
package/dist/index.js CHANGED
@@ -9,10 +9,13 @@
9
9
  */
10
10
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
11
11
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
12
- import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
12
+ import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
13
13
  import { getGzhContent } from "@wenyan-md/core/wrapper";
14
14
  import { publishToDraft } from "@wenyan-md/core/publish";
15
15
  import { themes } from "@wenyan-md/core/theme";
16
+ import { getNormalizeFilePath } from "./utils.js";
17
+ import fs from "node:fs/promises";
18
+ import path from "node:path";
16
19
  /**
17
20
  * Create an MCP server with capabilities for resources (to list/read notes),
18
21
  * tools (to create new notes), and prompts (to summarize notes).
@@ -43,14 +46,17 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
43
46
  properties: {
44
47
  content: {
45
48
  type: "string",
46
- description: "The original Markdown content to publish, preserving its frontmatter (if present).",
49
+ description: "The Markdown text to publish. REQUIRED if 'file' is not provided. Preserves frontmatter if present.",
50
+ },
51
+ file: {
52
+ type: "string",
53
+ description: "The path to the Markdown file (absolute or relative). REQUIRED if 'content' is not provided.",
47
54
  },
48
55
  theme_id: {
49
56
  type: "string",
50
57
  description: "ID of the theme to use (e.g., default, orangeheart, rainbow, lapis, pie, maize, purple, phycat).",
51
58
  },
52
59
  },
53
- required: ["content"],
54
60
  },
55
61
  },
56
62
  {
@@ -58,7 +64,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
58
64
  description: "List the themes compatible with the 'publish_article' tool to publish an article to '微信公众号'.",
59
65
  inputSchema: {
60
66
  type: "object",
61
- properties: {}
67
+ properties: {},
62
68
  },
63
69
  },
64
70
  ],
@@ -74,12 +80,33 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
74
80
  // level: "debug",
75
81
  // data: JSON.stringify(request.params.arguments),
76
82
  // });
77
- const content = String(request.params.arguments?.content || "");
83
+ const contentArg = request.params.arguments?.content;
84
+ const fileArg = request.params.arguments?.file;
85
+ if (!contentArg && !fileArg) {
86
+ throw new Error("You must provide either 'content' or 'file' to publish an article.");
87
+ }
88
+ let content = String(contentArg || "");
89
+ const file = String(fileArg || "");
78
90
  const themeId = String(request.params.arguments?.theme_id || "");
91
+ let absoluteDirPath;
92
+ if (!content && file) {
93
+ const normalizePath = getNormalizeFilePath(file);
94
+ content = await fs.readFile(normalizePath, "utf-8");
95
+ if (!content) {
96
+ throw new Error("Can't read content from the specified file.");
97
+ }
98
+ absoluteDirPath = path.dirname(normalizePath);
99
+ }
79
100
  const gzhContent = await getGzhContent(content, themeId, "solarized-light", true, true);
80
- const title = gzhContent.title ?? "this is title";
81
- const cover = gzhContent.cover ?? "";
82
- const response = await publishToDraft(title, gzhContent.content, cover);
101
+ if (!gzhContent.title) {
102
+ throw new Error("Can't extract a valid title from the frontmatter.");
103
+ }
104
+ if (!gzhContent.cover) {
105
+ throw new Error("Can't extract a valid cover from the frontmatter or article.");
106
+ }
107
+ const response = await publishToDraft(gzhContent.title, gzhContent.content, gzhContent.cover, {
108
+ relativePath: absoluteDirPath,
109
+ });
83
110
  return {
84
111
  content: [
85
112
  {
@@ -95,7 +122,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
95
122
  text: JSON.stringify({
96
123
  id: theme.id,
97
124
  name: theme.name,
98
- description: theme.description
125
+ description: theme.description,
99
126
  }),
100
127
  }));
101
128
  return {
@@ -0,0 +1 @@
1
+ export declare function getNormalizeFilePath(inputPath: string): string;
package/dist/utils.js ADDED
@@ -0,0 +1,27 @@
1
+ import path from "node:path";
2
+ /**
3
+ * 路径标准化工具函数
4
+ * 将 Windows 的反斜杠 \ 转换为正斜杠 /,并去除末尾斜杠
5
+ * 目的:在 Linux 容器内也能正确处理 Windows 路径字符串
6
+ */
7
+ function normalizePath(p) {
8
+ return p.replace(/\\/g, "/").replace(/\/+$/, "");
9
+ }
10
+ export function getNormalizeFilePath(inputPath) {
11
+ const isContainer = !!process.env.CONTAINERIZED;
12
+ if (isContainer) {
13
+ const hostFilePath = normalizePath(process.env.HOST_FILE_PATH || "");
14
+ const containerFilePath = normalizePath(process.env.CONTAINER_FILE_PATH || "/mnt/host-downloads");
15
+ let relativePart = normalizePath(inputPath);
16
+ if (relativePart.startsWith(hostFilePath)) {
17
+ relativePart = relativePart.slice(hostFilePath.length);
18
+ }
19
+ if (!relativePart.startsWith("/")) {
20
+ relativePart = "/" + relativePart;
21
+ }
22
+ return containerFilePath + relativePart;
23
+ }
24
+ else {
25
+ return path.resolve(inputPath);
26
+ }
27
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wenyan-md/mcp",
3
- "version": "1.0.8",
3
+ "version": "1.0.10",
4
4
  "description": "MCP server for Wenyan, a Markdown formatting tool that allows AI assistants to apply elegant built-in themes and publish articles directly to 微信公众号.",
5
5
  "author": "Lei <caol64@gmail.com> (https://github.com/caol64)",
6
6
  "license": "Apache-2.0",
@@ -36,14 +36,14 @@
36
36
  "devDependencies": {
37
37
  "@types/node": "^24.3.0",
38
38
  "dotenv-cli": "^10.0.0",
39
- "typescript": "^5.9.2",
40
- "vitest": "^3.2.4"
39
+ "openai": "^6.16.0",
40
+ "typescript": "^5.9.2"
41
41
  },
42
42
  "scripts": {
43
43
  "build": "tsc",
44
- "watch": "tsc --watch",
45
- "inspector": "pnpm build && pnpm dotenv -e .env.test -- node ./run-inspector.js",
46
- "test": "pnpx vitest run",
44
+ "inspector": "pnpm build && dotenv -e .env.test -- node ./run-inspector.js",
45
+ "test:list": "pnpm build && dotenv -e .env.test -- node ./test/list.js",
46
+ "test:publish": "pnpm build && dotenv -e .env.test -- node ./test/publish.js",
47
47
  "upgrade:core": "pnpm update @wenyan-md/core"
48
48
  }
49
49
  }