cc-lark 0.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.
Files changed (298) hide show
  1. package/.github/workflows/ci.yml +47 -0
  2. package/.github/workflows/release.yml +47 -0
  3. package/.github/workflows/sync-upstream.yml +127 -0
  4. package/.prettierrc.json +7 -0
  5. package/README.md +214 -0
  6. package/dist/core/api-error.d.ts +193 -0
  7. package/dist/core/api-error.d.ts.map +1 -0
  8. package/dist/core/api-error.js +263 -0
  9. package/dist/core/api-error.js.map +1 -0
  10. package/dist/core/auth-errors.d.ts +13 -0
  11. package/dist/core/auth-errors.d.ts.map +1 -0
  12. package/dist/core/auth-errors.js +14 -0
  13. package/dist/core/auth-errors.js.map +1 -0
  14. package/dist/core/config.d.ts +60 -0
  15. package/dist/core/config.d.ts.map +1 -0
  16. package/dist/core/config.js +115 -0
  17. package/dist/core/config.js.map +1 -0
  18. package/dist/core/device-flow.d.ts +80 -0
  19. package/dist/core/device-flow.d.ts.map +1 -0
  20. package/dist/core/device-flow.js +231 -0
  21. package/dist/core/device-flow.js.map +1 -0
  22. package/dist/core/index.d.ts +16 -0
  23. package/dist/core/index.d.ts.map +1 -0
  24. package/dist/core/index.js +16 -0
  25. package/dist/core/index.js.map +1 -0
  26. package/dist/core/lark-client.d.ts +136 -0
  27. package/dist/core/lark-client.d.ts.map +1 -0
  28. package/dist/core/lark-client.js +315 -0
  29. package/dist/core/lark-client.js.map +1 -0
  30. package/dist/core/token-store.d.ts +67 -0
  31. package/dist/core/token-store.d.ts.map +1 -0
  32. package/dist/core/token-store.js +215 -0
  33. package/dist/core/token-store.js.map +1 -0
  34. package/dist/core/types.d.ts +286 -0
  35. package/dist/core/types.d.ts.map +1 -0
  36. package/dist/core/types.js +11 -0
  37. package/dist/core/types.js.map +1 -0
  38. package/dist/core/uat-client.d.ts +64 -0
  39. package/dist/core/uat-client.d.ts.map +1 -0
  40. package/dist/core/uat-client.js +227 -0
  41. package/dist/core/uat-client.js.map +1 -0
  42. package/dist/core/version.d.ts +26 -0
  43. package/dist/core/version.d.ts.map +1 -0
  44. package/dist/core/version.js +50 -0
  45. package/dist/core/version.js.map +1 -0
  46. package/dist/index.d.ts +12 -0
  47. package/dist/index.d.ts.map +1 -0
  48. package/dist/index.js +116 -0
  49. package/dist/index.js.map +1 -0
  50. package/dist/tools/bitable/app.d.ts +20 -0
  51. package/dist/tools/bitable/app.d.ts.map +1 -0
  52. package/dist/tools/bitable/app.js +301 -0
  53. package/dist/tools/bitable/app.js.map +1 -0
  54. package/dist/tools/bitable/field.d.ts +19 -0
  55. package/dist/tools/bitable/field.d.ts.map +1 -0
  56. package/dist/tools/bitable/field.js +315 -0
  57. package/dist/tools/bitable/field.js.map +1 -0
  58. package/dist/tools/bitable/index.d.ts +21 -0
  59. package/dist/tools/bitable/index.d.ts.map +1 -0
  60. package/dist/tools/bitable/index.js +39 -0
  61. package/dist/tools/bitable/index.js.map +1 -0
  62. package/dist/tools/bitable/record.d.ts +22 -0
  63. package/dist/tools/bitable/record.d.ts.map +1 -0
  64. package/dist/tools/bitable/record.js +434 -0
  65. package/dist/tools/bitable/record.js.map +1 -0
  66. package/dist/tools/bitable/table.d.ts +21 -0
  67. package/dist/tools/bitable/table.d.ts.map +1 -0
  68. package/dist/tools/bitable/table.js +361 -0
  69. package/dist/tools/bitable/table.js.map +1 -0
  70. package/dist/tools/calendar/calendar.d.ts +18 -0
  71. package/dist/tools/calendar/calendar.d.ts.map +1 -0
  72. package/dist/tools/calendar/calendar.js +192 -0
  73. package/dist/tools/calendar/calendar.js.map +1 -0
  74. package/dist/tools/calendar/event.d.ts +20 -0
  75. package/dist/tools/calendar/event.d.ts.map +1 -0
  76. package/dist/tools/calendar/event.js +465 -0
  77. package/dist/tools/calendar/event.js.map +1 -0
  78. package/dist/tools/calendar/index.d.ts +19 -0
  79. package/dist/tools/calendar/index.d.ts.map +1 -0
  80. package/dist/tools/calendar/index.js +37 -0
  81. package/dist/tools/calendar/index.js.map +1 -0
  82. package/dist/tools/chat/chat.d.ts +11 -0
  83. package/dist/tools/chat/chat.d.ts.map +1 -0
  84. package/dist/tools/chat/chat.js +106 -0
  85. package/dist/tools/chat/chat.js.map +1 -0
  86. package/dist/tools/chat/index.d.ts +11 -0
  87. package/dist/tools/chat/index.d.ts.map +1 -0
  88. package/dist/tools/chat/index.js +20 -0
  89. package/dist/tools/chat/index.js.map +1 -0
  90. package/dist/tools/chat/members.d.ts +9 -0
  91. package/dist/tools/chat/members.d.ts.map +1 -0
  92. package/dist/tools/chat/members.js +80 -0
  93. package/dist/tools/chat/members.js.map +1 -0
  94. package/dist/tools/common/get-user.d.ts +11 -0
  95. package/dist/tools/common/get-user.d.ts.map +1 -0
  96. package/dist/tools/common/get-user.js +112 -0
  97. package/dist/tools/common/get-user.js.map +1 -0
  98. package/dist/tools/common/index.d.ts +11 -0
  99. package/dist/tools/common/index.d.ts.map +1 -0
  100. package/dist/tools/common/index.js +20 -0
  101. package/dist/tools/common/index.js.map +1 -0
  102. package/dist/tools/common/search-user.d.ts +9 -0
  103. package/dist/tools/common/search-user.d.ts.map +1 -0
  104. package/dist/tools/common/search-user.js +88 -0
  105. package/dist/tools/common/search-user.js.map +1 -0
  106. package/dist/tools/doc/create.d.ts +17 -0
  107. package/dist/tools/doc/create.d.ts.map +1 -0
  108. package/dist/tools/doc/create.js +159 -0
  109. package/dist/tools/doc/create.js.map +1 -0
  110. package/dist/tools/doc/fetch.d.ts +17 -0
  111. package/dist/tools/doc/fetch.d.ts.map +1 -0
  112. package/dist/tools/doc/fetch.js +123 -0
  113. package/dist/tools/doc/fetch.js.map +1 -0
  114. package/dist/tools/doc/index.d.ts +21 -0
  115. package/dist/tools/doc/index.d.ts.map +1 -0
  116. package/dist/tools/doc/index.js +33 -0
  117. package/dist/tools/doc/index.js.map +1 -0
  118. package/dist/tools/doc/shared.d.ts +69 -0
  119. package/dist/tools/doc/shared.d.ts.map +1 -0
  120. package/dist/tools/doc/shared.js +172 -0
  121. package/dist/tools/doc/shared.js.map +1 -0
  122. package/dist/tools/doc/update.d.ts +25 -0
  123. package/dist/tools/doc/update.d.ts.map +1 -0
  124. package/dist/tools/doc/update.js +208 -0
  125. package/dist/tools/doc/update.js.map +1 -0
  126. package/dist/tools/drive/file.d.ts +13 -0
  127. package/dist/tools/drive/file.d.ts.map +1 -0
  128. package/dist/tools/drive/file.js +212 -0
  129. package/dist/tools/drive/file.js.map +1 -0
  130. package/dist/tools/drive/index.d.ts +12 -0
  131. package/dist/tools/drive/index.d.ts.map +1 -0
  132. package/dist/tools/drive/index.js +25 -0
  133. package/dist/tools/drive/index.js.map +1 -0
  134. package/dist/tools/im/format-messages.d.ts +99 -0
  135. package/dist/tools/im/format-messages.d.ts.map +1 -0
  136. package/dist/tools/im/format-messages.js +277 -0
  137. package/dist/tools/im/format-messages.js.map +1 -0
  138. package/dist/tools/im/helpers.d.ts +53 -0
  139. package/dist/tools/im/helpers.d.ts.map +1 -0
  140. package/dist/tools/im/helpers.js +85 -0
  141. package/dist/tools/im/helpers.js.map +1 -0
  142. package/dist/tools/im/index.d.ts +25 -0
  143. package/dist/tools/im/index.d.ts.map +1 -0
  144. package/dist/tools/im/index.js +44 -0
  145. package/dist/tools/im/index.js.map +1 -0
  146. package/dist/tools/im/message-read.d.ts +19 -0
  147. package/dist/tools/im/message-read.d.ts.map +1 -0
  148. package/dist/tools/im/message-read.js +526 -0
  149. package/dist/tools/im/message-read.js.map +1 -0
  150. package/dist/tools/im/message.d.ts +22 -0
  151. package/dist/tools/im/message.d.ts.map +1 -0
  152. package/dist/tools/im/message.js +233 -0
  153. package/dist/tools/im/message.js.map +1 -0
  154. package/dist/tools/im/resource.d.ts +19 -0
  155. package/dist/tools/im/resource.d.ts.map +1 -0
  156. package/dist/tools/im/resource.js +185 -0
  157. package/dist/tools/im/resource.js.map +1 -0
  158. package/dist/tools/im/time-utils.d.ts +70 -0
  159. package/dist/tools/im/time-utils.d.ts.map +1 -0
  160. package/dist/tools/im/time-utils.js +277 -0
  161. package/dist/tools/im/time-utils.js.map +1 -0
  162. package/dist/tools/index.d.ts +85 -0
  163. package/dist/tools/index.d.ts.map +1 -0
  164. package/dist/tools/index.js +135 -0
  165. package/dist/tools/index.js.map +1 -0
  166. package/dist/tools/oauth.d.ts +15 -0
  167. package/dist/tools/oauth.d.ts.map +1 -0
  168. package/dist/tools/oauth.js +379 -0
  169. package/dist/tools/oauth.js.map +1 -0
  170. package/dist/tools/search/doc-search.d.ts +9 -0
  171. package/dist/tools/search/doc-search.d.ts.map +1 -0
  172. package/dist/tools/search/doc-search.js +219 -0
  173. package/dist/tools/search/doc-search.js.map +1 -0
  174. package/dist/tools/search/index.d.ts +11 -0
  175. package/dist/tools/search/index.d.ts.map +1 -0
  176. package/dist/tools/search/index.js +18 -0
  177. package/dist/tools/search/index.js.map +1 -0
  178. package/dist/tools/sheets/index.d.ts +11 -0
  179. package/dist/tools/sheets/index.d.ts.map +1 -0
  180. package/dist/tools/sheets/index.js +18 -0
  181. package/dist/tools/sheets/index.js.map +1 -0
  182. package/dist/tools/sheets/sheet.d.ts +11 -0
  183. package/dist/tools/sheets/sheet.d.ts.map +1 -0
  184. package/dist/tools/sheets/sheet.js +332 -0
  185. package/dist/tools/sheets/sheet.js.map +1 -0
  186. package/dist/tools/task/index.d.ts +12 -0
  187. package/dist/tools/task/index.d.ts.map +1 -0
  188. package/dist/tools/task/index.js +30 -0
  189. package/dist/tools/task/index.js.map +1 -0
  190. package/dist/tools/task/task.d.ts +13 -0
  191. package/dist/tools/task/task.d.ts.map +1 -0
  192. package/dist/tools/task/task.js +225 -0
  193. package/dist/tools/task/task.js.map +1 -0
  194. package/dist/tools/task/tasklist.d.ts +13 -0
  195. package/dist/tools/task/tasklist.d.ts.map +1 -0
  196. package/dist/tools/task/tasklist.js +206 -0
  197. package/dist/tools/task/tasklist.js.map +1 -0
  198. package/dist/tools/wiki/index.d.ts +11 -0
  199. package/dist/tools/wiki/index.d.ts.map +1 -0
  200. package/dist/tools/wiki/index.js +20 -0
  201. package/dist/tools/wiki/index.js.map +1 -0
  202. package/dist/tools/wiki/node.d.ts +11 -0
  203. package/dist/tools/wiki/node.d.ts.map +1 -0
  204. package/dist/tools/wiki/node.js +112 -0
  205. package/dist/tools/wiki/node.js.map +1 -0
  206. package/dist/tools/wiki/space.d.ts +11 -0
  207. package/dist/tools/wiki/space.d.ts.map +1 -0
  208. package/dist/tools/wiki/space.js +125 -0
  209. package/dist/tools/wiki/space.js.map +1 -0
  210. package/dist/utils/index.d.ts +8 -0
  211. package/dist/utils/index.d.ts.map +1 -0
  212. package/dist/utils/index.js +8 -0
  213. package/dist/utils/index.js.map +1 -0
  214. package/dist/utils/logger.d.ts +36 -0
  215. package/dist/utils/logger.d.ts.map +1 -0
  216. package/dist/utils/logger.js +101 -0
  217. package/dist/utils/logger.js.map +1 -0
  218. package/eslint.config.js +13 -0
  219. package/package.json +54 -0
  220. package/skills/feishu-bitable/SKILL.md +248 -0
  221. package/skills/feishu-bitable/references/examples.md +813 -0
  222. package/skills/feishu-bitable/references/field-properties.md +763 -0
  223. package/skills/feishu-bitable/references/record-values.md +911 -0
  224. package/skills/feishu-calendar/SKILL.md +244 -0
  225. package/skills/feishu-channel-rules/SKILL.md +18 -0
  226. package/skills/feishu-channel-rules/references/markdown-syntax.md +138 -0
  227. package/skills/feishu-create-doc/SKILL.md +719 -0
  228. package/skills/feishu-fetch-doc/SKILL.md +93 -0
  229. package/skills/feishu-im-read/SKILL.md +163 -0
  230. package/skills/feishu-task/SKILL.md +293 -0
  231. package/skills/feishu-troubleshoot/SKILL.md +70 -0
  232. package/skills/feishu-update-doc/SKILL.md +285 -0
  233. package/src/core/api-error.ts +342 -0
  234. package/src/core/auth-errors.ts +27 -0
  235. package/src/core/config.ts +134 -0
  236. package/src/core/device-flow.ts +314 -0
  237. package/src/core/index.ts +16 -0
  238. package/src/core/lark-client.ts +391 -0
  239. package/src/core/token-store.ts +249 -0
  240. package/src/core/types.ts +302 -0
  241. package/src/core/uat-client.ts +298 -0
  242. package/src/core/version.ts +53 -0
  243. package/src/index.ts +138 -0
  244. package/src/tools/bitable/app.ts +390 -0
  245. package/src/tools/bitable/field.ts +406 -0
  246. package/src/tools/bitable/index.ts +43 -0
  247. package/src/tools/bitable/record.ts +559 -0
  248. package/src/tools/bitable/table.ts +472 -0
  249. package/src/tools/calendar/calendar.ts +254 -0
  250. package/src/tools/calendar/event.ts +606 -0
  251. package/src/tools/calendar/index.ts +41 -0
  252. package/src/tools/chat/chat.ts +127 -0
  253. package/src/tools/chat/index.ts +24 -0
  254. package/src/tools/chat/members.ts +93 -0
  255. package/src/tools/common/get-user.ts +127 -0
  256. package/src/tools/common/index.ts +24 -0
  257. package/src/tools/common/search-user.ts +99 -0
  258. package/src/tools/doc/create.ts +184 -0
  259. package/src/tools/doc/fetch.ts +149 -0
  260. package/src/tools/doc/index.ts +38 -0
  261. package/src/tools/doc/shared.ts +228 -0
  262. package/src/tools/doc/update.ts +240 -0
  263. package/src/tools/drive/file.ts +265 -0
  264. package/src/tools/drive/index.ts +29 -0
  265. package/src/tools/im/format-messages.ts +391 -0
  266. package/src/tools/im/helpers.ts +109 -0
  267. package/src/tools/im/index.ts +49 -0
  268. package/src/tools/im/message-read.ts +676 -0
  269. package/src/tools/im/message.ts +303 -0
  270. package/src/tools/im/resource.ts +225 -0
  271. package/src/tools/im/time-utils.ts +347 -0
  272. package/src/tools/index.ts +205 -0
  273. package/src/tools/oauth.ts +460 -0
  274. package/src/tools/search/doc-search.ts +250 -0
  275. package/src/tools/search/index.ts +22 -0
  276. package/src/tools/sheets/index.ts +22 -0
  277. package/src/tools/sheets/sheet.ts +382 -0
  278. package/src/tools/task/index.ts +34 -0
  279. package/src/tools/task/task.ts +265 -0
  280. package/src/tools/task/tasklist.ts +262 -0
  281. package/src/tools/wiki/index.ts +24 -0
  282. package/src/tools/wiki/node.ts +131 -0
  283. package/src/tools/wiki/space.ts +152 -0
  284. package/src/utils/index.ts +8 -0
  285. package/src/utils/logger.ts +132 -0
  286. package/tests/core/config.test.ts +238 -0
  287. package/tests/core/device-flow.test.ts +490 -0
  288. package/tests/core/lark-client.test.ts +378 -0
  289. package/tests/core/token-store.test.ts +438 -0
  290. package/tests/index.test.ts +360 -0
  291. package/tests/tools/doc/create.test.ts +224 -0
  292. package/tests/tools/doc/fetch.test.ts +182 -0
  293. package/tests/tools/doc/shared.test.ts +183 -0
  294. package/tests/tools/doc/update.test.ts +330 -0
  295. package/tests/tools/im/format-messages.test.ts +184 -0
  296. package/tests/tools/im/time-utils.test.ts +178 -0
  297. package/tests/utils/logger.test.ts +140 -0
  298. package/tsconfig.json +20 -0
