feishu-user-plugin 1.1.0 → 1.1.1

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,6 +1,6 @@
1
1
  {
2
2
  "name": "feishu-user-plugin",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "All-in-one Feishu plugin for Claude Code — send messages as yourself, read chats, manage docs/tables/wiki. 33 tools + 9 skills, 3 auth layers.",
5
5
  "author": {
6
6
  "name": "EthanQC"
package/CHANGELOG.md CHANGED
@@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/), and this project adheres to [Semantic Versioning](https://semver.org/).
6
6
 
7
+ ## [1.1.1] - 2026-03-11
8
+
9
+ ### Fixed
10
+ - **read_messages can't read external groups**: `read_messages` now auto-falls back to UAT when bot API fails (e.g. bot not in group, external groups). No need to manually switch to `read_p2p_messages`.
11
+ - **Chat name resolution for external groups**: Added Strategy 3 using `search_contacts` (cookie-based) to find groups not visible to bot or `im.chat.search`.
12
+ - **Numeric chat IDs not accepted by read_messages**: `resolveToOcId` now passes through numeric IDs directly.
13
+
7
14
  ## [1.1.0] - 2026-03-11
8
15
 
9
16
  ### Fixed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "feishu-user-plugin",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "All-in-one Feishu plugin for Claude Code — send messages as yourself, read chats, manage docs/tables/wiki. 33 tools + 9 skills, 3 auth layers.",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: feishu-user-plugin
3
- version: "1.1.0"
3
+ version: "1.1.1"
4
4
  description: "All-in-one Feishu plugin — send messages as yourself, read group/P2P chats, manage docs/tables/wiki. Replaces and extends the official Feishu MCP."
5
5
  allowed-tools: send_to_user, send_to_group, send_as_user, send_image_as_user, send_file_as_user, send_post_as_user, send_sticker_as_user, send_audio_as_user, search_contacts, create_p2p_chat, get_chat_info, get_user_info, get_login_status, read_p2p_messages, list_user_chats, list_chats, read_messages, reply_message, forward_message, search_docs, read_doc, create_doc, list_bitable_tables, list_bitable_fields, search_bitable_records, create_bitable_record, update_bitable_record, list_wiki_spaces, search_wiki, list_wiki_nodes, list_files, create_folder, find_user
6
6
  user_invocable: true
@@ -30,7 +30,7 @@ All-in-one Feishu plugin for Claude Code with three auth layers:
30
30
  - `list_user_chats` — List group chats the user is in. Note: API only returns groups, not P2P. For P2P, use: `search_contacts` → `create_p2p_chat` → `read_p2p_messages`.
31
31
 
32
32
  ### Official API Tools (app credentials)
33
- - `list_chats` / `read_messages` — Chat history (read_messages accepts chat name, auto-resolves to oc_ ID)
33
+ - `list_chats` / `read_messages` — Chat history (accepts chat name, oc_ ID, or numeric ID; auto-falls back to UAT for external groups)
34
34
  - `reply_message` / `forward_message` — Message operations (as bot). reply_message only works for text messages.
35
35
  - `search_docs` / `read_doc` / `create_doc` — Document operations
36
36
  - `list_bitable_tables` / `list_bitable_fields` / `search_bitable_records` — Table queries
@@ -42,7 +42,7 @@ All-in-one Feishu plugin for Claude Code with three auth layers:
42
42
  ## Usage Patterns
43
43
  - Send text as yourself → `send_to_user` or `send_to_group`
44
44
  - Send rich content → `send_post_as_user` (formatted text), `send_image_as_user` (images)
45
- - Read group chat history → `read_messages` with chat name or oc_ ID
45
+ - Read any group chat history → `read_messages` with chat name or ID (auto-handles external groups via UAT fallback)
46
46
  - Read P2P chat history → `search_contacts` → `create_p2p_chat` → `read_p2p_messages`
47
47
  - Reply as user in thread → `send_as_user` with root_id
48
48
  - Reply as bot → `reply_message` (official API, text messages only)
package/src/cli.js CHANGED
File without changes
package/src/index.js CHANGED
@@ -48,6 +48,8 @@ class ChatIdMapper {
48
48
 
49
49
  async resolveToOcId(chatIdOrName, official) {
50
50
  if (chatIdOrName.startsWith('oc_')) return chatIdOrName;
51
+ // Also accept raw numeric IDs (from search_contacts)
52
+ if (/^\d+$/.test(chatIdOrName)) return chatIdOrName;
51
53
  // Strategy 1: Search in bot's group list cache
52
54
  const cached = await this.findByName(chatIdOrName, official);
53
55
  if (cached) return cached;
@@ -67,6 +69,27 @@ class ChatIdMapper {
67
69
  }
68
70
  return null;
69
71
  }
72
+
73
+ // Strategy 3: Use search_contacts (cookie-based) to find external groups by name
74
+ // Returns numeric chat_id that works with UAT readMessagesAsUser
75
+ async resolveViaContacts(chatName, userClient) {
76
+ if (!userClient) return null;
77
+ try {
78
+ const results = await userClient.search(chatName);
79
+ const groups = results.filter(r => r.type === 'group');
80
+ // Exact match first
81
+ for (const g of groups) {
82
+ if (g.title === chatName) return String(g.id);
83
+ }
84
+ // Partial match
85
+ for (const g of groups) {
86
+ if (g.title && g.title.includes(chatName)) return String(g.id);
87
+ }
88
+ } catch (e) {
89
+ console.error('[feishu-user-plugin] search_contacts fallback failed:', e.message);
90
+ }
91
+ return null;
92
+ }
70
93
  }
71
94
 
72
95
  // --- Client Singletons ---
@@ -306,11 +329,11 @@ const TOOLS = [
306
329
  },
307
330
  {
308
331
  name: 'read_messages',
309
- description: '[Official API] Read message history. Accepts oc_xxx ID or chat name (auto-searched via bot group list + im.chat.search). Returns newest messages first by default, with sender names resolved.',
332
+ description: '[Official API + UAT fallback] Read message history from any group. Accepts oc_xxx ID, numeric ID, or chat name (auto-searched). Auto-falls back to UAT for external groups the bot cannot access. Returns newest messages first by default, with sender names resolved.',
310
333
  inputSchema: {
311
334
  type: 'object',
312
335
  properties: {
313
- chat_id: { type: 'string', description: 'Chat ID (oc_xxx) or chat name (auto-searched)' },
336
+ chat_id: { type: 'string', description: 'Chat ID (oc_xxx), numeric ID, or chat name (auto-searched via bot groups, im.chat.search, and user contacts)' },
314
337
  page_size: { type: 'number', description: 'Messages to fetch (default 20, max 50)' },
315
338
  start_time: { type: 'string', description: 'Start timestamp in seconds (optional)' },
316
339
  end_time: { type: 'string', description: 'End timestamp in seconds (optional)' },
@@ -508,7 +531,7 @@ const TOOLS = [
508
531
  // --- Server ---
509
532
 
510
533
  const server = new Server(
511
- { name: 'feishu-user-plugin', version: '1.1.0' },
534
+ { name: 'feishu-user-plugin', version: '1.1.1' },
512
535
  { capabilities: { tools: {} } }
513
536
  );
514
537
 
@@ -653,14 +676,38 @@ async function handleTool(name, args) {
653
676
  return json(await getOfficialClient().listChats({ pageSize: args.page_size, pageToken: args.page_token }));
654
677
  case 'read_messages': {
655
678
  const official = getOfficialClient();
679
+ const msgOpts = { pageSize: args.page_size, startTime: args.start_time, endTime: args.end_time, sortType: args.sort_type };
656
680
  const resolvedChatId = await chatIdMapper.resolveToOcId(args.chat_id, official);
657
- if (!resolvedChatId) {
658
- return text(`Cannot resolve "${args.chat_id}" to oc_ ID. Searched bot's group list and used im.chat.search API no match found.\nTry: list_chats to see all bot groups, or provide the oc_xxx ID directly.\nIf the bot is not in this group, use read_p2p_messages with UAT instead.`);
681
+
682
+ // Try bot API first if we resolved an oc_ ID
683
+ if (resolvedChatId) {
684
+ try {
685
+ return json(await official.readMessages(resolvedChatId, msgOpts));
686
+ } catch (botErr) {
687
+ // Bot API failed (e.g. bot not in group, no permission) — fall through to UAT
688
+ console.error(`[feishu-user-plugin] read_messages bot API failed for ${resolvedChatId}: ${botErr.message}`);
689
+ if (official.hasUAT) {
690
+ try {
691
+ return json(await official.readMessagesAsUser(resolvedChatId, msgOpts));
692
+ } catch (uatErr) {
693
+ console.error(`[feishu-user-plugin] read_messages UAT fallback also failed for ${resolvedChatId}: ${uatErr.message}`);
694
+ }
695
+ }
696
+ throw botErr; // Re-throw original error if UAT also failed
697
+ }
659
698
  }
660
- return json(await official.readMessages(resolvedChatId, {
661
- pageSize: args.page_size, startTime: args.start_time, endTime: args.end_time,
662
- sortType: args.sort_type,
663
- }));
699
+
700
+ // Bot couldn't resolve the chat name — try search_contacts + UAT for external groups
701
+ if (official.hasUAT) {
702
+ let contactClient = null;
703
+ try { contactClient = await getUserClient(); } catch (_) {}
704
+ const contactChatId = await chatIdMapper.resolveViaContacts(args.chat_id, contactClient);
705
+ if (contactChatId) {
706
+ return json(await official.readMessagesAsUser(contactChatId, msgOpts));
707
+ }
708
+ }
709
+
710
+ return text(`Cannot resolve "${args.chat_id}" to a chat ID.\nSearched: bot's group list, im.chat.search API, and user contacts (search_contacts).\nTry: provide the oc_xxx or numeric chat ID directly.`);
664
711
  }
665
712
  case 'reply_message':
666
713
  return text(`Reply sent: ${(await getOfficialClient().replyMessage(args.message_id, args.text)).messageId}`);
@@ -725,7 +772,7 @@ async function main() {
725
772
  const hasCookie = !!process.env.LARK_COOKIE;
726
773
  const hasApp = !!(process.env.LARK_APP_ID && process.env.LARK_APP_SECRET);
727
774
  const hasUAT = !!process.env.LARK_USER_ACCESS_TOKEN;
728
- console.error(`[feishu-user-plugin] MCP Server v1.1.0 — ${TOOLS.length} tools`);
775
+ console.error(`[feishu-user-plugin] MCP Server v1.1.1 — ${TOOLS.length} tools`);
729
776
  console.error(`[feishu-user-plugin] Auth: Cookie=${hasCookie ? 'YES' : 'NO'} App=${hasApp ? 'YES' : 'NO'} UAT=${hasUAT ? 'YES' : 'NO'}`);
730
777
  if (!hasCookie) console.error('[feishu-user-plugin] WARNING: LARK_COOKIE not set — user identity tools (send_to_user, etc.) will fail');
731
778
  if (!hasApp) console.error('[feishu-user-plugin] WARNING: LARK_APP_ID/SECRET not set — official API tools (read_messages, docs, etc.) will fail');
@@ -274,7 +274,7 @@ async function testUAT() {
274
274
  }
275
275
 
276
276
  async function main() {
277
- console.log('=== feishu-user-plugin v1.1.0 — Comprehensive Test ===\n');
277
+ console.log('=== feishu-user-plugin v1.1.1 — Comprehensive Test ===\n');
278
278
 
279
279
  await testUserIdentity();
280
280
  console.log('');