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.
- package/.github/workflows/ci.yml +47 -0
- package/.github/workflows/release.yml +47 -0
- package/.github/workflows/sync-upstream.yml +127 -0
- package/.prettierrc.json +7 -0
- package/README.md +214 -0
- package/dist/core/api-error.d.ts +193 -0
- package/dist/core/api-error.d.ts.map +1 -0
- package/dist/core/api-error.js +263 -0
- package/dist/core/api-error.js.map +1 -0
- package/dist/core/auth-errors.d.ts +13 -0
- package/dist/core/auth-errors.d.ts.map +1 -0
- package/dist/core/auth-errors.js +14 -0
- package/dist/core/auth-errors.js.map +1 -0
- package/dist/core/config.d.ts +60 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +115 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/device-flow.d.ts +80 -0
- package/dist/core/device-flow.d.ts.map +1 -0
- package/dist/core/device-flow.js +231 -0
- package/dist/core/device-flow.js.map +1 -0
- package/dist/core/index.d.ts +16 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +16 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/lark-client.d.ts +136 -0
- package/dist/core/lark-client.d.ts.map +1 -0
- package/dist/core/lark-client.js +315 -0
- package/dist/core/lark-client.js.map +1 -0
- package/dist/core/token-store.d.ts +67 -0
- package/dist/core/token-store.d.ts.map +1 -0
- package/dist/core/token-store.js +215 -0
- package/dist/core/token-store.js.map +1 -0
- package/dist/core/types.d.ts +286 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +11 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/uat-client.d.ts +64 -0
- package/dist/core/uat-client.d.ts.map +1 -0
- package/dist/core/uat-client.js +227 -0
- package/dist/core/uat-client.js.map +1 -0
- package/dist/core/version.d.ts +26 -0
- package/dist/core/version.d.ts.map +1 -0
- package/dist/core/version.js +50 -0
- package/dist/core/version.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +116 -0
- package/dist/index.js.map +1 -0
- package/dist/tools/bitable/app.d.ts +20 -0
- package/dist/tools/bitable/app.d.ts.map +1 -0
- package/dist/tools/bitable/app.js +301 -0
- package/dist/tools/bitable/app.js.map +1 -0
- package/dist/tools/bitable/field.d.ts +19 -0
- package/dist/tools/bitable/field.d.ts.map +1 -0
- package/dist/tools/bitable/field.js +315 -0
- package/dist/tools/bitable/field.js.map +1 -0
- package/dist/tools/bitable/index.d.ts +21 -0
- package/dist/tools/bitable/index.d.ts.map +1 -0
- package/dist/tools/bitable/index.js +39 -0
- package/dist/tools/bitable/index.js.map +1 -0
- package/dist/tools/bitable/record.d.ts +22 -0
- package/dist/tools/bitable/record.d.ts.map +1 -0
- package/dist/tools/bitable/record.js +434 -0
- package/dist/tools/bitable/record.js.map +1 -0
- package/dist/tools/bitable/table.d.ts +21 -0
- package/dist/tools/bitable/table.d.ts.map +1 -0
- package/dist/tools/bitable/table.js +361 -0
- package/dist/tools/bitable/table.js.map +1 -0
- package/dist/tools/calendar/calendar.d.ts +18 -0
- package/dist/tools/calendar/calendar.d.ts.map +1 -0
- package/dist/tools/calendar/calendar.js +192 -0
- package/dist/tools/calendar/calendar.js.map +1 -0
- package/dist/tools/calendar/event.d.ts +20 -0
- package/dist/tools/calendar/event.d.ts.map +1 -0
- package/dist/tools/calendar/event.js +465 -0
- package/dist/tools/calendar/event.js.map +1 -0
- package/dist/tools/calendar/index.d.ts +19 -0
- package/dist/tools/calendar/index.d.ts.map +1 -0
- package/dist/tools/calendar/index.js +37 -0
- package/dist/tools/calendar/index.js.map +1 -0
- package/dist/tools/chat/chat.d.ts +11 -0
- package/dist/tools/chat/chat.d.ts.map +1 -0
- package/dist/tools/chat/chat.js +106 -0
- package/dist/tools/chat/chat.js.map +1 -0
- package/dist/tools/chat/index.d.ts +11 -0
- package/dist/tools/chat/index.d.ts.map +1 -0
- package/dist/tools/chat/index.js +20 -0
- package/dist/tools/chat/index.js.map +1 -0
- package/dist/tools/chat/members.d.ts +9 -0
- package/dist/tools/chat/members.d.ts.map +1 -0
- package/dist/tools/chat/members.js +80 -0
- package/dist/tools/chat/members.js.map +1 -0
- package/dist/tools/common/get-user.d.ts +11 -0
- package/dist/tools/common/get-user.d.ts.map +1 -0
- package/dist/tools/common/get-user.js +112 -0
- package/dist/tools/common/get-user.js.map +1 -0
- package/dist/tools/common/index.d.ts +11 -0
- package/dist/tools/common/index.d.ts.map +1 -0
- package/dist/tools/common/index.js +20 -0
- package/dist/tools/common/index.js.map +1 -0
- package/dist/tools/common/search-user.d.ts +9 -0
- package/dist/tools/common/search-user.d.ts.map +1 -0
- package/dist/tools/common/search-user.js +88 -0
- package/dist/tools/common/search-user.js.map +1 -0
- package/dist/tools/doc/create.d.ts +17 -0
- package/dist/tools/doc/create.d.ts.map +1 -0
- package/dist/tools/doc/create.js +159 -0
- package/dist/tools/doc/create.js.map +1 -0
- package/dist/tools/doc/fetch.d.ts +17 -0
- package/dist/tools/doc/fetch.d.ts.map +1 -0
- package/dist/tools/doc/fetch.js +123 -0
- package/dist/tools/doc/fetch.js.map +1 -0
- package/dist/tools/doc/index.d.ts +21 -0
- package/dist/tools/doc/index.d.ts.map +1 -0
- package/dist/tools/doc/index.js +33 -0
- package/dist/tools/doc/index.js.map +1 -0
- package/dist/tools/doc/shared.d.ts +69 -0
- package/dist/tools/doc/shared.d.ts.map +1 -0
- package/dist/tools/doc/shared.js +172 -0
- package/dist/tools/doc/shared.js.map +1 -0
- package/dist/tools/doc/update.d.ts +25 -0
- package/dist/tools/doc/update.d.ts.map +1 -0
- package/dist/tools/doc/update.js +208 -0
- package/dist/tools/doc/update.js.map +1 -0
- package/dist/tools/drive/file.d.ts +13 -0
- package/dist/tools/drive/file.d.ts.map +1 -0
- package/dist/tools/drive/file.js +212 -0
- package/dist/tools/drive/file.js.map +1 -0
- package/dist/tools/drive/index.d.ts +12 -0
- package/dist/tools/drive/index.d.ts.map +1 -0
- package/dist/tools/drive/index.js +25 -0
- package/dist/tools/drive/index.js.map +1 -0
- package/dist/tools/im/format-messages.d.ts +99 -0
- package/dist/tools/im/format-messages.d.ts.map +1 -0
- package/dist/tools/im/format-messages.js +277 -0
- package/dist/tools/im/format-messages.js.map +1 -0
- package/dist/tools/im/helpers.d.ts +53 -0
- package/dist/tools/im/helpers.d.ts.map +1 -0
- package/dist/tools/im/helpers.js +85 -0
- package/dist/tools/im/helpers.js.map +1 -0
- package/dist/tools/im/index.d.ts +25 -0
- package/dist/tools/im/index.d.ts.map +1 -0
- package/dist/tools/im/index.js +44 -0
- package/dist/tools/im/index.js.map +1 -0
- package/dist/tools/im/message-read.d.ts +19 -0
- package/dist/tools/im/message-read.d.ts.map +1 -0
- package/dist/tools/im/message-read.js +526 -0
- package/dist/tools/im/message-read.js.map +1 -0
- package/dist/tools/im/message.d.ts +22 -0
- package/dist/tools/im/message.d.ts.map +1 -0
- package/dist/tools/im/message.js +233 -0
- package/dist/tools/im/message.js.map +1 -0
- package/dist/tools/im/resource.d.ts +19 -0
- package/dist/tools/im/resource.d.ts.map +1 -0
- package/dist/tools/im/resource.js +185 -0
- package/dist/tools/im/resource.js.map +1 -0
- package/dist/tools/im/time-utils.d.ts +70 -0
- package/dist/tools/im/time-utils.d.ts.map +1 -0
- package/dist/tools/im/time-utils.js +277 -0
- package/dist/tools/im/time-utils.js.map +1 -0
- package/dist/tools/index.d.ts +85 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +135 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/oauth.d.ts +15 -0
- package/dist/tools/oauth.d.ts.map +1 -0
- package/dist/tools/oauth.js +379 -0
- package/dist/tools/oauth.js.map +1 -0
- package/dist/tools/search/doc-search.d.ts +9 -0
- package/dist/tools/search/doc-search.d.ts.map +1 -0
- package/dist/tools/search/doc-search.js +219 -0
- package/dist/tools/search/doc-search.js.map +1 -0
- package/dist/tools/search/index.d.ts +11 -0
- package/dist/tools/search/index.d.ts.map +1 -0
- package/dist/tools/search/index.js +18 -0
- package/dist/tools/search/index.js.map +1 -0
- package/dist/tools/sheets/index.d.ts +11 -0
- package/dist/tools/sheets/index.d.ts.map +1 -0
- package/dist/tools/sheets/index.js +18 -0
- package/dist/tools/sheets/index.js.map +1 -0
- package/dist/tools/sheets/sheet.d.ts +11 -0
- package/dist/tools/sheets/sheet.d.ts.map +1 -0
- package/dist/tools/sheets/sheet.js +332 -0
- package/dist/tools/sheets/sheet.js.map +1 -0
- package/dist/tools/task/index.d.ts +12 -0
- package/dist/tools/task/index.d.ts.map +1 -0
- package/dist/tools/task/index.js +30 -0
- package/dist/tools/task/index.js.map +1 -0
- package/dist/tools/task/task.d.ts +13 -0
- package/dist/tools/task/task.d.ts.map +1 -0
- package/dist/tools/task/task.js +225 -0
- package/dist/tools/task/task.js.map +1 -0
- package/dist/tools/task/tasklist.d.ts +13 -0
- package/dist/tools/task/tasklist.d.ts.map +1 -0
- package/dist/tools/task/tasklist.js +206 -0
- package/dist/tools/task/tasklist.js.map +1 -0
- package/dist/tools/wiki/index.d.ts +11 -0
- package/dist/tools/wiki/index.d.ts.map +1 -0
- package/dist/tools/wiki/index.js +20 -0
- package/dist/tools/wiki/index.js.map +1 -0
- package/dist/tools/wiki/node.d.ts +11 -0
- package/dist/tools/wiki/node.d.ts.map +1 -0
- package/dist/tools/wiki/node.js +112 -0
- package/dist/tools/wiki/node.js.map +1 -0
- package/dist/tools/wiki/space.d.ts +11 -0
- package/dist/tools/wiki/space.d.ts.map +1 -0
- package/dist/tools/wiki/space.js +125 -0
- package/dist/tools/wiki/space.js.map +1 -0
- package/dist/utils/index.d.ts +8 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +8 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/logger.d.ts +36 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +101 -0
- package/dist/utils/logger.js.map +1 -0
- package/eslint.config.js +13 -0
- package/package.json +54 -0
- package/skills/feishu-bitable/SKILL.md +248 -0
- package/skills/feishu-bitable/references/examples.md +813 -0
- package/skills/feishu-bitable/references/field-properties.md +763 -0
- package/skills/feishu-bitable/references/record-values.md +911 -0
- package/skills/feishu-calendar/SKILL.md +244 -0
- package/skills/feishu-channel-rules/SKILL.md +18 -0
- package/skills/feishu-channel-rules/references/markdown-syntax.md +138 -0
- package/skills/feishu-create-doc/SKILL.md +719 -0
- package/skills/feishu-fetch-doc/SKILL.md +93 -0
- package/skills/feishu-im-read/SKILL.md +163 -0
- package/skills/feishu-task/SKILL.md +293 -0
- package/skills/feishu-troubleshoot/SKILL.md +70 -0
- package/skills/feishu-update-doc/SKILL.md +285 -0
- package/src/core/api-error.ts +342 -0
- package/src/core/auth-errors.ts +27 -0
- package/src/core/config.ts +134 -0
- package/src/core/device-flow.ts +314 -0
- package/src/core/index.ts +16 -0
- package/src/core/lark-client.ts +391 -0
- package/src/core/token-store.ts +249 -0
- package/src/core/types.ts +302 -0
- package/src/core/uat-client.ts +298 -0
- package/src/core/version.ts +53 -0
- package/src/index.ts +138 -0
- package/src/tools/bitable/app.ts +390 -0
- package/src/tools/bitable/field.ts +406 -0
- package/src/tools/bitable/index.ts +43 -0
- package/src/tools/bitable/record.ts +559 -0
- package/src/tools/bitable/table.ts +472 -0
- package/src/tools/calendar/calendar.ts +254 -0
- package/src/tools/calendar/event.ts +606 -0
- package/src/tools/calendar/index.ts +41 -0
- package/src/tools/chat/chat.ts +127 -0
- package/src/tools/chat/index.ts +24 -0
- package/src/tools/chat/members.ts +93 -0
- package/src/tools/common/get-user.ts +127 -0
- package/src/tools/common/index.ts +24 -0
- package/src/tools/common/search-user.ts +99 -0
- package/src/tools/doc/create.ts +184 -0
- package/src/tools/doc/fetch.ts +149 -0
- package/src/tools/doc/index.ts +38 -0
- package/src/tools/doc/shared.ts +228 -0
- package/src/tools/doc/update.ts +240 -0
- package/src/tools/drive/file.ts +265 -0
- package/src/tools/drive/index.ts +29 -0
- package/src/tools/im/format-messages.ts +391 -0
- package/src/tools/im/helpers.ts +109 -0
- package/src/tools/im/index.ts +49 -0
- package/src/tools/im/message-read.ts +676 -0
- package/src/tools/im/message.ts +303 -0
- package/src/tools/im/resource.ts +225 -0
- package/src/tools/im/time-utils.ts +347 -0
- package/src/tools/index.ts +205 -0
- package/src/tools/oauth.ts +460 -0
- package/src/tools/search/doc-search.ts +250 -0
- package/src/tools/search/index.ts +22 -0
- package/src/tools/sheets/index.ts +22 -0
- package/src/tools/sheets/sheet.ts +382 -0
- package/src/tools/task/index.ts +34 -0
- package/src/tools/task/task.ts +265 -0
- package/src/tools/task/tasklist.ts +262 -0
- package/src/tools/wiki/index.ts +24 -0
- package/src/tools/wiki/node.ts +131 -0
- package/src/tools/wiki/space.ts +152 -0
- package/src/utils/index.ts +8 -0
- package/src/utils/logger.ts +132 -0
- package/tests/core/config.test.ts +238 -0
- package/tests/core/device-flow.test.ts +490 -0
- package/tests/core/lark-client.test.ts +378 -0
- package/tests/core/token-store.test.ts +438 -0
- package/tests/index.test.ts +360 -0
- package/tests/tools/doc/create.test.ts +224 -0
- package/tests/tools/doc/fetch.test.ts +182 -0
- package/tests/tools/doc/shared.test.ts +183 -0
- package/tests/tools/doc/update.test.ts +330 -0
- package/tests/tools/im/format-messages.test.ts +184 -0
- package/tests/tools/im/time-utils.test.ts +178 -0
- package/tests/utils/logger.test.ts +140 -0
- 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
|
+
}
|