@lmcl/ailo-mcp-feishu 0.0.7 → 0.0.9

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.
@@ -1,4 +1,5 @@
1
1
  import * as fs from "node:fs";
2
+ import * as os from "node:os";
2
3
  import * as path from "node:path";
3
4
  import * as Lark from "@larksuiteoapi/node-sdk";
4
5
  import { getWorkDir } from "@lmcl/ailo-mcp-sdk";
@@ -308,30 +309,30 @@ export class FeishuHandler {
308
309
  this.onMessageRead = handler;
309
310
  }
310
311
  buildBridgeMessage(opts) {
311
- const { chatId, text, chatType, senderId = "", senderName = "", chatName, mentionsSelf, timestamp, attachments } = opts;
312
+ const { chatId, text, chatType, senderId = "", senderName = "", chatName, attachments } = opts;
312
313
  const isPrivate = chatType === "私聊";
314
+ // 新 ContextTag 格式(kind/streamKey/routing):
315
+ // - TagChannel 由网关根据 displayName 自动注入,飞书不重复添加
316
+ // - chat_id 参与 stream_key 推导(streamKey=true),仅路由(routing=true)
317
+ // - 私聊:stream_key = feishu:{ou_xxx}(用户 ID)
318
+ // - 群聊:stream_key = feishu:{oc_xxx}(群 ID)
319
+ // - "@我" 是内容语义不是时空场,不放 contextTags
320
+ // - "时间" 由框架在接收时自动渲染,不由通道传入
313
321
  const tags = [
314
- { desc: "类型", value: chatType, core: true },
315
- { desc: "会话", value: chatId, core: true },
322
+ { kind: "conv_type", value: chatType, streamKey: false },
323
+ { kind: "chat_id", value: chatId, streamKey: true, routing: true },
316
324
  ];
317
- if (!isPrivate && chatName) {
318
- tags.push({ desc: "群名", value: chatName, core: true });
325
+ if (!isPrivate) {
326
+ // 群聊:群名作为 stream_key 语义补充,group 标签不参与 stream_key(chat_id 已足够)
327
+ const groupName = chatName || `群${chatId.slice(-8)}`;
328
+ tags.push({ kind: "group", value: groupName, streamKey: false });
319
329
  }
320
- else if (!isPrivate && chatId) {
321
- tags.push({ desc: "群名", value: `群${chatId.slice(-8)}`, core: true });
330
+ if (senderName) {
331
+ tags.push({ kind: "participant", value: senderName, streamKey: false });
322
332
  }
323
- tags.push({ desc: "昵称", value: senderName, core: isPrivate }, { desc: "用户", value: senderId, core: isPrivate });
324
- if (mentionsSelf) {
325
- tags.push({ desc: "@我", value: "是", core: isPrivate });
326
- }
327
- if (timestamp != null && timestamp > 0) {
328
- const d = new Date(timestamp);
329
- const pad = (n) => String(n).padStart(2, "0");
330
- tags.push({
331
- desc: "时间",
332
- value: `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`,
333
- core: false,
334
- });
333
+ if (senderId) {
334
+ // sender_id 仅路由备用(recall agent 按参与者查询时使用),不展示在历史邮戳
335
+ tags.push({ kind: "sender_id", value: senderId, streamKey: false, routing: true });
335
336
  }
336
337
  return { text, contextTags: tags, attachments };
337
338
  }
@@ -490,77 +491,43 @@ export class FeishuHandler {
490
491
  return "p2p";
491
492
  }
492
493
  /**
493
- * 下载消息资源到本地缓存,返回 path。失败时返回 null(调用方传 ref)。
494
- * 路径按年/月组织:blobs/YYYY/MM/
495
- * 文件名:时间戳_原始file_name(必须提供 file_name,否则不保存,走 ref
494
+ * 保存附件到本地,返回绝对路径。
495
+ * 收到飞书消息时,先调用此函数保存图片/视频/文件,再传给 LLM。
496
+ * 失败返回 null,不传 ref
496
497
  */
497
- async downloadToCache(messageId, fileKey, resourceType, aidoType, fileName) {
498
- const trimmed = fileName?.trim();
499
- if (!trimmed)
500
- return null;
501
- const workDir = getWorkDir();
502
- if (!workDir)
503
- return null;
498
+ async saveResourceToLocal(messageId, fileKey, resourceType, aidoType, fileName) {
499
+ const workDir = getWorkDir() ?? path.join(os.tmpdir(), "ailo-mcp-feishu-blobs");
504
500
  const now = new Date();
505
- const year = String(now.getFullYear());
506
- const month = String(now.getMonth() + 1).padStart(2, "0");
507
- const cacheDir = path.join(workDir, "blobs", year, month);
501
+ const cacheDir = path.join(workDir, "blobs", String(now.getFullYear()), String(now.getMonth() + 1).padStart(2, "0"));
502
+ await fs.promises.mkdir(cacheDir, { recursive: true });
503
+ const sanitized = fileName.replace(/[/\\?*:|"<>]/g, "_").slice(0, 200);
504
+ const outPath = path.join(cacheDir, `${Date.now()}_${sanitized}`);
505
+ let buffer = null;
508
506
  try {
509
- await fs.promises.mkdir(cacheDir, { recursive: true });
510
- }
511
- catch {
512
- return null;
513
- }
514
- const sanitized = trimmed.replace(/[/\\?*:|"<>]/g, "_").slice(0, 200);
515
- const filename = `${Date.now()}_${sanitized}`;
516
- const outPath = path.join(cacheDir, filename);
517
- const tryMessageResource = async () => {
518
507
  const res = await this.client.im.v1.messageResource.get({
519
508
  params: { type: resourceType },
520
509
  path: { message_id: messageId, file_key: fileKey },
521
510
  });
522
- if (!res?.getReadableStream)
523
- return null;
524
- return streamToBuffer(res.getReadableStream());
525
- };
526
- try {
527
- let buffer = await tryMessageResource();
528
- if (!buffer && resourceType === "image" && aidoType === "image") {
511
+ if (res?.getReadableStream)
512
+ buffer = await streamToBuffer(res.getReadableStream());
513
+ }
514
+ catch {
515
+ // messageResource 可能失败,图片可尝试 image.get
516
+ }
517
+ if (!buffer && resourceType === "image" && aidoType === "image") {
518
+ try {
529
519
  const res = await this.client.im.v1.image.get({ path: { image_key: fileKey } });
530
- if (res?.getReadableStream) {
520
+ if (res?.getReadableStream)
531
521
  buffer = await streamToBuffer(res.getReadableStream());
532
- }
533
522
  }
534
- if (!buffer)
535
- return null;
536
- await fs.promises.writeFile(outPath, buffer);
537
- return outPath;
523
+ catch {
524
+ // ignore
525
+ }
538
526
  }
539
- catch (err) {
540
- console.warn(`[feishu] downloadToCache failed (${resourceType}):`, err);
527
+ if (!buffer)
541
528
  return null;
542
- }
543
- }
544
- /**
545
- * 获取消息资源并转为 attachment。有 file_name 时用原名;无则用默认名(如图片用 image_xxx.png)。
546
- * 下载到本地后传绝对路径给 LLM,否则 LLM 无法访问。
547
- */
548
- async fetchMessageResourceAttachment(messageId, fileKey, resourceType, aidoType, fileName) {
549
- const effectiveName = fileName?.trim() || this.defaultFileNameForResource(aidoType, fileKey);
550
- const pathOrNull = await this.downloadToCache(messageId, fileKey, resourceType, aidoType, effectiveName);
551
- if (pathOrNull) {
552
- return {
553
- type: aidoType,
554
- path: path.resolve(pathOrNull),
555
- name: path.basename(pathOrNull),
556
- };
557
- }
558
- return { type: aidoType, ref: `im:${messageId}:${fileKey}:${aidoType}`, channel: "feishu" };
559
- }
560
- /** 无 file_name 时的默认文件名(如图片、无名的文件等) */
561
- defaultFileNameForResource(aidoType, fileKey) {
562
- const ext = { image: "png", audio: "mp3", video: "mp4", file: "bin" }[aidoType] ?? "bin";
563
- return `${aidoType}_${fileKey.slice(-12)}.${ext}`;
529
+ await fs.promises.writeFile(outPath, buffer);
530
+ return path.resolve(outPath);
564
531
  }
565
532
  /**
566
533
  * 使用飞书 WebSocket 长连接接收事件,无需配置回调地址。
@@ -658,18 +625,11 @@ export class FeishuHandler {
658
625
  else if (messageType === "post") {
659
626
  text = extractTextFromPostContent(rawContent);
660
627
  const postImageKeys = [...new Set(extractImageKeysFromPostContent(rawContent))];
661
- if (postImageKeys.length > 0) {
662
- console.error(`[feishu] post: fetching ${postImageKeys.length} image(s) from message ${messageId}`);
663
- for (const imageKey of postImageKeys) {
664
- const attach = await this.fetchMessageResourceAttachment(messageId, imageKey, "image", "image");
665
- if (attach) {
666
- attachments.push(attach);
667
- console.error(`[feishu] post: fetched image ok (${attach.path ? "path" : "ref"})`);
668
- }
669
- else {
670
- console.warn(`[feishu] post: failed to fetch image ${imageKey}`);
671
- }
672
- }
628
+ for (const imageKey of postImageKeys) {
629
+ const fileName = `image_${imageKey.slice(-12)}.png`;
630
+ const absPath = await this.saveResourceToLocal(messageId, imageKey, "image", "image", fileName);
631
+ if (absPath)
632
+ attachments.push({ type: "image", path: absPath, name: path.basename(absPath) });
673
633
  }
674
634
  }
675
635
  else {
@@ -679,20 +639,18 @@ export class FeishuHandler {
679
639
  const content = JSON.parse(rawContent || "{}");
680
640
  const fileKey = content[mediaConfig.contentKey];
681
641
  if (fileKey) {
682
- const fileName = content["file_name"] ?? content["fileName"];
683
- const attach = await this.fetchMessageResourceAttachment(messageId, fileKey, mediaConfig.resourceType, mediaConfig.aidoType, fileName);
684
- if (attach)
685
- attachments.push(attach);
642
+ const fileName = content["file_name"] ?? content["fileName"] ?? `${mediaConfig.aidoType}_${fileKey.slice(-12)}.${mediaConfig.aidoType === "image" ? "png" : mediaConfig.aidoType === "video" ? "mp4" : mediaConfig.aidoType === "audio" ? "mp3" : "bin"}`;
643
+ const absPath = await this.saveResourceToLocal(messageId, fileKey, mediaConfig.resourceType, mediaConfig.aidoType, fileName);
644
+ if (absPath)
645
+ attachments.push({ type: mediaConfig.aidoType, path: absPath, name: path.basename(absPath) });
686
646
  else
687
647
  text = `[无法获取${mediaConfig.aidoType}资源]`;
688
648
  }
689
649
  else {
690
- console.warn(`[feishu] ${messageType} message missing ${mediaConfig.contentKey} in content`);
691
650
  text = "[无法解析的媒体消息]";
692
651
  }
693
652
  }
694
- catch (err) {
695
- console.warn(`[feishu] failed to parse ${messageType} message content:`, err);
653
+ catch {
696
654
  text = "[无法解析的媒体消息]";
697
655
  }
698
656
  }
package/dist/index.js CHANGED
@@ -25,6 +25,8 @@ function feishuBuildChannelPrompt() {
25
25
  const mcpServer = createFeishuMcpServer(handler);
26
26
  runMcpChannel({
27
27
  handler,
28
+ displayName: "飞书",
29
+ defaultRequiresResponse: true,
28
30
  buildChannelPrompt: feishuBuildChannelPrompt,
29
31
  mcpServer,
30
32
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lmcl/ailo-mcp-feishu",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
4
4
  "description": "Ailo 飞书/Lark 通道 MCP",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -14,7 +14,7 @@
14
14
  },
15
15
  "dependencies": {
16
16
  "@larksuiteoapi/node-sdk": "^1.56.1",
17
- "@lmcl/ailo-mcp-sdk": "^0.0.3",
17
+ "@lmcl/ailo-mcp-sdk": "^0.0.4",
18
18
  "@modelcontextprotocol/sdk": "^1.26.0",
19
19
  "dotenv": "^16.4.5",
20
20
  "form-data": "^4.0.0",
@@ -1,4 +1,5 @@
1
1
  import * as fs from "node:fs";
2
+ import * as os from "node:os";
2
3
  import * as path from "node:path";
3
4
  import * as Lark from "@larksuiteoapi/node-sdk";
4
5
  import { getWorkDir, type BridgeHandler, type BridgeMessage, type ContextTag } from "@lmcl/ailo-mcp-sdk";
@@ -380,33 +381,36 @@ export class FeishuHandler implements BridgeHandler {
380
381
  timestamp?: number;
381
382
  attachments?: Array<{ type: string; path?: string; url?: string; base64?: string; mime?: string; name?: string }>;
382
383
  }): BridgeMessage {
383
- const { chatId, text, chatType, senderId = "", senderName = "", chatName, mentionsSelf, timestamp, attachments } = opts;
384
+ const { chatId, text, chatType, senderId = "", senderName = "", chatName, attachments } = opts;
384
385
  const isPrivate = chatType === "私聊";
386
+
387
+ // 新 ContextTag 格式(kind/streamKey/routing):
388
+ // - TagChannel 由网关根据 displayName 自动注入,飞书不重复添加
389
+ // - chat_id 参与 stream_key 推导(streamKey=true),仅路由(routing=true)
390
+ // - 私聊:stream_key = feishu:{ou_xxx}(用户 ID)
391
+ // - 群聊:stream_key = feishu:{oc_xxx}(群 ID)
392
+ // - "@我" 是内容语义不是时空场,不放 contextTags
393
+ // - "时间" 由框架在接收时自动渲染,不由通道传入
385
394
  const tags: ContextTag[] = [
386
- { desc: "类型", value: chatType, core: true },
387
- { desc: "会话", value: chatId, core: true },
395
+ { kind: "conv_type", value: chatType, streamKey: false },
396
+ { kind: "chat_id", value: chatId, streamKey: true, routing: true },
388
397
  ];
389
- if (!isPrivate && chatName) {
390
- tags.push({ desc: "群名", value: chatName, core: true });
391
- } else if (!isPrivate && chatId) {
392
- tags.push({ desc: "群名", value: `群${chatId.slice(-8)}`, core: true });
398
+
399
+ if (!isPrivate) {
400
+ // 群聊:群名作为 stream_key 语义补充,group 标签不参与 stream_key(chat_id 已足够)
401
+ const groupName = chatName || `群${chatId.slice(-8)}`;
402
+ tags.push({ kind: "group", value: groupName, streamKey: false });
393
403
  }
394
- tags.push(
395
- { desc: "昵称", value: senderName, core: isPrivate },
396
- { desc: "用户", value: senderId, core: isPrivate }
397
- );
398
- if (mentionsSelf) {
399
- tags.push({ desc: "@我", value: "是", core: isPrivate });
404
+
405
+ if (senderName) {
406
+ tags.push({ kind: "participant", value: senderName, streamKey: false });
400
407
  }
401
- if (timestamp != null && timestamp > 0) {
402
- const d = new Date(timestamp);
403
- const pad = (n: number) => String(n).padStart(2, "0");
404
- tags.push({
405
- desc: "时间",
406
- value: `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`,
407
- core: false,
408
- });
408
+
409
+ if (senderId) {
410
+ // sender_id 仅路由备用(recall agent 按参与者查询时使用),不展示在历史邮戳
411
+ tags.push({ kind: "sender_id", value: senderId, streamKey: false, routing: true });
409
412
  }
413
+
410
414
  return { text, contextTags: tags, attachments };
411
415
  }
412
416
 
@@ -579,93 +583,45 @@ export class FeishuHandler implements BridgeHandler {
579
583
  }
580
584
 
581
585
  /**
582
- * 下载消息资源到本地缓存,返回 path。失败时返回 null(调用方传 ref)。
583
- * 路径按年/月组织:blobs/YYYY/MM/
584
- * 文件名:时间戳_原始file_name(必须提供 file_name,否则不保存,走 ref
586
+ * 保存附件到本地,返回绝对路径。
587
+ * 收到飞书消息时,先调用此函数保存图片/视频/文件,再传给 LLM。
588
+ * 失败返回 null,不传 ref
585
589
  */
586
- private async downloadToCache(
590
+ private async saveResourceToLocal(
587
591
  messageId: string,
588
592
  fileKey: string,
589
593
  resourceType: string,
590
594
  aidoType: "image" | "audio" | "video" | "file",
591
595
  fileName: string
592
596
  ): Promise<string | null> {
593
- const trimmed = fileName?.trim();
594
- if (!trimmed) return null;
595
- const workDir = getWorkDir();
596
- if (!workDir) return null;
597
+ const workDir = getWorkDir() ?? path.join(os.tmpdir(), "ailo-mcp-feishu-blobs");
597
598
  const now = new Date();
598
- const year = String(now.getFullYear());
599
- const month = String(now.getMonth() + 1).padStart(2, "0");
600
- const cacheDir = path.join(workDir, "blobs", year, month);
601
- try {
602
- await fs.promises.mkdir(cacheDir, { recursive: true });
603
- } catch {
604
- return null;
605
- }
606
- const sanitized = trimmed.replace(/[/\\?*:|"<>]/g, "_").slice(0, 200);
607
- const filename = `${Date.now()}_${sanitized}`;
608
- const outPath = path.join(cacheDir, filename);
599
+ const cacheDir = path.join(workDir, "blobs", String(now.getFullYear()), String(now.getMonth() + 1).padStart(2, "0"));
600
+ await fs.promises.mkdir(cacheDir, { recursive: true });
601
+ const sanitized = fileName.replace(/[/\\?*:|"<>]/g, "_").slice(0, 200);
602
+ const outPath = path.join(cacheDir, `${Date.now()}_${sanitized}`);
609
603
 
610
- const tryMessageResource = async (): Promise<Buffer | null> => {
604
+ let buffer: Buffer | null = null;
605
+ try {
611
606
  const res = await this.client.im.v1.messageResource.get({
612
607
  params: { type: resourceType },
613
608
  path: { message_id: messageId, file_key: fileKey },
614
609
  });
615
- if (!res?.getReadableStream) return null;
616
- return streamToBuffer(res.getReadableStream());
617
- };
618
-
619
- try {
620
- let buffer: Buffer | null = await tryMessageResource();
621
- if (!buffer && resourceType === "image" && aidoType === "image") {
610
+ if (res?.getReadableStream) buffer = await streamToBuffer(res.getReadableStream());
611
+ } catch {
612
+ // messageResource 可能失败,图片可尝试 image.get
613
+ }
614
+ if (!buffer && resourceType === "image" && aidoType === "image") {
615
+ try {
622
616
  const res = await this.client.im.v1.image.get({ path: { image_key: fileKey } });
623
- if (res?.getReadableStream) {
624
- buffer = await streamToBuffer(res.getReadableStream());
625
- }
617
+ if (res?.getReadableStream) buffer = await streamToBuffer(res.getReadableStream());
618
+ } catch {
619
+ // ignore
626
620
  }
627
- if (!buffer) return null;
628
- await fs.promises.writeFile(outPath, buffer);
629
- return outPath;
630
- } catch (err) {
631
- console.warn(`[feishu] downloadToCache failed (${resourceType}):`, err);
632
- return null;
633
- }
634
- }
635
-
636
- /**
637
- * 获取消息资源并转为 attachment。有 file_name 时用原名;无则用默认名(如图片用 image_xxx.png)。
638
- * 下载到本地后传绝对路径给 LLM,否则 LLM 无法访问。
639
- */
640
- private async fetchMessageResourceAttachment(
641
- messageId: string,
642
- fileKey: string,
643
- resourceType: string,
644
- aidoType: "image" | "audio" | "video" | "file",
645
- fileName?: string
646
- ): Promise<FeishuAttachment | null> {
647
- const effectiveName = fileName?.trim() || this.defaultFileNameForResource(aidoType, fileKey);
648
- const pathOrNull = await this.downloadToCache(
649
- messageId,
650
- fileKey,
651
- resourceType,
652
- aidoType,
653
- effectiveName
654
- );
655
- if (pathOrNull) {
656
- return {
657
- type: aidoType,
658
- path: path.resolve(pathOrNull),
659
- name: path.basename(pathOrNull),
660
- };
661
621
  }
662
- return { type: aidoType, ref: `im:${messageId}:${fileKey}:${aidoType}`, channel: "feishu" };
663
- }
664
-
665
- /** 无 file_name 时的默认文件名(如图片、无名的文件等) */
666
- private defaultFileNameForResource(aidoType: string, fileKey: string): string {
667
- const ext = { image: "png", audio: "mp3", video: "mp4", file: "bin" }[aidoType] ?? "bin";
668
- return `${aidoType}_${fileKey.slice(-12)}.${ext}`;
622
+ if (!buffer) return null;
623
+ await fs.promises.writeFile(outPath, buffer);
624
+ return path.resolve(outPath);
669
625
  }
670
626
 
671
627
  /**
@@ -781,22 +737,10 @@ export class FeishuHandler implements BridgeHandler {
781
737
  } else if (messageType === "post") {
782
738
  text = extractTextFromPostContent(rawContent);
783
739
  const postImageKeys = [...new Set(extractImageKeysFromPostContent(rawContent))];
784
- if (postImageKeys.length > 0) {
785
- console.error(`[feishu] post: fetching ${postImageKeys.length} image(s) from message ${messageId}`);
786
- for (const imageKey of postImageKeys) {
787
- const attach = await this.fetchMessageResourceAttachment(
788
- messageId,
789
- imageKey,
790
- "image",
791
- "image"
792
- );
793
- if (attach) {
794
- attachments.push(attach);
795
- console.error(`[feishu] post: fetched image ok (${attach.path ? "path" : "ref"})`);
796
- } else {
797
- console.warn(`[feishu] post: failed to fetch image ${imageKey}`);
798
- }
799
- }
740
+ for (const imageKey of postImageKeys) {
741
+ const fileName = `image_${imageKey.slice(-12)}.png`;
742
+ const absPath = await this.saveResourceToLocal(messageId, imageKey, "image", "image", fileName);
743
+ if (absPath) attachments.push({ type: "image", path: absPath, name: path.basename(absPath) });
800
744
  }
801
745
  } else {
802
746
  const mediaConfig = MEDIA_MESSAGE_CONFIG[messageType];
@@ -805,24 +749,20 @@ export class FeishuHandler implements BridgeHandler {
805
749
  const content = JSON.parse(rawContent || "{}") as Record<string, string>;
806
750
  const fileKey = content[mediaConfig.contentKey];
807
751
  if (fileKey) {
808
- const fileName = content["file_name"] ?? content["fileName"];
809
- const attach = await this.fetchMessageResourceAttachment(
752
+ const fileName = content["file_name"] ?? content["fileName"] ?? `${mediaConfig.aidoType}_${fileKey.slice(-12)}.${mediaConfig.aidoType === "image" ? "png" : mediaConfig.aidoType === "video" ? "mp4" : mediaConfig.aidoType === "audio" ? "mp3" : "bin"}`;
753
+ const absPath = await this.saveResourceToLocal(
810
754
  messageId,
811
755
  fileKey,
812
756
  mediaConfig.resourceType,
813
757
  mediaConfig.aidoType,
814
758
  fileName
815
759
  );
816
- if (attach) attachments.push(attach);
760
+ if (absPath) attachments.push({ type: mediaConfig.aidoType, path: absPath, name: path.basename(absPath) });
817
761
  else text = `[无法获取${mediaConfig.aidoType}资源]`;
818
762
  } else {
819
- console.warn(
820
- `[feishu] ${messageType} message missing ${mediaConfig.contentKey} in content`
821
- );
822
763
  text = "[无法解析的媒体消息]";
823
764
  }
824
- } catch (err) {
825
- console.warn(`[feishu] failed to parse ${messageType} message content:`, err);
765
+ } catch {
826
766
  text = "[无法解析的媒体消息]";
827
767
  }
828
768
  }
package/src/index.ts CHANGED
@@ -32,6 +32,8 @@ const mcpServer = createFeishuMcpServer(handler);
32
32
 
33
33
  runMcpChannel({
34
34
  handler,
35
+ displayName: "飞书",
36
+ defaultRequiresResponse: true,
35
37
  buildChannelPrompt: feishuBuildChannelPrompt,
36
38
  mcpServer,
37
39
  });