feishu-mcp 0.1.6 → 0.1.8

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
@@ -34,24 +34,25 @@
34
34
 
35
35
  ## 🛠️ 工具功能详情
36
36
 
37
- | 功能类别 | 工具名称 | 描述 | 使用场景 | 状态 |
38
- |---------|--------------------------------------------------------------|-------------------|---------------|------|
39
- | **文档管理** | `create_feishu_document` | 创建新的飞书文档 | 从零开始创建文档 | ✅ 已完成 |
40
- | | `get_feishu_document_info` | 获取文档基本信息 | 验证文档存在性和权限 | ✅ 已完成 |
41
- | | `get_feishu_document_blocks` | 获取文档块结构 | 了解文档层级结构 | ✅ 已完成 |
42
- | **内容编辑** | `batch_create_feishu_blocks` | 批量创建多个块 | 高效创建连续内容 | ✅ 已完成 |
43
- | | `update_feishu_block_text` | 更新块文本内容 | 修改现有内容 | ✅ 已完成 |
44
- | | `delete_feishu_document_blocks` | 删除文档块 | 清理和重构文档内容 | ✅ 已完成 |
45
- | **文件夹管理** | `get_feishu_folder_files` | 获取文件夹文件列表 | 浏览文件夹内容 | ✅ 已完成 |
46
- | | `create_feishu_folder` | 创建新文件夹 | 组织文档结构 | ✅ 已完成 |
47
- | **搜索功能** | `search_feishu_documents` | 搜索文档 | 查找特定内容 | ✅ 已完成 |
48
- | **工具功能** | `convert_feishu_wiki_to_document_id` | Wiki链接转换 | 将Wiki链接转为文档ID | ✅ 已完成 |
49
- | | `get_feishu_image_resource` | 获取图片资源 | 下载文档中的图片 | ✅ 已完成 |
50
- | | `get_feishu_whiteboard_content` | 获取画板内容 | 获取画板中的图形元素和结构(流程图、思维导图等) | ✅ 已完成 |
51
- | **高级功能** | `create_feishu_table` | 创建和编辑表格 | 结构化数据展示 | ✅ 已完成 |
52
- | | 流程图插入 | 支持流程图和思维导图 | 流程梳理和可视化 | ✅ 已完成 |
53
- | 图片插入 | `upload_and_bind_image_to_block` | 支持插入本地和远程图片 | 修改文档内容 | ✅ 已完成 |
54
- | | 公式支持 | 支持数学公式 | 学术和技术文档 | ✅ 已完成 |
37
+ | 功能类别 | 工具名称 | 描述 | 使用场景 | 状态 |
38
+ |---------|--------------------------------------|-------------|--------------------------|-------|
39
+ | **文档管理** | `create_feishu_document` | 创建新的飞书文档 | 从零开始创建文档 | ✅ 已完成 |
40
+ | | `get_feishu_document_info` | 获取文档基本信息 | 验证文档存在性和权限 | ✅ 已完成 |
41
+ | | `get_feishu_document_blocks` | 获取文档块结构 | 了解文档层级结构 | ✅ 已完成 |
42
+ | **内容编辑** | `batch_create_feishu_blocks` | 批量创建多个块 | 高效创建连续内容 | ✅ 已完成 |
43
+ | | `update_feishu_block_text` | 更新块文本内容 | 修改现有内容 | ✅ 已完成 |
44
+ | | `delete_feishu_document_blocks` | 删除文档块 | 清理和重构文档内容 | ✅ 已完成 |
45
+ | **文件夹管理** | `get_feishu_folder_files` | 获取文件夹文件列表 | 浏览文件夹内容 | ✅ 已完成 |
46
+ | | `create_feishu_folder` | 创建新文件夹 | 组织文档结构 | ✅ 已完成 |
47
+ | **搜索功能** | `search_feishu_documents` | 搜索文档 | 查找特定内容 | ✅ 已完成 |
48
+ | **工具功能** | `get_feishu_document_info` | 获取wiki文档信息 | 将Wiki链接转为文档ID、创建wiki子节点 | ✅ 已完成 |
49
+ | | `get_feishu_image_resource` | 获取图片资源 | 下载文档中的图片 | ✅ 已完成 |
50
+ | | `get_feishu_whiteboard_content` | 获取画板内容 | 获取画板中的图形元素和结构(流程图、思维导图等) | ✅ 已完成 |
51
+ | **高级功能** | `create_feishu_table` | 创建和编辑表格 | 结构化数据展示 | ✅ 已完成 |
52
+ | | 流程图插入 | 支持流程图和思维导图 | 流程梳理和可视化 | ✅ 已完成 |
53
+ | | 流程图插入(画板形式) | 支持流程图和思维导图 | 流程梳理和可视化 | ✅ 已完成 |
54
+ | 图片插入 | `upload_and_bind_image_to_block` | 支持插入本地和远程图片 | 修改文档内容 | ✅ 已完成 |
55
+ | | 公式支持 | 支持数学公式 | 学术和技术文档 | ✅ 已完成 |
55
56
 
56
57
  ### 🎨 支持的样式功能(基本支持md所有格式)
57
58
 
