@yoreland/lark-cli-mcp 1.0.0 → 1.1.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/README.md +37 -13
- package/bin/cli.mjs +5 -4
- package/package.json +1 -1
- package/server.mjs +337 -4
package/README.md
CHANGED
|
@@ -56,17 +56,41 @@ npx -y @yoreland/lark-cli-mcp -- <args> # passthrough to bundled lark-cli
|
|
|
56
56
|
|
|
57
57
|
---
|
|
58
58
|
|
|
59
|
-
## The
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
|
64
|
-
|
|
|
65
|
-
| `
|
|
66
|
-
| `
|
|
67
|
-
| `
|
|
68
|
-
| `
|
|
69
|
-
| `
|
|
59
|
+
## The tools
|
|
60
|
+
|
|
61
|
+
### Messaging (IM)
|
|
62
|
+
|
|
63
|
+
| Tool | What it does |
|
|
64
|
+
| ------------------------ | ----------------------- |
|
|
65
|
+
| `feishu_send_message` | Send a message |
|
|
66
|
+
| `feishu_get_messages` | Read recent messages |
|
|
67
|
+
| `feishu_reply_message` | Reply (thread optional) |
|
|
68
|
+
| `feishu_search_messages` | Search messages |
|
|
69
|
+
| `feishu_list_chats` | Find group chats |
|
|
70
|
+
| `feishu_search_user` | Find a user (→ open_id) |
|
|
71
|
+
| `feishu_get_thread` | View a thread |
|
|
72
|
+
|
|
73
|
+
### Docs / Wiki / Drive
|
|
74
|
+
|
|
75
|
+
| Tool | What it does |
|
|
76
|
+
| ----------------------- | ---------------------------------- |
|
|
77
|
+
| `feishu_search_docs` | Search docs / wiki / sheets |
|
|
78
|
+
| `feishu_doc_fetch` | Read a document |
|
|
79
|
+
| `feishu_doc_create` | Create a document (markdown) |
|
|
80
|
+
| `feishu_doc_update` | Update a document |
|
|
81
|
+
| `feishu_drive_search` | Search Drive files (type filters) |
|
|
82
|
+
| `feishu_wiki_node_list` | List wiki nodes |
|
|
83
|
+
| `feishu_wiki_node_get` | Get a wiki node (accepts URL) |
|
|
84
|
+
|
|
85
|
+
### Bitable (multi-dimensional tables)
|
|
86
|
+
|
|
87
|
+
| Tool | What it does |
|
|
88
|
+
| --------------------------- | ------------------------------------- |
|
|
89
|
+
| `feishu_base_table_list` | List tables in a base |
|
|
90
|
+
| `feishu_base_field_list` | List fields of a table |
|
|
91
|
+
| `feishu_base_record_list` | List records (filter/sort) |
|
|
92
|
+
| `feishu_base_record_search` | Search records |
|
|
93
|
+
| `feishu_base_record_upsert` | Create/update a record |
|
|
70
94
|
|
|
71
95
|
### Talk to it naturally
|
|
72
96
|
|
|
@@ -87,7 +111,7 @@ The workshop host creates **one** Feishu custom app and configures it so attende
|
|
|
87
111
|
|
|
88
112
|
1. [Feishu Open Platform](https://open.feishu.cn) → create an internal custom app → note **App ID / App Secret**.
|
|
89
113
|
2. Enable **User token scopes** matching the `im`, `contact`, `search` domains (message read/write, reply, chat read, user search, message search).
|
|
90
|
-
3. Distribute the App ID/Secret to attendees via `lark-cli config` (or a pre-bound config). The login step requests scopes via `--domain im,contact,
|
|
114
|
+
3. Distribute the App ID/Secret to attendees via `lark-cli config` (or a pre-bound config). The login step requests scopes via `--domain im,contact,docs,wiki,drive,base`.
|
|
91
115
|
|
|
92
116
|
`auth` uses **OAuth Device Flow**, so no `redirect URL` / `localhost:3000` callback configuration is required.
|
|
93
117
|
|
|
@@ -97,7 +121,7 @@ The workshop host creates **one** Feishu custom app and configures it so attende
|
|
|
97
121
|
|
|
98
122
|
**`missing required scope(s)`** — re-login with the needed domain:
|
|
99
123
|
```bash
|
|
100
|
-
npx -y @yoreland/lark-cli-mcp auth --domain im,contact,
|
|
124
|
+
npx -y @yoreland/lark-cli-mcp auth --domain im,contact,docs,wiki,drive,base
|
|
101
125
|
```
|
|
102
126
|
|
|
103
127
|
**Client shows "No tools loaded"** — run `npx -y @yoreland/lark-cli-mcp doctor`; confirm Node ≥18 and that `auth status` is OK.
|
package/bin/cli.mjs
CHANGED
|
@@ -19,10 +19,11 @@ import { fileURLToPath } from "node:url";
|
|
|
19
19
|
|
|
20
20
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
21
21
|
|
|
22
|
-
// Default scopes
|
|
23
|
-
// `im`
|
|
24
|
-
// `
|
|
25
|
-
|
|
22
|
+
// Default scopes (user identity) covering IM + docs/wiki/drive/bitable.
|
|
23
|
+
// `im` = messaging; `contact` = user lookup; `docs`/`wiki`/`drive` = cloud docs;
|
|
24
|
+
// `base` = Bitable (multi-dimensional tables).
|
|
25
|
+
// NOTE: there is no `search` domain in lark-cli; message search lives under `im`.
|
|
26
|
+
const DEFAULT_DOMAINS = "im,contact,docs,wiki,drive,base";
|
|
26
27
|
|
|
27
28
|
function resolveLarkCli() {
|
|
28
29
|
if (process.env.LARK_CLI_BIN && existsSync(process.env.LARK_CLI_BIN)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yoreland/lark-cli-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "MCP server that wraps lark-cli to operate Feishu/Lark as your own user identity (send/read/reply/search messages). Designed for one-line npx launch.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/server.mjs
CHANGED
|
@@ -156,6 +156,192 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
156
156
|
required: ["message_id"],
|
|
157
157
|
},
|
|
158
158
|
},
|
|
159
|
+
{
|
|
160
|
+
name: "feishu_search_docs",
|
|
161
|
+
description:
|
|
162
|
+
"搜索飞书云文档(文档/Wiki/表格),按关键词。返回标题、token、URL 等",
|
|
163
|
+
inputSchema: {
|
|
164
|
+
type: "object",
|
|
165
|
+
properties: {
|
|
166
|
+
query: { type: "string", description: "搜索关键词" },
|
|
167
|
+
count: { type: "number", description: "结果数量,默认 15,最大 20" },
|
|
168
|
+
},
|
|
169
|
+
required: ["query"],
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
name: "feishu_doc_fetch",
|
|
174
|
+
description: "读取一篇飞书文档的内容(传文档 URL 或 token)",
|
|
175
|
+
inputSchema: {
|
|
176
|
+
type: "object",
|
|
177
|
+
properties: {
|
|
178
|
+
doc: { type: "string", description: "文档 URL 或 token" },
|
|
179
|
+
},
|
|
180
|
+
required: ["doc"],
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
name: "feishu_doc_create",
|
|
185
|
+
description: "新建一篇飞书文档(Markdown 内容)",
|
|
186
|
+
inputSchema: {
|
|
187
|
+
type: "object",
|
|
188
|
+
properties: {
|
|
189
|
+
title: { type: "string", description: "文档标题" },
|
|
190
|
+
markdown: { type: "string", description: "Markdown 内容" },
|
|
191
|
+
folder_token: {
|
|
192
|
+
type: "string",
|
|
193
|
+
description: "目标文件夹 token(可选)",
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
required: ["title"],
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
name: "feishu_doc_update",
|
|
201
|
+
description: "更新一篇飞书文档内容",
|
|
202
|
+
inputSchema: {
|
|
203
|
+
type: "object",
|
|
204
|
+
properties: {
|
|
205
|
+
doc: { type: "string", description: "文档 URL 或 token" },
|
|
206
|
+
markdown: { type: "string", description: "新的 Markdown 内容" },
|
|
207
|
+
mode: {
|
|
208
|
+
type: "string",
|
|
209
|
+
description:
|
|
210
|
+
"更新模式:append | overwrite | replace_all | insert_before | insert_after(默认 append)",
|
|
211
|
+
},
|
|
212
|
+
new_title: { type: "string", description: "同时更新标题(可选)" },
|
|
213
|
+
},
|
|
214
|
+
required: ["doc", "markdown"],
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
name: "feishu_drive_search",
|
|
219
|
+
description:
|
|
220
|
+
"在云盘中搜索文件(doc/sheet/bitable/file 等),支持类型筛选",
|
|
221
|
+
inputSchema: {
|
|
222
|
+
type: "object",
|
|
223
|
+
properties: {
|
|
224
|
+
query: { type: "string", description: "搜索关键词(可空,纯按筛选浏览)" },
|
|
225
|
+
doc_types: {
|
|
226
|
+
type: "string",
|
|
227
|
+
description:
|
|
228
|
+
"类型逗号分隔:doc,sheet,bitable,mindnote,file,wiki,docx,folder,slides",
|
|
229
|
+
},
|
|
230
|
+
mine: { type: "boolean", description: "仅我拥有的文档" },
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
name: "feishu_wiki_node_list",
|
|
236
|
+
description: "列出某个 Wiki 空间或父节点下的节点",
|
|
237
|
+
inputSchema: {
|
|
238
|
+
type: "object",
|
|
239
|
+
properties: {
|
|
240
|
+
space_id: {
|
|
241
|
+
type: "string",
|
|
242
|
+
description: "Wiki 空间 ID(个人库用 my_library)",
|
|
243
|
+
},
|
|
244
|
+
parent_node_token: {
|
|
245
|
+
type: "string",
|
|
246
|
+
description: "父节点 token(可选,省略则列根节点)",
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
required: ["space_id"],
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
name: "feishu_wiki_node_get",
|
|
254
|
+
description: "读取一个 Wiki 节点详情(支持传 Lark URL / node_token / obj_token)",
|
|
255
|
+
inputSchema: {
|
|
256
|
+
type: "object",
|
|
257
|
+
properties: {
|
|
258
|
+
node_token: {
|
|
259
|
+
type: "string",
|
|
260
|
+
description: "wiki node_token / obj_token / Lark URL",
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
required: ["node_token"],
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
name: "feishu_base_table_list",
|
|
268
|
+
description: "列出某个多维表格(Bitable/Base)下的所有数据表",
|
|
269
|
+
inputSchema: {
|
|
270
|
+
type: "object",
|
|
271
|
+
properties: {
|
|
272
|
+
base_token: { type: "string", description: "多维表格 app_token / URL" },
|
|
273
|
+
},
|
|
274
|
+
required: ["base_token"],
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
name: "feishu_base_field_list",
|
|
279
|
+
description: "列出多维表格某个数据表的字段(列)",
|
|
280
|
+
inputSchema: {
|
|
281
|
+
type: "object",
|
|
282
|
+
properties: {
|
|
283
|
+
base_token: { type: "string", description: "多维表格 app_token / URL" },
|
|
284
|
+
table_id: { type: "string", description: "数据表 ID(tbl_xxx)或表名" },
|
|
285
|
+
},
|
|
286
|
+
required: ["base_token", "table_id"],
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
name: "feishu_base_record_list",
|
|
291
|
+
description: "列出多维表格某个数据表的记录(行),支持筛选/排序",
|
|
292
|
+
inputSchema: {
|
|
293
|
+
type: "object",
|
|
294
|
+
properties: {
|
|
295
|
+
base_token: { type: "string", description: "多维表格 app_token / URL" },
|
|
296
|
+
table_id: { type: "string", description: "数据表 ID 或表名" },
|
|
297
|
+
limit: { type: "number", description: "返回条数 1-200,默认 100" },
|
|
298
|
+
filter_json: {
|
|
299
|
+
type: "string",
|
|
300
|
+
description: "筛选条件 JSON(与视图 filter 同结构,可选)",
|
|
301
|
+
},
|
|
302
|
+
sort_json: {
|
|
303
|
+
type: "string",
|
|
304
|
+
description: '排序 JSON,如 [{"field":"Updated","desc":true}](可选)',
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
required: ["base_token", "table_id"],
|
|
308
|
+
},
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
name: "feishu_base_record_search",
|
|
312
|
+
description: "在多维表格数据表中检索记录",
|
|
313
|
+
inputSchema: {
|
|
314
|
+
type: "object",
|
|
315
|
+
properties: {
|
|
316
|
+
base_token: { type: "string", description: "多维表格 app_token / URL" },
|
|
317
|
+
table_id: { type: "string", description: "数据表 ID 或表名" },
|
|
318
|
+
limit: { type: "number", description: "返回条数 1-200,默认 10" },
|
|
319
|
+
},
|
|
320
|
+
required: ["base_token", "table_id"],
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
name: "feishu_base_record_upsert",
|
|
325
|
+
description:
|
|
326
|
+
"在多维表格数据表中创建或更新一条记录(给 record_id 则更新,否则创建)",
|
|
327
|
+
inputSchema: {
|
|
328
|
+
type: "object",
|
|
329
|
+
properties: {
|
|
330
|
+
base_token: { type: "string", description: "多维表格 app_token / URL" },
|
|
331
|
+
table_id: { type: "string", description: "数据表 ID 或表名" },
|
|
332
|
+
fields_json: {
|
|
333
|
+
type: "string",
|
|
334
|
+
description:
|
|
335
|
+
'字段值 JSON,如 {"Name":"Alice","Status":"Todo"}(不要包 fields)',
|
|
336
|
+
},
|
|
337
|
+
record_id: {
|
|
338
|
+
type: "string",
|
|
339
|
+
description: "记录 ID(传则更新该记录,不传则新建)",
|
|
340
|
+
},
|
|
341
|
+
},
|
|
342
|
+
required: ["base_token", "table_id", "fields_json"],
|
|
343
|
+
},
|
|
344
|
+
},
|
|
159
345
|
],
|
|
160
346
|
}));
|
|
161
347
|
|
|
@@ -181,8 +367,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
181
367
|
if (chat_id) cmd.push("--chat-id", chat_id);
|
|
182
368
|
else cmd.push("--user-id", user_id);
|
|
183
369
|
if (count) cmd.push("--page-size", String(count));
|
|
184
|
-
if (start_time) cmd.push("--start
|
|
185
|
-
if (end_time) cmd.push("--end
|
|
370
|
+
if (start_time) cmd.push("--start", start_time);
|
|
371
|
+
if (end_time) cmd.push("--end", end_time);
|
|
186
372
|
return await run(cmd);
|
|
187
373
|
}
|
|
188
374
|
case "feishu_reply_message": {
|
|
@@ -211,8 +397,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
211
397
|
keyword,
|
|
212
398
|
];
|
|
213
399
|
if (chat_id) cmd.push("--chat-id", chat_id);
|
|
214
|
-
if (start_time) cmd.push("--start
|
|
215
|
-
if (end_time) cmd.push("--end
|
|
400
|
+
if (start_time) cmd.push("--start", start_time);
|
|
401
|
+
if (end_time) cmd.push("--end", end_time);
|
|
216
402
|
return await run(cmd);
|
|
217
403
|
}
|
|
218
404
|
case "feishu_list_chats": {
|
|
@@ -245,6 +431,153 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
245
431
|
if (count) cmd.push("--page-size", String(count));
|
|
246
432
|
return await run(cmd);
|
|
247
433
|
}
|
|
434
|
+
|
|
435
|
+
// ---- Docs / Wiki / Drive ----
|
|
436
|
+
case "feishu_search_docs": {
|
|
437
|
+
const { query, count } = args;
|
|
438
|
+
const cmd = ["docs", "+search", "--as", "user", "--query", query];
|
|
439
|
+
if (count) cmd.push("--page-size", String(count));
|
|
440
|
+
return await run(cmd);
|
|
441
|
+
}
|
|
442
|
+
case "feishu_doc_fetch": {
|
|
443
|
+
const { doc } = args;
|
|
444
|
+
return await run(["docs", "+fetch", "--as", "user", "--doc", doc]);
|
|
445
|
+
}
|
|
446
|
+
case "feishu_doc_create": {
|
|
447
|
+
const { title, markdown, folder_token } = args;
|
|
448
|
+
const cmd = ["docs", "+create", "--as", "user", "--title", title];
|
|
449
|
+
if (markdown) cmd.push("--markdown", markdown);
|
|
450
|
+
if (folder_token) cmd.push("--folder-token", folder_token);
|
|
451
|
+
return await run(cmd);
|
|
452
|
+
}
|
|
453
|
+
case "feishu_doc_update": {
|
|
454
|
+
const { doc, markdown, mode, new_title } = args;
|
|
455
|
+
const cmd = [
|
|
456
|
+
"docs",
|
|
457
|
+
"+update",
|
|
458
|
+
"--as",
|
|
459
|
+
"user",
|
|
460
|
+
"--doc",
|
|
461
|
+
doc,
|
|
462
|
+
"--markdown",
|
|
463
|
+
markdown,
|
|
464
|
+
"--mode",
|
|
465
|
+
mode || "append",
|
|
466
|
+
];
|
|
467
|
+
if (new_title) cmd.push("--new-title", new_title);
|
|
468
|
+
return await run(cmd);
|
|
469
|
+
}
|
|
470
|
+
case "feishu_drive_search": {
|
|
471
|
+
const { query, doc_types, mine } = args;
|
|
472
|
+
const cmd = ["drive", "+search", "--as", "user"];
|
|
473
|
+
if (query) cmd.push("--query", query);
|
|
474
|
+
if (doc_types) cmd.push("--doc-types", doc_types);
|
|
475
|
+
if (mine === true) cmd.push("--mine");
|
|
476
|
+
return await run(cmd);
|
|
477
|
+
}
|
|
478
|
+
case "feishu_wiki_node_list": {
|
|
479
|
+
const { space_id, parent_node_token } = args;
|
|
480
|
+
const cmd = [
|
|
481
|
+
"wiki",
|
|
482
|
+
"+node-list",
|
|
483
|
+
"--as",
|
|
484
|
+
"user",
|
|
485
|
+
"--space-id",
|
|
486
|
+
space_id,
|
|
487
|
+
];
|
|
488
|
+
if (parent_node_token)
|
|
489
|
+
cmd.push("--parent-node-token", parent_node_token);
|
|
490
|
+
return await run(cmd);
|
|
491
|
+
}
|
|
492
|
+
case "feishu_wiki_node_get": {
|
|
493
|
+
const { node_token } = args;
|
|
494
|
+
return await run([
|
|
495
|
+
"wiki",
|
|
496
|
+
"+node-get",
|
|
497
|
+
"--as",
|
|
498
|
+
"user",
|
|
499
|
+
"--node-token",
|
|
500
|
+
node_token,
|
|
501
|
+
]);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// ---- Bitable / Base ----
|
|
505
|
+
case "feishu_base_table_list": {
|
|
506
|
+
const { base_token } = args;
|
|
507
|
+
return await run([
|
|
508
|
+
"base",
|
|
509
|
+
"+table-list",
|
|
510
|
+
"--as",
|
|
511
|
+
"user",
|
|
512
|
+
"--base-token",
|
|
513
|
+
base_token,
|
|
514
|
+
]);
|
|
515
|
+
}
|
|
516
|
+
case "feishu_base_field_list": {
|
|
517
|
+
const { base_token, table_id } = args;
|
|
518
|
+
return await run([
|
|
519
|
+
"base",
|
|
520
|
+
"+field-list",
|
|
521
|
+
"--as",
|
|
522
|
+
"user",
|
|
523
|
+
"--base-token",
|
|
524
|
+
base_token,
|
|
525
|
+
"--table-id",
|
|
526
|
+
table_id,
|
|
527
|
+
]);
|
|
528
|
+
}
|
|
529
|
+
case "feishu_base_record_list": {
|
|
530
|
+
const { base_token, table_id, limit, filter_json, sort_json } = args;
|
|
531
|
+
const cmd = [
|
|
532
|
+
"base",
|
|
533
|
+
"+record-list",
|
|
534
|
+
"--as",
|
|
535
|
+
"user",
|
|
536
|
+
"--base-token",
|
|
537
|
+
base_token,
|
|
538
|
+
"--table-id",
|
|
539
|
+
table_id,
|
|
540
|
+
];
|
|
541
|
+
if (limit) cmd.push("--limit", String(limit));
|
|
542
|
+
if (filter_json) cmd.push("--filter-json", filter_json);
|
|
543
|
+
if (sort_json) cmd.push("--sort-json", sort_json);
|
|
544
|
+
return await run(cmd);
|
|
545
|
+
}
|
|
546
|
+
case "feishu_base_record_search": {
|
|
547
|
+
const { base_token, table_id, limit } = args;
|
|
548
|
+
const cmd = [
|
|
549
|
+
"base",
|
|
550
|
+
"+record-search",
|
|
551
|
+
"--as",
|
|
552
|
+
"user",
|
|
553
|
+
"--base-token",
|
|
554
|
+
base_token,
|
|
555
|
+
"--table-id",
|
|
556
|
+
table_id,
|
|
557
|
+
];
|
|
558
|
+
if (limit) cmd.push("--limit", String(limit));
|
|
559
|
+
return await run(cmd);
|
|
560
|
+
}
|
|
561
|
+
case "feishu_base_record_upsert": {
|
|
562
|
+
const { base_token, table_id, fields_json, record_id } = args;
|
|
563
|
+
const cmd = [
|
|
564
|
+
"base",
|
|
565
|
+
"+record-upsert",
|
|
566
|
+
"--as",
|
|
567
|
+
"user",
|
|
568
|
+
"--base-token",
|
|
569
|
+
base_token,
|
|
570
|
+
"--table-id",
|
|
571
|
+
table_id,
|
|
572
|
+
"--json",
|
|
573
|
+
fields_json,
|
|
574
|
+
"--format",
|
|
575
|
+
"json",
|
|
576
|
+
];
|
|
577
|
+
if (record_id) cmd.push("--record-id", record_id);
|
|
578
|
+
return await run(cmd);
|
|
579
|
+
}
|
|
580
|
+
|
|
248
581
|
default:
|
|
249
582
|
return error(`未知工具: ${name}`);
|
|
250
583
|
}
|