feishu-user-plugin 1.0.0
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/.claude-plugin/plugin.json +9 -0
- package/.env.example +18 -0
- package/.mcp.json.example +13 -0
- package/CHANGELOG.md +62 -0
- package/LICENSE +21 -0
- package/README.md +473 -0
- package/package.json +57 -0
- package/proto/lark.proto +317 -0
- package/skills/feishu-user-plugin/SKILL.md +103 -0
- package/skills/feishu-user-plugin/references/CLAUDE.md +94 -0
- package/skills/feishu-user-plugin/references/digest.md +26 -0
- package/skills/feishu-user-plugin/references/doc.md +27 -0
- package/skills/feishu-user-plugin/references/drive.md +24 -0
- package/skills/feishu-user-plugin/references/reply.md +23 -0
- package/skills/feishu-user-plugin/references/search.md +22 -0
- package/skills/feishu-user-plugin/references/send.md +28 -0
- package/skills/feishu-user-plugin/references/status.md +22 -0
- package/skills/feishu-user-plugin/references/table.md +32 -0
- package/skills/feishu-user-plugin/references/wiki.md +26 -0
- package/src/client.js +364 -0
- package/src/index.js +697 -0
- package/src/oauth-auto.js +196 -0
- package/src/oauth.js +215 -0
- package/src/official.js +365 -0
- package/src/test-all.js +324 -0
- package/src/test-comprehensive.js +301 -0
- package/src/test-send.js +67 -0
- package/src/utils.js +39 -0
package/proto/lark.proto
ADDED
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
syntax = "proto3";
|
|
2
|
+
|
|
3
|
+
// ============================================================
|
|
4
|
+
// Feishu/Lark Internal Protobuf Protocol
|
|
5
|
+
// Reverse-engineered from Feishu Web Client
|
|
6
|
+
// Reference: https://github.com/cv-cat/LarkAgentX
|
|
7
|
+
// ============================================================
|
|
8
|
+
|
|
9
|
+
// --- Transport Layer ---
|
|
10
|
+
|
|
11
|
+
message Frame {
|
|
12
|
+
optional uint64 seqid = 1;
|
|
13
|
+
optional uint64 logid = 2;
|
|
14
|
+
optional int32 service = 3;
|
|
15
|
+
optional int32 method = 4;
|
|
16
|
+
repeated ExtendedEntry headers = 5;
|
|
17
|
+
optional string payloadEncoding = 6;
|
|
18
|
+
optional string payloadType = 7;
|
|
19
|
+
optional bytes payload = 8;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
message Packet {
|
|
23
|
+
optional string sid = 1;
|
|
24
|
+
optional PayloadType payloadType = 2;
|
|
25
|
+
optional int32 cmd = 3;
|
|
26
|
+
optional uint32 status = 4;
|
|
27
|
+
optional bytes payload = 5;
|
|
28
|
+
optional string cid = 6;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
enum PayloadType {
|
|
32
|
+
TYPE_UNKNOWN = 0;
|
|
33
|
+
PB2 = 1;
|
|
34
|
+
JSON = 2;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
message ExtendedEntry {
|
|
38
|
+
optional string key = 1;
|
|
39
|
+
optional string value = 2;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// --- Message Types ---
|
|
43
|
+
|
|
44
|
+
enum Type {
|
|
45
|
+
UNKNOWN = 0;
|
|
46
|
+
POST = 2;
|
|
47
|
+
FILE = 3;
|
|
48
|
+
TEXT = 4;
|
|
49
|
+
IMAGE = 5;
|
|
50
|
+
SYSTEM = 6;
|
|
51
|
+
AUDIO = 7;
|
|
52
|
+
STICKER = 10;
|
|
53
|
+
CARD = 14;
|
|
54
|
+
MEDIA = 15;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
enum FromType {
|
|
58
|
+
UNKNOWN_FROMTYPE = 0;
|
|
59
|
+
USER = 1;
|
|
60
|
+
BOT = 2;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
enum ChatType {
|
|
64
|
+
UNKNOWN_CHAT_TYPE = 0;
|
|
65
|
+
P2P = 1;
|
|
66
|
+
GROUP = 2;
|
|
67
|
+
TOPIC_GROUP = 3;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// --- Send Message (cmd=5) ---
|
|
71
|
+
|
|
72
|
+
message PutMessageRequest {
|
|
73
|
+
optional Type type = 1;
|
|
74
|
+
optional Content content = 2;
|
|
75
|
+
optional string chatId = 3;
|
|
76
|
+
optional string rootId = 4;
|
|
77
|
+
optional string parentId = 5;
|
|
78
|
+
optional string cid = 6;
|
|
79
|
+
optional bool isNotified = 7;
|
|
80
|
+
optional bool sendToChat = 8;
|
|
81
|
+
optional int32 version = 9;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// --- Receive Messages (WebSocket push) ---
|
|
85
|
+
|
|
86
|
+
message PushMessagesRequest {
|
|
87
|
+
map<string, MessageEntity> messages = 1;
|
|
88
|
+
map<string, bool> participatedMessageIds = 3;
|
|
89
|
+
map<string, bool> forcePush = 8;
|
|
90
|
+
map<string, bool> messagesAtMe = 9;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
message MessageEntity {
|
|
94
|
+
optional string id = 1;
|
|
95
|
+
optional Type type = 2;
|
|
96
|
+
optional string fromId = 3;
|
|
97
|
+
optional int64 createTime = 4;
|
|
98
|
+
optional bytes content = 5;
|
|
99
|
+
optional MessageStatus status = 6;
|
|
100
|
+
optional FromType fromType = 7;
|
|
101
|
+
optional string rootId = 8;
|
|
102
|
+
optional string parentId = 9;
|
|
103
|
+
optional string chatId = 10;
|
|
104
|
+
optional int64 lastModifyTime = 11;
|
|
105
|
+
optional string cid = 12;
|
|
106
|
+
optional int32 position = 13;
|
|
107
|
+
optional int64 updateTime = 14;
|
|
108
|
+
optional bool isNotified = 15;
|
|
109
|
+
optional string threadId = 20;
|
|
110
|
+
optional ChatType chatType = 46;
|
|
111
|
+
|
|
112
|
+
enum MessageStatus {
|
|
113
|
+
UNKNOWN_STATUS = 0;
|
|
114
|
+
NORMAL = 1;
|
|
115
|
+
DELETED = 2;
|
|
116
|
+
MODIFIED = 3;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// --- Content ---
|
|
121
|
+
|
|
122
|
+
message Content {
|
|
123
|
+
optional string text = 1;
|
|
124
|
+
optional string imageKey = 2;
|
|
125
|
+
optional string title = 3;
|
|
126
|
+
optional string fileKey = 6;
|
|
127
|
+
optional string audioKey = 7;
|
|
128
|
+
optional string fileName = 11;
|
|
129
|
+
optional RichText richText = 14;
|
|
130
|
+
optional string stickerSetId = 24;
|
|
131
|
+
optional string stickerId = 25;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
message TextContent {
|
|
135
|
+
optional string text = 1;
|
|
136
|
+
optional RichText richText = 3;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
message RichText {
|
|
140
|
+
repeated string elementIds = 1;
|
|
141
|
+
optional string innerText = 2;
|
|
142
|
+
optional RichTextElements elements = 3;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
message RichTextElements {
|
|
146
|
+
map<string, RichTextElement> dictionary = 1;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
message RichTextElement {
|
|
150
|
+
optional Tag tag = 1;
|
|
151
|
+
map<string, string> style = 2;
|
|
152
|
+
optional bytes property = 3;
|
|
153
|
+
repeated string childIds = 4;
|
|
154
|
+
|
|
155
|
+
enum Tag {
|
|
156
|
+
UNKNOWN_TAG = 0;
|
|
157
|
+
TEXT = 1;
|
|
158
|
+
IMG = 2;
|
|
159
|
+
P = 3;
|
|
160
|
+
FIGURE = 4;
|
|
161
|
+
AT = 5;
|
|
162
|
+
A = 6;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
message TextProperty {
|
|
167
|
+
optional string content = 1;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// --- Chat Operations ---
|
|
171
|
+
|
|
172
|
+
// Create P2P chat (cmd=13)
|
|
173
|
+
message PutChatRequest {
|
|
174
|
+
optional Type type = 1;
|
|
175
|
+
repeated string userIds = 2;
|
|
176
|
+
repeated string chatterIds = 6;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
message PutChatResponse {
|
|
180
|
+
optional Chat chat = 1;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Get group info (cmd=64)
|
|
184
|
+
message GetGroupInfoRequest {
|
|
185
|
+
optional string chatId = 1;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
message GetGroupInfoResponse {
|
|
189
|
+
optional Chat chat = 1;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
message Chat {
|
|
193
|
+
optional string id = 1;
|
|
194
|
+
optional ChatTypeEnum type = 2;
|
|
195
|
+
optional string lastMessageId = 3;
|
|
196
|
+
optional string name = 4;
|
|
197
|
+
optional string ownerId = 6;
|
|
198
|
+
optional int32 newMessageCount = 7;
|
|
199
|
+
optional int64 updateTime = 9;
|
|
200
|
+
optional string description = 11;
|
|
201
|
+
optional int32 memberCount = 12;
|
|
202
|
+
optional bool isPublic = 14;
|
|
203
|
+
optional int32 lastMessagePosition = 15;
|
|
204
|
+
optional int32 userCount = 16;
|
|
205
|
+
optional int64 createTime = 18;
|
|
206
|
+
optional string tenantId = 25;
|
|
207
|
+
optional bool isDissolved = 30;
|
|
208
|
+
optional bool isCrossTenant = 41;
|
|
209
|
+
optional Announcement announcement = 24;
|
|
210
|
+
|
|
211
|
+
enum ChatTypeEnum {
|
|
212
|
+
UNKNOWN_CHAT = 0;
|
|
213
|
+
P2P_CHAT = 1;
|
|
214
|
+
GROUP_CHAT = 2;
|
|
215
|
+
TOPIC_GROUP_CHAT = 3;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
message Announcement {
|
|
219
|
+
optional string content = 1;
|
|
220
|
+
optional int64 updateTime = 2;
|
|
221
|
+
optional string lastEditorId = 3;
|
|
222
|
+
optional string docUrl = 4;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// --- Search (cmd=11021) ---
|
|
227
|
+
|
|
228
|
+
message UniversalSearchRequest {
|
|
229
|
+
optional SearchCommonRequestHeader header = 1;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
message SearchCommonRequestHeader {
|
|
233
|
+
optional string searchSession = 1;
|
|
234
|
+
optional int32 sessionSeqId = 2;
|
|
235
|
+
optional string query = 3;
|
|
236
|
+
optional string paginationToken = 4;
|
|
237
|
+
optional SearchContext searchContext = 5;
|
|
238
|
+
optional string locale = 6;
|
|
239
|
+
optional int32 pageSize = 11;
|
|
240
|
+
|
|
241
|
+
message SearchContext {
|
|
242
|
+
optional string tagName = 1;
|
|
243
|
+
repeated EntityItem entityItems = 2;
|
|
244
|
+
optional CommonFilter commonFilter = 3;
|
|
245
|
+
optional string sourceKey = 5;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
message EntityItem {
|
|
250
|
+
optional SearchEntityType type = 1;
|
|
251
|
+
optional EntityFilter filter = 2;
|
|
252
|
+
|
|
253
|
+
message EntityFilter {
|
|
254
|
+
oneof filter {
|
|
255
|
+
GroupChatFilter groupChatFilter = 2;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
message GroupChatFilter {}
|
|
261
|
+
|
|
262
|
+
message CommonFilter {
|
|
263
|
+
optional bool includeOuterTenant = 1;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
enum SearchEntityType {
|
|
267
|
+
SEARCH_UNKNOWN = 0;
|
|
268
|
+
SEARCH_USER = 1;
|
|
269
|
+
SEARCH_BOT = 2;
|
|
270
|
+
SEARCH_GROUP_CHAT = 3;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
message UniversalSearchResponse {
|
|
274
|
+
optional SearchResponseHeader header = 1;
|
|
275
|
+
repeated SearchResult results = 2;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
message SearchResponseHeader {
|
|
279
|
+
optional int32 total = 3;
|
|
280
|
+
optional bool hasMore = 4;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
message SearchResult {
|
|
284
|
+
optional string id = 1;
|
|
285
|
+
optional SearchEntityType type = 2;
|
|
286
|
+
optional string titleHighlighted = 3;
|
|
287
|
+
optional string summaryHighlighted = 4;
|
|
288
|
+
optional string avatarKey = 6;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// --- User Info (cmd=5023) ---
|
|
292
|
+
|
|
293
|
+
message GetUserInfoRequest {
|
|
294
|
+
optional int64 chatId = 1;
|
|
295
|
+
optional int64 userId = 3;
|
|
296
|
+
optional int32 userType = 4;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
message UserInfo {
|
|
300
|
+
optional UserInfoDetail userInfoDetail = 1;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
message UserInfoDetail {
|
|
304
|
+
optional Detail detail = 2;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
message Detail {
|
|
308
|
+
optional bytes nickname = 2;
|
|
309
|
+
optional bytes nickname1 = 4;
|
|
310
|
+
optional bytes nickname4 = 17;
|
|
311
|
+
repeated LocaleEntry locales = 18;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
message LocaleEntry {
|
|
315
|
+
optional string key_string = 1;
|
|
316
|
+
optional string translation = 2;
|
|
317
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: feishu-user-plugin
|
|
3
|
+
version: "1.0.0"
|
|
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
|
+
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
|
+
user_invocable: true
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Feishu User Plugin
|
|
10
|
+
|
|
11
|
+
All-in-one Feishu plugin for Claude Code with three auth layers:
|
|
12
|
+
- **User Identity** (cookie auth): Send messages as yourself — text, image, file, rich text, sticker, audio
|
|
13
|
+
- **Official API** (app credentials): Read group messages, docs, tables, wiki, drive, contacts
|
|
14
|
+
- **User OAuth** (user_access_token): Read P2P (direct message) chat history
|
|
15
|
+
|
|
16
|
+
This plugin replaces and extends the official Feishu MCP. No need to install two packages.
|
|
17
|
+
|
|
18
|
+
## Trigger Conditions
|
|
19
|
+
|
|
20
|
+
Activate when the user mentions:
|
|
21
|
+
- Sending Feishu messages ("发飞书消息给 XXX", "send a message to XXX on Feishu")
|
|
22
|
+
- Reading Feishu chats ("读飞书群聊 / 单聊", "read Feishu chat history")
|
|
23
|
+
- Feishu documents ("搜飞书文档", "search Feishu docs")
|
|
24
|
+
- Feishu tables ("查飞书表格", "query Bitable")
|
|
25
|
+
- Feishu wiki ("搜飞书知识库", "search wiki")
|
|
26
|
+
- Login status ("飞书登录状态", "check Feishu login")
|
|
27
|
+
|
|
28
|
+
## 9 Built-in Skills
|
|
29
|
+
|
|
30
|
+
### /send — Send message as yourself
|
|
31
|
+
Parse "recipient: message" format, auto-detect user vs group, confirm before sending.
|
|
32
|
+
|
|
33
|
+
### /reply — Read messages and reply
|
|
34
|
+
Search chat → read recent messages → show summary → user picks a message → reply.
|
|
35
|
+
|
|
36
|
+
### /digest — Chat message digest
|
|
37
|
+
Search chat → read N days of messages → filter valuable content → summarize key insights.
|
|
38
|
+
|
|
39
|
+
### /search — Search contacts
|
|
40
|
+
Search users and groups by name, display results grouped by type.
|
|
41
|
+
|
|
42
|
+
### /doc — Document operations
|
|
43
|
+
Search (search_docs), read (read_doc), create (create_doc) — three in one.
|
|
44
|
+
|
|
45
|
+
### /table — Bitable operations
|
|
46
|
+
Query (list tables → list fields → search records), create records, update records.
|
|
47
|
+
|
|
48
|
+
### /wiki — Wiki management
|
|
49
|
+
List spaces (list_wiki_spaces), search content (search_wiki), browse nodes (list_wiki_nodes).
|
|
50
|
+
|
|
51
|
+
### /drive — Drive file management
|
|
52
|
+
List files in folders, create new folders in Feishu Drive.
|
|
53
|
+
|
|
54
|
+
### /status — Check login status
|
|
55
|
+
Check cookie / app credentials / UAT — all three auth layers at once.
|
|
56
|
+
|
|
57
|
+
## Auth Configuration
|
|
58
|
+
|
|
59
|
+
| Env Variable | Who Provides | Purpose | Required |
|
|
60
|
+
|---|---|---|---|
|
|
61
|
+
| LARK_COOKIE | **You** | Send messages as yourself | Yes (for messaging) |
|
|
62
|
+
| LARK_APP_ID | **You** (create a Feishu app) | Official API access | Yes |
|
|
63
|
+
| LARK_APP_SECRET | **You** (from your Feishu app) | Official API access | Yes |
|
|
64
|
+
| LARK_USER_ACCESS_TOKEN | **You** (OAuth flow, optional) | Read P2P chat history | Optional |
|
|
65
|
+
|
|
66
|
+
### Getting Your Cookie (Automated via Playwright)
|
|
67
|
+
|
|
68
|
+
**Prerequisite: Playwright MCP must be installed.** If not, run:
|
|
69
|
+
```
|
|
70
|
+
npx @anthropic-ai/claude-code mcp add playwright -- npx @anthropic-ai/mcp-server-playwright
|
|
71
|
+
```
|
|
72
|
+
Then restart Claude Code.
|
|
73
|
+
|
|
74
|
+
**Automated flow (recommended, zero manual cookie copying):**
|
|
75
|
+
> Just tell Claude Code: "Help me set up my Feishu cookie"
|
|
76
|
+
>
|
|
77
|
+
> Claude Code will automatically:
|
|
78
|
+
> 1. Open feishu.cn in a browser via Playwright
|
|
79
|
+
> 2. Show you the QR code — scan it with Feishu mobile app
|
|
80
|
+
> 3. Extract the full cookie (including HttpOnly) via `context.cookies()`
|
|
81
|
+
> 4. Write it to your `.mcp.json` LARK_COOKIE field
|
|
82
|
+
> 5. Prompt you to restart Claude Code
|
|
83
|
+
|
|
84
|
+
**Manual fallback (if Playwright is unavailable):**
|
|
85
|
+
1. Open https://www.feishu.cn/messenger/ and log in
|
|
86
|
+
2. DevTools → **Network** tab → Disable cache → Reload
|
|
87
|
+
3. Click the first request → Request Headers → **Cookie** → right-click → Copy value
|
|
88
|
+
4. Paste into your `.mcp.json` env `LARK_COOKIE` field
|
|
89
|
+
|
|
90
|
+
> Do NOT use `document.cookie` or Application → Cookies — they miss HttpOnly cookies required for auth.
|
|
91
|
+
|
|
92
|
+
### Creating a Feishu App (for Official API)
|
|
93
|
+
|
|
94
|
+
1. Go to https://open.feishu.cn/app → Create Custom App (自建应用)
|
|
95
|
+
2. Add scopes: `im:message`, `im:message:readonly`, `im:chat:readonly`, `contact:user.base:readonly`
|
|
96
|
+
3. Copy the App ID and App Secret to your `.mcp.json`
|
|
97
|
+
4. Add the bot to any group chats you want to read
|
|
98
|
+
|
|
99
|
+
## Known Limitations
|
|
100
|
+
|
|
101
|
+
- Image/file sending requires uploading via Official API first to get keys
|
|
102
|
+
- CARD message type (type=14) not yet supported
|
|
103
|
+
- Cookie session valid for ~12h, auto-refreshed via built-in heartbeat (4h interval)
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# feishu-user-plugin — Claude Code Instructions
|
|
2
|
+
|
|
3
|
+
## What This Is
|
|
4
|
+
All-in-one Feishu plugin for Claude Code with three auth layers:
|
|
5
|
+
- **User Identity** (cookie auth): Send messages (text, image, file, post, sticker, audio) as yourself
|
|
6
|
+
- **Official API** (app credentials): Read group messages, docs, tables, wiki, drive, contacts
|
|
7
|
+
- **User OAuth UAT** (user_access_token): Read P2P chat history
|
|
8
|
+
|
|
9
|
+
## Tool Categories
|
|
10
|
+
|
|
11
|
+
### User Identity — Messaging (reverse-engineered, cookie-based)
|
|
12
|
+
- `send_to_user` — Search user + send text (one step, most common)
|
|
13
|
+
- `send_to_group` — Search group + send text (one step)
|
|
14
|
+
- `send_as_user` — Send text to any chat by ID, supports reply threading (root_id/parent_id)
|
|
15
|
+
- `send_image_as_user` — Send image (requires image_key from upload)
|
|
16
|
+
- `send_file_as_user` — Send file (requires file_key from upload)
|
|
17
|
+
- `send_post_as_user` — Send rich text with title + formatted paragraphs
|
|
18
|
+
- `send_sticker_as_user` — Send sticker/emoji
|
|
19
|
+
- `send_audio_as_user` — Send audio message
|
|
20
|
+
|
|
21
|
+
### User Identity — Contacts & Info
|
|
22
|
+
- `search_contacts` — Search users/groups by name
|
|
23
|
+
- `create_p2p_chat` — Create/get P2P chat
|
|
24
|
+
- `get_chat_info` — Group details (name, members, owner)
|
|
25
|
+
- `get_user_info` — User display name lookup (from search cache)
|
|
26
|
+
- `get_login_status` — Check cookie, app, and UAT status
|
|
27
|
+
|
|
28
|
+
### User OAuth UAT Tools (P2P chat reading)
|
|
29
|
+
- `read_p2p_messages` — Read P2P (direct message) chat history. Requires OAuth setup.
|
|
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
|
+
|
|
32
|
+
### Official API Tools (app credentials)
|
|
33
|
+
- `list_chats` / `read_messages` — Chat history (read_messages accepts chat name, auto-resolves to oc_ ID)
|
|
34
|
+
- `reply_message` / `forward_message` — Message operations (as bot). reply_message only works for text messages.
|
|
35
|
+
- `search_docs` / `read_doc` / `create_doc` — Document operations
|
|
36
|
+
- `list_bitable_tables` / `list_bitable_fields` / `search_bitable_records` — Table queries
|
|
37
|
+
- `create_bitable_record` / `update_bitable_record` — Table writes
|
|
38
|
+
- `list_wiki_spaces` / `search_wiki` / `list_wiki_nodes` — Wiki
|
|
39
|
+
- `list_files` / `create_folder` — Drive
|
|
40
|
+
- `find_user` — Contact lookup by email/mobile
|
|
41
|
+
|
|
42
|
+
## Usage Patterns
|
|
43
|
+
- Send text as yourself → `send_to_user` or `send_to_group`
|
|
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
|
|
46
|
+
- Read P2P chat history → `search_contacts` → `create_p2p_chat` → `read_p2p_messages` (requires OAuth UAT)
|
|
47
|
+
- Reply as user in thread → `send_as_user` with root_id
|
|
48
|
+
- Reply as bot → `reply_message` (official API, text messages only)
|
|
49
|
+
- Diagnose issues → `get_login_status` first
|
|
50
|
+
|
|
51
|
+
## Auth & Session
|
|
52
|
+
- **LARK_COOKIE**: Required for user identity tools. Session auto-refreshed every 4h via heartbeat.
|
|
53
|
+
- **LARK_APP_ID + LARK_APP_SECRET**: Required for official API tools.
|
|
54
|
+
- **LARK_USER_ACCESS_TOKEN**: Required for P2P reading. Obtained via OAuth flow. Auto-refreshed via refresh_token.
|
|
55
|
+
|
|
56
|
+
## Automated Cookie Setup via Playwright
|
|
57
|
+
|
|
58
|
+
When LARK_COOKIE is missing or expired, automatically obtain it using Playwright MCP.
|
|
59
|
+
Do NOT ask the user to manually copy cookies from DevTools.
|
|
60
|
+
|
|
61
|
+
### Prerequisites
|
|
62
|
+
Playwright MCP must be available. If not, tell the user:
|
|
63
|
+
> Run: `npx @anthropic-ai/claude-code mcp add playwright -- npx @anthropic-ai/mcp-server-playwright`
|
|
64
|
+
> Then restart Claude Code.
|
|
65
|
+
|
|
66
|
+
### Automated Flow
|
|
67
|
+
1. `browser_navigate` → `https://www.feishu.cn/messenger/`
|
|
68
|
+
2. `browser_take_screenshot` → show QR code to user, ask them to scan with Feishu app
|
|
69
|
+
3. Poll `browser_snapshot` every 3-5s until messenger page loads
|
|
70
|
+
4. `browser_run_code`:
|
|
71
|
+
```js
|
|
72
|
+
const cookies = await context.cookies('https://www.feishu.cn');
|
|
73
|
+
const feishuCookies = cookies.filter(c => c.domain.endsWith('feishu.cn'));
|
|
74
|
+
const cookieStr = feishuCookies.map(c => c.name + '=' + c.value).join('; ');
|
|
75
|
+
```
|
|
76
|
+
5. Read user's `.mcp.json`, update `LARK_COOKIE` value
|
|
77
|
+
6. Tell user to restart Claude Code
|
|
78
|
+
|
|
79
|
+
### Cookie Refresh
|
|
80
|
+
When cookie auth fails, offer to re-run the Playwright flow. Heartbeat auto-refreshes every 4h.
|
|
81
|
+
|
|
82
|
+
## Troubleshooting
|
|
83
|
+
|
|
84
|
+
### If cookie authentication fails
|
|
85
|
+
- **Best method**: Playwright `context.cookies()` (includes HttpOnly)
|
|
86
|
+
- **Manual fallback**: Network tab → Disable cache → reload → first request → Request Headers → Cookie → Copy value
|
|
87
|
+
- Do NOT use `document.cookie` or Application → Cookies (misses HttpOnly cookies)
|
|
88
|
+
|
|
89
|
+
### If list_user_chats doesn't return P2P chats
|
|
90
|
+
- This is expected — the Feishu API only returns group chats at this endpoint.
|
|
91
|
+
- Use the P2P flow: `search_contacts` → `create_p2p_chat` → `read_p2p_messages`.
|
|
92
|
+
|
|
93
|
+
### If reply_message fails with error 230054
|
|
94
|
+
- "This operation is not supported for this message type" — only text messages can be replied to.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
整理飞书群聊或单聊的最近消息。
|
|
2
|
+
|
|
3
|
+
## 参数
|
|
4
|
+
- $ARGUMENTS:聊天名关键词,可选指定天数(默认 3 天)
|
|
5
|
+
|
|
6
|
+
## 执行步骤
|
|
7
|
+
|
|
8
|
+
### 群聊摘要
|
|
9
|
+
1. 用 `read_messages` 读取最近 N 天的消息(传群名自动解析,计算 start_time 时间戳)
|
|
10
|
+
2. 按以下标准筛选有价值内容:
|
|
11
|
+
- 包含可操作的具体方法论或工具推荐 → 高价值
|
|
12
|
+
- 包含带数据支撑的趋势分析 → 高价值
|
|
13
|
+
- 包含可复现的实践经验 → 高价值
|
|
14
|
+
- 纯寒暄/水聊 → 跳过
|
|
15
|
+
3. 向用户汇报:共读取 X 条,筛选 Y 条有价值内容,涉及的主题分类
|
|
16
|
+
4. 等用户确认后,用自己的语言概括核心观点
|
|
17
|
+
|
|
18
|
+
### 单聊摘要
|
|
19
|
+
1. 用 `search_contacts` 搜索对方 → `create_p2p_chat` 获取 chat_id
|
|
20
|
+
2. 用 `read_p2p_messages` 读取消息
|
|
21
|
+
3. 按同样标准筛选和汇总
|
|
22
|
+
|
|
23
|
+
## 注意
|
|
24
|
+
- 群聊需要机器人在群内
|
|
25
|
+
- 单聊需要 OAuth UAT 授权
|
|
26
|
+
- 如有外部链接可用 Playwright 抓取全文
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
操作飞书云文档:搜索、读取或创建。
|
|
2
|
+
|
|
3
|
+
## 参数
|
|
4
|
+
- $ARGUMENTS:操作类型 + 文档标识或内容
|
|
5
|
+
|
|
6
|
+
## 执行步骤
|
|
7
|
+
|
|
8
|
+
### 搜索文档
|
|
9
|
+
1. 用 `search_docs` 搜索关键词
|
|
10
|
+
2. 展示文档列表(标题、文档 ID)
|
|
11
|
+
|
|
12
|
+
### 读取文档
|
|
13
|
+
1. 用 `read_doc` 读取文档内容(传入 document_id)
|
|
14
|
+
2. 展示内容摘要
|
|
15
|
+
|
|
16
|
+
### 创建文档
|
|
17
|
+
1. 用 `create_doc` 创建新文档(传入标题和可选 folder_id)
|
|
18
|
+
2. 返回文档 ID
|
|
19
|
+
|
|
20
|
+
## 示例
|
|
21
|
+
- `/doc search MCP 协议`
|
|
22
|
+
- `/doc read doxcnXXXXXX`
|
|
23
|
+
- `/doc create 本周工作总结`
|
|
24
|
+
|
|
25
|
+
## 注意
|
|
26
|
+
- 文档操作使用 Official API(需要 LARK_APP_ID)
|
|
27
|
+
- 搜索结果受机器人权限范围限制
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
管理飞书云盘文件和文件夹。
|
|
2
|
+
|
|
3
|
+
## 参数
|
|
4
|
+
- $ARGUMENTS:操作类型 + 文件夹标识
|
|
5
|
+
|
|
6
|
+
## 执行步骤
|
|
7
|
+
|
|
8
|
+
### 列出文件
|
|
9
|
+
1. 用 `list_files` 列出文件夹内容
|
|
10
|
+
- 不传 folder_token 则列出根目录
|
|
11
|
+
- 传入 folder_token 则列出指定文件夹
|
|
12
|
+
|
|
13
|
+
### 创建文件夹
|
|
14
|
+
1. 用 `create_folder` 创建新文件夹
|
|
15
|
+
- 传入 name 和可选的 parent_token
|
|
16
|
+
|
|
17
|
+
## 示例
|
|
18
|
+
- `/drive list` — 列出根目录文件
|
|
19
|
+
- `/drive list folderXxx` — 列出指定文件夹
|
|
20
|
+
- `/drive create 项目资料` — 在根目录创建文件夹
|
|
21
|
+
|
|
22
|
+
## 注意
|
|
23
|
+
- 使用 Official API,需要 LARK_APP_ID
|
|
24
|
+
- 文件列表受机器人权限范围限制
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
读取飞书聊天的最近消息并回复。
|
|
2
|
+
|
|
3
|
+
## 参数
|
|
4
|
+
- $ARGUMENTS:群名关键词或 chat_id,可选指定消息数量(默认 10 条)
|
|
5
|
+
|
|
6
|
+
## 执行步骤
|
|
7
|
+
|
|
8
|
+
### 群聊消息
|
|
9
|
+
1. 用 `read_messages` 读取消息(直接传群名,会自动搜索解析为 oc_ ID)
|
|
10
|
+
2. 向用户展示最近 N 条消息摘要(标注发送人和时间)
|
|
11
|
+
3. 用户指定要回复哪条消息后,用 `reply_message` 回复(以机器人身份)
|
|
12
|
+
4. 如需以个人身份回复,用 `send_as_user` 并传 `root_id`(话题回复)
|
|
13
|
+
|
|
14
|
+
### 单聊消息(P2P)
|
|
15
|
+
1. 用 `search_contacts` 搜索对方用户名
|
|
16
|
+
2. 用 `create_p2p_chat` 获取单聊 chat_id(数字格式)
|
|
17
|
+
3. 用 `read_p2p_messages` 读取历史消息(需要 OAuth UAT)
|
|
18
|
+
4. 如需回复,用 `send_as_user` 发送
|
|
19
|
+
|
|
20
|
+
## 注意
|
|
21
|
+
- 群聊读取用 `read_messages`(Official API,机器人需在群内)
|
|
22
|
+
- 单聊读取用 `read_p2p_messages`(User OAuth,需要 UAT 授权)
|
|
23
|
+
- `reply_message` 只能回复**文本类型**消息,其他类型会返回 230054 错误
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
搜索飞书联系人或群组。
|
|
2
|
+
|
|
3
|
+
## 参数
|
|
4
|
+
- $ARGUMENTS: 搜索关键词
|
|
5
|
+
|
|
6
|
+
## 执行步骤
|
|
7
|
+
1. 使用 `search_contacts` 搜索 $ARGUMENTS
|
|
8
|
+
2. 将结果按类型分组展示:
|
|
9
|
+
- 用户(user):显示名称和 ID
|
|
10
|
+
- 群组(group):显示群名和 ID
|
|
11
|
+
- 机器人(bot):显示名称和 ID
|
|
12
|
+
3. 提示用户可用的后续操作:
|
|
13
|
+
- `/send 用户名: 消息` 发送消息
|
|
14
|
+
- `/reply 群名` 读取群聊并回复
|
|
15
|
+
- `/digest 群名` 整理聊天摘要
|
|
16
|
+
|
|
17
|
+
## 通过邮箱或手机号查找
|
|
18
|
+
如果用户提供了邮箱或手机号,改用 `find_user`:
|
|
19
|
+
```
|
|
20
|
+
find_user({ email: "xxx@xxx.com" })
|
|
21
|
+
find_user({ mobile: "+86xxx" })
|
|
22
|
+
```
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
以你本人的飞书身份发送消息(非机器人)。
|
|
2
|
+
|
|
3
|
+
## 参数
|
|
4
|
+
- $ARGUMENTS:收件人和消息内容,格式:"收件人: 消息内容"
|
|
5
|
+
|
|
6
|
+
## 执行步骤
|
|
7
|
+
1. 解析 $ARGUMENTS,以第一个冒号分离收件人和消息内容
|
|
8
|
+
2. 判断收件人类型:
|
|
9
|
+
- 如果是人名 → 使用 `send_to_user`(自动搜索用户 → 创建单聊 → 发送)
|
|
10
|
+
- 如果是群名 → 使用 `send_to_group`(自动搜索群 → 发送)
|
|
11
|
+
- 如果是 chat_id(纯数字)→ 使用 `send_as_user`
|
|
12
|
+
3. 发送前向用户确认收件人和消息内容
|
|
13
|
+
4. 发送并返回结果
|
|
14
|
+
|
|
15
|
+
## 富文本消息
|
|
16
|
+
如果消息包含格式化需求(加粗、链接、@人),改用 `send_post_as_user`:
|
|
17
|
+
```
|
|
18
|
+
send_post_as_user({
|
|
19
|
+
chat_id: "xxx",
|
|
20
|
+
title: "标题(可选)",
|
|
21
|
+
paragraphs: [[{tag:"text",text:"普通文本"}, {tag:"text",text:"加粗",style:["bold"]}]]
|
|
22
|
+
})
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## 注意
|
|
26
|
+
- 消息以你的**个人飞书身份**发送,不是机器人
|
|
27
|
+
- 支持发送给个人(单聊)和群组
|
|
28
|
+
- 如需回复某条消息的话题,用 `send_as_user` 并传 `root_id`
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
检查飞书所有认证层的登录状态。
|
|
2
|
+
|
|
3
|
+
## 执行步骤
|
|
4
|
+
|
|
5
|
+
1. 使用 `get_login_status` 检查三层认证状态:
|
|
6
|
+
- **Cookie 会话**:是否有效,当前登录用户
|
|
7
|
+
- **App 凭证**:LARK_APP_ID / LARK_APP_SECRET 是否配置
|
|
8
|
+
- **User Access Token**:UAT 是否可用(P2P 单聊读取需要)
|
|
9
|
+
|
|
10
|
+
2. 根据结果给出建议:
|
|
11
|
+
- Cookie 过期 → 提示用 Playwright 自动获取新 Cookie
|
|
12
|
+
- App 凭证缺失 → 提示配置 .mcp.json
|
|
13
|
+
- UAT 缺失 → 提示运行 `npx feishu-user-plugin oauth`
|
|
14
|
+
|
|
15
|
+
## Cookie 自动获取(Playwright)
|
|
16
|
+
如果 Cookie 过期且 Playwright MCP 可用:
|
|
17
|
+
1. `browser_navigate` → `https://www.feishu.cn/messenger/`
|
|
18
|
+
2. `browser_take_screenshot` → 展示二维码让用户扫码
|
|
19
|
+
3. 轮询 `browser_snapshot` 直到登录成功
|
|
20
|
+
4. `browser_run_code`: `const cookies = await context.cookies('https://www.feishu.cn'); cookies.filter(c => c.domain.endsWith('feishu.cn')).map(c => c.name + '=' + c.value).join('; ')`
|
|
21
|
+
5. 更新 `.mcp.json` 中的 LARK_COOKIE
|
|
22
|
+
6. 提示重启 Claude Code
|