feishu-mcp 0.0.2 → 0.0.4

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
@@ -21,8 +21,13 @@
21
21
  - 插入图表:支持各类数据可视化图表
22
22
  - 插入流程图:支持流程图和思维导图
23
23
  - 插入公式:支持数学公式和科学符号
24
- - 图表、流程图的内容识别
24
+ - 图表、流程图的内容识别
25
25
 
26
+ 快速开始,详见[配置](#配置)部分:
27
+
28
+ ```bash
29
+ npx feishu-mcp --feishu-app-id=<你的飞书应用ID> --feishu-app-secret=<你的飞书应用密钥>
30
+ ```
26
31
 
27
32
  ## 工作原理
28
33
 
@@ -32,6 +37,49 @@
32
37
  4. Cursor 将从飞书获取相关元数据并使用它来辅助编写代码。
33
38
 
34
39
  这个 MCP 服务器专为 Cursor 设计。在响应来自[飞书 API](https://open.feishu.cn/document/home/introduction-to-lark-open-platform/overview) 的内容之前,它会简化和转换响应,确保只向模型提供最相关的文档信息。
40
+ ## 安装
41
+
42
+ ### 使用 NPM 快速运行服务器
43
+
44
+ 你可以使用 NPM 快速运行服务器,无需安装或构建仓库:
45
+
46
+ ```bash
47
+ npx feishu-mcp --feishu-app-id=<你的飞书应用ID> --feishu-app-secret=<你的飞书应用密钥>
48
+
49
+ # 或
50
+ pnpx feishu-mcp --feishu-app-id=<你的飞书应用ID> --feishu-app-secret=<你的飞书应用密钥>
51
+
52
+ # 或
53
+ yarn dlx feishu-mcp --feishu-app-id=<你的飞书应用ID> --feishu-app-secret=<你的飞书应用密钥>
54
+
55
+ # 或
56
+ bunx feishu-mcp --feishu-app-id=<你的飞书应用ID> --feishu-app-secret=<你的飞书应用密钥>
57
+ ```
58
+
59
+ 已发布到smithery平台,可访问:https://smithery.ai/server/@cso1z/feishu-mcp
60
+
61
+ 关于如何创建飞书应用和获取应用凭证的说明可以在[这里](https://open.feishu.cn/document/home/develop-a-bot-in-5-minutes/create-an-app)找到。
62
+
63
+ ### 使用配置文件的工具的 JSON 配置
64
+
65
+ 许多工具如 Windsurf、Cline 和 [Claude Desktop](https://claude.ai/download) 使用配置文件来启动服务器。
66
+
67
+ `feishu-mcp` 服务器可以通过在配置文件中添加以下内容来配置:
68
+
69
+ ```json
70
+ {
71
+ "mcpServers": {
72
+ "feishu-mcp": {
73
+ "command": "npx",
74
+ "args": ["-y", "feishu-mcp", "--stdio"],
75
+ "env": {
76
+ "FEISHU_APP_ID": "<你的飞书应用ID>",
77
+ "FEISHU_APP_SECRET": "<你的飞书应用密钥>"
78
+ }
79
+ }
80
+ }
81
+ }
82
+ ```
35
83
 
36
84
  ### 从本地源代码运行服务器
37
85
 
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { resolve } from "path";
3
3
  import { config } from "dotenv";
4
- import { startServer } from "./index";
4
+ import { startServer } from "./index.js";
5
5
  // Load .env from the current working directory
6
6
  config({ path: resolve(process.cwd(), ".env") });
7
7
  startServer().catch((error) => {
package/dist/index.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
2
- import { FeishuMcpServer } from "./server";
3
- import { getServerConfig } from "./config";
2
+ import { FeishuMcpServer } from "./server.js";
3
+ import { getServerConfig } from "./config.js";
4
+ import { fileURLToPath } from 'url';
5
+ import { resolve } from 'path';
4
6
  export async function startServer() {
5
7
  // Check if we're running in stdio mode (e.g., via CLI)
6
8
  const isStdioMode = process.env.NODE_ENV === "cli" || process.argv.includes("--stdio");
@@ -14,6 +16,7 @@ export async function startServer() {
14
16
  console.log(`Feishu App ID: ${feishuConfig.appId.substring(0, 4)}...${feishuConfig.appId.substring(feishuConfig.appId.length - 4)}`);
15
17
  console.log(`Feishu App Secret: ${feishuConfig.appSecret.substring(0, 4)}...${feishuConfig.appSecret.substring(feishuConfig.appSecret.length - 4)}`);
16
18
  const server = new FeishuMcpServer(feishuConfig);
19
+ console.log(`isStdioMode:${isStdioMode}`);
17
20
  if (isStdioMode) {
18
21
  const transport = new StdioServerTransport();
19
22
  await server.connect(transport);
@@ -23,10 +26,17 @@ export async function startServer() {
23
26
  await server.startHttpServer(config.port);
24
27
  }
25
28
  }
26
- // If this file is being run directly, start the server
27
- if (import.meta.url === `file://${process.argv[1]}`) {
29
+ // 跨平台兼容的方式检查是否直接运行
30
+ const currentFilePath = fileURLToPath(import.meta.url);
31
+ const executedFilePath = resolve(process.argv[1]);
32
+ console.log(`meta.url:${currentFilePath} argv:${executedFilePath}`);
33
+ if (currentFilePath === executedFilePath) {
34
+ console.log(`startServer`);
28
35
  startServer().catch((error) => {
29
- console.error("Failed to start server:", error);
36
+ console.error('Failed to start server:', error);
30
37
  process.exit(1);
31
38
  });
32
39
  }
40
+ else {
41
+ console.log(`not startServer`);
42
+ }
package/dist/server.js CHANGED
@@ -1,11 +1,15 @@
1
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
- import { z } from "zod";
3
- import { FeishuService } from "./services/feishu";
4
- import express from "express";
5
- import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { z } from 'zod';
3
+ import { FeishuService } from './services/feishu.js';
4
+ import express from 'express';
5
+ import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
6
6
  export const Logger = {
7
- log: (...args) => { console.log(...args); },
8
- error: (...args) => { console.error(...args); },
7
+ log: (...args) => {
8
+ console.log(...args);
9
+ },
10
+ error: (...args) => {
11
+ console.error(...args);
12
+ },
9
13
  };
10
14
  export class FeishuMcpServer {
11
15
  constructor(feishuConfig) {
@@ -38,8 +42,8 @@ export class FeishuMcpServer {
38
42
  throw new Error('飞书服务初始化失败');
39
43
  }
40
44
  this.server = new McpServer({
41
- name: "Feishu MCP Server",
42
- version: "0.0.1",
45
+ name: 'Feishu MCP Server',
46
+ version: '0.0.1',
43
47
  }, {
44
48
  capabilities: {
45
49
  logging: {},
@@ -50,10 +54,12 @@ export class FeishuMcpServer {
50
54
  }
51
55
  registerTools() {
52
56
  // 添加创建飞书文档工具
53
- // 添加创建飞书文档工具
54
- this.server.tool("create_feishu_doc", "Create a new Feishu document", {
55
- title: z.string().describe("Document title"),
56
- folderToken: z.string().optional().describe("Folder token where the document will be created. If not provided, the document will be created in the root directory"),
57
+ this.server.tool('create_feishu_doc', 'Create a new Feishu document', {
58
+ title: z.string().describe('Document title'),
59
+ folderToken: z
60
+ .string()
61
+ .optional()
62
+ .describe('Folder token where the document will be created. If not provided, the document will be created in the root directory'),
57
63
  }, async ({ title, folderToken }) => {
58
64
  try {
59
65
  Logger.log(`开始创建飞书文档,标题: ${title}${folderToken ? `,文件夹Token: ${folderToken}` : ''}`);
@@ -61,13 +67,13 @@ export class FeishuMcpServer {
61
67
  const newDoc = await this.feishuService.createDocument(title, folderToken);
62
68
  Logger.log(`飞书文档创建成功,文档ID: ${newDoc?.objToken || newDoc?.document_id}`);
63
69
  return {
64
- content: [{ type: "text", text: JSON.stringify(newDoc, null, 2) }],
70
+ content: [{ type: 'text', text: JSON.stringify(newDoc, null, 2) }],
65
71
  };
66
72
  }
67
73
  catch (error) {
68
74
  Logger.error(`创建飞书文档失败:`, error);
69
75
  return {
70
- content: [{ type: "text", text: `创建飞书文档失败: ${error}` }],
76
+ content: [{ type: 'text', text: `创建飞书文档失败: ${error}` }],
71
77
  };
72
78
  }
73
79
  });
@@ -100,203 +106,489 @@ export class FeishuMcpServer {
100
106
  // },
101
107
  // );
102
108
  // 添加获取飞书文档内容工具
103
- this.server.tool("get_feishu_doc_content", "Get the plain text content of a Feishu document", {
104
- documentId: z.string().describe("Document ID or URL. Supported formats:\n1. Standard document URL: https://xxx.feishu.cn/docs/xxx or https://xxx.feishu.cn/docx/xxx\n2. API URL: https://open.feishu.cn/open-apis/doc/v2/documents/xxx\n3. Direct document ID (e.g., JcKbdlokYoPIe0xDzJ1cduRXnRf)"),
105
- lang: z.number().optional().default(0).describe("Language code. Default is 0 (Chinese)"),
109
+ this.server.tool('get_feishu_doc_content', 'Get the plain text content of a Feishu document', {
110
+ documentId: z
111
+ .string()
112
+ .describe('Document ID or URL. Supported formats:\n1. Standard document URL: https://xxx.feishu.cn/docs/xxx or https://xxx.feishu.cn/docx/xxx\n2. API URL: https://open.feishu.cn/open-apis/doc/v2/documents/xxx\n3. Direct document ID (e.g., JcKbdlokYoPIe0xDzJ1cduRXnRf)'),
113
+ lang: z
114
+ .number()
115
+ .optional()
116
+ .default(0)
117
+ .describe('Language code. Default is 0 (Chinese)'),
106
118
  }, async ({ documentId, lang }) => {
107
119
  try {
108
120
  if (!this.feishuService) {
109
121
  return {
110
- content: [{ type: "text", text: "Feishu service is not initialized. Please check the configuration" }],
122
+ content: [
123
+ {
124
+ type: 'text',
125
+ text: 'Feishu service is not initialized. Please check the configuration',
126
+ },
127
+ ],
111
128
  };
112
129
  }
113
130
  Logger.log(`开始获取飞书文档内容,文档ID: ${documentId},语言: ${lang}`);
114
131
  const content = await this.feishuService.getDocumentContent(documentId, lang);
115
132
  Logger.log(`飞书文档内容获取成功,内容长度: ${content.length}字符`);
116
133
  return {
117
- content: [{ type: "text", text: content }],
134
+ content: [{ type: 'text', text: content }],
118
135
  };
119
136
  }
120
137
  catch (error) {
121
138
  Logger.error(`获取飞书文档内容失败:`, error);
122
139
  return {
123
- content: [{ type: "text", text: `获取飞书文档内容失败: ${error}` }],
140
+ content: [{ type: 'text', text: `获取飞书文档内容失败: ${error}` }],
124
141
  };
125
142
  }
126
143
  });
127
144
  // 添加获取飞书文档块工具
128
- this.server.tool("get_feishu_doc_blocks", "When document structure is needed, obtain the block information about the Feishu document for content analysis or block insertion", {
129
- documentId: z.string().describe("Document ID or URL. Supported formats:\n1. Standard document URL: https://xxx.feishu.cn/docs/xxx or https://xxx.feishu.cn/docx/xxx\n2. API URL: https://open.feishu.cn/open-apis/doc/v2/documents/xxx\n3. Direct document ID (e.g., JcKbdlokYoPIe0xDzJ1cduRXnRf)"),
130
- pageSize: z.number().optional().default(500).describe("Number of blocks per page. Default is 500"),
145
+ this.server.tool('get_feishu_doc_blocks', 'When document structure is needed, obtain the block information about the Feishu document for content analysis or block insertion', {
146
+ documentId: z
147
+ .string()
148
+ .describe('Document ID or URL. Supported formats:\n1. Standard document URL: https://xxx.feishu.cn/docs/xxx or https://xxx.feishu.cn/docx/xxx\n2. API URL: https://open.feishu.cn/open-apis/doc/v2/documents/xxx\n3. Direct document ID (e.g., JcKbdlokYoPIe0xDzJ1cduRXnRf)'),
149
+ pageSize: z
150
+ .number()
151
+ .optional()
152
+ .default(500)
153
+ .describe('Number of blocks per page. Default is 500'),
131
154
  }, async ({ documentId, pageSize }) => {
132
155
  try {
133
156
  if (!this.feishuService) {
134
157
  return {
135
- content: [{ type: "text", text: "Feishu service is not initialized. Please check the configuration" }],
158
+ content: [
159
+ {
160
+ type: 'text',
161
+ text: 'Feishu service is not initialized. Please check the configuration',
162
+ },
163
+ ],
136
164
  };
137
165
  }
138
166
  Logger.log(`开始获取飞书文档块,文档ID: ${documentId},页大小: ${pageSize}`);
139
167
  const blocks = await this.feishuService.getDocumentBlocks(documentId, pageSize);
140
168
  Logger.log(`飞书文档块获取成功,共 ${blocks.length} 个块`);
141
169
  return {
142
- content: [{ type: "text", text: JSON.stringify(blocks, null, 2) }],
170
+ content: [{ type: 'text', text: JSON.stringify(blocks, null, 2) }],
143
171
  };
144
172
  }
145
173
  catch (error) {
146
174
  Logger.error(`获取飞书文档块失败:`, error);
147
175
  return {
148
- content: [{ type: "text", text: `获取飞书文档块失败: ${error}` }],
176
+ content: [{ type: 'text', text: `获取飞书文档块失败: ${error}` }],
149
177
  };
150
178
  }
151
179
  });
152
180
  // 添加创建飞书文档块工具
153
- this.server.tool("create_feishu_text_block", "Create a new text block in a Feishu document (AI will automatically convert Markdown syntax to corresponding style attributes: **bold** → bold:true, *italic* → italic:true, ~~strikethrough~~ → strikethrough:true, `code` → inline_code:true)", {
154
- documentId: z.string().describe("Document ID or URL. Supported formats:\n1. Standard document URL: https://xxx.feishu.cn/docs/xxx or https://xxx.feishu.cn/docx/xxx\n2. API URL: https://open.feishu.cn/open-apis/doc/v2/documents/xxx\n3. Direct document ID (e.g., JcKbdlokYoPIe0xDzJ1cduRXnRf)"),
155
- parentBlockId: z.string().describe("Parent block ID (NOT URL) where the new block will be added as a child. This should be the raw block ID without any URL prefix. When adding blocks at the page level (root level), use the extracted document ID from documentId parameter"),
156
- textContents: z.array(z.object({
157
- text: z.string().describe("Text content"),
158
- style: z.object({
159
- bold: z.boolean().optional().describe("Whether to make text bold. Default is false"),
160
- italic: z.boolean().optional().describe("Whether to make text italic. Default is false"),
161
- underline: z.boolean().optional().describe("Whether to add underline. Default is false"),
162
- strikethrough: z.boolean().optional().describe("Whether to add strikethrough. Default is false"),
163
- inline_code: z.boolean().optional().describe("Whether to format as inline code. Default is false"),
164
- text_color: z.number().optional().describe("Text color as a number. Default is 0")
165
- }).optional().describe("Text style settings")
166
- })).describe("Array of text content objects. A block can contain multiple text segments with different styles"),
167
- align: z.number().optional().default(1).describe("Text alignment: 1 for left, 2 for center, 3 for right. Default is 1"),
168
- index: z.number().optional().default(0).describe("Insertion position index. Default is 0 (insert at the beginning). If unsure about the position, use the get_feishu_doc_blocks tool first to understand the document structure. For consecutive insertions, calculate the next position as previous_index + 1 to avoid querying document structure repeatedly")
169
- }, async ({ documentId, parentBlockId, textContents, align = 1, index }) => {
170
- try {
171
- if (!this.feishuService) {
172
- return {
173
- content: [{ type: "text", text: "Feishu service is not initialized. Please check the configuration" }],
174
- };
175
- }
176
- // 处理Markdown语法转换
177
- const processedTextContents = textContents.map(content => {
178
- let { text, style = {} } = content;
179
- // 创建一个新的style对象,避免修改原始对象
180
- const newStyle = { ...style };
181
- // 处理粗体 **text**
182
- if (text.match(/\*\*([^*]+)\*\*/g)) {
183
- text = text.replace(/\*\*([^*]+)\*\*/g, "$1");
184
- newStyle.bold = true;
185
- }
186
- // 处理斜体 *text*
187
- if (text.match(/(?<!\*)\*([^*]+)\*(?!\*)/g)) {
188
- text = text.replace(/(?<!\*)\*([^*]+)\*(?!\*)/g, "$1");
189
- newStyle.italic = true;
190
- }
191
- // 处理删除线 ~~text~~
192
- if (text.match(/~~([^~]+)~~/g)) {
193
- text = text.replace(/~~([^~]+)~~/g, "$1");
194
- newStyle.strikethrough = true;
195
- }
196
- // 处理行内代码 `code`
197
- if (text.match(/`([^`]+)`/g)) {
198
- text = text.replace(/`([^`]+)`/g, "$1");
199
- newStyle.inline_code = true;
200
- }
201
- return { text, style: newStyle };
202
- });
203
- Logger.log(`开始创建飞书文本块,文档ID: ${documentId},父块ID: ${parentBlockId},对齐方式: ${align},插入位置: ${index}`);
204
- const result = await this.feishuService.createTextBlock(documentId, parentBlockId, processedTextContents, align, index);
205
- Logger.log(`飞书文本块创建成功`);
206
- return {
207
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
208
- };
209
- }
210
- catch (error) {
211
- Logger.error(`创建飞书文本块失败:`, error);
212
- return {
213
- content: [{ type: "text", text: `创建飞书文本块失败: ${error}` }],
214
- };
215
- }
216
- });
181
+ // this.server.tool(
182
+ // "create_feishu_text_block",
183
+ // "Create a new text block in a Feishu document (AI will automatically convert Markdown syntax to corresponding style attributes: **bold** bold:true, *italic* italic:true, ~~strikethrough~~ strikethrough:true, `code` inline_code:true)",
184
+ // {
185
+ // documentId: z.string().describe("Document ID or URL. Supported formats:\n1. Standard document URL: https://xxx.feishu.cn/docs/xxx or https://xxx.feishu.cn/docx/xxx\n2. API URL: https://open.feishu.cn/open-apis/doc/v2/documents/xxx\n3. Direct document ID (e.g., JcKbdlokYoPIe0xDzJ1cduRXnRf)"),
186
+ // parentBlockId: z.string().describe("Parent block ID (NOT URL) where the new block will be added as a child. This should be the raw block ID without any URL prefix. When adding blocks at the page level (root level), use the extracted document ID from documentId parameter"),
187
+ // textContents: z.array(
188
+ // z.object({
189
+ // text: z.string().describe("Text content"),
190
+ // style: z.object({
191
+ // bold: z.boolean().optional().describe("Whether to make text bold. Default is false"),
192
+ // italic: z.boolean().optional().describe("Whether to make text italic. Default is false"),
193
+ // underline: z.boolean().optional().describe("Whether to add underline. Default is false"),
194
+ // strikethrough: z.boolean().optional().describe("Whether to add strikethrough. Default is false"),
195
+ // inline_code: z.boolean().optional().describe("Whether to format as inline code. Default is false"),
196
+ // text_color: z.number().optional().describe("Text color as a number. Default is 0")
197
+ // }).optional().describe("Text style settings")
198
+ // })
199
+ // ).describe("Array of text content objects. A block can contain multiple text segments with different styles"),
200
+ // align: z.number().optional().default(1).describe("Text alignment: 1 for left, 2 for center, 3 for right. Default is 1"),
201
+ // index: z.number().optional().default(0).describe("Insertion position index. Default is 0 (insert at the beginning). If unsure about the position, use the get_feishu_doc_blocks tool first to understand the document structure. For consecutive insertions, calculate the next position as previous_index + 1 to avoid querying document structure repeatedly")
202
+ // },
203
+ // async ({ documentId, parentBlockId, textContents, align = 1, index }) => {
204
+ // try {
205
+ // if (!this.feishuService) {
206
+ // return {
207
+ // content: [{ type: "text", text: "Feishu service is not initialized. Please check the configuration" }],
208
+ // };
209
+ // }
210
+ //
211
+ // // 处理Markdown语法转换
212
+ // const processedTextContents = textContents.map(content => {
213
+ // let { text, style = {} } = content;
214
+ //
215
+ // // 创建一个新的style对象,避免修改原始对象
216
+ // const newStyle = { ...style };
217
+ //
218
+ // // 处理粗体 **text**
219
+ // if (text.match(/\*\*([^*]+)\*\*/g)) {
220
+ // text = text.replace(/\*\*([^*]+)\*\*/g, "$1");
221
+ // newStyle.bold = true;
222
+ // }
223
+ //
224
+ // // 处理斜体 *text*
225
+ // if (text.match(/(?<!\*)\*([^*]+)\*(?!\*)/g)) {
226
+ // text = text.replace(/(?<!\*)\*([^*]+)\*(?!\*)/g, "$1");
227
+ // newStyle.italic = true;
228
+ // }
229
+ //
230
+ // // 处理删除线 ~~text~~
231
+ // if (text.match(/~~([^~]+)~~/g)) {
232
+ // text = text.replace(/~~([^~]+)~~/g, "$1");
233
+ // newStyle.strikethrough = true;
234
+ // }
235
+ //
236
+ // // 处理行内代码 `code`
237
+ // if (text.match(/`([^`]+)`/g)) {
238
+ // text = text.replace(/`([^`]+)`/g, "$1");
239
+ // newStyle.inline_code = true;
240
+ // }
241
+ //
242
+ // return { text, style: newStyle };
243
+ // });
244
+ //
245
+ // Logger.log(`开始创建飞书文本块,文档ID: ${documentId},父块ID: ${parentBlockId},对齐方式: ${align},插入位置: ${index}`);
246
+ // const result = await this.feishuService.createTextBlock(documentId, parentBlockId, processedTextContents, align, index);
247
+ // Logger.log(`飞书文本块创建成功`);
248
+ //
249
+ // return {
250
+ // content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
251
+ // };
252
+ // } catch (error) {
253
+ // Logger.error(`创建飞书文本块失败:`, error);
254
+ // return {
255
+ // content: [{ type: "text", text: `创建飞书文本块失败: ${error}` }],
256
+ // };
257
+ // }
258
+ // },
259
+ // );
217
260
  // 添加创建飞书代码块工具
218
- this.server.tool("create_feishu_code_block", "Create a new code block in a Feishu document", {
219
- documentId: z.string().describe("Document ID or URL. Supported formats:\n1. Standard document URL: https://xxx.feishu.cn/docs/xxx or https://xxx.feishu.cn/docx/xxx\n2. API URL: https://open.feishu.cn/open-apis/doc/v2/documents/xxx\n3. Direct document ID (e.g., JcKbdlokYoPIe0xDzJ1cduRXnRf)"),
220
- parentBlockId: z.string().describe("Parent block ID (NOT URL) where the new block will be added as a child. This should be the raw block ID without any URL prefix. When adding blocks at the page level (root level), use the extracted document ID from documentId parameter"),
221
- code: z.string().describe("Code content"),
222
- language: z.number().optional().default(0).describe("Programming language code as a number. Examples: 1: PlainText; 7: Bash; 8: CSharp; 9: C++; 10: C; 12: CSS; 22: Go; 24: HTML; 29: Java; 30: JavaScript; 32: Kotlin; 43: PHP; 49: Python; 52: Ruby; 53: Rust; 56: SQL; 60: Shell; 61: Swift; 63: TypeScript. Default is 0"),
223
- wrap: z.boolean().optional().default(false).describe("Whether to enable automatic line wrapping. Default is false"),
224
- index: z.number().optional().default(0).describe("Insertion position index. Default is 0 (insert at the beginning). If unsure about the position, use the get_feishu_doc_blocks tool first to understand the document structure. For consecutive insertions, calculate the next position as previous_index + 1 to avoid querying document structure repeatedly")
225
- }, async ({ documentId, parentBlockId, code, language = 0, wrap = false, index = 0 }) => {
261
+ // this.server.tool(
262
+ // "create_feishu_code_block",
263
+ // "Create a new code block in a Feishu document",
264
+ // {
265
+ // documentId: z.string().describe("Document ID or URL. Supported formats:\n1. Standard document URL: https://xxx.feishu.cn/docs/xxx or https://xxx.feishu.cn/docx/xxx\n2. API URL: https://open.feishu.cn/open-apis/doc/v2/documents/xxx\n3. Direct document ID (e.g., JcKbdlokYoPIe0xDzJ1cduRXnRf)"),
266
+ // parentBlockId: z.string().describe("Parent block ID (NOT URL) where the new block will be added as a child. This should be the raw block ID without any URL prefix. When adding blocks at the page level (root level), use the extracted document ID from documentId parameter"),
267
+ // code: z.string().describe("Code content"),
268
+ // language: z.number().optional().default(0).describe("Programming language code as a number. Examples: 1: PlainText; 7: Bash; 8: CSharp; 9: C++; 10: C; 12: CSS; 22: Go; 24: HTML; 29: Java; 30: JavaScript; 32: Kotlin; 43: PHP; 49: Python; 52: Ruby; 53: Rust; 56: SQL; 60: Shell; 61: Swift; 63: TypeScript. Default is 0"),
269
+ // wrap: z.boolean().optional().default(false).describe("Whether to enable automatic line wrapping. Default is false"),
270
+ // index: z.number().optional().default(0).describe("Insertion position index. Default is 0 (insert at the beginning). If unsure about the position, use the get_feishu_doc_blocks tool first to understand the document structure. For consecutive insertions, calculate the next position as previous_index + 1 to avoid querying document structure repeatedly")
271
+ // },
272
+ // async ({ documentId, parentBlockId, code, language = 0, wrap = false, index = 0 }) => {
273
+ // try {
274
+ // if (!this.feishuService) {
275
+ // return {
276
+ // content: [{ type: "text", text: "Feishu service is not initialized. Please check the configuration" }],
277
+ // };
278
+ // }
279
+ //
280
+ // Logger.log(`开始创建飞书代码块,文档ID: ${documentId},父块ID: ${parentBlockId},语言: ${language},自动换行: ${wrap},插入位置: ${index}`);
281
+ // const result = await this.feishuService.createCodeBlock(documentId, parentBlockId, code, language, wrap, index);
282
+ // Logger.log(`飞书代码块创建成功`);
283
+ //
284
+ // return {
285
+ // content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
286
+ // };
287
+ // } catch (error) {
288
+ // Logger.error(`创建飞书代码块失败:`, error);
289
+ // return {
290
+ // content: [{ type: "text", text: `创建飞书代码块失败: ${error}` }],
291
+ // };
292
+ // }
293
+ // },
294
+ // );
295
+ // 添加批量创建飞书块工具
296
+ this.server.tool('create_feishu_blocks', 'Create multiple blocks in a Feishu document at once with a single API call', {
297
+ documentId: z
298
+ .string()
299
+ .describe('Document ID or URL. Supported formats:\n1. Standard document URL: https://xxx.feishu.cn/docs/xxx or https://xxx.feishu.cn/docx/xxx\n2. API URL: https://open.feishu.cn/open-apis/doc/v2/documents/xxx\n3. Direct document ID (e.g., JcKbdlokYoPIe0xDzJ1cduRXnRf)'),
300
+ parentBlockId: z
301
+ .string()
302
+ .describe('Parent block ID (NOT URL) where the new blocks will be added as children. This should be the raw block ID without any URL prefix. When adding blocks at the page level (root level), use the extracted document ID from documentId parameter'),
303
+ children: z
304
+ .array(z.object({
305
+ block_type: z
306
+ .number()
307
+ .describe('Block type number: 2 for text, 3-11 for headings (level+2), 14 for code'),
308
+ content: z
309
+ .any()
310
+ .describe('Block content object that follows Feishu API format'),
311
+ }))
312
+ .describe('Array of block objects to create in a single API call'),
313
+ index: z
314
+ .number()
315
+ .optional()
316
+ .default(0)
317
+ .describe('Insertion position index. Default is 0 (insert at the beginning)'),
318
+ }, async ({ documentId, parentBlockId, children, index = 0 }) => {
226
319
  try {
227
320
  if (!this.feishuService) {
228
321
  return {
229
- content: [{ type: "text", text: "Feishu service is not initialized. Please check the configuration" }],
322
+ content: [
323
+ {
324
+ type: 'text',
325
+ text: 'Feishu service is not initialized. Please check the configuration',
326
+ },
327
+ ],
230
328
  };
231
329
  }
232
- Logger.log(`开始创建飞书代码块,文档ID: ${documentId},父块ID: ${parentBlockId},语言: ${language},自动换行: ${wrap},插入位置: ${index}`);
233
- const result = await this.feishuService.createCodeBlock(documentId, parentBlockId, code, language, wrap, index);
234
- Logger.log(`飞书代码块创建成功`);
330
+ Logger.log(`开始批量创建飞书块,文档ID: ${documentId},父块ID: ${parentBlockId},块数量: ${children.length},插入位置: ${index}`);
331
+ const result = await this.feishuService.createDocumentBlocks(documentId, parentBlockId, children, index);
332
+ Logger.log(`飞书块批量创建成功`);
235
333
  return {
236
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
334
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
237
335
  };
238
336
  }
239
337
  catch (error) {
240
- Logger.error(`创建飞书代码块失败:`, error);
338
+ Logger.error(`批量创建飞书块失败:`, error);
241
339
  return {
242
- content: [{ type: "text", text: `创建飞书代码块失败: ${error}` }],
340
+ content: [{ type: 'text', text: `批量创建飞书块失败: ${error}` }],
243
341
  };
244
342
  }
245
343
  });
246
- // 添加创建飞书标题块工具
247
- this.server.tool("create_feishu_heading_block", "Create a heading block in a Feishu document with specified level (1-9)", {
248
- documentId: z.string().describe("Document ID or URL. Supported formats:\n1. Standard document URL: https://xxx.feishu.cn/docs/xxx or https://xxx.feishu.cn/docx/xxx\n2. API URL: https://open.feishu.cn/open-apis/doc/v2/documents/xxx\n3. Direct document ID (e.g., JcKbdlokYoPIe0xDzJ1cduRXnRf)"),
249
- parentBlockId: z.string().describe("Parent block ID (NOT URL) where the new block will be added as a child. This should be the raw block ID without any URL prefix. When adding blocks at the page level (root level), use the extracted document ID from documentId parameter"),
250
- level: z.number().min(1).max(9).describe("Heading level from 1 to 9, where 1 is the largest heading (h1) and 9 is the smallest (h9)"),
251
- content: z.string().describe("Heading text content"),
252
- index: z.number().optional().default(0).describe("Insertion position index. Default is 0 (insert at the beginning). If unsure about the position, use the get_feishu_doc_blocks tool first to understand the document structure. For consecutive insertions, calculate the next position as previous_index + 1 to avoid querying document structure repeatedly")
253
- }, async ({ documentId, parentBlockId, level, content, index = 0 }) => {
344
+ // 添加通用飞书块创建工具(支持文本、代码、标题)
345
+ this.server.tool('create_feishu_common_block', 'Create common blocks in a Feishu document (supports text, code, and heading blocks)', {
346
+ documentId: z
347
+ .string()
348
+ .describe('Document ID or URL. Supported formats:\n1. Standard document URL: https://xxx.feishu.cn/docs/xxx or https://xxx.feishu.cn/docx/xxx\n2. API URL: https://open.feishu.cn/open-apis/doc/v2/documents/xxx\n3. Direct document ID (e.g., JcKbdlokYoPIe0xDzJ1cduRXnRf)'),
349
+ parentBlockId: z
350
+ .string()
351
+ .describe('Parent block ID (NOT URL) where the new blocks will be added as children. This should be the raw block ID without any URL prefix. When adding blocks at the page level (root level), use the extracted document ID from documentId parameter'),
352
+ startIndex: z
353
+ .number()
354
+ .optional()
355
+ .default(0)
356
+ .describe('Starting insertion position index. Default is 0 (insert at the beginning). If individual blocks have their own index specified, those will take precedence'),
357
+ blocks: z
358
+ .array(z.object({
359
+ blockType: z
360
+ .enum(['text', 'code', 'heading'])
361
+ .describe("Type of block to create: 'text', 'code', or 'heading'"),
362
+ options: z
363
+ .object({
364
+ // 文本块选项 - 当blockType为'text'时使用
365
+ text: z
366
+ .object({
367
+ // 文本内容数组,每个元素包含文本内容和样式
368
+ textStyles: z
369
+ .array(z.object({
370
+ text: z.string().describe('Text segment content'),
371
+ style: z
372
+ .object({
373
+ bold: z
374
+ .boolean()
375
+ .optional()
376
+ .describe('Whether to make text bold. Default is false'),
377
+ italic: z
378
+ .boolean()
379
+ .optional()
380
+ .describe('Whether to make text italic. Default is false'),
381
+ underline: z
382
+ .boolean()
383
+ .optional()
384
+ .describe('Whether to add underline. Default is false'),
385
+ strikethrough: z
386
+ .boolean()
387
+ .optional()
388
+ .describe('Whether to add strikethrough. Default is false'),
389
+ inline_code: z
390
+ .boolean()
391
+ .optional()
392
+ .describe('Whether to format as inline code. Default is false'),
393
+ text_color: z
394
+ .number()
395
+ .optional()
396
+ .describe('Text color as a number. Default is 0'),
397
+ })
398
+ .optional()
399
+ .describe('Text style settings'),
400
+ }))
401
+ .optional()
402
+ .describe('Array of text content objects with styles. If not provided, content will be used as plain text'),
403
+ align: z
404
+ .number()
405
+ .optional()
406
+ .default(1)
407
+ .describe('Text alignment: 1 for left, 2 for center, 3 for right. Default is 1'),
408
+ })
409
+ .optional()
410
+ .describe("Text block options. Only used when blockType is 'text'"),
411
+ // 代码块选项 - 当blockType为'code'时使用
412
+ code: z
413
+ .object({
414
+ code: z.string().describe('Code content'),
415
+ language: z
416
+ .number()
417
+ .optional()
418
+ .default(0)
419
+ .describe('Programming language code as a number. Available options:\n1: PlainText, 2: ABAP, 3: Ada, 4: Apache, 5: Apex, 6: Assembly Language, 7: Bash, 8: CSharp, 9: C++, 10: C, 11: COBOL, 12: CSS, 13: CoffeeScript, 14: D, 15: Dart, 16: Delphi, 17: Django, 18: Dockerfile, 19: Erlang, 20: Fortran, 22: Go, 23: Groovy, 24: HTML, 25: HTMLBars, 26: HTTP, 27: Haskell, 28: JSON, 29: Java, 30: JavaScript, 31: Julia, 32: Kotlin, 33: LateX, 34: Lisp, 36: Lua, 37: MATLAB, 38: Makefile, 39: Markdown, 40: Nginx, 41: Objective-C, 43: PHP, 44: Perl, 46: Power Shell, 47: Prolog, 48: ProtoBuf, 49: Python, 50: R, 52: Ruby, 53: Rust, 54: SAS, 55: SCSS, 56: SQL, 57: Scala, 58: Scheme, 60: Shell, 61: Swift, 62: Thrift, 63: TypeScript, 64: VBScript, 65: Visual Basic, 66: XML, 67: YAML, 68: CMake, 69: Diff, 70: Gherkin, 71: GraphQL, 72: OpenGL Shading Language, 73: Properties, 74: Solidity, 75: TOML'),
420
+ wrap: z
421
+ .boolean()
422
+ .optional()
423
+ .default(false)
424
+ .describe('Whether to enable automatic line wrapping for code blocks. Default is false'),
425
+ })
426
+ .optional()
427
+ .describe("Code block options. Only used when blockType is 'code'"),
428
+ // 标题块选项 - 当blockType为'heading'时使用
429
+ heading: z
430
+ .object({
431
+ level: z
432
+ .number()
433
+ .min(1)
434
+ .max(9)
435
+ .describe('Heading level from 1 to 9, where 1 is the largest heading (h1) and 9 is the smallest (h9)'),
436
+ content: z.string().describe('Heading text content'),
437
+ align: z
438
+ .number()
439
+ .optional()
440
+ .default(1)
441
+ .describe('Text alignment: 1 for left, 2 for center, 3 for right. Default is 1'),
442
+ })
443
+ .optional()
444
+ .describe("Heading block options. Only used when blockType is 'heading'"),
445
+ })
446
+ .optional()
447
+ .default({}),
448
+ }))
449
+ .describe('Array of block configurations to create in a single API call'),
450
+ }, async ({ documentId, parentBlockId, startIndex = 0, blocks }) => {
254
451
  try {
255
452
  if (!this.feishuService) {
256
453
  return {
257
- content: [{ type: "text", text: "Feishu service is not initialized. Please check the configuration" }],
454
+ content: [
455
+ {
456
+ type: 'text',
457
+ text: 'Feishu service is not initialized. Please check the configuration',
458
+ },
459
+ ],
258
460
  };
259
461
  }
260
- Logger.log(`开始创建飞书标题块,文档ID: ${documentId},父块ID: ${parentBlockId},标题级别: ${level},插入位置: ${index}`);
261
- const result = await this.feishuService.createHeadingBlock(documentId, parentBlockId, content, level, index);
262
- Logger.log(`飞书标题块创建成功`);
462
+ Logger.log(`开始批量创建飞书块,文档ID: ${documentId},父块ID: ${parentBlockId},块数量: ${blocks.length},起始插入位置: ${startIndex}`);
463
+ // 准备要创建的块内容数组
464
+ const blockContents = [];
465
+ // 处理每个块配置
466
+ for (const blockConfig of blocks) {
467
+ const { blockType, options = {} } = blockConfig;
468
+ // 使用指定的索引或当前索引
469
+ let blockContent;
470
+ switch (blockType) {
471
+ case 'text':
472
+ // 处理文本块
473
+ const textOptions = options.text || {
474
+ textStyles: [],
475
+ align: 1,
476
+ };
477
+ // 确保textStyles是一个有效的数组
478
+ const textStyles = textOptions.textStyles || [];
479
+ // 如果textStyles为空,添加一个默认的空文本
480
+ if (textStyles.length === 0) {
481
+ textStyles.push({
482
+ text: '',
483
+ style: {}, // 添加空的style对象作为默认值
484
+ });
485
+ }
486
+ const align = textOptions.align || 1;
487
+ blockContent = this.feishuService.createTextBlockContent(textStyles, align);
488
+ break;
489
+ case 'code':
490
+ // 处理代码块
491
+ const codeOptions = options.code;
492
+ if (codeOptions == null) {
493
+ break;
494
+ }
495
+ const codeContent = codeOptions.code || '';
496
+ const language = codeOptions.language || 0;
497
+ const wrap = codeOptions.wrap || false;
498
+ blockContent = this.feishuService.createCodeBlockContent(codeContent, language, wrap);
499
+ break;
500
+ case 'heading':
501
+ // 处理标题块
502
+ if (options.heading == null ||
503
+ options.heading.content == null) {
504
+ break;
505
+ }
506
+ const headingOptions = options.heading || {};
507
+ const headingContent = headingOptions.content || '';
508
+ const level = headingOptions.level || 1;
509
+ const headingAlign = headingOptions.align || 1;
510
+ blockContent = this.feishuService.createHeadingBlockContent(headingContent, level, headingAlign);
511
+ break;
512
+ }
513
+ if (blockContent) {
514
+ blockContents.push(blockContent);
515
+ Logger.log(`已准备${blockType}块,内容: ${JSON.stringify(blockContent).substring(0, 100)}...`);
516
+ }
517
+ }
518
+ // 批量创建所有块
519
+ const result = await this.feishuService.createDocumentBlocks(documentId, parentBlockId, blockContents, startIndex);
520
+ Logger.log(`飞书块批量创建成功,共创建 ${blockContents.length} 个块`);
263
521
  return {
264
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
522
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
265
523
  };
266
524
  }
267
525
  catch (error) {
268
- Logger.error(`创建飞书标题块失败:`, error);
526
+ Logger.error(`批量创建飞书块失败:`, error);
269
527
  return {
270
- content: [{ type: "text", text: `创建飞书标题块失败: ${error}` }],
528
+ content: [{ type: 'text', text: `批量创建飞书块失败: ${error}` }],
271
529
  };
272
530
  }
273
531
  });
532
+ // // 添加创建飞书标题块工具
533
+ // this.server.tool(
534
+ // "create_feishu_heading_block",
535
+ // "Create a heading block in a Feishu document with specified level (1-9)",
536
+ // {
537
+ // documentId: z.string().describe("Document ID or URL. Supported formats:\n1. Standard document URL: https://xxx.feishu.cn/docs/xxx or https://xxx.feishu.cn/docx/xxx\n2. API URL: https://open.feishu.cn/open-apis/doc/v2/documents/xxx\n3. Direct document ID (e.g., JcKbdlokYoPIe0xDzJ1cduRXnRf)"),
538
+ // parentBlockId: z.string().describe("Parent block ID (NOT URL) where the new block will be added as a child. This should be the raw block ID without any URL prefix. When adding blocks at the page level (root level), use the extracted document ID from documentId parameter"),
539
+ // level: z.number().min(1).max(9).describe("Heading level from 1 to 9, where 1 is the largest heading (h1) and 9 is the smallest (h9)"),
540
+ // content: z.string().describe("Heading text content"),
541
+ // index: z.number().optional().default(0).describe("Insertion position index. Default is 0 (insert at the beginning). If unsure about the position, use the get_feishu_doc_blocks tool first to understand the document structure. For consecutive insertions, calculate the next position as previous_index + 1 to avoid querying document structure repeatedly")
542
+ // },
543
+ // async ({ documentId, parentBlockId, level, content, index = 0 }) => {
544
+ // try {
545
+ // if (!this.feishuService) {
546
+ // return {
547
+ // content: [{ type: "text", text: "Feishu service is not initialized. Please check the configuration" }],
548
+ // };
549
+ // }
550
+ //
551
+ // Logger.log(`开始创建飞书标题块,文档ID: ${documentId},父块ID: ${parentBlockId},标题级别: ${level},插入位置: ${index}`);
552
+ // const result = await this.feishuService.createHeadingBlock(documentId, parentBlockId, content, level, index);
553
+ // Logger.log(`飞书标题块创建成功`);
554
+ //
555
+ // return {
556
+ // content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
557
+ // };
558
+ // } catch (error) {
559
+ // Logger.error(`创建飞书标题块失败:`, error);
560
+ // return {
561
+ // content: [{ type: "text", text: `创建飞书标题块失败: ${error}` }],
562
+ // };
563
+ // }
564
+ // },
565
+ // );
274
566
  }
275
567
  async connect(transport) {
276
568
  // Logger.log("Connecting to transport...");
277
569
  await this.server.connect(transport);
278
570
  Logger.log = (...args) => {
279
571
  this.server.server.sendLoggingMessage({
280
- level: "info",
572
+ level: 'info',
281
573
  data: args,
282
574
  });
283
575
  };
284
576
  Logger.error = (...args) => {
285
577
  this.server.server.sendLoggingMessage({
286
- level: "error",
578
+ level: 'error',
287
579
  data: args,
288
580
  });
289
581
  };
290
- Logger.log("Server connected and ready to process requests");
582
+ Logger.log('Server connected and ready to process requests');
291
583
  }
292
584
  async startHttpServer(port) {
293
585
  const app = express();
294
- app.get("/sse", async (_req, res) => {
295
- console.log("New SSE connection established");
296
- this.sseTransport = new SSEServerTransport("/messages", res);
586
+ app.get('/sse', async (_req, res) => {
587
+ console.log('New SSE connection established');
588
+ this.sseTransport = new SSEServerTransport('/messages', res);
297
589
  await this.server.connect(this.sseTransport);
298
590
  });
299
- app.post("/messages", async (req, res) => {
591
+ app.post('/messages', async (req, res) => {
300
592
  if (!this.sseTransport) {
301
593
  res.sendStatus(400);
302
594
  return;
@@ -1,5 +1,5 @@
1
1
  import axios, { AxiosError } from "axios";
2
- import { Logger } from "../server";
2
+ import { Logger } from "../server.js";
3
3
  export class FeishuService {
4
4
  constructor(appId, appSecret) {
5
5
  Object.defineProperty(this, "appId", {
@@ -336,8 +336,107 @@ export class FeishuService {
336
336
  throw error;
337
337
  }
338
338
  }
339
+ // 创建文本块内容
340
+ createTextBlockContent(textContents, align = 1) {
341
+ return {
342
+ block_type: 2, // 2表示文本块
343
+ text: {
344
+ elements: textContents.map(content => ({
345
+ text_run: {
346
+ content: content.text,
347
+ text_element_style: content.style || {}
348
+ }
349
+ })),
350
+ style: {
351
+ align: align // 1 居左,2 居中,3 居右
352
+ }
353
+ }
354
+ };
355
+ }
356
+ // 创建代码块内容
357
+ createCodeBlockContent(code, language = 0, wrap = false) {
358
+ return {
359
+ block_type: 14, // 14表示代码块
360
+ code: {
361
+ elements: [
362
+ {
363
+ text_run: {
364
+ content: code,
365
+ text_element_style: {
366
+ bold: false,
367
+ inline_code: false,
368
+ italic: false,
369
+ strikethrough: false,
370
+ underline: false
371
+ }
372
+ }
373
+ }
374
+ ],
375
+ style: {
376
+ language: language,
377
+ wrap: wrap
378
+ }
379
+ }
380
+ };
381
+ }
382
+ // 创建标题块内容
383
+ createHeadingBlockContent(text, level = 1, align = 1) {
384
+ // 确保标题级别在有效范围内(1-9)
385
+ const safeLevel = Math.max(1, Math.min(9, level));
386
+ // 根据标题级别设置block_type和对应的属性名
387
+ // 飞书API中,一级标题的block_type为3,二级标题为4,以此类推
388
+ const blockType = 2 + safeLevel; // 一级标题为3,二级标题为4,以此类推
389
+ const headingKey = `heading${safeLevel}`; // heading1, heading2, ...
390
+ // 构建块内容
391
+ const blockContent = {
392
+ block_type: blockType
393
+ };
394
+ // 设置对应级别的标题属性
395
+ blockContent[headingKey] = {
396
+ elements: [
397
+ {
398
+ text_run: {
399
+ content: text,
400
+ text_element_style: {}
401
+ }
402
+ }
403
+ ],
404
+ style: {
405
+ align: align,
406
+ folded: false
407
+ }
408
+ };
409
+ return blockContent;
410
+ }
411
+ // 批量创建文档块
412
+ async createDocumentBlocks(documentId, parentBlockId, blockContents, index = 0) {
413
+ try {
414
+ const docId = this.extractDocIdFromUrl(documentId);
415
+ if (!docId) {
416
+ throw new Error(`无效的文档ID: ${documentId}`);
417
+ }
418
+ Logger.log(`开始批量创建文档块,文档ID: ${docId},父块ID: ${parentBlockId},块数量: ${blockContents.length},插入位置: ${index}`);
419
+ const endpoint = `/docx/v1/documents/${docId}/blocks/${parentBlockId}/children?document_revision_id=-1`;
420
+ Logger.log(`准备请求API端点: ${endpoint}`);
421
+ const data = {
422
+ children: blockContents,
423
+ index: index
424
+ };
425
+ Logger.log(`请求数据: ${JSON.stringify(data, null, 2)}`);
426
+ const response = await this.request(endpoint, 'POST', data);
427
+ if (response.code !== 0) {
428
+ throw new Error(`批量创建文档块失败: ${response.msg}`);
429
+ }
430
+ Logger.log(`批量文档块创建成功: ${JSON.stringify(response.data, null, 2)}`);
431
+ return response.data;
432
+ }
433
+ catch (error) {
434
+ Logger.error(`批量创建文档块失败:`, error);
435
+ throw error;
436
+ }
437
+ }
339
438
  // 创建标题块
340
- async createHeadingBlock(documentId, parentBlockId, text, level = 1, index = 0) {
439
+ async createHeadingBlock(documentId, parentBlockId, text, level = 1, index = 0, align = 1) {
341
440
  try {
342
441
  const docId = this.extractDocIdFromUrl(documentId);
343
442
  if (!docId) {
@@ -365,7 +464,7 @@ export class FeishuService {
365
464
  }
366
465
  ],
367
466
  style: {
368
- align: 1,
467
+ align: align,
369
468
  folded: false
370
469
  }
371
470
  };
@@ -0,0 +1,135 @@
1
+ /**
2
+ * 构建批量创建块的请求数据
3
+ * @param blocks 块内容数组
4
+ * @param index 插入位置索引
5
+ * @returns 请求数据对象
6
+ */
7
+ export function buildCreateBlocksRequest(blocks, index = 0) {
8
+ return {
9
+ children: blocks,
10
+ index
11
+ };
12
+ }
13
+ /**
14
+ * 创建文本块内容
15
+ * @param text 文本内容
16
+ * @param style 文本样式
17
+ * @param align 对齐方式:1左对齐,2居中,3右对齐
18
+ * @returns 文本块内容对象
19
+ */
20
+ export function createTextBlockContent(textContents, align = 1) {
21
+ return {
22
+ block_type: 2, // 2表示文本块
23
+ text: {
24
+ elements: textContents.map(content => ({
25
+ text_run: {
26
+ content: content.text,
27
+ text_element_style: content.style || {}
28
+ }
29
+ })),
30
+ style: {
31
+ align: align // 1 居左,2 居中,3 居右
32
+ }
33
+ }
34
+ };
35
+ }
36
+ /**
37
+ * 创建代码块内容
38
+ * @param code 代码内容
39
+ * @param language 语言类型代码
40
+ * @param wrap 是否自动换行
41
+ * @returns 代码块内容对象
42
+ */
43
+ export function createCodeBlockContent(code, language = 0, wrap = false) {
44
+ return {
45
+ block_type: 14, // 14表示代码块
46
+ code: {
47
+ elements: [
48
+ {
49
+ text_run: {
50
+ content: code,
51
+ text_element_style: {
52
+ bold: false,
53
+ inline_code: false,
54
+ italic: false,
55
+ strikethrough: false,
56
+ underline: false
57
+ }
58
+ }
59
+ }
60
+ ],
61
+ style: {
62
+ language: language,
63
+ wrap: wrap
64
+ }
65
+ }
66
+ };
67
+ }
68
+ /**
69
+ * 创建标题块内容
70
+ * @param text 标题文本
71
+ * @param level 标题级别(1-9)
72
+ * @param align 对齐方式:1左对齐,2居中,3右对齐
73
+ * @returns 标题块内容对象
74
+ */
75
+ export function createHeadingBlockContent(text, level = 1, align = 1) {
76
+ // 确保标题级别在有效范围内(1-9)
77
+ const safeLevel = Math.max(1, Math.min(9, level));
78
+ // 根据标题级别设置block_type和对应的属性名
79
+ // 飞书API中,一级标题的block_type为3,二级标题为4,以此类推
80
+ const blockType = 2 + safeLevel; // 一级标题为3,二级标题为4,以此类推
81
+ const headingKey = `heading${safeLevel}`; // heading1, heading2, ...
82
+ // 构建块内容
83
+ const blockContent = {
84
+ block_type: blockType
85
+ };
86
+ // 设置对应级别的标题属性
87
+ blockContent[headingKey] = {
88
+ elements: [
89
+ {
90
+ text_run: {
91
+ content: text,
92
+ text_element_style: {}
93
+ }
94
+ }
95
+ ],
96
+ style: {
97
+ align: align,
98
+ folded: false
99
+ }
100
+ };
101
+ return blockContent;
102
+ }
103
+ /**
104
+ * 处理Markdown语法转换
105
+ * @param textContents 文本内容数组
106
+ * @returns 处理后的文本内容数组
107
+ */
108
+ export function processMarkdownSyntax(textContents) {
109
+ return textContents.map(content => {
110
+ let { text, style = {} } = content;
111
+ // 创建一个新的style对象,避免修改原始对象
112
+ const newStyle = { ...style };
113
+ // 处理粗体 **text**
114
+ if (text.match(/\*\*([^*]+)\*\*/g)) {
115
+ text = text.replace(/\*\*([^*]+)\*\*/g, "$1");
116
+ newStyle.bold = true;
117
+ }
118
+ // 处理斜体 *text*
119
+ if (text.match(/(?<!\*)\*([^*]+)\*(?!\*)/g)) {
120
+ text = text.replace(/(?<!\*)\*([^*]+)\*(?!\*)/g, "$1");
121
+ newStyle.italic = true;
122
+ }
123
+ // 处理删除线 ~~text~~
124
+ if (text.match(/~~([^~]+)~~/g)) {
125
+ text = text.replace(/~~([^~]+)~~/g, "$1");
126
+ newStyle.strikethrough = true;
127
+ }
128
+ // 处理行内代码 `code`
129
+ if (text.match(/`([^`]+)`/g)) {
130
+ text = text.replace(/`([^`]+)`/g, "$1");
131
+ newStyle.inline_code = true;
132
+ }
133
+ return { text, style: newStyle };
134
+ });
135
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "feishu-mcp",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "Model Context Protocol server for Feishu integration",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",