feishu-mcp 0.0.6 → 0.0.8

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.
@@ -0,0 +1,119 @@
1
+ import { z } from 'zod';
2
+ // 文档ID或URL参数定义
3
+ export const DocumentIdSchema = z.string().describe('Document ID or URL (required). Supports the following formats:\n' +
4
+ '1. Standard document URL: https://xxx.feishu.cn/docs/xxx or https://xxx.feishu.cn/docx/xxx\n' +
5
+ '2. Direct document ID: e.g., JcKbdlokYoPIe0xDzJ1cduRXnRf\n' +
6
+ 'Note: Wiki links require conversion with convert_feishu_wiki_to_document_id first.');
7
+ // 父块ID参数定义
8
+ export const ParentBlockIdSchema = z.string().describe('Parent block ID (required). Target block ID where content will be added, without any URL prefix. ' +
9
+ 'For page-level (root level) insertion, extract and use only the document ID portion (not the full URL) as parentBlockId. ' +
10
+ 'Obtain existing block IDs using the get_feishu_document_blocks tool.');
11
+ // 块ID参数定义
12
+ export const BlockIdSchema = z.string().describe('Block ID (required). The ID of the specific block to get content from. You can obtain block IDs using the get_feishu_document_blocks tool.');
13
+ // 插入位置索引参数定义
14
+ export const IndexSchema = z.number().describe('Insertion position index (required). Specifies where the block should be inserted. Use 0 to insert at the beginning. ' +
15
+ 'Use get_feishu_document_blocks tool to understand document structure if unsure. ' +
16
+ 'For consecutive insertions, calculate next index as previous index + 1.');
17
+ // 起始插入位置索引参数定义
18
+ export const StartIndexSchema = z.number().describe('Starting insertion position index (required). Specifies where the first block should be inserted. Use 0 to insert at the beginning. ' +
19
+ 'Use get_feishu_document_blocks tool to understand document structure if unsure.');
20
+ // 结束位置索引参数定义
21
+ export const EndIndexSchema = z.number().describe('Ending position index (required). Specifies the end of the range for deletion (exclusive). ' +
22
+ 'For example, to delete blocks 2, 3, and 4, use startIndex=2, endIndex=5. ' +
23
+ 'To delete a single block at position 2, use startIndex=2, endIndex=3.');
24
+ // 文本对齐方式参数定义
25
+ export const AlignSchema = z.number().optional().default(1).describe('Text alignment: 1 for left (default), 2 for center, 3 for right.');
26
+ // 文本对齐方式参数定义(带验证)
27
+ export const AlignSchemaWithValidation = z.number().optional().default(1).refine(val => val === 1 || val === 2 || val === 3, { message: "Alignment must be one of: 1 (left), 2 (center), or 3 (right)" }).describe('Text alignment (optional): 1 for left (default), 2 for center, 3 for right. Only these three values are allowed.');
28
+ // 文本样式属性定义
29
+ export const TextStylePropertiesSchema = {
30
+ bold: z.boolean().optional().describe('Whether to make text bold. Default is false, equivalent to **text** in Markdown.'),
31
+ italic: z.boolean().optional().describe('Whether to make text italic. Default is false, equivalent to *text* in Markdown.'),
32
+ underline: z.boolean().optional().describe('Whether to add underline. Default is false.'),
33
+ strikethrough: z.boolean().optional().describe('Whether to add strikethrough. Default is false, equivalent to ~~text~~ in Markdown.'),
34
+ inline_code: z.boolean().optional().describe('Whether to format as inline code. Default is false, equivalent to `code` in Markdown.'),
35
+ text_color: z.number().optional().refine(val => !val || (val >= 0 && val <= 7), {
36
+ message: "Text color must be between 0 and 7 inclusive"
37
+ }).describe('Text color value. Default is 0 (black). Available values are only: 1 (gray), 2 (brown), 3 (orange), 4 (yellow), 5 (green), 6 (blue), 7 (purple). Values outside this range will cause an error.'),
38
+ background_color: z.number().optional().refine(val => !val || (val >= 1 && val <= 7), {
39
+ message: "Background color must be between 1 and 7 inclusive"
40
+ }).describe('Background color value. Available values are only: 1 (gray), 2 (brown), 3 (orange), 4 (yellow), 5 (green), 6 (blue), 7 (purple). Values outside this range will cause an error.')
41
+ };
42
+ // 文本样式对象定义
43
+ export const TextStyleSchema = z.object(TextStylePropertiesSchema).optional().describe('Text style settings. Explicitly set style properties instead of relying on Markdown syntax conversion.');
44
+ // 文本内容单元定义
45
+ export const TextElementSchema = z.object({
46
+ text: z.string().describe('Text content. Provide plain text without markdown syntax; use style object for formatting.'),
47
+ style: TextStyleSchema
48
+ });
49
+ // 文本内容数组定义
50
+ export const TextElementsArraySchema = z.array(TextElementSchema).describe('Array of text content objects. A block can contain multiple text segments with different styles. Example: [{text:"Hello",style:{bold:true}},{text:" World",style:{italic:true}}]');
51
+ // 代码块语言参数定义
52
+ export const CodeLanguageSchema = z.number().optional().default(1).describe("Programming language code (optional). Common language codes:\n" +
53
+ "1: PlainText; 2: ABAP; 3: Ada; 4: Apache; 5: Apex; 6: Assembly; 7: Bash; 8: CSharp; 9: C++; 10: C; " +
54
+ "11: COBOL; 12: CSS; 13: CoffeeScript; 14: D; 15: Dart; 16: Delphi; 17: Django; 18: Dockerfile; 19: Erlang; 20: Fortran; " +
55
+ "22: Go; 23: Groovy; 24: HTML; 25: HTMLBars; 26: HTTP; 27: Haskell; 28: JSON; 29: Java; 30: JavaScript; " +
56
+ "31: Julia; 32: Kotlin; 33: LateX; 34: Lisp; 36: Lua; 37: MATLAB; 38: Makefile; 39: Markdown; 40: Nginx; " +
57
+ "41: Objective-C; 43: PHP; 44: Perl; 46: PowerShell; 47: Prolog; 48: ProtoBuf; 49: Python; 50: R; " +
58
+ "52: Ruby; 53: Rust; 54: SAS; 55: SCSS; 56: SQL; 57: Scala; 58: Scheme; 60: Shell; 61: Swift; 62: Thrift; " +
59
+ "63: TypeScript; 64: VBScript; 65: Visual Basic; 66: XML; 67: YAML; 68: CMake; 69: Diff; 70: Gherkin; 71: GraphQL. " +
60
+ "Default is 1 (PlainText).");
61
+ // 代码块自动换行参数定义
62
+ export const CodeWrapSchema = z.boolean().optional().default(false).describe('Whether to enable automatic line wrapping. Default is false.');
63
+ // 文本样式段落定义 - 用于批量创建块工具
64
+ export const TextStyleBlockSchema = z.object({
65
+ textStyles: z.array(z.object({
66
+ text: z.string().describe('Text segment content. The actual text to display.'),
67
+ style: TextStyleSchema
68
+ })).describe('Array of text content objects with styles. A block can contain multiple text segments with different styles. Example: [{text:"Hello",style:{bold:true}},{text:" World",style:{italic:true}}]'),
69
+ align: z.number().optional().default(1).describe('Text alignment: 1 for left (default), 2 for center, 3 for right.'),
70
+ });
71
+ // 代码块内容定义 - 用于批量创建块工具
72
+ export const CodeBlockSchema = z.object({
73
+ code: z.string().describe('Code content. The complete code text to display.'),
74
+ language: CodeLanguageSchema,
75
+ wrap: CodeWrapSchema,
76
+ });
77
+ // 标题块内容定义 - 用于批量创建块工具
78
+ export const HeadingBlockSchema = z.object({
79
+ level: z.number().min(1).max(9).describe('Heading level from 1 to 9, where 1 is the largest (h1) and 9 is the smallest (h9).'),
80
+ content: z.string().describe('Heading text content. The actual text of the heading.'),
81
+ align: AlignSchemaWithValidation,
82
+ });
83
+ // 列表块内容定义 - 用于批量创建块工具
84
+ export const ListBlockSchema = z.object({
85
+ content: z.string().describe('List item content. The actual text of the list item.'),
86
+ isOrdered: z.boolean().optional().default(false).describe('Whether this is an ordered (numbered) list item. Default is false (bullet point/unordered).'),
87
+ align: AlignSchemaWithValidation,
88
+ });
89
+ // 块类型枚举 - 用于批量创建块工具
90
+ export const BlockTypeEnum = z.enum(['text', 'code', 'heading', 'list']).describe("Block type (required): 'text', 'code', 'heading', or 'list'. Choose based on the content type you need to create. " +
91
+ "IMPORTANT: For headings use 'heading' (not 'heading1', 'heading2', etc), and specify level within options.");
92
+ // 块配置定义 - 用于批量创建块工具
93
+ export const BlockConfigSchema = z.object({
94
+ blockType: BlockTypeEnum,
95
+ options: z.union([
96
+ z.object({ text: TextStyleBlockSchema }).describe("Text block options. Only used when blockType is 'text'."),
97
+ z.object({ code: CodeBlockSchema }).describe("Code block options. Only used when blockType is 'code'."),
98
+ z.object({ heading: HeadingBlockSchema }).describe("Heading block options. Only used when blockType is 'heading'."),
99
+ z.object({ list: ListBlockSchema }).describe("List block options. Only used when blockType is 'list'."),
100
+ ]).describe('Options for the specific block type. Must provide the corresponding options object based on blockType.'),
101
+ });
102
+ // 媒体ID参数定义
103
+ export const MediaIdSchema = z.string().describe('Media ID (required). The unique identifier for a media resource (image, file, etc.) in Feishu. ' +
104
+ 'Usually obtained from image blocks or file references in documents. ' +
105
+ 'Format is typically like "boxcnrHpsg1QDqXAAAyachabcef".');
106
+ // 额外参数定义 - 用于媒体资源下载
107
+ export const MediaExtraSchema = z.string().optional().describe('Extra parameters for media download (optional). ' +
108
+ 'These parameters are passed directly to the Feishu API and can modify how the media is returned.');
109
+ // 文件夹Token参数定义
110
+ export const FolderTokenSchema = z.string().describe('Folder token (required). The unique identifier for a folder in Feishu. ' +
111
+ 'Format is an alphanumeric string like "FWK2fMleClICfodlHHWc4Mygnhb".');
112
+ // 文件夹名称参数定义
113
+ export const FolderNameSchema = z.string().describe('Folder name (required). The name for the new folder to be created.');
114
+ // 排序方式参数定义
115
+ export const OrderBySchema = z.string().optional().default('EditedTime').describe('Order by field (optional). Specifies how to sort the file list. Available values: ' +
116
+ '"EditedTime" (default), "CreatedTime", "Name". For user-friendly display, case insensitive.');
117
+ // 排序方向参数定义
118
+ export const DirectionSchema = z.string().optional().default('DESC').describe('Sort direction (optional). Specifies the sort order. Available values: ' +
119
+ '"DESC" (default) for descending order, "ASC" for ascending order. Case sensitive.');
@@ -0,0 +1,221 @@
1
+ import { Config } from './config.js';
2
+ import { Logger } from './logger.js';
3
+ /**
4
+ * 缓存管理器类
5
+ * 提供内存缓存功能,支持TTL和最大容量限制
6
+ * 只用于缓存用户token和wiki转docid结果
7
+ */
8
+ export class CacheManager {
9
+ /**
10
+ * 私有构造函数,用于单例模式
11
+ */
12
+ constructor() {
13
+ Object.defineProperty(this, "cache", {
14
+ enumerable: true,
15
+ configurable: true,
16
+ writable: true,
17
+ value: void 0
18
+ });
19
+ Object.defineProperty(this, "config", {
20
+ enumerable: true,
21
+ configurable: true,
22
+ writable: true,
23
+ value: void 0
24
+ });
25
+ this.cache = new Map();
26
+ this.config = Config.getInstance();
27
+ // 定期清理过期缓存
28
+ setInterval(() => {
29
+ this.cleanExpiredCache();
30
+ }, 60000); // 每分钟清理一次过期缓存
31
+ }
32
+ /**
33
+ * 获取缓存管理器实例
34
+ * @returns 缓存管理器实例
35
+ */
36
+ static getInstance() {
37
+ if (!CacheManager.instance) {
38
+ CacheManager.instance = new CacheManager();
39
+ }
40
+ return CacheManager.instance;
41
+ }
42
+ /**
43
+ * 设置缓存
44
+ * @param key 缓存键
45
+ * @param data 缓存数据
46
+ * @param ttl 缓存生存时间(秒),默认使用配置中的TTL
47
+ * @returns 是否成功设置缓存
48
+ */
49
+ set(key, data, ttl) {
50
+ if (!this.config.cache.enabled) {
51
+ return false;
52
+ }
53
+ // 如果缓存已达到最大容量,清理最早的条目
54
+ if (this.cache.size >= this.config.cache.maxSize) {
55
+ this.cleanOldestCache();
56
+ }
57
+ const now = Date.now();
58
+ const actualTtl = ttl || this.config.cache.ttl;
59
+ this.cache.set(key, {
60
+ data,
61
+ timestamp: now,
62
+ expiresAt: now + (actualTtl * 1000)
63
+ });
64
+ Logger.debug(`缓存设置: ${key} (TTL: ${actualTtl}秒)`);
65
+ return true;
66
+ }
67
+ /**
68
+ * 获取缓存
69
+ * @param key 缓存键
70
+ * @returns 缓存数据,如果未找到或已过期则返回null
71
+ */
72
+ get(key) {
73
+ if (!this.config.cache.enabled) {
74
+ return null;
75
+ }
76
+ const cacheItem = this.cache.get(key);
77
+ if (!cacheItem) {
78
+ Logger.debug(`缓存未命中: ${key}`);
79
+ return null;
80
+ }
81
+ // 检查是否过期
82
+ if (Date.now() > cacheItem.expiresAt) {
83
+ Logger.debug(`缓存已过期: ${key}`);
84
+ this.cache.delete(key);
85
+ return null;
86
+ }
87
+ Logger.debug(`缓存命中: ${key}`);
88
+ return cacheItem.data;
89
+ }
90
+ /**
91
+ * 删除缓存
92
+ * @param key 缓存键
93
+ * @returns 是否成功删除
94
+ */
95
+ delete(key) {
96
+ if (!this.config.cache.enabled) {
97
+ return false;
98
+ }
99
+ const result = this.cache.delete(key);
100
+ if (result) {
101
+ Logger.debug(`缓存删除: ${key}`);
102
+ }
103
+ return result;
104
+ }
105
+ /**
106
+ * 清空所有缓存
107
+ */
108
+ clear() {
109
+ if (!this.config.cache.enabled) {
110
+ return;
111
+ }
112
+ const size = this.cache.size;
113
+ this.cache.clear();
114
+ Logger.debug(`清空全部缓存,删除了 ${size} 条记录`);
115
+ }
116
+ /**
117
+ * 根据前缀清除缓存
118
+ * @param prefix 缓存键前缀
119
+ * @returns 清除的缓存数量
120
+ */
121
+ clearByPrefix(prefix) {
122
+ if (!this.config.cache.enabled) {
123
+ return 0;
124
+ }
125
+ let count = 0;
126
+ for (const key of this.cache.keys()) {
127
+ if (key.startsWith(prefix)) {
128
+ this.cache.delete(key);
129
+ count++;
130
+ }
131
+ }
132
+ if (count > 0) {
133
+ Logger.debug(`按前缀清除缓存: ${prefix}, 删除了 ${count} 条记录`);
134
+ }
135
+ return count;
136
+ }
137
+ /**
138
+ * 清理过期缓存
139
+ * @returns 清理的缓存数量
140
+ */
141
+ cleanExpiredCache() {
142
+ if (!this.config.cache.enabled) {
143
+ return 0;
144
+ }
145
+ const now = Date.now();
146
+ let count = 0;
147
+ for (const [key, item] of this.cache.entries()) {
148
+ if (now > item.expiresAt) {
149
+ this.cache.delete(key);
150
+ count++;
151
+ }
152
+ }
153
+ if (count > 0) {
154
+ Logger.debug(`清理过期缓存,删除了 ${count} 条记录`);
155
+ }
156
+ return count;
157
+ }
158
+ /**
159
+ * 清理最旧的缓存
160
+ * @param count 要清理的条目数,默认为1
161
+ */
162
+ cleanOldestCache(count = 1) {
163
+ if (!this.config.cache.enabled || this.cache.size === 0) {
164
+ return;
165
+ }
166
+ // 按时间戳排序
167
+ const entries = Array.from(this.cache.entries())
168
+ .sort((a, b) => a[1].timestamp - b[1].timestamp);
169
+ // 删除最早的几条记录
170
+ const toDelete = Math.min(count, entries.length);
171
+ for (let i = 0; i < toDelete; i++) {
172
+ this.cache.delete(entries[i][0]);
173
+ }
174
+ Logger.debug(`清理最旧缓存,删除了 ${toDelete} 条记录`);
175
+ }
176
+ /**
177
+ * 获取缓存统计信息
178
+ * @returns 缓存统计信息对象
179
+ */
180
+ getStats() {
181
+ return {
182
+ size: this.cache.size,
183
+ enabled: this.config.cache.enabled,
184
+ maxSize: this.config.cache.maxSize,
185
+ ttl: this.config.cache.ttl
186
+ };
187
+ }
188
+ /**
189
+ * 缓存访问令牌
190
+ * @param token 访问令牌
191
+ * @param expiresInSeconds 过期时间(秒)
192
+ * @returns 是否成功设置缓存
193
+ */
194
+ cacheToken(token, expiresInSeconds) {
195
+ return this.set('access_token', token, expiresInSeconds);
196
+ }
197
+ /**
198
+ * 获取缓存的访问令牌
199
+ * @returns 访问令牌,如果未找到或已过期则返回null
200
+ */
201
+ getToken() {
202
+ return this.get('access_token');
203
+ }
204
+ /**
205
+ * 缓存Wiki到文档ID的转换结果
206
+ * @param wikiToken Wiki Token
207
+ * @param documentId 文档ID
208
+ * @returns 是否成功设置缓存
209
+ */
210
+ cacheWikiToDocId(wikiToken, documentId) {
211
+ return this.set(`wiki:${wikiToken}`, documentId);
212
+ }
213
+ /**
214
+ * 获取缓存的Wiki转换结果
215
+ * @param wikiToken Wiki Token
216
+ * @returns 文档ID,如果未找到或已过期则返回null
217
+ */
218
+ getWikiToDocId(wikiToken) {
219
+ return this.get(`wiki:${wikiToken}`);
220
+ }
221
+ }