feishu-user-plugin 1.3.0 → 1.3.2

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/src/index.js CHANGED
@@ -144,12 +144,17 @@ const TOOLS = [
144
144
  // ========== User Identity — Send Messages ==========
145
145
  {
146
146
  name: 'send_as_user',
147
- description: '[User Identity] Send a text message as the logged-in Feishu user. Supports reply threading.',
147
+ description: '[User Identity] Send a text message as the logged-in Feishu user. Supports reply threading and real @-mentions (triggers push notifications).',
148
148
  inputSchema: {
149
149
  type: 'object',
150
150
  properties: {
151
151
  chat_id: { type: 'string', description: 'Target chat ID (numeric)' },
152
- text: { type: 'string', description: 'Message text' },
152
+ text: { type: 'string', description: 'Message text. If `ats` is provided, include the display marker for each @ in this text (default marker is `@<name>`).' },
153
+ ats: {
154
+ type: 'array',
155
+ description: 'Optional @-mentions. Each entry: {userId: "ou_xxx", name: "DisplayName"}. The text must contain each @<name> marker in order — it gets spliced into a real AT element so the mentioned user receives a notification.',
156
+ items: { type: 'object', properties: { userId: { type: 'string' }, name: { type: 'string' }, marker: { type: 'string' } } },
157
+ },
153
158
  root_id: { type: 'string', description: 'Thread root message ID (for reply, optional)' },
154
159
  parent_id: { type: 'string', description: 'Parent message ID (for nested reply, optional)' },
155
160
  },
@@ -164,6 +169,11 @@ const TOOLS = [
164
169
  properties: {
165
170
  user_name: { type: 'string', description: 'Recipient name (Chinese or English)' },
166
171
  text: { type: 'string', description: 'Message text' },
172
+ ats: {
173
+ type: 'array',
174
+ description: 'Optional @-mentions. Same format as send_as_user.ats: [{userId, name}]. Text must contain the `@<name>` marker for each entry.',
175
+ items: { type: 'object', properties: { userId: { type: 'string' }, name: { type: 'string' }, marker: { type: 'string' } } },
176
+ },
167
177
  },
168
178
  required: ['user_name', 'text'],
169
179
  },
@@ -176,6 +186,11 @@ const TOOLS = [
176
186
  properties: {
177
187
  group_name: { type: 'string', description: 'Group chat name' },
178
188
  text: { type: 'string', description: 'Message text' },
189
+ ats: {
190
+ type: 'array',
191
+ description: 'Optional @-mentions that trigger real notifications. Each entry: {userId, name}. Text must contain `@<name>` marker for each entry.',
192
+ items: { type: 'object', properties: { userId: { type: 'string' }, name: { type: 'string' }, marker: { type: 'string' } } },
193
+ },
179
194
  },
180
195
  required: ['group_name', 'text'],
181
196
  },
@@ -222,7 +237,7 @@ const TOOLS = [
222
237
  },
223
238
  {
224
239
  name: 'send_post_as_user',
225
- description: '[User Identity] Send a rich text (POST) message with title and formatted paragraphs.',
240
+ description: '[User Identity] Send a rich text (POST) message with title and formatted paragraphs. Supports real @-mentions that trigger notifications.',
226
241
  inputSchema: {
227
242
  type: 'object',
228
243
  properties: {
@@ -230,7 +245,7 @@ const TOOLS = [
230
245
  title: { type: 'string', description: 'Post title (optional)' },
231
246
  paragraphs: {
232
247
  type: 'array',
233
- description: 'Array of paragraphs. Each paragraph is an array of elements: {tag:"text",text:"..."} or {tag:"a",href:"...",text:"..."} or {tag:"at",userId:"..."}',
248
+ description: 'Array of paragraphs. Each paragraph is an array of elements:\n• {tag:"text",text:"..."} plain text\n• {tag:"a",href:"https://...",text:"display"} hyperlink\n• {tag:"at",userId:"ou_xxx",name:"Display Name"} — real @-mention (triggers notification)',
234
249
  items: { type: 'array', items: { type: 'object' } },
235
250
  },
236
251
  root_id: { type: 'string', description: 'Thread root message ID (optional)' },
@@ -541,49 +556,9 @@ const TOOLS = [
541
556
  required: ['app_token', 'table_id'],
542
557
  },
543
558
  },
544
- {
545
- name: 'create_bitable_record',
546
- description: '[Official API] Create a new record (row) in a Bitable table.',
547
- inputSchema: {
548
- type: 'object',
549
- properties: {
550
- app_token: { type: 'string', description: 'Bitable app token' },
551
- table_id: { type: 'string', description: 'Table ID' },
552
- fields: { type: 'object', description: 'Field name → value mapping' },
553
- },
554
- required: ['app_token', 'table_id', 'fields'],
555
- },
556
- },
557
- {
558
- name: 'update_bitable_record',
559
- description: '[Official API] Update an existing record in a Bitable table.',
560
- inputSchema: {
561
- type: 'object',
562
- properties: {
563
- app_token: { type: 'string', description: 'Bitable app token' },
564
- table_id: { type: 'string', description: 'Table ID' },
565
- record_id: { type: 'string', description: 'Record ID to update' },
566
- fields: { type: 'object', description: 'Field name → new value mapping' },
567
- },
568
- required: ['app_token', 'table_id', 'record_id', 'fields'],
569
- },
570
- },
571
- {
572
- name: 'delete_bitable_record',
573
- description: '[Official API] Delete a record (row) from a Bitable table.',
574
- inputSchema: {
575
- type: 'object',
576
- properties: {
577
- app_token: { type: 'string', description: 'Bitable app token' },
578
- table_id: { type: 'string', description: 'Table ID' },
579
- record_id: { type: 'string', description: 'Record ID to delete' },
580
- },
581
- required: ['app_token', 'table_id', 'record_id'],
582
- },
583
- },
584
559
  {
585
560
  name: 'batch_create_bitable_records',
586
- description: '[Official API] Batch create multiple records (rows) in a Bitable table. Max 500 per call.',
561
+ description: '[Official API] Create one or more records (rows) in a Bitable table. Pass a single record or up to 500.',
587
562
  inputSchema: {
588
563
  type: 'object',
589
564
  properties: {
@@ -596,7 +571,7 @@ const TOOLS = [
596
571
  },
597
572
  {
598
573
  name: 'batch_update_bitable_records',
599
- description: '[Official API] Batch update multiple records in a Bitable table. Max 500 per call.',
574
+ description: '[Official API] Update one or more records in a Bitable table. Pass a single record or up to 500.',
600
575
  inputSchema: {
601
576
  type: 'object',
602
577
  properties: {
@@ -609,7 +584,7 @@ const TOOLS = [
609
584
  },
610
585
  {
611
586
  name: 'batch_delete_bitable_records',
612
- description: '[Official API] Batch delete multiple records from a Bitable table. Max 500 per call.',
587
+ description: '[Official API] Delete one or more records from a Bitable table. Pass a single ID or up to 500.',
613
588
  inputSchema: {
614
589
  type: 'object',
615
590
  properties: {
@@ -714,13 +689,13 @@ const TOOLS = [
714
689
  // ========== IM — Bot Send / Edit / Delete ==========
715
690
  {
716
691
  name: 'send_message_as_bot',
717
- description: '[Official API] Send a message as the bot to any chat. Supports text, post, interactive, etc.',
692
+ description: '[Official API] Send a message as the bot to any chat. Supports text, post, interactive, etc. This is the reliable path for @-mentions: include `<at user_id="ou_xxx">Name</at>` inline in text content and Feishu resolves it to a real @-notification.',
718
693
  inputSchema: {
719
694
  type: 'object',
720
695
  properties: {
721
696
  chat_id: { type: 'string', description: 'Target chat_id (oc_xxx) or open_id' },
722
697
  msg_type: { type: 'string', description: 'Message type: text, post, image, interactive, etc.', enum: ['text', 'post', 'image', 'interactive', 'share_chat', 'share_user', 'audio', 'media', 'file', 'sticker'] },
723
- content: { description: 'Message content (string or object, auto-serialized). For text: {"text":"hello"}' },
698
+ content: { description: 'Message content (string or object, auto-serialized). Plain text: {"text":"hello"}. Text with @-mention: {"text":"<at user_id=\\"ou_xxx\\">Alice</at> hi"} — the inline tag becomes a real @-notification.' },
724
699
  },
725
700
  required: ['chat_id', 'msg_type', 'content'],
726
701
  },
@@ -777,19 +752,13 @@ const TOOLS = [
777
752
  // ========== IM — Pin Messages ==========
778
753
  {
779
754
  name: 'pin_message',
780
- description: '[Official API] Pin a message in a chat.',
755
+ description: '[Official API] Pin or unpin a message in a chat.',
781
756
  inputSchema: {
782
757
  type: 'object',
783
- properties: { message_id: { type: 'string', description: 'Message ID to pin' } },
784
- required: ['message_id'],
785
- },
786
- },
787
- {
788
- name: 'unpin_message',
789
- description: '[Official API] Unpin a message from a chat.',
790
- inputSchema: {
791
- type: 'object',
792
- properties: { message_id: { type: 'string', description: 'Message ID to unpin' } },
758
+ properties: {
759
+ message_id: { type: 'string', description: 'Message ID' },
760
+ pinned: { type: 'boolean', description: 'true to pin, false to unpin', default: true },
761
+ },
793
762
  required: ['message_id'],
794
763
  },
795
764
  },
@@ -835,27 +804,16 @@ const TOOLS = [
835
804
  },
836
805
  },
837
806
  {
838
- name: 'add_members',
839
- description: '[Official API] Add users to a group chat.',
807
+ name: 'manage_members',
808
+ description: '[Official API] Add or remove members from a group chat.',
840
809
  inputSchema: {
841
810
  type: 'object',
842
811
  properties: {
843
- chat_id: { type: 'string', description: 'Chat ID (oc_xxx)' },
844
- user_ids: { type: 'array', items: { type: 'string' }, description: 'Array of user open_ids to add' },
812
+ chat_id: { type: 'string', description: 'Group chat ID (oc_xxx)' },
813
+ member_ids: { type: 'array', items: { type: 'string' }, description: 'Array of user open_ids' },
814
+ action: { type: 'string', enum: ['add', 'remove'], description: 'Action to perform' },
845
815
  },
846
- required: ['chat_id', 'user_ids'],
847
- },
848
- },
849
- {
850
- name: 'remove_members',
851
- description: '[Official API] Remove users from a group chat.',
852
- inputSchema: {
853
- type: 'object',
854
- properties: {
855
- chat_id: { type: 'string', description: 'Chat ID (oc_xxx)' },
856
- user_ids: { type: 'array', items: { type: 'string' }, description: 'Array of user open_ids to remove' },
857
- },
858
- required: ['chat_id', 'user_ids'],
816
+ required: ['chat_id', 'member_ids', 'action'],
859
817
  },
860
818
  },
861
819
 
@@ -929,165 +887,111 @@ const TOOLS = [
929
887
  },
930
888
  },
931
889
 
932
- // ========== Drive — File Operations ==========
933
890
  {
934
- name: 'copy_file',
935
- description: '[Official API] Copy a file/doc in Drive.',
891
+ name: 'get_bitable_meta',
892
+ description: '[Official API] Get metadata of a Bitable app (name, revision, etc.).',
936
893
  inputSchema: {
937
894
  type: 'object',
938
895
  properties: {
939
- file_token: { type: 'string', description: 'File token to copy' },
940
- name: { type: 'string', description: 'New file name' },
941
- folder_token: { type: 'string', description: 'Destination folder token (optional)' },
942
- type: { type: 'string', description: 'File type: file, doc, sheet, bitable, docx, mindnote, slides (optional)' },
943
- },
944
- required: ['file_token', 'name'],
945
- },
946
- },
947
- {
948
- name: 'move_file',
949
- description: '[Official API] Move a file to another folder in Drive.',
950
- inputSchema: {
951
- type: 'object',
952
- properties: {
953
- file_token: { type: 'string', description: 'File token to move' },
954
- folder_token: { type: 'string', description: 'Destination folder token' },
955
- },
956
- required: ['file_token', 'folder_token'],
957
- },
958
- },
959
- {
960
- name: 'delete_file',
961
- description: '[Official API] Delete a file/folder from Drive.',
962
- inputSchema: {
963
- type: 'object',
964
- properties: {
965
- file_token: { type: 'string', description: 'File token to delete' },
966
- type: { type: 'string', description: 'Type: file, folder, doc, sheet, bitable, docx, mindnote, slides' },
896
+ app_token: { type: 'string', description: 'Bitable app token' },
967
897
  },
968
- required: ['file_token'],
898
+ required: ['app_token'],
969
899
  },
970
900
  },
971
-
972
- // ========== Calendar ==========
973
901
  {
974
- name: 'list_calendars',
975
- description: '[Official API] List all calendars accessible by the bot.',
976
- inputSchema: { type: 'object', properties: {} },
977
- },
978
- {
979
- name: 'create_calendar_event',
980
- description: '[Official API] Create a calendar event.',
902
+ name: 'update_bitable_table',
903
+ description: '[Official API] Rename a data table in a Bitable app.',
981
904
  inputSchema: {
982
905
  type: 'object',
983
906
  properties: {
984
- calendar_id: { type: 'string', description: 'Calendar ID (from list_calendars)' },
985
- summary: { type: 'string', description: 'Event title' },
986
- start_time: { type: 'string', description: 'Start time (RFC3339 or Unix timestamp string)' },
987
- end_time: { type: 'string', description: 'End time (RFC3339 or Unix timestamp string)' },
988
- description: { type: 'string', description: 'Event description (optional)' },
989
- location: { type: 'string', description: 'Event location (optional)' },
907
+ app_token: { type: 'string', description: 'Bitable app token' },
908
+ table_id: { type: 'string', description: 'Table ID' },
909
+ name: { type: 'string', description: 'New table name' },
990
910
  },
991
- required: ['calendar_id', 'summary', 'start_time', 'end_time'],
911
+ required: ['app_token', 'table_id', 'name'],
992
912
  },
993
913
  },
994
914
  {
995
- name: 'list_calendar_events',
996
- description: '[Official API] List events in a calendar.',
915
+ name: 'create_bitable_view',
916
+ description: '[Official API] Create a new view in a Bitable table.',
997
917
  inputSchema: {
998
918
  type: 'object',
999
919
  properties: {
1000
- calendar_id: { type: 'string', description: 'Calendar ID' },
1001
- start_time: { type: 'string', description: 'Start time filter (Unix timestamp string, optional)' },
1002
- end_time: { type: 'string', description: 'End time filter (Unix timestamp string, optional)' },
1003
- page_size: { type: 'number', description: 'Items per page (default 50)' },
920
+ app_token: { type: 'string', description: 'Bitable app token' },
921
+ table_id: { type: 'string', description: 'Table ID' },
922
+ view_name: { type: 'string', description: 'View name' },
923
+ view_type: { type: 'string', description: 'View type: grid (default), kanban, gallery, form, gantt, calendar', default: 'grid' },
1004
924
  },
1005
- required: ['calendar_id'],
925
+ required: ['app_token', 'table_id', 'view_name'],
1006
926
  },
1007
927
  },
1008
928
  {
1009
- name: 'delete_calendar_event',
1010
- description: '[Official API] Delete a calendar event.',
929
+ name: 'delete_bitable_view',
930
+ description: '[Official API] Delete a view from a Bitable table.',
1011
931
  inputSchema: {
1012
932
  type: 'object',
1013
933
  properties: {
1014
- calendar_id: { type: 'string', description: 'Calendar ID' },
1015
- event_id: { type: 'string', description: 'Event ID to delete' },
934
+ app_token: { type: 'string', description: 'Bitable app token' },
935
+ table_id: { type: 'string', description: 'Table ID' },
936
+ view_id: { type: 'string', description: 'View ID to delete' },
1016
937
  },
1017
- required: ['calendar_id', 'event_id'],
938
+ required: ['app_token', 'table_id', 'view_id'],
1018
939
  },
1019
940
  },
1020
941
  {
1021
- name: 'get_freebusy',
1022
- description: '[Official API] Check free/busy status of users for a time range.',
942
+ name: 'copy_bitable',
943
+ description: '[Official API] Copy a Bitable app to create a new one.',
1023
944
  inputSchema: {
1024
945
  type: 'object',
1025
946
  properties: {
1026
- user_ids: { type: 'array', items: { type: 'string' }, description: 'Array of user open_ids to check' },
1027
- start_time: { type: 'string', description: 'Range start (RFC3339)' },
1028
- end_time: { type: 'string', description: 'Range end (RFC3339)' },
947
+ app_token: { type: 'string', description: 'Bitable app token to copy' },
948
+ name: { type: 'string', description: 'New Bitable name' },
949
+ folder_id: { type: 'string', description: 'Destination folder token (optional)' },
1029
950
  },
1030
- required: ['user_ids', 'start_time', 'end_time'],
951
+ required: ['app_token', 'name'],
1031
952
  },
1032
953
  },
1033
954
 
1034
- // ========== Tasks ==========
955
+ // ========== Drive — File Operations ==========
1035
956
  {
1036
- name: 'create_task',
1037
- description: '[Official API] Create a new task in Feishu Tasks.',
957
+ name: 'copy_file',
958
+ description: '[Official API] Copy a file/doc in Drive.',
1038
959
  inputSchema: {
1039
960
  type: 'object',
1040
961
  properties: {
1041
- summary: { type: 'string', description: 'Task title/summary' },
1042
- description: { type: 'string', description: 'Task description (optional)' },
1043
- due: { type: 'string', description: 'Due date/time (RFC3339 or Unix timestamp string, optional)' },
962
+ file_token: { type: 'string', description: 'File token to copy' },
963
+ name: { type: 'string', description: 'New file name' },
964
+ folder_token: { type: 'string', description: 'Destination folder token (optional)' },
965
+ type: { type: 'string', description: 'File type: file, doc, sheet, bitable, docx, mindnote, slides (optional)' },
1044
966
  },
1045
- required: ['summary'],
1046
- },
1047
- },
1048
- {
1049
- name: 'get_task',
1050
- description: '[Official API] Get task details by ID.',
1051
- inputSchema: {
1052
- type: 'object',
1053
- properties: { task_id: { type: 'string', description: 'Task ID' } },
1054
- required: ['task_id'],
967
+ required: ['file_token', 'name'],
1055
968
  },
1056
969
  },
1057
970
  {
1058
- name: 'list_tasks',
1059
- description: '[Official API] List tasks.',
971
+ name: 'move_file',
972
+ description: '[Official API] Move a file to another folder in Drive.',
1060
973
  inputSchema: {
1061
974
  type: 'object',
1062
975
  properties: {
1063
- page_size: { type: 'number', description: 'Items per page (default 50)' },
1064
- page_token: { type: 'string', description: 'Pagination token' },
976
+ file_token: { type: 'string', description: 'File token to move' },
977
+ folder_token: { type: 'string', description: 'Destination folder token' },
1065
978
  },
979
+ required: ['file_token', 'folder_token'],
1066
980
  },
1067
981
  },
1068
982
  {
1069
- name: 'update_task',
1070
- description: '[Official API] Update a task (title, description, due date, etc.).',
983
+ name: 'delete_file',
984
+ description: '[Official API] Delete a file/folder from Drive.',
1071
985
  inputSchema: {
1072
986
  type: 'object',
1073
987
  properties: {
1074
- task_id: { type: 'string', description: 'Task ID' },
1075
- summary: { type: 'string', description: 'New title (optional)' },
1076
- description: { type: 'string', description: 'New description (optional)' },
1077
- due: { type: 'string', description: 'New due date (optional)' },
988
+ file_token: { type: 'string', description: 'File token to delete' },
989
+ type: { type: 'string', description: 'Type: file, folder, doc, sheet, bitable, docx, mindnote, slides' },
1078
990
  },
1079
- required: ['task_id'],
1080
- },
1081
- },
1082
- {
1083
- name: 'complete_task',
1084
- description: '[Official API] Mark a task as complete.',
1085
- inputSchema: {
1086
- type: 'object',
1087
- properties: { task_id: { type: 'string', description: 'Task ID to complete' } },
1088
- required: ['task_id'],
991
+ required: ['file_token'],
1089
992
  },
1090
993
  },
994
+
1091
995
  ];
1092
996
 
1093
997
  // --- Server ---
@@ -1119,7 +1023,7 @@ async function handleTool(name, args) {
1119
1023
 
1120
1024
  case 'send_as_user': {
1121
1025
  const c = await getUserClient();
1122
- const r = await c.sendMessage(args.chat_id, args.text, { rootId: args.root_id, parentId: args.parent_id });
1026
+ const r = await c.sendMessage(args.chat_id, args.text, { rootId: args.root_id, parentId: args.parent_id, ats: args.ats });
1123
1027
  return sendResult(r, `Text sent as user to ${args.chat_id}`);
1124
1028
  }
1125
1029
  case 'send_to_user': {
@@ -1134,7 +1038,7 @@ async function handleTool(name, args) {
1134
1038
  const user = users[0];
1135
1039
  const chatId = await c.createChat(user.id);
1136
1040
  if (!chatId) return text(`Failed to create chat with ${user.title}`);
1137
- const r = await c.sendMessage(chatId, args.text);
1041
+ const r = await c.sendMessage(chatId, args.text, { ats: args.ats });
1138
1042
  return sendResult(r, `Text sent to ${user.title} (chat: ${chatId})`);
1139
1043
  }
1140
1044
  case 'send_to_group': {
@@ -1147,7 +1051,7 @@ async function handleTool(name, args) {
1147
1051
  return text(`Multiple groups match "${args.group_name}":\n${candidates}\nUse search_contacts to find the exact group, then send_as_user with the ID.`);
1148
1052
  }
1149
1053
  const group = groups[0];
1150
- const r = await c.sendMessage(group.id, args.text);
1054
+ const r = await c.sendMessage(group.id, args.text, { ats: args.ats });
1151
1055
  return sendResult(r, `Text sent to group "${group.title}" (${group.id})`);
1152
1056
  }
1153
1057
 
@@ -1331,14 +1235,19 @@ async function handleTool(name, args) {
1331
1235
  return json(await getOfficialClient().readDoc(args.document_id));
1332
1236
  case 'get_doc_blocks':
1333
1237
  return json(await getOfficialClient().getDocBlocks(args.document_id));
1334
- case 'create_doc':
1335
- return text(`Document created: ${(await getOfficialClient().createDoc(args.title, args.folder_id)).documentId}`);
1238
+ case 'create_doc': {
1239
+ const official = getOfficialClient();
1240
+ const ownership = official.hasUAT ? ' (as user)' : '';
1241
+ return text(`Document created${ownership}: ${(await official.createDoc(args.title, args.folder_id)).documentId}`);
1242
+ }
1336
1243
 
1337
1244
  // --- Official API: Bitable ---
1338
1245
 
1339
1246
  case 'create_bitable': {
1340
- const r = await getOfficialClient().createBitable(args.name, args.folder_id);
1341
- return text(`Bitable created: ${r.appToken}${r.url ? `\nURL: ${r.url}` : ''}`);
1247
+ const official = getOfficialClient();
1248
+ const ownership = official.hasUAT ? ' (as user)' : '';
1249
+ const r = await official.createBitable(args.name, args.folder_id);
1250
+ return text(`Bitable created${ownership}: ${r.appToken}\nURL: ${r.url || ''}`);
1342
1251
  }
1343
1252
  case 'list_bitable_tables':
1344
1253
  return json(await getOfficialClient().listBitableTables(args.app_token));
@@ -1368,12 +1277,6 @@ async function handleTool(name, args) {
1368
1277
  return json(await getOfficialClient().searchBitableRecords(args.app_token, args.table_id, {
1369
1278
  filter: args.filter, sort: args.sort, pageSize: args.page_size,
1370
1279
  }));
1371
- case 'create_bitable_record':
1372
- return text(`Record created: ${(await getOfficialClient().createBitableRecord(args.app_token, args.table_id, args.fields)).recordId}`);
1373
- case 'update_bitable_record':
1374
- return text(`Record updated: ${(await getOfficialClient().updateBitableRecord(args.app_token, args.table_id, args.record_id, args.fields)).recordId}`);
1375
- case 'delete_bitable_record':
1376
- return text(`Record deleted: ${(await getOfficialClient().deleteBitableRecord(args.app_token, args.table_id, args.record_id)).deleted}`);
1377
1280
  case 'batch_create_bitable_records':
1378
1281
  return json(await getOfficialClient().batchCreateBitableRecords(args.app_token, args.table_id, args.records));
1379
1282
  case 'batch_update_bitable_records':
@@ -1394,8 +1297,11 @@ async function handleTool(name, args) {
1394
1297
 
1395
1298
  case 'list_files':
1396
1299
  return json(await getOfficialClient().listFiles(args.folder_token));
1397
- case 'create_folder':
1398
- return text(`Folder created: ${(await getOfficialClient().createFolder(args.name, args.parent_token)).token}`);
1300
+ case 'create_folder': {
1301
+ const official = getOfficialClient();
1302
+ const ownership = official.hasUAT ? ' (as user)' : '';
1303
+ return text(`Folder created${ownership}: ${(await official.createFolder(args.name, args.parent_token)).token}`);
1304
+ }
1399
1305
 
1400
1306
  // --- Official API: Contact ---
1401
1307
 
@@ -1434,9 +1340,7 @@ async function handleTool(name, args) {
1434
1340
  // --- Official API: Pins ---
1435
1341
 
1436
1342
  case 'pin_message':
1437
- return json(await getOfficialClient().pinMessage(args.message_id));
1438
- case 'unpin_message':
1439
- return text(`Unpinned: ${(await getOfficialClient().unpinMessage(args.message_id)).deleted}`);
1343
+ return json(await getOfficialClient().pinMessage(args.message_id, args.pinned !== false));
1440
1344
 
1441
1345
  // --- Official API: Chat Management ---
1442
1346
 
@@ -1446,13 +1350,12 @@ async function handleTool(name, args) {
1446
1350
  return text(`Group updated: ${(await getOfficialClient().updateChat(args.chat_id, { name: args.name, description: args.description })).updated}`);
1447
1351
  case 'list_members':
1448
1352
  return json(await getOfficialClient().listChatMembers(args.chat_id, { pageSize: args.page_size, pageToken: args.page_token }));
1449
- case 'add_members': {
1450
- const r = await getOfficialClient().addChatMembers(args.chat_id, args.user_ids);
1451
- return text(r.invalidIds.length ? `Added (invalid IDs: ${r.invalidIds.join(', ')})` : 'Members added');
1452
- }
1453
- case 'remove_members': {
1454
- const r = await getOfficialClient().removeChatMembers(args.chat_id, args.user_ids);
1455
- return text(r.invalidIds.length ? `Removed (invalid IDs: ${r.invalidIds.join(', ')})` : 'Members removed');
1353
+ case 'manage_members': {
1354
+ const official = getOfficialClient();
1355
+ if (args.action === 'remove') {
1356
+ return json(await official.removeChatMembers(args.chat_id, args.member_ids));
1357
+ }
1358
+ return json(await official.addChatMembers(args.chat_id, args.member_ids));
1456
1359
  }
1457
1360
 
1458
1361
  // --- Official API: Doc Block Editing ---
@@ -1470,6 +1373,16 @@ async function handleTool(name, args) {
1470
1373
  return json(await getOfficialClient().getBitableRecord(args.app_token, args.table_id, args.record_id));
1471
1374
  case 'delete_bitable_table':
1472
1375
  return text(`Table deleted: ${(await getOfficialClient().deleteBitableTable(args.app_token, args.table_id)).deleted}`);
1376
+ case 'get_bitable_meta':
1377
+ return json(await getOfficialClient().getBitableMeta(args.app_token));
1378
+ case 'update_bitable_table':
1379
+ return text(`Table renamed: ${(await getOfficialClient().updateBitableTable(args.app_token, args.table_id, args.name)).name}`);
1380
+ case 'create_bitable_view':
1381
+ return json(await getOfficialClient().createBitableView(args.app_token, args.table_id, args.view_name, args.view_type));
1382
+ case 'delete_bitable_view':
1383
+ return text(`View deleted: ${(await getOfficialClient().deleteBitableView(args.app_token, args.table_id, args.view_id)).deleted}`);
1384
+ case 'copy_bitable':
1385
+ return json(await getOfficialClient().copyBitable(args.app_token, args.name, args.folder_id));
1473
1386
 
1474
1387
  // --- Official API: Drive File Operations ---
1475
1388
 
@@ -1480,53 +1393,22 @@ async function handleTool(name, args) {
1480
1393
  case 'delete_file':
1481
1394
  return text(`File deleted: task=${(await getOfficialClient().deleteFile(args.file_token, args.type)).taskId}`);
1482
1395
 
1483
- // --- Official API: Calendar ---
1484
-
1485
- case 'list_calendars':
1486
- return json(await getOfficialClient().listCalendars());
1487
- case 'create_calendar_event': {
1488
- const event = { summary: args.summary, description: args.description || '' };
1489
- event.start_time = { timestamp: args.start_time };
1490
- event.end_time = { timestamp: args.end_time };
1491
- if (args.location) event.location = { name: args.location };
1492
- return json(await getOfficialClient().createCalendarEvent(args.calendar_id, event));
1493
- }
1494
- case 'list_calendar_events':
1495
- return json(await getOfficialClient().listCalendarEvents(args.calendar_id, {
1496
- startTime: args.start_time, endTime: args.end_time, pageSize: args.page_size,
1497
- }));
1498
- case 'delete_calendar_event':
1499
- return text(`Event deleted: ${(await getOfficialClient().deleteCalendarEvent(args.calendar_id, args.event_id)).deleted}`);
1500
- case 'get_freebusy':
1501
- return json(await getOfficialClient().getFreeBusy(args.user_ids, args.start_time, args.end_time));
1502
-
1503
- // --- Official API: Tasks ---
1504
-
1505
- case 'create_task': {
1506
- const task = { summary: args.summary };
1507
- if (args.description) task.description = args.description;
1508
- if (args.due) task.due = { timestamp: args.due };
1509
- return json(await getOfficialClient().createTask(task));
1510
- }
1511
- case 'get_task':
1512
- return json(await getOfficialClient().getTask(args.task_id));
1513
- case 'list_tasks':
1514
- return json(await getOfficialClient().listTasks({ pageSize: args.page_size, pageToken: args.page_token }));
1515
- case 'update_task': {
1516
- const task = {};
1517
- if (args.summary) task.summary = args.summary;
1518
- if (args.description) task.description = args.description;
1519
- if (args.due) task.due = { timestamp: args.due };
1520
- return json(await getOfficialClient().updateTask(args.task_id, task));
1521
- }
1522
- case 'complete_task':
1523
- return text(`Task completed: ${(await getOfficialClient().completeTask(args.task_id)).completed}`);
1524
-
1525
1396
  default:
1526
1397
  return text(`Unknown tool: ${name}`);
1527
1398
  }
1528
1399
  }
1529
1400
 
1401
+ // --- Process-level error handlers ---
1402
+ // Prevent stray promise rejections or uncaught exceptions from killing the MCP server.
1403
+ process.on('uncaughtException', (err) => {
1404
+ console.error('[feishu-user-plugin] Uncaught exception:', err.message);
1405
+ console.error(err.stack);
1406
+ });
1407
+
1408
+ process.on('unhandledRejection', (reason) => {
1409
+ console.error('[feishu-user-plugin] Unhandled rejection:', reason);
1410
+ });
1411
+
1530
1412
  async function main() {
1531
1413
  const transport = new StdioServerTransport();
1532
1414
  await server.connect(transport);
package/src/oauth.js CHANGED
@@ -22,7 +22,8 @@ const APP_SECRET = creds.LARK_APP_SECRET;
22
22
  const PORT = 9997;
23
23
  const REDIRECT_URI = `http://127.0.0.1:${PORT}/callback`;
24
24
  // offline_access is required to get refresh_token for auto-renewal
25
- const SCOPES = 'offline_access im:message im:message:readonly im:chat:readonly contact:user.base:readonly';
25
+ // Write scopes (docx:document, drive:drive, bitable:app) allow creating resources as the user, not the app
26
+ const SCOPES = 'offline_access auth:user.id:read im:message im:message:readonly im:chat im:chat:readonly contact:user.base:readonly contact:user.id:readonly docx:document drive:drive bitable:app wiki:wiki:readonly';
26
27
 
27
28
  if (!APP_ID || !APP_SECRET) {
28
29
  console.error('Missing LARK_APP_ID or LARK_APP_SECRET.');