@@ -65,6 +66,7 @@
65
66
  - **公式**:在文本块中插入数学公式,支持LaTeX语法
66
67
  - **mermaid图表**:支持流程图、时序图、思维导图、类图、饼图等等
67
68
  - **表格**:支持创建多行列表格,单元格可包含文本、标题、列表、代码块等多种内容类型
69
+ - **飞书文档画板**:支持飞书文档的画板创建,提供更加更为丰富和多样化的内容。
68
70
 
69
71
  ---
70
72
 
@@ -80,7 +82,12 @@
80
82
  - ~~**支持表格创建**:创建包含各种块类型的复杂表格,支持样式控制~~ 0.1.2 ✅
81
83
  - ~~**支持飞书多用户user认证**:一人部署,可以多人使用~~ 0.1.3 ✅
82
84
  - ~~**支持user_access_token自动刷新**:无需频繁授权,提高使用体验~~ 0.1.6 ✅
83
-
85
+ - ~~**支持授权范围校验**:对应用授权进行验证,以确保其符合当前工具的要求。如未满足条件,将提供友好的指引,以便用户更顺畅地使用~~ 0.1.7 ✅
86
+ - ~~**支持创建画板内容**:与Mermaid图表相比,画板能够展示更为丰富和多样化的内容,提供更为友好和愉悦的视觉体验~~ (飞书应用配置发生变更) 0.1.7 ✅
87
+ - **提取环境变量中的 feishuAppId 和 feishuAppSecret**:将飞书配置从环境变量中分离出来,以便在诸如 cursor 等客户端中进行设置,从而支持一个服务共享给多个团队使用。
88
+ - ~~**支持知识库和我的文档库**:实现知识库、我的文档库 节点遍历、节点创建、文件创建、搜索等功能~~ (飞书应用配置发生变更) 0.1.8 ✅
89
+ - **版本更新通知**:在发布新版本时,及时向用户提供相关提示与说明。
90
+ - **stdio模式user认证问题**:修复stdio模式下飞书user认证失败问题
84
91
  ---
85
92
 
86
93
  ## 🔧 飞书配置教程
@@ -108,14 +115,9 @@ npx feishu-mcp@latest --feishu-app-id=<你的飞书应用ID> --feishu-app-secret
108
115
  cd Feishu-MCP
109
116
  ```
110
117
 
111
- 2. **安装依赖**
112
- ```bash
113
- pnpm install
114
- ```
118
+ 2. **配置环境变量(复制一份.env.example保存为.env文件)**
115
119
 
116
- 3. **配置环境变量(复制一份.env.example保存为.env文件)**
117
-
118
- 4. **编辑 .env 文件**
120
+ 3. **编辑 .env 文件**
119
121
  在项目根目录下找到并用任意文本编辑器打开 `.env` 文件,填写你的飞书应用凭证:
120
122
  ```env
121
123
  FEISHU_APP_ID=cli_xxxxx
@@ -124,10 +126,27 @@ npx feishu-mcp@latest --feishu-app-id=<你的飞书应用ID> --feishu-app-secret
124
126
  FEISHU_AUTH_TYPE=tenant/user
125
127
  ```
126
128
 
127
- 5. **运行服务器**
128
- ```bash
129
- pnpm run dev
130
- ```
129
+ 4. **运行服务器**
130
+
131
+ 方式一:本地运行
132
+ - **安装依赖**
133
+ ```bash
134
+ pnpm install
135
+ ```
136
+ - **启动服务**
137
+ ```bash
138
+ pnpm run dev
139
+ ```
140
+
141
+ 方式二:使用 Docker Compose
142
+ - **启动服务**
143
+ ```bash
144
+ docker-compose up -d
145
+ ```
146
+ - **查看日志**
147
+ ```bash
148
+ docker-compose logs -f
149
+ ```
131
150
 
132
151
  ## ⚙️ 项目配置
133
152
 
@@ -142,12 +161,12 @@ npx feishu-mcp@latest --feishu-app-id=<你的飞书应用ID> --feishu-app-secret
142
161
 
143
162
  ### 配置文件方式(适用于 Cursor、Cline 等)
144
163
 
145
- ```json
164
+ ```
146
165
  {
147
166
  "mcpServers": {
148
167
  "feishu-mcp": {
149
168
  "command": "npx",
150
- "args": ["-y", "feishu-mcp", "--stdio"],
169
+ "args": ["-y", "feishu-@latest", "--stdio"],
151
170
  "env": {
152
171
  "FEISHU_APP_ID": "<你的飞书应用ID>",
153
172
  "FEISHU_APP_SECRET": "<你的飞书应用密钥>",
@@ -160,6 +179,9 @@ npx feishu-mcp@latest --feishu-app-id=<你的飞书应用ID> --feishu-app-secret
160
179
  }
161
180
  }
162
181
  ```
182
+
183
+ **⚠️ 重要提示** : `http://localhost:3333/sse?userKey=123456` 中userKey表示连接用户的标识,是非常重要的配置,请填写并尽可能随机
184
+
163
185
  ---
164
186
 
165
187
  ## 📝 使用贴士(重要)
