feishu-mcp 0.2.2 → 0.2.7
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/LICENSE +21 -21
- package/README.md +356 -266
- package/dist/cli/auth.js +117 -0
- package/dist/cli/commands/auth.js +141 -0
- package/dist/cli/commands/config.js +86 -0
- package/dist/cli/commands/guide.js +68 -0
- package/dist/cli/dispatcher.js +96 -0
- package/dist/cli/index.js +95 -0
- package/dist/manager/sseConnectionManager.js +2 -0
- package/dist/mcp/feishuMcp.js +25 -25
- package/dist/mcp/tools/blockTools.js +295 -0
- package/dist/mcp/tools/documentTools.js +105 -0
- package/dist/mcp/tools/feishuBlockTools.js +1 -285
- package/dist/mcp/tools/feishuTools.js +1 -67
- package/dist/mcp/tools/folderTools.js +99 -0
- package/dist/mcp/tools/toolHelpers.js +155 -0
- package/dist/modules/FeatureModule.js +1 -0
- package/dist/modules/ModuleRegistry.js +63 -0
- package/dist/modules/calendar/index.js +11 -0
- package/dist/modules/calendar/services/FeishuCalendarService.js +6 -0
- package/dist/modules/calendar/tools/calendarTools.js +6 -0
- package/dist/modules/document/index.js +15 -0
- package/dist/modules/document/services/FeishuBlockService.js +410 -0
- package/dist/modules/document/services/FeishuDocumentService.js +110 -0
- package/dist/modules/document/services/FeishuFoldService.js +187 -0
- package/dist/modules/document/services/FeishuSearchService.js +232 -0
- package/dist/modules/document/services/FeishuWhiteboardService.js +80 -0
- package/dist/modules/document/services/blockFactory.js +521 -0
- package/dist/modules/document/toolApi/blockToolApi.js +160 -0
- package/dist/modules/document/toolApi/documentToolApi.js +65 -0
- package/dist/modules/document/toolApi/folderToolApi.js +73 -0
- package/dist/modules/document/toolApi/index.js +3 -0
- package/dist/modules/document/tools/blockTools.js +138 -0
- package/dist/modules/document/tools/documentTools.js +64 -0
- package/dist/modules/document/tools/folderTools.js +46 -0
- package/dist/modules/document/tools/toolHelpers.js +155 -0
- package/dist/modules/index.js +5 -0
- package/dist/modules/member/index.js +11 -0
- package/dist/modules/member/services/FeishuMemberService.js +41 -0
- package/dist/modules/member/toolApi/index.js +1 -0
- package/dist/modules/member/toolApi/memberToolApi.js +54 -0
- package/dist/modules/member/tools/memberTools.js +26 -0
- package/dist/modules/task/index.js +11 -0
- package/dist/modules/task/services/FeishuTaskService.js +271 -0
- package/dist/modules/task/toolApi/__tests__/taskToolApi.test.js +98 -0
- package/dist/modules/task/toolApi/index.js +1 -0
- package/dist/modules/task/toolApi/taskToolApi.js +128 -0
- package/dist/modules/task/tools/taskTools.js +93 -0
- package/dist/server.js +43 -24
- package/dist/services/baseService.js +11 -2
- package/dist/services/blockFactory.js +167 -0
- package/dist/services/callbackService.js +1 -1
- package/dist/services/constants/feishuScopes.js +94 -0
- package/dist/services/feishu/FeishuBaseApiService.js +47 -0
- package/dist/services/feishu/FeishuBlockService.js +410 -0
- package/dist/services/feishu/FeishuDocumentBlockService.js +403 -0
- package/dist/services/feishu/FeishuDocumentService.js +110 -0
- package/dist/services/feishu/FeishuDriveService.js +62 -0
- package/dist/services/feishu/FeishuFoldService.js +187 -0
- package/dist/services/feishu/FeishuImageService.js +219 -0
- package/dist/services/feishu/FeishuScopeValidator.js +177 -0
- package/dist/services/feishu/FeishuSearchService.js +232 -0
- package/dist/services/feishu/FeishuWhiteboardService.js +80 -0
- package/dist/services/feishu/FeishuWikiService.js +134 -0
- package/dist/services/feishuApiService.js +246 -1760
- package/dist/services/feishuAuthService.js +43 -0
- package/dist/types/documentSchema.js +232 -0
- package/dist/types/feishuSchema.js +54 -77
- package/dist/types/memberSchema.js +35 -0
- package/dist/types/taskSchema.js +166 -0
- package/dist/utils/auth/tokenCacheManager.js +16 -3
- package/dist/utils/auth/tokenRefreshManager.js +2 -1
- package/dist/utils/config.js +47 -0
- package/dist/utils/document.js +116 -116
- package/dist/utils/error.js +0 -11
- package/dist/utils/paramUtils.js +0 -31
- package/package.json +77 -76
|
@@ -2,17 +2,7 @@ import { z } from 'zod';
|
|
|
2
2
|
import { formatErrorMessage } from '../../utils/error.js';
|
|
3
3
|
import { Logger } from '../../utils/logger.js';
|
|
4
4
|
import { detectMimeType } from '../../utils/document.js';
|
|
5
|
-
import { DocumentIdSchema, ParentBlockIdSchema, BlockIdSchema, IndexSchema, StartIndexSchema, EndIndexSchema,
|
|
6
|
-
// AlignSchema,
|
|
7
|
-
// AlignSchemaWithValidation,
|
|
8
|
-
TextElementsArraySchema,
|
|
9
|
-
// CodeLanguageSchema,
|
|
10
|
-
// CodeWrapSchema,
|
|
11
|
-
BlockConfigSchema, MediaIdSchema, MediaExtraSchema, ImagesArraySchema,
|
|
12
|
-
// MermaidCodeSchema,
|
|
13
|
-
// ImageWidthSchema,
|
|
14
|
-
// ImageHeightSchema
|
|
15
|
-
TableCreateSchema, WhiteboardFillArraySchema } from '../../types/feishuSchema.js';
|
|
5
|
+
import { DocumentIdSchema, ParentBlockIdSchema, BlockIdSchema, IndexSchema, StartIndexSchema, EndIndexSchema, TextElementsArraySchema, BlockConfigSchema, MediaIdSchema, MediaExtraSchema, ImagesArraySchema, TableCreateSchema, WhiteboardFillArraySchema } from '../../types/feishuSchema.js';
|
|
16
6
|
/**
|
|
17
7
|
* 注册飞书块相关的MCP工具
|
|
18
8
|
* @param server MCP服务器实例
|
|
@@ -264,233 +254,6 @@ export function registerFeishuBlockTools(server, feishuService) {
|
|
|
264
254
|
};
|
|
265
255
|
}
|
|
266
256
|
});
|
|
267
|
-
// 添加创建飞书文本块工具
|
|
268
|
-
// server.tool(
|
|
269
|
-
// "create_feishu_text_block",
|
|
270
|
-
// "Creates a new text block with precise style control. Unlike markdown-based formatting, this tool lets you explicitly set text styles for each text segment. Ideal for formatted documents where exact styling control is needed. NOTE: If creating multiple blocks at once, use batch_create_feishu_blocks tool instead for better 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.",
|
|
271
|
-
// {
|
|
272
|
-
// documentId: DocumentIdSchema,
|
|
273
|
-
// parentBlockId: ParentBlockIdSchema,
|
|
274
|
-
// textContents: TextElementsArraySchema,
|
|
275
|
-
// align: AlignSchema,
|
|
276
|
-
// index: IndexSchema
|
|
277
|
-
// },
|
|
278
|
-
// async ({ documentId, parentBlockId, textContents, align = 1, index }) => {
|
|
279
|
-
// try {
|
|
280
|
-
// if (!feishuService) {
|
|
281
|
-
// return {
|
|
282
|
-
// content: [{ type: "text", text: "Feishu service is not initialized. Please check the configuration" }],
|
|
283
|
-
// };
|
|
284
|
-
// }
|
|
285
|
-
//
|
|
286
|
-
// Logger.info(`开始创建飞书文本块,文档ID: ${documentId},父块ID: ${parentBlockId},对齐方式: ${align},插入位置: ${index}`);
|
|
287
|
-
// const result = await feishuService.createTextBlock(documentId, parentBlockId, textContents, align, index);
|
|
288
|
-
// Logger.info(`飞书文本块创建成功`);
|
|
289
|
-
//
|
|
290
|
-
// return {
|
|
291
|
-
// content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
292
|
-
// };
|
|
293
|
-
// } catch (error) {
|
|
294
|
-
// Logger.error(`创建飞书文本块失败:`, error);
|
|
295
|
-
// const errorMessage = formatErrorMessage(error);
|
|
296
|
-
// return {
|
|
297
|
-
// content: [{ type: "text", text: `创建飞书文本块失败: ${errorMessage}` }],
|
|
298
|
-
// };
|
|
299
|
-
// }
|
|
300
|
-
// },
|
|
301
|
-
// );
|
|
302
|
-
//
|
|
303
|
-
// // 添加创建飞书代码块工具
|
|
304
|
-
// server.tool(
|
|
305
|
-
// "create_feishu_code_block",
|
|
306
|
-
// "Creates a new code block with syntax highlighting and formatting options. Ideal for technical documentation, tutorials, or displaying code examples with proper formatting and language-specific highlighting. NOTE: If creating multiple blocks at once, use batch_create_feishu_blocks tool instead for better 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.",
|
|
307
|
-
// {
|
|
308
|
-
// documentId: DocumentIdSchema,
|
|
309
|
-
// parentBlockId: ParentBlockIdSchema,
|
|
310
|
-
// code: z.string().describe("Code content (required). The complete code text to display."),
|
|
311
|
-
// language: CodeLanguageSchema,
|
|
312
|
-
// wrap: CodeWrapSchema,
|
|
313
|
-
// index: IndexSchema
|
|
314
|
-
// },
|
|
315
|
-
// async ({ documentId, parentBlockId, code, language = 1, wrap = false, index = 0 }) => {
|
|
316
|
-
// try {
|
|
317
|
-
// if (!feishuService) {
|
|
318
|
-
// return {
|
|
319
|
-
// content: [{ type: "text", text: "Feishu service is not initialized. Please check the configuration" }],
|
|
320
|
-
// };
|
|
321
|
-
// }
|
|
322
|
-
//
|
|
323
|
-
// Logger.info(`开始创建飞书代码块,文档ID: ${documentId},父块ID: ${parentBlockId},语言: ${language},自动换行: ${wrap},插入位置: ${index}`);
|
|
324
|
-
// const result = await feishuService.createCodeBlock(documentId, parentBlockId, code, language, wrap, index);
|
|
325
|
-
// Logger.info(`飞书代码块创建成功`);
|
|
326
|
-
//
|
|
327
|
-
// return {
|
|
328
|
-
// content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
329
|
-
// };
|
|
330
|
-
// } catch (error) {
|
|
331
|
-
// Logger.error(`创建飞书代码块失败:`, error);
|
|
332
|
-
// const errorMessage = formatErrorMessage(error);
|
|
333
|
-
// return {
|
|
334
|
-
// content: [{ type: "text", text: `创建飞书代码块失败: ${errorMessage}` }],
|
|
335
|
-
// };
|
|
336
|
-
// }
|
|
337
|
-
// },
|
|
338
|
-
// );
|
|
339
|
-
//
|
|
340
|
-
// // 添加创建飞书标题块工具
|
|
341
|
-
// server.tool(
|
|
342
|
-
// "create_feishu_heading_block",
|
|
343
|
-
// "Creates a heading block with customizable level and alignment. Use this tool to add section titles, chapter headings, or any hierarchical structure elements to your document. Supports nine heading levels for different emphasis needs. NOTE: If creating multiple blocks at once, use batch_create_feishu_blocks tool instead for better 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.",
|
|
344
|
-
// {
|
|
345
|
-
// documentId: DocumentIdSchema,
|
|
346
|
-
// parentBlockId: ParentBlockIdSchema,
|
|
347
|
-
// level: z.number().min(1).max(9).describe("Heading level (required). Integer between 1 and 9, where 1 is the largest heading (h1) and 9 is the smallest (h9)."),
|
|
348
|
-
// content: z.string().describe("Heading text content (required). The actual text of the heading."),
|
|
349
|
-
// align: AlignSchemaWithValidation,
|
|
350
|
-
// index: IndexSchema
|
|
351
|
-
// },
|
|
352
|
-
// async ({ documentId, parentBlockId, level, content, align = 1, index = 0 }) => {
|
|
353
|
-
// try {
|
|
354
|
-
// if (!feishuService) {
|
|
355
|
-
// return {
|
|
356
|
-
// content: [{ type: "text", text: "Feishu service is not initialized. Please check the configuration" }],
|
|
357
|
-
// };
|
|
358
|
-
// }
|
|
359
|
-
//
|
|
360
|
-
// // 确保align值在合法范围内(1-3)
|
|
361
|
-
// if (align !== 1 && align !== 2 && align !== 3) {
|
|
362
|
-
// return {
|
|
363
|
-
// content: [{ type: "text", text: "错误: 对齐方式(align)参数必须是1(居左)、2(居中)或3(居右)中的一个值。" }],
|
|
364
|
-
// };
|
|
365
|
-
// }
|
|
366
|
-
//
|
|
367
|
-
// Logger.info(`开始创建飞书标题块,文档ID: ${documentId},父块ID: ${parentBlockId},标题级别: ${level},对齐方式: ${align},插入位置: ${index}`);
|
|
368
|
-
// const result = await feishuService.createHeadingBlock(documentId, parentBlockId, content, level, index, align);
|
|
369
|
-
// Logger.info(`飞书标题块创建成功`);
|
|
370
|
-
//
|
|
371
|
-
// return {
|
|
372
|
-
// content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
373
|
-
// };
|
|
374
|
-
// } catch (error) {
|
|
375
|
-
// Logger.error(`创建飞书标题块失败:`, error);
|
|
376
|
-
// const errorMessage = formatErrorMessage(error);
|
|
377
|
-
// return {
|
|
378
|
-
// content: [{ type: "text", text: `创建飞书标题块失败: ${errorMessage}` }],
|
|
379
|
-
// };
|
|
380
|
-
// }
|
|
381
|
-
// },
|
|
382
|
-
// );
|
|
383
|
-
//
|
|
384
|
-
// // 添加创建飞书列表块工具
|
|
385
|
-
// server.tool(
|
|
386
|
-
// "create_feishu_list_block",
|
|
387
|
-
// "Creates a list item block (either ordered or unordered). Perfect for creating hierarchical and structured content with bullet points or numbered lists. NOTE: If creating multiple blocks at once, use batch_create_feishu_blocks tool instead for better 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.",
|
|
388
|
-
// {
|
|
389
|
-
// documentId: DocumentIdSchema,
|
|
390
|
-
// parentBlockId: ParentBlockIdSchema,
|
|
391
|
-
// content: z.string().describe("List item content (required). The actual text of the list item."),
|
|
392
|
-
// isOrdered: z.boolean().optional().default(false).describe("Whether this is an ordered (numbered) list item. Default is false (bullet point/unordered)."),
|
|
393
|
-
// align: AlignSchemaWithValidation,
|
|
394
|
-
// index: IndexSchema
|
|
395
|
-
// },
|
|
396
|
-
// async ({ documentId, parentBlockId, content, isOrdered = false, align = 1, index = 0 }) => {
|
|
397
|
-
// try {
|
|
398
|
-
// if (!feishuService) {
|
|
399
|
-
// return {
|
|
400
|
-
// content: [{ type: "text", text: "Feishu service is not initialized. Please check the configuration" }],
|
|
401
|
-
// };
|
|
402
|
-
// }
|
|
403
|
-
//
|
|
404
|
-
// // 确保align值在合法范围内(1-3)
|
|
405
|
-
// if (align !== 1 && align !== 2 && align !== 3) {
|
|
406
|
-
// return {
|
|
407
|
-
// content: [{ type: "text", text: "错误: 对齐方式(align)参数必须是1(居左)、2(居中)或3(居右)中的一个值。" }],
|
|
408
|
-
// };
|
|
409
|
-
// }
|
|
410
|
-
//
|
|
411
|
-
// const listType = isOrdered ? "有序" : "无序";
|
|
412
|
-
// Logger.info(`开始创建飞书${listType}列表块,文档ID: ${documentId},父块ID: ${parentBlockId},对齐方式: ${align},插入位置: ${index}`);
|
|
413
|
-
// const result = await feishuService.createListBlock(documentId, parentBlockId, content, isOrdered, index, align);
|
|
414
|
-
// Logger.info(`飞书${listType}列表块创建成功`);
|
|
415
|
-
//
|
|
416
|
-
// return {
|
|
417
|
-
// content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
418
|
-
// };
|
|
419
|
-
// } catch (error) {
|
|
420
|
-
// Logger.error(`创建飞书列表块失败:`, error);
|
|
421
|
-
// const errorMessage = formatErrorMessage(error);
|
|
422
|
-
// return {
|
|
423
|
-
// content: [{ type: "text", text: `创建飞书列表块失败: ${errorMessage}` }],
|
|
424
|
-
// };
|
|
425
|
-
// }
|
|
426
|
-
// },
|
|
427
|
-
// );
|
|
428
|
-
// 添加创建飞书Mermaid块工具
|
|
429
|
-
// server.tool(
|
|
430
|
-
// "create_feishu_mermaid_block",
|
|
431
|
-
// "Creates a new Mermaid block in a Feishu document. This tool allows you to insert a Mermaid diagram by specifying the Mermaid code. 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.",
|
|
432
|
-
// {
|
|
433
|
-
// documentId: DocumentIdSchema,
|
|
434
|
-
// parentBlockId: ParentBlockIdSchema,
|
|
435
|
-
// mermaidCode: MermaidCodeSchema,
|
|
436
|
-
// index: IndexSchema
|
|
437
|
-
// },
|
|
438
|
-
// async ({ documentId, parentBlockId, mermaidCode, index }) => {
|
|
439
|
-
// try {
|
|
440
|
-
// if (!feishuService) {
|
|
441
|
-
// return {
|
|
442
|
-
// content: [{ type: "text", text: "Feishu service is not initialized. Please check the configuration" }],
|
|
443
|
-
// };
|
|
444
|
-
// }
|
|
445
|
-
// Logger.info(`开始创建飞书Mermaid块,文档ID: ${documentId},父块ID: ${parentBlockId},插入位置: ${index}`);
|
|
446
|
-
// const result = await (feishuService as any).createMermaidBlock(documentId, parentBlockId, mermaidCode, index);
|
|
447
|
-
// Logger.info(`飞书Mermaid块创建成功`);
|
|
448
|
-
// return {
|
|
449
|
-
// content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
450
|
-
// };
|
|
451
|
-
// } catch (error) {
|
|
452
|
-
// Logger.error(`创建飞书Mermaid块失败:`, error);
|
|
453
|
-
// const errorMessage = formatErrorMessage(error);
|
|
454
|
-
// return {
|
|
455
|
-
// content: [{ type: "text", text: `创建飞书Mermaid块失败: ${errorMessage}` }],
|
|
456
|
-
// };
|
|
457
|
-
// }
|
|
458
|
-
// },
|
|
459
|
-
// );
|
|
460
|
-
// 添加飞书Wiki文档ID转换工具
|
|
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
|
-
// );
|
|
494
257
|
// 添加删除文档块工具
|
|
495
258
|
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.', {
|
|
496
259
|
documentId: DocumentIdSchema,
|
|
@@ -552,53 +315,6 @@ export function registerFeishuBlockTools(server, feishuService) {
|
|
|
552
315
|
};
|
|
553
316
|
}
|
|
554
317
|
});
|
|
555
|
-
// 添加创建飞书图片块工具
|
|
556
|
-
// server.tool(
|
|
557
|
-
// 'create_feishu_image_block',
|
|
558
|
-
// 'Creates a complete image block in a Feishu document by uploading an image from a local path or URL and setting it to the block. This tool handles the entire 3-step process: (1) Creates an empty image block, (2) Downloads/reads the image and uploads it as media resource, (3) Sets the image content to the block. Supports local file paths and HTTP/HTTPS URLs. Use this when you want to insert images into Feishu documents. 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.',
|
|
559
|
-
// {
|
|
560
|
-
// documentId: DocumentIdSchema,
|
|
561
|
-
// parentBlockId: ParentBlockIdSchema,
|
|
562
|
-
// imagePathOrUrl: ImagePathOrUrlSchema,
|
|
563
|
-
// fileName: ImageFileNameSchema,
|
|
564
|
-
// width: ImageWidthSchema,
|
|
565
|
-
// height: ImageHeightSchema,
|
|
566
|
-
// index: IndexSchema
|
|
567
|
-
// },
|
|
568
|
-
// async ({ documentId, parentBlockId, imagePathOrUrl, fileName, width, height, index = 0 }) => {
|
|
569
|
-
// try {
|
|
570
|
-
// if (!feishuService) {
|
|
571
|
-
// return {
|
|
572
|
-
// content: [{ type: 'text', text: 'Feishu service is not initialized. Please check the configuration' }],
|
|
573
|
-
// };
|
|
574
|
-
// }
|
|
575
|
-
//
|
|
576
|
-
// Logger.info(`开始创建飞书图片块,文档ID: ${documentId},父块ID: ${parentBlockId},图片源: ${imagePathOrUrl},插入位置: ${index}`);
|
|
577
|
-
//
|
|
578
|
-
// const result = await feishuService.createImageBlock(documentId, parentBlockId, imagePathOrUrl, {
|
|
579
|
-
// fileName,
|
|
580
|
-
// width,
|
|
581
|
-
// height,
|
|
582
|
-
// index
|
|
583
|
-
// });
|
|
584
|
-
//
|
|
585
|
-
// Logger.info(`飞书图片块创建成功,块ID: ${result.imageBlockId}`);
|
|
586
|
-
//
|
|
587
|
-
// return {
|
|
588
|
-
// content: [{
|
|
589
|
-
// type: 'text',
|
|
590
|
-
// text: `图片块创建成功!\n\n块ID: ${result.imageBlockId}\n文件Token: ${result.fileToken}\n文档修订ID: ${result.documentRevisionId}\n\n完整结果:\n${JSON.stringify(result, null, 2)}`
|
|
591
|
-
// }],
|
|
592
|
-
// };
|
|
593
|
-
// } catch (error) {
|
|
594
|
-
// Logger.error(`创建飞书图片块失败:`, error);
|
|
595
|
-
// const errorMessage = formatErrorMessage(error);
|
|
596
|
-
// return {
|
|
597
|
-
// content: [{ type: 'text', text: `创建飞书图片块失败: ${errorMessage}` }],
|
|
598
|
-
// };
|
|
599
|
-
// }
|
|
600
|
-
// },
|
|
601
|
-
// );
|
|
602
318
|
// 添加图片上传绑定工具
|
|
603
319
|
server.tool('upload_and_bind_image_to_block', 'Uploads images from local paths or URLs and binds them to existing empty image blocks. This tool is used after creating image blocks with batch_create_feishu_blocks tool. It handles uploading the image media and setting the image content to the specified block IDs. Supports local file paths and HTTP/HTTPS URLs. Each image upload and binding is processed independently, and all results are returned in order.', {
|
|
604
320
|
documentId: DocumentIdSchema,
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { formatErrorMessage } from '../../utils/error.js';
|
|
2
2
|
import { Logger } from '../../utils/logger.js';
|
|
3
|
-
import { DocumentIdSchema, DocumentIdOrWikiIdSchema, DocumentTypeSchema,
|
|
4
|
-
// BlockIdSchema,
|
|
5
|
-
SearchKeySchema, SearchTypeSchema, PageTokenSchema, OffsetSchema, WhiteboardIdSchema, DocumentTitleSchema, FolderTokenOptionalSchema, WikiSpaceNodeContextSchema, } from '../../types/feishuSchema.js';
|
|
3
|
+
import { DocumentIdSchema, DocumentIdOrWikiIdSchema, DocumentTypeSchema, SearchKeySchema, SearchTypeSchema, PageTokenSchema, OffsetSchema, WhiteboardIdSchema, DocumentTitleSchema, FolderTokenOptionalSchema, WikiSpaceNodeContextSchema, } from '../../types/feishuSchema.js';
|
|
6
4
|
/**
|
|
7
5
|
* 注册飞书相关的MCP工具
|
|
8
6
|
* @param server MCP服务器实例
|
|
@@ -110,38 +108,6 @@ export function registerFeishuTools(server, feishuService) {
|
|
|
110
108
|
};
|
|
111
109
|
}
|
|
112
110
|
});
|
|
113
|
-
// 添加获取飞书文档内容工具
|
|
114
|
-
// server.tool(
|
|
115
|
-
// 'get_feishu_document_content',
|
|
116
|
-
// 'Retrieves the plain text content of a Feishu document. Ideal for content analysis, processing, or when you need to extract text without formatting. The content maintains the document structure but without styling. 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.',
|
|
117
|
-
// {
|
|
118
|
-
// documentId: DocumentIdSchema,
|
|
119
|
-
// lang: z.number().optional().default(0).describe('Language code (optional). Default is 0 (Chinese). Use 1 for English if available.'),
|
|
120
|
-
// },
|
|
121
|
-
// async ({ documentId, lang }) => {
|
|
122
|
-
// try {
|
|
123
|
-
// if (!feishuService) {
|
|
124
|
-
// return {
|
|
125
|
-
// content: [{ type: 'text', text: 'Feishu service is not initialized. Please check the configuration' }],
|
|
126
|
-
// };
|
|
127
|
-
// }
|
|
128
|
-
//
|
|
129
|
-
// Logger.info(`开始获取飞书文档内容,文档ID: ${documentId},语言: ${lang}`);
|
|
130
|
-
// const content = await feishuService.getDocumentContent(documentId, lang);
|
|
131
|
-
// Logger.info(`飞书文档内容获取成功,内容长度: ${content.length}字符`);
|
|
132
|
-
//
|
|
133
|
-
// return {
|
|
134
|
-
// content: [{ type: 'text', text: content }],
|
|
135
|
-
// };
|
|
136
|
-
// } catch (error) {
|
|
137
|
-
// Logger.error(`获取飞书文档内容失败:`, error);
|
|
138
|
-
// const errorMessage = formatErrorMessage(error);
|
|
139
|
-
// return {
|
|
140
|
-
// content: [{ type: 'text', text: `获取飞书文档内容失败: ${errorMessage}` }],
|
|
141
|
-
// };
|
|
142
|
-
// }
|
|
143
|
-
// },
|
|
144
|
-
// );
|
|
145
111
|
// 添加获取飞书文档块工具
|
|
146
112
|
server.tool('get_feishu_document_blocks', 'Retrieves the block structure information of a Feishu document. Essential to use before inserting content to understand document structure and determine correct insertion positions. Returns a detailed hierarchy of blocks with their IDs, types, and 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.', {
|
|
147
113
|
documentId: DocumentIdSchema,
|
|
@@ -202,38 +168,6 @@ export function registerFeishuTools(server, feishuService) {
|
|
|
202
168
|
};
|
|
203
169
|
}
|
|
204
170
|
});
|
|
205
|
-
// 添加获取块内容工具
|
|
206
|
-
// server.tool(
|
|
207
|
-
// 'get_feishu_block_content',
|
|
208
|
-
// 'Retrieves the detailed content and structure of a specific block in a Feishu document. Useful for inspecting block properties, formatting, and content, especially before making updates or for debugging purposes. 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.',
|
|
209
|
-
// {
|
|
210
|
-
// documentId: DocumentIdSchema,
|
|
211
|
-
// blockId: BlockIdSchema,
|
|
212
|
-
// },
|
|
213
|
-
// async ({ documentId, blockId }) => {
|
|
214
|
-
// try {
|
|
215
|
-
// if (!feishuService) {
|
|
216
|
-
// return {
|
|
217
|
-
// content: [{ type: 'text', text: '飞书服务未初始化,请检查配置' }],
|
|
218
|
-
// };
|
|
219
|
-
// }
|
|
220
|
-
//
|
|
221
|
-
// Logger.info(`开始获取飞书块内容,文档ID: ${documentId},块ID: ${blockId}`);
|
|
222
|
-
// const blockContent = await feishuService.getBlockContent(documentId, blockId);
|
|
223
|
-
// Logger.info(`飞书块内容获取成功,块类型: ${blockContent.block_type}`);
|
|
224
|
-
//
|
|
225
|
-
// return {
|
|
226
|
-
// content: [{ type: 'text', text: JSON.stringify(blockContent, null, 2) }],
|
|
227
|
-
// };
|
|
228
|
-
// } catch (error) {
|
|
229
|
-
// Logger.error(`获取飞书块内容失败:`, error);
|
|
230
|
-
// const errorMessage = formatErrorMessage(error);
|
|
231
|
-
// return {
|
|
232
|
-
// content: [{ type: 'text', text: `获取飞书块内容失败: ${errorMessage}` }],
|
|
233
|
-
// };
|
|
234
|
-
// }
|
|
235
|
-
// },
|
|
236
|
-
// );
|
|
237
171
|
// 添加搜索文档工具(支持文档和知识库搜索)
|
|
238
172
|
server.tool('search_feishu_documents', 'Searches for documents and/or Wiki knowledge base nodes in Feishu. Supports keyword-based search with type filtering (document, wiki, or both). Returns document and wiki information including title, type, and owner. Supports pagination: use offset for document search pagination and pageToken for wiki search pagination. Each type (document or wiki) can return up to 100 results maximum per search. Default page size is 20 items.', {
|
|
239
173
|
searchKey: SearchKeySchema,
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { formatErrorMessage } from '../../utils/error.js';
|
|
2
|
+
import { Logger } from '../../utils/logger.js';
|
|
3
|
+
import { FolderTokenSchema, FolderTokenOptionalSchema, FolderNameSchema, WikiSpaceNodeContextSchema, } from '../../types/feishuSchema.js';
|
|
4
|
+
import { errorResponse, validateFolderOrWikiContext } from './toolHelpers.js';
|
|
5
|
+
/**
|
|
6
|
+
* 注册飞书文件夹相关的MCP工具
|
|
7
|
+
*/
|
|
8
|
+
export function registerFolderTools(server, feishuService) {
|
|
9
|
+
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 () => {
|
|
10
|
+
try {
|
|
11
|
+
Logger.info(`开始获取飞书根文件夹信息、知识空间列表和我的知识库`);
|
|
12
|
+
const result = {
|
|
13
|
+
root_folder: null,
|
|
14
|
+
wiki_spaces: [],
|
|
15
|
+
my_library: null,
|
|
16
|
+
};
|
|
17
|
+
try {
|
|
18
|
+
const folderInfo = await feishuService.getRootFolderInfo();
|
|
19
|
+
result.root_folder = folderInfo?.data ?? folderInfo;
|
|
20
|
+
Logger.info(`飞书根文件夹信息获取成功,token: ${result.root_folder?.token}`);
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
Logger.error(`获取飞书根文件夹信息失败:`, error);
|
|
24
|
+
result.root_folder = { error: formatErrorMessage(error, '获取根文件夹信息失败') };
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
result.wiki_spaces = (await feishuService.getAllWikiSpacesList(20)) ?? [];
|
|
28
|
+
Logger.info(`知识空间列表获取成功,共 ${result.wiki_spaces.length} 个空间`);
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
Logger.error(`获取知识空间列表失败:`, error);
|
|
32
|
+
result.wiki_spaces = [];
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
const myLibrary = await feishuService.getWikiSpaceInfo('my_library', 'en');
|
|
36
|
+
const libraryData = myLibrary?.data ?? myLibrary;
|
|
37
|
+
result.my_library = libraryData?.space ?? libraryData;
|
|
38
|
+
Logger.info(`我的知识库获取成功,space_id: ${result.my_library?.space_id}`);
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
Logger.error(`获取我的知识库失败:`, error);
|
|
42
|
+
result.my_library = { error: formatErrorMessage(error, '获取我的知识库失败') };
|
|
43
|
+
}
|
|
44
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
Logger.error(`获取飞书信息失败:`, error);
|
|
48
|
+
return errorResponse(formatErrorMessage(error, '获取飞书信息失败'));
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
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.', {
|
|
52
|
+
folderToken: FolderTokenOptionalSchema,
|
|
53
|
+
wikiContext: WikiSpaceNodeContextSchema,
|
|
54
|
+
}, async ({ folderToken, wikiContext }) => {
|
|
55
|
+
try {
|
|
56
|
+
const validationError = validateFolderOrWikiContext(folderToken, wikiContext);
|
|
57
|
+
if (validationError)
|
|
58
|
+
return validationError;
|
|
59
|
+
// 模式一:飞书文档目录模式
|
|
60
|
+
if (folderToken) {
|
|
61
|
+
Logger.info(`开始获取飞书文件夹中的文件清单,文件夹Token: ${folderToken}`);
|
|
62
|
+
const fileList = await feishuService.getFolderFileList(folderToken);
|
|
63
|
+
Logger.info(`飞书文件夹中的文件清单获取成功,共 ${fileList.files?.length ?? 0} 个文件`);
|
|
64
|
+
return { content: [{ type: 'text', text: JSON.stringify(fileList, null, 2) }] };
|
|
65
|
+
}
|
|
66
|
+
// 模式二:知识库节点模式
|
|
67
|
+
if (!wikiContext)
|
|
68
|
+
return errorResponse('错误:内部参数状态异常。');
|
|
69
|
+
const { spaceId, parentNodeToken } = wikiContext;
|
|
70
|
+
if (!spaceId) {
|
|
71
|
+
return errorResponse('错误:使用 wikiContext 模式时,必须提供 spaceId。');
|
|
72
|
+
}
|
|
73
|
+
Logger.info(`开始获取知识空间子节点列表,知识空间ID: ${spaceId}, 父节点Token: ${parentNodeToken ?? 'null(根节点)'}`);
|
|
74
|
+
const nodeList = await feishuService.getAllWikiSpaceNodes(spaceId, parentNodeToken);
|
|
75
|
+
Logger.info(`知识空间子节点列表获取成功,共 ${Array.isArray(nodeList) ? nodeList.length : 0} 个节点`);
|
|
76
|
+
return { content: [{ type: 'text', text: JSON.stringify({ nodes: nodeList ?? [] }, null, 2) }] };
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
Logger.error(`获取文件列表失败:`, error);
|
|
80
|
+
return errorResponse(`获取文件列表失败: ${formatErrorMessage(error)}`);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
// 添加创建文件夹工具
|
|
84
|
+
server.tool('create_feishu_folder', 'Creates a new folder in a specified parent folder. Use this to organize documents and files within your Feishu Drive structure. Returns the token and URL of the newly created folder.', {
|
|
85
|
+
folderToken: FolderTokenSchema,
|
|
86
|
+
folderName: FolderNameSchema,
|
|
87
|
+
}, async ({ folderToken, folderName }) => {
|
|
88
|
+
try {
|
|
89
|
+
Logger.info(`开始创建飞书文件夹,父文件夹Token: ${folderToken},文件夹名称: ${folderName}`);
|
|
90
|
+
const result = await feishuService.createFolder(folderToken, folderName);
|
|
91
|
+
Logger.info(`飞书文件夹创建成功,token: ${result.token},URL: ${result.url}`);
|
|
92
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
Logger.error(`创建飞书文件夹失败:`, error);
|
|
96
|
+
return errorResponse(`创建飞书文件夹失败: ${formatErrorMessage(error)}`);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { formatErrorMessage } from '../../utils/error.js';
|
|
2
|
+
import { Logger } from '../../utils/logger.js';
|
|
3
|
+
export const WHITEBOARD_NODE_THUMBNAIL_THRESHOLD = 200;
|
|
4
|
+
export const BATCH_SIZE = 50;
|
|
5
|
+
/** wiki 链接转换提示,在所有需要 documentId 的编辑类工具中复用 */
|
|
6
|
+
export const WIKI_NOTE = 'For Feishu wiki links (https://xxx.feishu.cn/wiki/xxx), ' +
|
|
7
|
+
'use get_feishu_document_info first to obtain the documentId, then use that ID for editing operations.';
|
|
8
|
+
const BLOCK_TYPE_IMAGE = 27;
|
|
9
|
+
const BLOCK_TYPE_WHITEBOARD = 43;
|
|
10
|
+
export function errorResponse(text) {
|
|
11
|
+
return { content: [{ type: 'text', text }] };
|
|
12
|
+
}
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// 参数校验
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
/**
|
|
17
|
+
* 校验 folderToken 与 wikiContext 的互斥关系(文件夹模式 vs 知识库模式)。
|
|
18
|
+
* 返回 null 表示校验通过,否则返回可直接 return 的错误响应。
|
|
19
|
+
*/
|
|
20
|
+
export function validateFolderOrWikiContext(folderToken, wikiContext) {
|
|
21
|
+
if (folderToken && wikiContext) {
|
|
22
|
+
return errorResponse('错误:不能同时提供 folderToken 和 wikiContext 参数,请选择其中一种模式。\n' +
|
|
23
|
+
'- 使用 folderToken 在飞书文档目录中操作\n' +
|
|
24
|
+
'- 使用 wikiContext 在知识库中操作');
|
|
25
|
+
}
|
|
26
|
+
if (!folderToken && !wikiContext) {
|
|
27
|
+
return errorResponse('错误:必须提供 folderToken(飞书文档目录模式)或 wikiContext(知识库节点模式)参数之一。');
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* 将 BlockConfig 数组转换为飞书 API 所需的块内容数组。
|
|
33
|
+
* 遇到单个块处理失败时立即终止。
|
|
34
|
+
*
|
|
35
|
+
* 注:contents 类型为 any[] 以匹配 FeishuApiService.createDocumentBlocks 的参数类型。
|
|
36
|
+
*/
|
|
37
|
+
export function prepareBlockContents(configs, feishuService) {
|
|
38
|
+
const contents = [];
|
|
39
|
+
for (const { blockType, options = {} } of configs) {
|
|
40
|
+
try {
|
|
41
|
+
const blockContent = feishuService.createBlockContent(blockType, options);
|
|
42
|
+
if (blockContent) {
|
|
43
|
+
contents.push(blockContent);
|
|
44
|
+
Logger.info(`已准备 ${blockType} 块,内容: ${JSON.stringify(blockContent).substring(0, 100)}...`);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
Logger.warn(`创建 ${blockType} 块失败,跳过此块`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
Logger.error(`处理块类型 ${blockType} 时出错: ${err}`);
|
|
52
|
+
return {
|
|
53
|
+
ok: false,
|
|
54
|
+
error: errorResponse(`处理块类型"${blockType}"时出错: ${err}\n请检查该块类型的配置是否正确。`),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return { ok: true, contents };
|
|
59
|
+
}
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
// 特殊块提取与提示
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
/**
|
|
64
|
+
* 从块列表中提取图片块(block_type=27)和画板块(block_type=43)。
|
|
65
|
+
*/
|
|
66
|
+
export function extractSpecialBlocks(blocks) {
|
|
67
|
+
return {
|
|
68
|
+
imageBlocks: blocks.filter(b => b.block_type === BLOCK_TYPE_IMAGE),
|
|
69
|
+
whiteboardBlocks: blocks.filter(b => b.block_type === BLOCK_TYPE_WHITEBOARD),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* 构建特殊块的 JSON 提示对象,用于展开合并到 JSON 响应中。
|
|
74
|
+
*/
|
|
75
|
+
export function buildSpecialBlockHints(imageBlocks, whiteboardBlocks) {
|
|
76
|
+
const hints = {};
|
|
77
|
+
if (imageBlocks.length > 0) {
|
|
78
|
+
hints.imageBlocksInfo = {
|
|
79
|
+
count: imageBlocks.length,
|
|
80
|
+
blockIds: imageBlocks.map(b => b.block_id),
|
|
81
|
+
reminder: '检测到图片块已创建!请使用 upload_and_bind_image_to_block 工具上传图片并绑定到对应的块ID。',
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
if (whiteboardBlocks.length > 0) {
|
|
85
|
+
hints.whiteboardBlocksInfo = {
|
|
86
|
+
count: whiteboardBlocks.length,
|
|
87
|
+
blocks: whiteboardBlocks.map(b => ({
|
|
88
|
+
blockId: b.block_id,
|
|
89
|
+
token: b.board?.token,
|
|
90
|
+
align: b.board?.align,
|
|
91
|
+
})),
|
|
92
|
+
reminder: '检测到画板块已创建!请使用 fill_whiteboard_with_plantuml 工具填充画板内容,使用返回的 token 作为 whiteboardId 参数。支持 PlantUML (syntax_type: "plantuml") 和 Mermaid (syntax_type: "mermaid") 两种格式。',
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
return hints;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* 构建文本形式的特殊块提示,追加到现有响应文本后。
|
|
99
|
+
*/
|
|
100
|
+
export function appendSpecialBlockTextHints(base, imageBlocks, whiteboardBlocks) {
|
|
101
|
+
const sections = [base];
|
|
102
|
+
if (imageBlocks.length > 0) {
|
|
103
|
+
const lines = [
|
|
104
|
+
`\n\n🖼️ 检测到 ${imageBlocks.length} 个图片块 (block_type: 27)!`,
|
|
105
|
+
'💡 提示:如需查看图片内容,可使用 get_feishu_image_resource 工具下载图片。',
|
|
106
|
+
'图片信息:',
|
|
107
|
+
...imageBlocks.map((b, idx) => {
|
|
108
|
+
const parts = [` ${idx + 1}. 块ID: ${b.block_id}`];
|
|
109
|
+
if (b.image?.token)
|
|
110
|
+
parts.push(`媒体ID: ${b.image.token}`);
|
|
111
|
+
return parts.join(',');
|
|
112
|
+
}),
|
|
113
|
+
'📝 注意:只有在需要查看图片内容时才调用上述工具,仅了解文档结构时无需获取。',
|
|
114
|
+
];
|
|
115
|
+
sections.push(lines.join('\n'));
|
|
116
|
+
}
|
|
117
|
+
if (whiteboardBlocks.length > 0) {
|
|
118
|
+
const lines = [
|
|
119
|
+
`\n\n⚠️ 检测到 ${whiteboardBlocks.length} 个画板块 (block_type: 43)!`,
|
|
120
|
+
'💡 提示:如需获取画板具体内容,可使用 get_feishu_whiteboard_content 工具。',
|
|
121
|
+
'画板信息:',
|
|
122
|
+
...whiteboardBlocks.map((b, idx) => {
|
|
123
|
+
const parts = [` ${idx + 1}. 块ID: ${b.block_id}`];
|
|
124
|
+
if (b.board?.token)
|
|
125
|
+
parts.push(`画板ID: ${b.board.token}`);
|
|
126
|
+
return parts.join(',');
|
|
127
|
+
}),
|
|
128
|
+
'📝 注意:只有在需要分析画板内容时才调用上述工具,仅了解文档结构时无需获取。',
|
|
129
|
+
];
|
|
130
|
+
sections.push(lines.join('\n'));
|
|
131
|
+
}
|
|
132
|
+
return sections.join('');
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* 从飞书 API 抛出的错误对象中提取结构化错误信息。
|
|
136
|
+
* 飞书 API 错误可能包含 apiError.code / apiError.msg / apiError.log_id 等字段。
|
|
137
|
+
*/
|
|
138
|
+
export function extractFeishuApiError(err) {
|
|
139
|
+
if (err && typeof err === 'object') {
|
|
140
|
+
const e = err;
|
|
141
|
+
const apiError = e.apiError;
|
|
142
|
+
if (apiError && apiError.code !== undefined && apiError.msg) {
|
|
143
|
+
return {
|
|
144
|
+
message: String(apiError.msg),
|
|
145
|
+
code: Number(apiError.code),
|
|
146
|
+
logId: apiError.log_id != null ? String(apiError.log_id) : undefined,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
if (typeof e.err === 'string')
|
|
150
|
+
return { message: e.err };
|
|
151
|
+
if (typeof e.message === 'string')
|
|
152
|
+
return { message: e.message };
|
|
153
|
+
}
|
|
154
|
+
return { message: formatErrorMessage(err) };
|
|
155
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|