package/src/index.ts ADDED
@@ -0,0 +1,138 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
4
+ * SPDX-License-Identifier: MIT
5
+ *
6
+ * MCP Server entry point for cc-lark.
7
+ *
8
+ * This is the main executable that starts the MCP Server and registers
9
+ * all Feishu/Lark tools for Claude Code.
10
+ */
11
+
12
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
13
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
14
+ import { z } from 'zod';
15
+ import { logger } from './utils/logger.js';
16
+ import { getPackageVersion } from './core/version.js';
17
+ import { loadConfig, validateConfig } from './core/config.js';
18
+ import { LarkClient } from './core/lark-client.js';
19
+ import { registerAllTools } from './tools/index.js';
20
+
21
+ const log = logger('server');
22
+
23
+ // ---------------------------------------------------------------------------
24
+ // Server initialization
25
+ // ---------------------------------------------------------------------------
26
+
27
+ /**
28
+ * Create and configure the MCP Server instance.
29
+ */
30
+ function createServer(): McpServer {
31
+ const version = getPackageVersion();
32
+ const server = new McpServer(
33
+ {
34
+ name: 'cc-lark',
35
+ version,
36
+ },
37
+ {
38
+ capabilities: {
39
+ tools: {},
40
+ },
41
+ }
42
+ );
43
+
44
+ return server;
45
+ }
46
+
47
+ // ---------------------------------------------------------------------------
48
+ // Main entry point
49
+ // ---------------------------------------------------------------------------
50
+
51
+ async function main(): Promise<void> {
52
+ log.info('Starting cc-lark MCP Server...', { version: getPackageVersion() });
53
+
54
+ // Load and validate configuration
55
+ const config = loadConfig();
56
+ const validation = validateConfig(config);
57
+
58
+ if (!validation.valid) {
59
+ log.error('Invalid configuration', { errors: validation.errors });
60
+ // Continue anyway - tools can report configuration errors when called
61
+ } else {
62
+ log.info('Configuration loaded', {
63
+ appId: config.appId,
64
+ brand: config.brand,
65
+ hasUserAccessToken: !!config.userAccessToken,
66
+ });
67
+ }
68
+
69
+ // Initialize LarkClient from environment (singleton)
70
+ let larkClient: LarkClient | null = null;
71
+ if (validation.valid) {
72
+ try {
73
+ larkClient = LarkClient.getInstance();
74
+ log.info('LarkClient initialized', { appId: larkClient.appId, brand: larkClient.brand });
75
+ } catch (err) {
76
+ log.error('Failed to initialize LarkClient', { error: err instanceof Error ? err.message : String(err) });
77
+ }
78
+ }
79
+
80
+ // Create MCP Server
81
+ const server = createServer();
82
+
83
+ // Register all tools
84
+ registerAllTools(server, larkClient, config);
85
+
86
+ log.info('Tools registered');
87
+
88
+ // Create stdio transport
89
+ const transport = new StdioServerTransport();
90
+
91
+ // Handle graceful shutdown
92
+ let isShuttingDown = false;
93
+
94
+ const shutdown = async (signal: string): Promise<void> => {
95
+ if (isShuttingDown) return;
96
+ isShuttingDown = true;
97
+
98
+ log.info(`Received ${signal}, shutting down...`);
99
+
100
+ try {
101
+ await server.close();
102
+ log.info('Server closed');
103
+ } catch (err) {
104
+ log.error('Error during shutdown', { error: err instanceof Error ? err.message : String(err) });
105
+ }
106
+
107
+ process.exit(0);
108
+ };
109
+
110
+ process.on('SIGINT', () => shutdown('SIGINT'));
111
+ process.on('SIGTERM', () => shutdown('SIGTERM'));
112
+
113
+ // Handle uncaught errors
114
+ process.on('uncaughtException', (err) => {
115
+ log.error('Uncaught exception', { error: err.message, stack: err.stack });
116
+ process.exit(1);
117
+ });
118
+
119
+ process.on('unhandledRejection', (reason) => {
120
+ log.error('Unhandled rejection', { reason: String(reason) });
121
+ process.exit(1);
122
+ });
123
+
124
+ // Connect to transport
125
+ try {
126
+ await server.connect(transport);
127
+ log.info('Server connected to stdio transport');
128
+ } catch (err) {
129
+ log.error('Failed to connect server', { error: err instanceof Error ? err.message : String(err) });
130
+ process.exit(1);
131
+ }
132
+ }
133
+
134
+ // Run the server
135
+ main().catch((err) => {
136
+ log.error('Server startup failed', { error: err instanceof Error ? err.message : String(err) });
137
+ process.exit(1);
138
+ });
@@ -0,0 +1,390 @@
1
+ /**
2
+ * Copyright (c) 2026 ByteDance Ltd. and/or its affiliates
3
+ * SPDX-License-Identifier: MIT
4
+ *
5
+ * feishu_bitable_app tool - Manage Feishu Bitable apps (multidimensional tables).
6
+ *
7
+ * Actions: create, get, list, patch, copy
8
+ *
9
+ * Uses the Feishu Bitable v1 API:
10
+ * - create: POST /open-apis/bitable/v1/apps
11
+ * - get: GET /open-apis/bitable/v1/apps/:app_token
12
+ * - list: GET /open-apis/drive/v1/files (filtered by type=bitable)
13
+ * - patch: PATCH /open-apis/bitable/v1/apps/:app_token
14
+ * - copy: POST /open-apis/bitable/v1/apps/:app_token/copy
15
+ *
16
+ * Adapted from openclaw-lark for MCP Server architecture.
17
+ */
18
+
19
+ import { z } from 'zod';
20
+ import type { ToolRegistry } from '../index.js';
21
+ import { LarkClient } from '../../core/lark-client.js';
22
+ import { getValidAccessToken, NeedAuthorizationError } from '../../core/uat-client.js';
23
+ import { assertLarkOk } from '../../core/api-error.js';
24
+ import { json, jsonError, type ToolResult } from '../im/helpers.js';
25
+ import { logger } from '../../utils/logger.js';
26
+
27
+ const log = logger('tools:bitable:app');
28
+
29
+ // ---------------------------------------------------------------------------
30
+ // Input schemas
31
+ // ---------------------------------------------------------------------------
32
+
33
+ const createActionSchema = {
34
+ action: z.literal('create').describe('Create a new Bitable app'),
35
+ name: z.string().describe('Name of the Bitable app'),
36
+ folder_token: z.string().optional().describe('Folder token (optional, defaults to My Space)'),
37
+ };
38
+
39
+ const getActionSchema = {
40
+ action: z.literal('get').describe('Get Bitable app metadata'),
41
+ app_token: z.string().describe('Bitable app token'),
42
+ };
43
+
44
+ const listActionSchema = {
45
+ action: z.literal('list').describe('List Bitable apps in a folder'),
46
+ folder_token: z.string().optional().describe('Folder token (optional, defaults to My Space)'),
47
+ page_size: z.number().min(1).max(200).optional().describe('Page size (default 50)'),
48
+ page_token: z.string().optional().describe('Pagination token'),
49
+ };
50
+
51
+ const patchActionSchema = {
52
+ action: z.literal('patch').describe('Update Bitable app metadata'),
53
+ app_token: z.string().describe('Bitable app token'),
54
+ name: z.string().optional().describe('New name'),
55
+ is_advanced: z.boolean().optional().describe('Enable advanced permissions'),
56
+ };
57
+
58
+ const copyActionSchema = {
59
+ action: z.literal('copy').describe('Copy a Bitable app'),
60
+ app_token: z.string().describe('Source Bitable app token'),
61
+ name: z.string().describe('Name for the copy'),
62
+ folder_token: z.string().optional().describe('Target folder token'),
63
+ };
64
+
65
+ // ---------------------------------------------------------------------------
66
+ // Tool registration
67
+ // ---------------------------------------------------------------------------
68
+
69
+ export function registerBitableAppTool(registry: ToolRegistry): void {
70
+ // Register create action
71
+ registry.register({
72
+ name: 'feishu_bitable_app_create',
73
+ description: [
74
+ 'Create a new Feishu Bitable (multidimensional table).',
75
+ '',
76
+ 'Parameters:',
77
+ '- name: Name of the Bitable',
78
+ '- folder_token: Optional folder token (defaults to My Space)',
79
+ '',
80
+ 'Returns the created Bitable app token and info.',
81
+ 'Requires OAuth authorization.',
82
+ ].join('\n'),
83
+ inputSchema: createActionSchema,
84
+ handler: async (args, context) => handleCreate(args, context),
85
+ });
86
+
87
+ // Register get action
88
+ registry.register({
89
+ name: 'feishu_bitable_app_get',
90
+ description: [
91
+ 'Get Feishu Bitable app metadata.',
92
+ '',
93
+ 'Parameters:',
94
+ '- app_token: Bitable app token',
95
+ '',
96
+ 'Returns the Bitable app info.',
97
+ 'Requires OAuth authorization.',
98
+ ].join('\n'),
99
+ inputSchema: getActionSchema,
100
+ handler: async (args, context) => handleGet(args, context),
101
+ });
102
+
103
+ // Register list action
104
+ registry.register({
105
+ name: 'feishu_bitable_app_list',
106
+ description: [
107
+ 'List Feishu Bitable apps in a folder.',
108
+ '',
109
+ 'Parameters:',
110
+ '- folder_token: Optional folder token (defaults to My Space)',
111
+ '- page_size: Page size (default 50)',
112
+ '- page_token: Pagination token',
113
+ '',
114
+ 'Returns list of Bitable apps.',
115
+ 'Requires OAuth authorization.',
116
+ ].join('\n'),
117
+ inputSchema: listActionSchema,
118
+ handler: async (args, context) => handleList(args, context),
119
+ });
120
+
121
+ // Register patch action
122
+ registry.register({
123
+ name: 'feishu_bitable_app_patch',
124
+ description: [
125
+ 'Update Feishu Bitable app metadata.',
126
+ '',
127
+ 'Parameters:',
128
+ '- app_token: Bitable app token',
129
+ '- name: New name (optional)',
130
+ '- is_advanced: Enable advanced permissions (optional)',
131
+ '',
132
+ 'Returns the updated Bitable app info.',
133
+ 'Requires OAuth authorization.',
134
+ ].join('\n'),
135
+ inputSchema: patchActionSchema,
136
+ handler: async (args, context) => handlePatch(args, context),
137
+ });
138
+
139
+ // Register copy action
140
+ registry.register({
141
+ name: 'feishu_bitable_app_copy',
142
+ description: [
143
+ 'Copy a Feishu Bitable app.',
144
+ '',
145
+ 'Parameters:',
146
+ '- app_token: Source Bitable app token',
147
+ '- name: Name for the copy',
148
+ '- folder_token: Target folder token (optional)',
149
+ '',
150
+ 'Returns the new Bitable app token and info.',
151
+ 'Requires OAuth authorization.',
152
+ ].join('\n'),
153
+ inputSchema: copyActionSchema,
154
+ handler: async (args, context) => handleCopy(args, context),
155
+ });
156
+
157
+ log.debug('feishu_bitable_app tools registered');
158
+ }
159
+
160
+ // ---------------------------------------------------------------------------
161
+ // Handlers
162
+ // ---------------------------------------------------------------------------
163
+
164
+ async function getAccessToken(context: { larkClient: LarkClient | null; config: import('../../core/types.js').FeishuConfig }): Promise<string | ToolResult> {
165
+ const { larkClient, config } = context;
166
+ if (!larkClient) {
167
+ return jsonError('LarkClient not initialized. Check FEISHU_APP_ID and FEISHU_APP_SECRET.');
168
+ }
169
+ const { appId, appSecret, brand } = config;
170
+ if (!appId || !appSecret) {
171
+ return jsonError('Missing FEISHU_APP_ID or FEISHU_APP_SECRET.');
172
+ }
173
+
174
+ const { listStoredTokens } = await import('../../core/token-store.js');
175
+ const tokens = await listStoredTokens(appId);
176
+ if (tokens.length === 0) {
177
+ return jsonError(
178
+ 'No user authorization found. Please use the feishu_oauth tool with action="authorize" to authorize a user first.'
179
+ );
180
+ }
181
+
182
+ const userOpenId = tokens[0].userOpenId;
183
+
184
+ try {
185
+ return await getValidAccessToken({
186
+ userOpenId,
187
+ appId,
188
+ appSecret,
189
+ domain: brand ?? 'feishu',
190
+ });
191
+ } catch (err) {
192
+ if (err instanceof NeedAuthorizationError) {
193
+ return jsonError(
194
+ `User authorization required or expired. Please use feishu_oauth tool with action="authorize" to re-authorize.`,
195
+ { userOpenId }
196
+ );
197
+ }
198
+ throw err;
199
+ }
200
+ }
201
+
202
+ async function handleCreate(
203
+ args: unknown,
204
+ context: { larkClient: LarkClient | null; config: import('../../core/types.js').FeishuConfig }
205
+ ): Promise<ToolResult> {
206
+ const p = args as z.infer<ReturnType<typeof z.object<typeof createActionSchema>>>;
207
+ const { larkClient, config } = context;
208
+
209
+ const accessTokenResult = await getAccessToken(context);
210
+ if (typeof accessTokenResult === 'object' && 'content' in accessTokenResult) {
211
+ return accessTokenResult;
212
+ }
213
+ const accessToken = accessTokenResult;
214
+
215
+ log.info(`create: name=${p.name}, folder_token=${p.folder_token ?? 'my_space'}`);
216
+
217
+ const Lark = await import('@larksuiteoapi/node-sdk');
218
+ const opts = Lark.withUserAccessToken(accessToken);
219
+
220
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
221
+ const data: any = { name: p.name };
222
+ if (p.folder_token) {
223
+ data.folder_token = p.folder_token;
224
+ }
225
+
226
+ const res = await larkClient!.sdk.bitable.app.create({ data }, opts);
227
+ assertLarkOk(res);
228
+
229
+ log.info(`create: created app ${res.data?.app?.app_token}`);
230
+
231
+ return json({
232
+ app: res.data?.app,
233
+ });
234
+ }
235
+
236
+ async function handleGet(
237
+ args: unknown,
238
+ context: { larkClient: LarkClient | null; config: import('../../core/types.js').FeishuConfig }
239
+ ): Promise<ToolResult> {
240
+ const p = args as z.infer<ReturnType<typeof z.object<typeof getActionSchema>>>;
241
+ const { larkClient } = context;
242
+
243
+ const accessTokenResult = await getAccessToken(context);
244
+ if (typeof accessTokenResult === 'object' && 'content' in accessTokenResult) {
245
+ return accessTokenResult;
246
+ }
247
+ const accessToken = accessTokenResult;
248
+
249
+ log.info(`get: app_token=${p.app_token}`);
250
+
251
+ const Lark = await import('@larksuiteoapi/node-sdk');
252
+ const opts = Lark.withUserAccessToken(accessToken);
253
+
254
+ const res = await larkClient!.sdk.bitable.app.get(
255
+ {
256
+ path: { app_token: p.app_token },
257
+ },
258
+ opts
259
+ );
260
+ assertLarkOk(res);
261
+
262
+ log.info(`get: returned app ${p.app_token}`);
263
+
264
+ return json({
265
+ app: res.data?.app,
266
+ });
267
+ }
268
+
269
+ async function handleList(
270
+ args: unknown,
271
+ context: { larkClient: LarkClient | null; config: import('../../core/types.js').FeishuConfig }
272
+ ): Promise<ToolResult> {
273
+ const p = args as z.infer<ReturnType<typeof z.object<typeof listActionSchema>>>;
274
+ const { larkClient } = context;
275
+
276
+ const accessTokenResult = await getAccessToken(context);
277
+ if (typeof accessTokenResult === 'object' && 'content' in accessTokenResult) {
278
+ return accessTokenResult;
279
+ }
280
+ const accessToken = accessTokenResult;
281
+
282
+ log.info(`list: folder_token=${p.folder_token ?? 'my_space'}, page_size=${p.page_size ?? 50}`);
283
+
284
+ const Lark = await import('@larksuiteoapi/node-sdk');
285
+ const opts = Lark.withUserAccessToken(accessToken);
286
+
287
+ const res = await larkClient!.sdk.drive.v1.file.list(
288
+ {
289
+ params: {
290
+ folder_token: p.folder_token || '',
291
+ page_size: p.page_size,
292
+ page_token: p.page_token,
293
+ },
294
+ },
295
+ opts
296
+ );
297
+ assertLarkOk(res);
298
+
299
+ // Filter for bitable type files
300
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
301
+ const data = res.data as any;
302
+ const bitables = data?.files?.filter((f: any) => f.type === 'bitable') || [];
303
+
304
+ log.info(`list: returned ${bitables.length} bitable apps`);
305
+
306
+ return json({
307
+ apps: bitables,
308
+ has_more: data?.has_more ?? false,
309
+ page_token: data?.next_page_token,
310
+ });
311
+ }
312
+
313
+ async function handlePatch(
314
+ args: unknown,
315
+ context: { larkClient: LarkClient | null; config: import('../../core/types.js').FeishuConfig }
316
+ ): Promise<ToolResult> {
317
+ const p = args as z.infer<ReturnType<typeof z.object<typeof patchActionSchema>>>;
318
+ const { larkClient } = context;
319
+
320
+ const accessTokenResult = await getAccessToken(context);
321
+ if (typeof accessTokenResult === 'object' && 'content' in accessTokenResult) {
322
+ return accessTokenResult;
323
+ }
324
+ const accessToken = accessTokenResult;
325
+
326
+ log.info(`patch: app_token=${p.app_token}, name=${p.name}, is_advanced=${p.is_advanced}`);
327
+
328
+ const Lark = await import('@larksuiteoapi/node-sdk');
329
+ const opts = Lark.withUserAccessToken(accessToken);
330
+
331
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
332
+ const updateData: any = {};
333
+ if (p.name !== undefined) updateData.name = p.name;
334
+ if (p.is_advanced !== undefined) updateData.is_advanced = p.is_advanced;
335
+
336
+ const res = await larkClient!.sdk.bitable.app.update(
337
+ {
338
+ path: { app_token: p.app_token },
339
+ data: updateData,
340
+ },
341
+ opts
342
+ );
343
+ assertLarkOk(res);
344
+
345
+ log.info(`patch: updated app ${p.app_token}`);
346
+
347
+ return json({
348
+ app: res.data?.app,
349
+ });
350
+ }
351
+
352
+ async function handleCopy(
353
+ args: unknown,
354
+ context: { larkClient: LarkClient | null; config: import('../../core/types.js').FeishuConfig }
355
+ ): Promise<ToolResult> {
356
+ const p = args as z.infer<ReturnType<typeof z.object<typeof copyActionSchema>>>;
357
+ const { larkClient } = context;
358
+
359
+ const accessTokenResult = await getAccessToken(context);
360
+ if (typeof accessTokenResult === 'object' && 'content' in accessTokenResult) {
361
+ return accessTokenResult;
362
+ }
363
+ const accessToken = accessTokenResult;
364
+
365
+ log.info(`copy: app_token=${p.app_token}, name=${p.name}, folder_token=${p.folder_token ?? 'my_space'}`);
366
+
367
+ const Lark = await import('@larksuiteoapi/node-sdk');
368
+ const opts = Lark.withUserAccessToken(accessToken);
369
+
370
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
371
+ const data: any = { name: p.name };
372
+ if (p.folder_token) {
373
+ data.folder_token = p.folder_token;
374
+ }
375
+
376
+ const res = await larkClient!.sdk.bitable.app.copy(
377
+ {
378
+ path: { app_token: p.app_token },
379
+ data,
380
+ },
381
+ opts
382
+ );
383
+ assertLarkOk(res);
384
+
385
+ log.info(`copy: created copy ${res.data?.app?.app_token}`);
386
+
387
+ return json({
388
+ app: res.data?.app,
389
+ });
390
+ }