@@ -178,8 +200,11 @@ npx feishu-mcp@latest --feishu-app-id=<你的飞书应用ID> --feishu-app-secret
178
200
 
179
201
  4. ### **使用飞书user认证**:
180
202
  user认证与tenant认证在增加权限时是有区分的,所以**在初次由tenant切换到user时需要注意配置的权限**;为了区分不同的用户需要在配置mcp server服务的url增加query参数:userKey,**该值是用户的唯一标识 所以最好在设置时越随机越好**
181
- ---
182
203
 
204
+ 5. ### **强烈建议使用user认证**:
205
+ tenant认证有诸多限制,比如文件访问权限、飞书openapi兼容(不支持搜索wiki文档)、文档创建编辑记录等方面都不如user认证。
206
+
207
+ ---
183
208
  ## 🚨 故障排查
184
209
 
185
210
  ### 权限问题排查
@@ -203,6 +228,21 @@ npx feishu-mcp@latest --feishu-app-id=<你的飞书应用ID> --feishu-app-secret
203
228
 
204
229
  ---
205
230
 
231
+ ## 📚 开发者 Wiki
232
+
233
+ 详细的开发文档和技术指南,为学习者和贡献者提供全面的指导:
234
+
235
+ - **[Wiki 首页](https://github.com/cso1z/Feishu-MCP/wiki)** - 完整的文档索引和快速导航
236
+ - **[架构设计](https://github.com/cso1z/Feishu-MCP/wiki/架构设计)** - 整体架构和技术栈说明
237
+ - **[核心模块详解](https://github.com/cso1z/Feishu-MCP/wiki/核心模块详解)** - 各模块的实现细节和代码示例
238
+ - **[认证与授权](https://github.com/cso1z/Feishu-MCP/wiki/认证与授权机制)** - Token 管理和多用户支持机制
239
+ - **[开发者指南](https://github.com/cso1z/Feishu-MCP/wiki/开发者指南)** - 环境搭建、开发流程、调试技巧
240
+ - **[API 参考](https://github.com/cso1z/Feishu-MCP/wiki/API-参考文档)** - 所有工具函数的详细文档
241
+ - **[最佳实践](https://github.com/cso1z/Feishu-MCP/wiki/最佳实践)** - 代码规范、性能优化、安全实践
242
+ - **[MCP 协议实现](https://github.com/cso1z/Feishu-MCP/wiki/MCP-协议实现)** - MCP 协议详解和传输层实现
243
+
244
+ ---
245
+
206
246
  ## 💖 支持项目
207
247
 
208
248
  如果这个项目帮助到了你,请考虑:
@@ -6,7 +6,7 @@ import { registerFeishuBlockTools } from './tools/feishuBlockTools.js';
6
6
  import { registerFeishuFolderTools } from './tools/feishuFolderTools.js';
7
7
  const serverInfo = {
8
8
  name: "Feishu MCP Server",
9
- version: "0.1.6",
9
+ version: "0.1.8",
10
10
  };
11
11
  const serverOptions = {
12
12
  capabilities: { logging: {}, tools: {} },
@@ -12,7 +12,7 @@ BlockConfigSchema, MediaIdSchema, MediaExtraSchema, ImagesArraySchema,
12
12
  // MermaidCodeSchema,
13
13
  // ImageWidthSchema,
14
14
  // ImageHeightSchema
15
- TableCreateSchema } from '../../types/feishuSchema.js';
15
+ TableCreateSchema, WhiteboardFillArraySchema } from '../../types/feishuSchema.js';
16
16
  /**
17
17
  * 注册飞书块相关的MCP工具
18
18
  * @param server MCP服务器实例
@@ -20,7 +20,7 @@ TableCreateSchema } from '../../types/feishuSchema.js';
20
20
  */
21
21
  export function registerFeishuBlockTools(server, feishuService) {
22
22
  // 添加更新块文本内容工具
23
- server.tool('update_feishu_block_text', 'Updates the text content and styling of a specific block in a Feishu document. Can be used to modify content in existing text, code, or heading blocks while preserving the block type and other properties. Note: For Feishu wiki links (https://xxx.feishu.cn/wiki/xxx) you must first use convert_feishu_wiki_to_document_id tool to obtain a compatible document ID.', {
23
+ server.tool('update_feishu_block_text', 'Updates the text content and styling of a specific block in a Feishu document. Can be used to modify content in existing text, code, or heading blocks while preserving the block type and other properties. Note: For Feishu wiki links (https://xxx.feishu.cn/wiki/xxx), use get_feishu_document_info to get document information, then use the returned documentId for editing operations.', {
24
24
  documentId: DocumentIdSchema,
25
25
  blockId: BlockIdSchema,
26
26
  textElements: TextElementsArraySchema,
@@ -47,7 +47,7 @@ export function registerFeishuBlockTools(server, feishuService) {
47
47
  }
48
48
  });
49
49
  // 添加通用飞书块创建工具(支持文本、代码、标题)
50
- server.tool('batch_create_feishu_blocks', 'PREFERRED: Efficiently creates multiple blocks (text, code, heading, list) in a single API call. USE THIS TOOL when creating multiple consecutive blocks at the same position - reduces API calls by up to 90%. KEY FEATURES: (1) Handles any number of blocks by auto-batching large requests (>50 blocks), (2) Creates blocks at consecutive positions in a document, (3) Supports direct heading level format (e.g. "heading1", "heading2") or standard "heading" type with level in options. CORRECT FORMAT: mcp_feishu_batch_create_feishu_blocks({documentId:"doc123",parentBlockId:"para123",startIndex:0,blocks:[{blockType:"text",options:{...}},{blockType:"heading1",options:{heading:{content:"Title"}}}]}). For separate positions, use individual block creation tools instead. For wiki links (https://xxx.feishu.cn/wiki/xxx), first convert with convert_feishu_wiki_to_document_id tool.', {
50
+ server.tool('batch_create_feishu_blocks', 'PREFERRED: Efficiently creates multiple blocks (text, code, heading, list, image, mermaid, whiteboard) in a single API call. USE THIS TOOL when creating multiple consecutive blocks at the same position - reduces API calls by up to 90%. KEY FEATURES: (1) Handles any number of blocks by auto-batching large requests (>50 blocks), (2) Creates blocks at consecutive positions in a document, (3) Supports direct heading level format (e.g. "heading1", "heading2") or standard "heading" type with level in options. CORRECT FORMAT: mcp_feishu_batch_create_feishu_blocks({documentId:"doc123",parentBlockId:"para123",startIndex:0,blocks:[{blockType:"text",options:{...}},{blockType:"heading1",options:{heading:{content:"Title"}}}]}). For whiteboard blocks, use blockType:"whiteboard" with options:{whiteboard:{align:1}}. After creating a whiteboard block, you will receive a token in the response (board.token field) which can be used with fill_whiteboard_with_plantuml tool. The fill_whiteboard_with_plantuml tool supports both PlantUML (syntax_type: 1) and Mermaid (syntax_type: 2) formats. For separate positions, use individual block creation tools instead. For wiki links (https://xxx.feishu.cn/wiki/xxx), use get_feishu_document_info to get document information, then use the returned documentId for editing operations.', {
51
51
  documentId: DocumentIdSchema,
52
52
  parentBlockId: ParentBlockIdSchema,
53
53
  index: IndexSchema,
@@ -110,6 +110,9 @@ export function registerFeishuBlockTools(server, feishuService) {
110
110
  // 检查是否有图片块(block_type=27)
111
111
  const imageBlocks = result.children?.filter((child) => child.block_type === 27) || [];
112
112
  const hasImageBlocks = imageBlocks.length > 0;
113
+ // 检查是否有画板块(block_type=43)
114
+ const whiteboardBlocks = result.children?.filter((child) => child.block_type === 43) || [];
115
+ const hasWhiteboardBlocks = whiteboardBlocks.length > 0;
113
116
  const responseData = {
114
117
  ...result,
115
118
  nextIndex: index + blockContents.length,
@@ -120,6 +123,17 @@ export function registerFeishuBlockTools(server, feishuService) {
120
123
  blockIds: imageBlocks.map((block) => block.block_id),
121
124
  reminder: "检测到图片块已创建!请使用 upload_and_bind_image_to_block 工具上传图片并绑定到对应的块ID。"
122
125
  }
126
+ }),
127
+ ...(hasWhiteboardBlocks && {
128
+ whiteboardBlocksInfo: {
129
+ count: whiteboardBlocks.length,
130
+ blocks: whiteboardBlocks.map((block) => ({
131
+ blockId: block.block_id,
132
+ token: block.board?.token,
133
+ align: block.board?.align
134
+ })),
135
+ reminder: "检测到画板块已创建!请使用 fill_whiteboard_with_plantuml 工具填充画板内容,使用返回的 token 作为 whiteboardId 参数。支持 PlantUML (syntax_type: 1) 和 Mermaid (syntax_type: 2) 两种格式。"
136
+ }
123
137
  })
124
138
  };
125
139
  return {
@@ -201,12 +215,26 @@ export function registerFeishuBlockTools(server, feishuService) {
201
215
  allImageBlocks.push(...imageBlocks);
202
216
  });
203
217
  const hasImageBlocks = allImageBlocks.length > 0;
204
- const responseText = `所有飞书块创建成功,共分 ${totalBatches} 批创建了 ${createdBlocksCount} 个块。\n\n` +
218
+ // 检查所有批次中是否有画板块(block_type=43)
219
+ const allWhiteboardBlocks = [];
220
+ results.forEach(batchResult => {
221
+ const whiteboardBlocks = batchResult.children?.filter((child) => child.block_type === 43) || [];
222
+ allWhiteboardBlocks.push(...whiteboardBlocks);
223
+ });
224
+ const hasWhiteboardBlocks = allWhiteboardBlocks.length > 0;
225
+ let responseText = `所有飞书块创建成功,共分 ${totalBatches} 批创建了 ${createdBlocksCount} 个块。\n\n` +
205
226
  `最后一批结果: ${JSON.stringify(results[results.length - 1], null, 2)}\n\n` +
206
- `下一个索引位置: ${currentStartIndex},总创建块数: ${createdBlocksCount}` +
207
- (hasImageBlocks ? `\n\n⚠️ 检测到 ${allImageBlocks.length} 个图片块已创建!\n` +
227
+ `下一个索引位置: ${currentStartIndex},总创建块数: ${createdBlocksCount}`;
228
+ if (hasImageBlocks) {
229
+ responseText += `\n\n⚠️ 检测到 ${allImageBlocks.length} 个图片块已创建!\n` +
208
230
  `图片块IDs: ${allImageBlocks.map(block => block.block_id).join(', ')}\n` +
209
- `请使用 upload_and_bind_image_to_block 工具上传图片并绑定到对应的块ID。` : '');
231
+ `请使用 upload_and_bind_image_to_block 工具上传图片并绑定到对应的块ID。`;
232
+ }
233
+ if (hasWhiteboardBlocks) {
234
+ responseText += `\n\n⚠️ 检测到 ${allWhiteboardBlocks.length} 个画板块已创建!\n` +
235
+ `画板块信息:\n${allWhiteboardBlocks.map((block) => ` - blockId: ${block.block_id}, token: ${block.board?.token || 'N/A'}\n`).join('')}` +
236
+ `请使用 fill_whiteboard_with_plantuml 工具填充画板内容,使用返回的 token 作为 whiteboardId 参数。支持 PlantUML (syntax_type: 1) 和 Mermaid (syntax_type: 2) 两种格式。`;
237
+ }
210
238
  return {
211
239
  content: [
212
240
  {
@@ -430,34 +458,41 @@ export function registerFeishuBlockTools(server, feishuService) {
430
458
  // },
431
459
  // );
432
460
  // 添加飞书Wiki文档ID转换工具
433
- server.tool('convert_feishu_wiki_to_document_id', 'Converts a Feishu Wiki document link to a compatible document ID. This conversion is required before using wiki links with any other Feishu document tools.', {
434
- wikiUrl: z.string().describe('Wiki URL or Token (required). Supports complete URL formats like https://xxx.feishu.cn/wiki/xxxxx or direct use of the Token portion'),
435
- }, async ({ wikiUrl }) => {
436
- try {
437
- if (!feishuService) {
438
- return {
439
- content: [{ type: 'text', text: '飞书服务未初始化,请检查配置' }],
440
- };
441
- }
442
- Logger.info(`开始转换Wiki文档链接,输入: ${wikiUrl}`);
443
- const documentId = await feishuService.convertWikiToDocumentId(wikiUrl);
444
- Logger.info(`Wiki文档转换成功,可用的文档ID为: ${documentId}`);
445
- return {
446
- content: [
447
- { type: 'text', text: `Converted Wiki link to Document ID: ${documentId}\n\nUse this Document ID with other Feishu document tools.` }
448
- ],
449
- };
450
- }
451
- catch (error) {
452
- Logger.error(`转换Wiki文档链接失败:`, error);
453
- const errorMessage = formatErrorMessage(error);
454
- return {
455
- content: [{ type: 'text', text: `转换Wiki文档链接失败: ${errorMessage}` }],
456
- };
457
- }
458
- });
461
+ // server.tool(
462
+ // 'convert_feishu_wiki_to_document_id',
463
+ // 'Converts a Feishu Wiki document link to a compatible document ID. This conversion is required before using wiki links with any other Feishu document tools.',
464
+ // {
465
+ // wikiUrl: z.string().describe('Wiki URL or Token (required). Supports complete URL formats like https://xxx.feishu.cn/wiki/xxxxx or direct use of the Token portion'),
466
+ // },
467
+ // async ({ wikiUrl }) => {
468
+ // try {
469
+ // if (!feishuService) {
470
+ // return {
471
+ // content: [{ type: 'text', text: '飞书服务未初始化,请检查配置' }],
472
+ // };
473
+ // }
474
+ //
475
+ // Logger.info(`开始转换Wiki文档链接,输入: ${wikiUrl}`);
476
+ // const documentId = await feishuService.convertWikiToDocumentId(wikiUrl);
477
+ //
478
+ // Logger.info(`Wiki文档转换成功,可用的文档ID为: ${documentId}`);
479
+ //
480
+ // return {
481
+ // content: [
482
+ // { type: 'text', text: `Converted Wiki link to Document ID: ${documentId}\n\nUse this Document ID with other Feishu document tools.` }
483
+ // ],
484
+ // };
485
+ // } catch (error) {
486
+ // Logger.error(`转换Wiki文档链接失败:`, error);
487
+ // const errorMessage = formatErrorMessage(error);
488
+ // return {
489
+ // content: [{ type: 'text', text: `转换Wiki文档链接失败: ${errorMessage}` }],
490
+ // };
491
+ // }
492
+ // },
493
+ // );
459
494
  // 添加删除文档块工具
460
- server.tool('delete_feishu_document_blocks', 'Deletes one or more consecutive blocks from a Feishu document. Use this tool to remove unwanted content, clean up document structure, or clear space before inserting new content. Supports batch deletion for efficiency. Note: For Feishu wiki links (https://xxx.feishu.cn/wiki/xxx) you must first use convert_feishu_wiki_to_document_id tool to obtain a compatible document ID.', {
495
+ server.tool('delete_feishu_document_blocks', 'Deletes one or more consecutive blocks from a Feishu document. Use this tool to remove unwanted content, clean up document structure, or clear space before inserting new content. Supports batch deletion for efficiency. Note: For Feishu wiki links (https://xxx.feishu.cn/wiki/xxx), use get_feishu_document_info to get document information, then use the returned documentId for editing operations.', {
461
496
  documentId: DocumentIdSchema,
462
497
  parentBlockId: ParentBlockIdSchema,
463
498
  startIndex: StartIndexSchema,
@@ -619,7 +654,7 @@ export function registerFeishuBlockTools(server, feishuService) {
619
654
  }
620
655
  });
621
656
  // 添加创建飞书表格工具
622
- server.tool('create_feishu_table', 'Creates a table block in a Feishu document with specified rows and columns. Each cell can contain different types of content blocks (text, lists, code, etc.). This tool creates the complete table structure including table cells and their content. Note: For Feishu wiki links (https://xxx.feishu.cn/wiki/xxx) you must first use convert_feishu_wiki_to_document_id tool to obtain a compatible document ID.', {
657
+ server.tool('create_feishu_table', 'Creates a table block in a Feishu document with specified rows and columns. Each cell can contain different types of content blocks (text, lists, code, etc.). This tool creates the complete table structure including table cells and their content. Note: For Feishu wiki links (https://xxx.feishu.cn/wiki/xxx), use get_feishu_document_info to get document information, then use the returned documentId for editing operations.', {
623
658
  documentId: DocumentIdSchema,
624
659
  parentBlockId: ParentBlockIdSchema,
625
660
  index: IndexSchema,
@@ -659,4 +694,97 @@ export function registerFeishuBlockTools(server, feishuService) {
659
694
  };
660
695
  }
661
696
  });
697
+ // 添加批量填充画板工具(支持 PlantUML 和 Mermaid)
698
+ server.tool('fill_whiteboard_with_plantuml', 'Batch fills multiple whiteboard blocks with diagram content (PlantUML or Mermaid). Use this tool after creating whiteboard blocks with batch_create_feishu_blocks tool. Each item in the array should contain whiteboardId (the token from board.token field), code and syntax_type. Supports both PlantUML (syntax_type: 1) and Mermaid (syntax_type: 2) formats. Returns detailed results including which whiteboards were filled successfully and which failed, along with failure reasons. The same whiteboard can be filled multiple times.', {
699
+ whiteboards: WhiteboardFillArraySchema,
700
+ }, async ({ whiteboards }) => {
701
+ try {
702
+ if (!feishuService) {
703
+ return {
704
+ content: [{ type: 'text', text: '飞书服务未初始化,请检查配置' }],
705
+ };
706
+ }
707
+ if (!whiteboards || whiteboards.length === 0) {
708
+ return {
709
+ content: [{ type: 'text', text: '错误:画板数组不能为空' }],
710
+ };
711
+ }
712
+ Logger.info(`开始批量填充画板内容,共 ${whiteboards.length} 个画板`);
713
+ const results = [];
714
+ let successCount = 0;
715
+ let failCount = 0;
716
+ // 逐个处理每个画板
717
+ for (let i = 0; i < whiteboards.length; i++) {
718
+ const item = whiteboards[i];
719
+ const { whiteboardId, code, syntax_type } = item;
720
+ const syntaxTypeName = syntax_type === 1 ? 'PlantUML' : 'Mermaid';
721
+ Logger.info(`处理第 ${i + 1}/${whiteboards.length} 个画板,画板ID: ${whiteboardId},语法类型: ${syntaxTypeName}`);
722
+ try {
723
+ const result = await feishuService.createDiagramNode(whiteboardId, code, syntax_type);
724
+ Logger.info(`画板填充成功,画板ID: ${whiteboardId}`);
725
+ successCount++;
726
+ results.push({
727
+ whiteboardId: whiteboardId,
728
+ syntaxType: syntaxTypeName,
729
+ status: 'success',
730
+ nodeId: result.node_id,
731
+ result: result
732
+ });
733
+ }
734
+ catch (error) {
735
+ Logger.error(`画板填充失败,画板ID: ${whiteboardId}`, error);
736
+ failCount++;
737
+ // 提取详细的错误信息
738
+ let errorMessage = formatErrorMessage(error);
739
+ let errorCode;
740
+ let logId;
741
+ if (error?.apiError) {
742
+ const apiError = error.apiError;
743
+ if (apiError.code !== undefined && apiError.msg) {
744
+ errorCode = apiError.code;
745
+ errorMessage = apiError.msg;
746
+ if (apiError.log_id) {
747
+ logId = apiError.log_id;
748
+ }
749
+ }
750
+ }
751
+ else if (error?.err) {
752
+ errorMessage = error.err;
753
+ }
754
+ else if (error?.message) {
755
+ errorMessage = error.message;
756
+ }
757
+ results.push({
758
+ whiteboardId: whiteboardId,
759
+ syntaxType: syntaxTypeName,
760
+ status: 'failed',
761
+ error: {
762
+ message: errorMessage,
763
+ code: errorCode,
764
+ logId: logId,
765
+ details: error
766
+ }
767
+ });
768
+ }
769
+ }
770
+ // 构建返回结果
771
+ const summary = {
772
+ total: whiteboards.length,
773
+ success: successCount,
774
+ failed: failCount,
775
+ results: results
776
+ };
777
+ Logger.info(`批量填充画板完成,成功: ${successCount},失败: ${failCount}`);
778
+ return {
779
+ content: [{ type: 'text', text: JSON.stringify(summary, null, 2) }],
780
+ };
781
+ }
782
+ catch (error) {
783
+ Logger.error(`批量填充画板内容失败:`, error);
784
+ const errorMessage = formatErrorMessage(error);
785
+ return {
786
+ content: [{ type: 'text', text: `批量填充画板内容失败: ${errorMessage}\n\n错误详情: ${JSON.stringify(error, null, 2)}` }],
787
+ };
788
+ }
789
+ });
662
790
  }
@@ -1,61 +1,126 @@
1
1
  import { formatErrorMessage } from '../../utils/error.js';
2
2
  import { Logger } from '../../utils/logger.js';
3
- import { FolderTokenSchema, FolderNameSchema, } from '../../types/feishuSchema.js';
4
- import { Config } from '../../utils/config.js';
3
+ import { FolderTokenSchema, FolderTokenOptionalSchema, FolderNameSchema, WikiSpaceNodeContextSchema, } from '../../types/feishuSchema.js';
5
4
  /**
6
5
  * 注册飞书文件夹相关的MCP工具
7
6
  * @param server MCP服务器实例
8
7
  * @param feishuService 飞书API服务实例
9
8
  */
10
9
  export function registerFeishuFolderTools(server, feishuService) {
11
- const config = Config.getInstance();
12
10
  // 添加获取根文件夹信息工具
13
- if (config.feishu.authType === 'user') {
14
- server.tool('get_feishu_root_folder_info', 'Retrieves basic information about the root folder in Feishu Drive. Returns the token, ID and user ID of the root folder, which can be used for subsequent folder operations.', {}, async () => {
15
- try {
16
- if (!feishuService) {
17
- return {
18
- content: [{ type: 'text', text: '飞书服务未初始化,请检查配置' }],
19
- };
20
- }
21
- Logger.info(`开始获取飞书根文件夹信息`);
22
- const folderInfo = await feishuService.getRootFolderInfo();
23
- Logger.info(`飞书根文件夹信息获取成功,token: ${folderInfo.token}`);
11
+ server.tool('get_feishu_root_folder_info', 'Retrieves the root folder in Feishu Drive, wiki spaces list, and "My Library". Use this when you need to browse folders or wiki spaces from the root. If you know the wiki node name, you can also use search_feishu_documents to directly locate specific wiki nodes instead of traversing from root. Returns root folder token, all wiki spaces, and personal library information.', {}, async () => {
12
+ try {
13
+ if (!feishuService) {
24
14
  return {
25
- content: [{ type: 'text', text: JSON.stringify(folderInfo, null, 2) }],
15
+ content: [{ type: 'text', text: '飞书服务未初始化,请检查配置' }],
26
16
  };
27
17
  }
18
+ Logger.info(`开始获取飞书根文件夹信息、知识空间列表和我的知识库`);
19
+ const result = {
20
+ root_folder: null,
21
+ wiki_spaces: null,
22
+ my_library: null,
23
+ };
24
+ // 获取根文件夹信息
25
+ try {
26
+ const folderInfo = await feishuService.getRootFolderInfo();
27
+ result.root_folder = folderInfo?.data || folderInfo;
28
+ Logger.info(`飞书根文件夹信息获取成功,token: ${result.root_folder?.token}`);
29
+ }
28
30
  catch (error) {
29
31
  Logger.error(`获取飞书根文件夹信息失败:`, error);
30
- const errorMessage = formatErrorMessage(error, '获取飞书根文件夹信息失败');
31
- return {
32
- content: [{ type: 'text', text: errorMessage }],
33
- };
32
+ result.root_folder = { error: formatErrorMessage(error, '获取根文件夹信息失败') };
33
+ }
34
+ // 获取知识空间列表(遍历所有分页)
35
+ try {
36
+ const wikiSpaces = await feishuService.getAllWikiSpacesList(20);
37
+ result.wiki_spaces = wikiSpaces || [];
38
+ Logger.info(`知识空间列表获取成功,共 ${Array.isArray(result.wiki_spaces) ? result.wiki_spaces.length : 0} 个空间`);
39
+ }
40
+ catch (error) {
41
+ Logger.error(`获取知识空间列表失败:`, error);
42
+ result.wiki_spaces = [];
34
43
  }
35
- });
36
- }
44
+ // 获取"我的知识库"(通过传入 my_library 作为 space_id)
45
+ try {
46
+ const myLibrary = await feishuService.getWikiSpaceInfo('my_library', 'en');
47
+ // 提取 space 对象的内容,去掉 space 这一层
48
+ const libraryData = myLibrary?.data || myLibrary;
49
+ result.my_library = libraryData?.space || libraryData;
50
+ Logger.info(`我的知识库获取成功,space_id: ${result.my_library?.space_id}`);
51
+ }
52
+ catch (error) {
53
+ Logger.error(`获取我的知识库失败:`, error);
54
+ result.my_library = { error: formatErrorMessage(error, '获取我的知识库失败') };
55
+ }
56
+ return {
57
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
58
+ };
59
+ }
60
+ catch (error) {
61
+ Logger.error(`获取飞书信息失败:`, error);
62
+ const errorMessage = formatErrorMessage(error, '获取飞书信息失败');
63
+ return {
64
+ content: [{ type: 'text', text: errorMessage }],
65
+ };
66
+ }
67
+ });
37
68
  // 添加获取文件夹中的文件清单工具
38
- server.tool('get_feishu_folder_files', 'Retrieves a list of files and subfolders in a specified folder. Use this to explore folder contents, view file metadata, and get URLs and tokens for further operations.', {
39
- folderToken: FolderTokenSchema,
40
- }, async ({ folderToken, }) => {
69
+ server.tool('get_feishu_folder_files', 'Retrieves a list of files and subfolders in a specified folder or wiki space node. Supports two modes: (1) Feishu Drive folder mode: use folderToken to get files in a Feishu Drive folder. (2) Wiki space node mode: use wikiContext with spaceId (and optional parentNodeToken) to get documents under a wiki space node. If parentNodeToken is not provided, retrieves nodes from the root of the wiki space. Only one mode can be used at a time - provide either folderToken OR wikiContext.', {
70
+ folderToken: FolderTokenOptionalSchema,
71
+ wikiContext: WikiSpaceNodeContextSchema,
72
+ }, async ({ folderToken, wikiContext }) => {
41
73
  try {
42
74
  if (!feishuService) {
43
75
  return {
44
76
  content: [{ type: 'text', text: '飞书服务未初始化,请检查配置' }],
45
77
  };
46
78
  }
47
- Logger.info(`开始获取飞书文件夹中的文件清单,文件夹Token: ${folderToken}`);
48
- const fileList = await feishuService.getFolderFileList(folderToken);
49
- Logger.info(`飞书文件夹中的文件清单获取成功,共 ${fileList.files?.length || 0} 个文件`);
79
+ // 验证参数:必须提供 folderToken 或 wikiContext 之一,但不能同时提供
80
+ if (folderToken && wikiContext) {
81
+ return {
82
+ content: [{ type: 'text', text: '错误:不能同时提供 folderToken 和 wikiContext 参数,请选择其中一种模式。' }],
83
+ };
84
+ }
85
+ if (!folderToken && !wikiContext) {
86
+ return {
87
+ content: [{ type: 'text', text: '错误:必须提供 folderToken(飞书文档目录模式)或 wikiContext(知识库节点模式)参数之一。' }],
88
+ };
89
+ }
90
+ // 模式一:飞书文档目录模式
91
+ if (folderToken) {
92
+ Logger.info(`开始获取飞书文件夹中的文件清单,文件夹Token: ${folderToken}`);
93
+ const fileList = await feishuService.getFolderFileList(folderToken);
94
+ Logger.info(`飞书文件夹中的文件清单获取成功,共 ${fileList.files?.length || 0} 个文件`);
95
+ return {
96
+ content: [{ type: 'text', text: JSON.stringify(fileList, null, 2) }],
97
+ };
98
+ }
99
+ // 模式二:知识库节点模式
100
+ if (wikiContext) {
101
+ const { spaceId, parentNodeToken } = wikiContext;
102
+ if (!spaceId) {
103
+ return {
104
+ content: [{ type: 'text', text: '错误:使用 wikiContext 模式时,必须提供 spaceId。' }],
105
+ };
106
+ }
107
+ Logger.info(`开始获取知识空间子节点列表,知识空间ID: ${spaceId}, 父节点Token: ${parentNodeToken || 'null(根节点)'}`);
108
+ const nodeList = await feishuService.getAllWikiSpaceNodes(spaceId, parentNodeToken);
109
+ Logger.info(`知识空间子节点列表获取成功,共 ${Array.isArray(nodeList) ? nodeList.length : 0} 个节点`);
110
+ return {
111
+ content: [{ type: 'text', text: JSON.stringify({ nodes: nodeList || [] }, null, 2) }],
112
+ };
113
+ }
114
+ // 理论上不会到达这里
50
115
  return {
51
- content: [{ type: 'text', text: JSON.stringify(fileList, null, 2) }],
116
+ content: [{ type: 'text', text: '错误:未知错误' }],
52
117
  };
53
118
  }
54
119
  catch (error) {
55
- Logger.error(`获取飞书文件夹中的文件清单失败:`, error);
120
+ Logger.error(`获取文件列表失败:`, error);
56
121
  const errorMessage = formatErrorMessage(error);
57
122
  return {
58
- content: [{ type: 'text', text: `获取飞书文件夹中的文件清单失败: ${errorMessage}` }],
123
+ content: [{ type: 'text', text: `获取文件列表失败: ${errorMessage}` }],
59
124
  };
60
125
  }
61
126
  });