feishu-mcp 0.0.10 → 0.0.11
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/dist/manager/sseConnectionManager.js +104 -0
- package/dist/mcp/feishuMcp.js +67 -0
- package/dist/mcp/tools/feishuBlockTools.js +427 -0
- package/dist/mcp/tools/feishuFolderTools.js +86 -0
- package/dist/mcp/tools/feishuTools.js +137 -0
- package/dist/server.js +39 -637
- package/dist/services/blockFactory.js +29 -11
- package/dist/services/feishuApiService.js +90 -3
- package/dist/types/feishuSchema.js +8 -7
- package/dist/utils/logger.js +27 -11
- package/package.json +1 -1
|
@@ -34,6 +34,28 @@ export class BlockFactory {
|
|
|
34
34
|
}
|
|
35
35
|
return BlockFactory.instance;
|
|
36
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* 获取默认的文本元素样式
|
|
39
|
+
* @returns 默认文本元素样式
|
|
40
|
+
*/
|
|
41
|
+
static getDefaultTextElementStyle() {
|
|
42
|
+
return {
|
|
43
|
+
bold: false,
|
|
44
|
+
inline_code: false,
|
|
45
|
+
italic: false,
|
|
46
|
+
strikethrough: false,
|
|
47
|
+
underline: false
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* 应用默认文本样式
|
|
52
|
+
* @param style 已有样式(可选)
|
|
53
|
+
* @returns 合并后的样式
|
|
54
|
+
*/
|
|
55
|
+
static applyDefaultTextStyle(style) {
|
|
56
|
+
const defaultStyle = BlockFactory.getDefaultTextElementStyle();
|
|
57
|
+
return style ? { ...defaultStyle, ...style } : defaultStyle;
|
|
58
|
+
}
|
|
37
59
|
/**
|
|
38
60
|
* 创建块内容
|
|
39
61
|
* @param type 块类型
|
|
@@ -68,7 +90,7 @@ export class BlockFactory {
|
|
|
68
90
|
elements: textContents.map(content => ({
|
|
69
91
|
text_run: {
|
|
70
92
|
content: content.text,
|
|
71
|
-
text_element_style: content.style
|
|
93
|
+
text_element_style: BlockFactory.applyDefaultTextStyle(content.style)
|
|
72
94
|
}
|
|
73
95
|
})),
|
|
74
96
|
style: {
|
|
@@ -84,6 +106,8 @@ export class BlockFactory {
|
|
|
84
106
|
*/
|
|
85
107
|
createCodeBlock(options) {
|
|
86
108
|
const { code, language = 0, wrap = false } = options;
|
|
109
|
+
// 校验 language 合法性,飞书API只允许1~75
|
|
110
|
+
const safeLanguage = language >= 1 && language <= 75 ? language : 1;
|
|
87
111
|
return {
|
|
88
112
|
block_type: 14, // 14表示代码块
|
|
89
113
|
code: {
|
|
@@ -91,18 +115,12 @@ export class BlockFactory {
|
|
|
91
115
|
{
|
|
92
116
|
text_run: {
|
|
93
117
|
content: code,
|
|
94
|
-
text_element_style:
|
|
95
|
-
bold: false,
|
|
96
|
-
inline_code: false,
|
|
97
|
-
italic: false,
|
|
98
|
-
strikethrough: false,
|
|
99
|
-
underline: false
|
|
100
|
-
}
|
|
118
|
+
text_element_style: BlockFactory.getDefaultTextElementStyle()
|
|
101
119
|
}
|
|
102
120
|
}
|
|
103
121
|
],
|
|
104
122
|
style: {
|
|
105
|
-
language:
|
|
123
|
+
language: safeLanguage,
|
|
106
124
|
wrap: wrap
|
|
107
125
|
}
|
|
108
126
|
}
|
|
@@ -131,7 +149,7 @@ export class BlockFactory {
|
|
|
131
149
|
{
|
|
132
150
|
text_run: {
|
|
133
151
|
content: text,
|
|
134
|
-
text_element_style:
|
|
152
|
+
text_element_style: BlockFactory.getDefaultTextElementStyle()
|
|
135
153
|
}
|
|
136
154
|
}
|
|
137
155
|
],
|
|
@@ -162,7 +180,7 @@ export class BlockFactory {
|
|
|
162
180
|
{
|
|
163
181
|
text_run: {
|
|
164
182
|
content: text,
|
|
165
|
-
text_element_style:
|
|
183
|
+
text_element_style: BlockFactory.getDefaultTextElementStyle()
|
|
166
184
|
}
|
|
167
185
|
}
|
|
168
186
|
],
|
|
@@ -229,7 +229,7 @@ export class FeishuApiService extends BaseApiService {
|
|
|
229
229
|
const elements = textElements.map(item => ({
|
|
230
230
|
text_run: {
|
|
231
231
|
content: item.text,
|
|
232
|
-
text_element_style: item.style
|
|
232
|
+
text_element_style: BlockFactory.applyDefaultTextStyle(item.style)
|
|
233
233
|
}
|
|
234
234
|
}));
|
|
235
235
|
const data = {
|
|
@@ -308,8 +308,13 @@ export class FeishuApiService extends BaseApiService {
|
|
|
308
308
|
* @returns 创建结果
|
|
309
309
|
*/
|
|
310
310
|
async createTextBlock(documentId, parentBlockId, textContents, align = 1, index = 0) {
|
|
311
|
+
// 处理文本内容样式
|
|
312
|
+
const processedTextContents = textContents.map(item => ({
|
|
313
|
+
text: item.text,
|
|
314
|
+
style: BlockFactory.applyDefaultTextStyle(item.style)
|
|
315
|
+
}));
|
|
311
316
|
const blockContent = this.blockFactory.createTextBlock({
|
|
312
|
-
textContents,
|
|
317
|
+
textContents: processedTextContents,
|
|
313
318
|
align
|
|
314
319
|
});
|
|
315
320
|
return this.createDocumentBlock(documentId, parentBlockId, blockContent, index);
|
|
@@ -466,6 +471,33 @@ export class FeishuApiService extends BaseApiService {
|
|
|
466
471
|
*/
|
|
467
472
|
createBlockContent(blockType, options) {
|
|
468
473
|
try {
|
|
474
|
+
// 处理特殊的heading标题格式,如heading1, heading2等
|
|
475
|
+
if (typeof blockType === 'string' && blockType.startsWith('heading')) {
|
|
476
|
+
// 使用正则表达式匹配"heading"后跟1-9的数字格式
|
|
477
|
+
const headingMatch = blockType.match(/^heading([1-9])$/);
|
|
478
|
+
if (headingMatch) {
|
|
479
|
+
// 提取数字部分,例如从"heading1"中提取"1"
|
|
480
|
+
const level = parseInt(headingMatch[1], 10);
|
|
481
|
+
// 额外的安全检查,确保level在1-9范围内
|
|
482
|
+
if (level >= 1 && level <= 9) {
|
|
483
|
+
// 使用level参数创建标题块
|
|
484
|
+
if (!options || Object.keys(options).length === 0) {
|
|
485
|
+
// 没有提供选项时创建默认选项
|
|
486
|
+
options = { heading: { level, content: '', align: 1 } };
|
|
487
|
+
}
|
|
488
|
+
else if (!('heading' in options)) {
|
|
489
|
+
// 提供了选项但没有heading字段
|
|
490
|
+
options = { heading: { level, content: '', align: 1 } };
|
|
491
|
+
}
|
|
492
|
+
else if (options.heading && !('level' in options.heading)) {
|
|
493
|
+
// 提供了heading但没有level字段
|
|
494
|
+
options.heading.level = level;
|
|
495
|
+
}
|
|
496
|
+
blockType = BlockType.HEADING; // 将blockType转为标准的heading类型
|
|
497
|
+
Logger.info(`转换特殊标题格式: ${blockType}${level} -> standard heading with level=${level}`);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
469
501
|
// 使用枚举类型来避免字符串错误
|
|
470
502
|
const blockTypeEnum = blockType;
|
|
471
503
|
// 构建块配置
|
|
@@ -473,12 +505,19 @@ export class FeishuApiService extends BaseApiService {
|
|
|
473
505
|
type: blockTypeEnum,
|
|
474
506
|
options: {}
|
|
475
507
|
};
|
|
508
|
+
// 根据块类型处理不同的选项
|
|
476
509
|
switch (blockTypeEnum) {
|
|
477
510
|
case BlockType.TEXT:
|
|
478
511
|
if ('text' in options && options.text) {
|
|
479
512
|
const textOptions = options.text;
|
|
513
|
+
// 处理文本样式,应用默认样式
|
|
514
|
+
const textStyles = textOptions.textStyles || [];
|
|
515
|
+
const processedTextStyles = textStyles.map((item) => ({
|
|
516
|
+
text: item.text,
|
|
517
|
+
style: BlockFactory.applyDefaultTextStyle(item.style)
|
|
518
|
+
}));
|
|
480
519
|
blockConfig.options = {
|
|
481
|
-
textContents:
|
|
520
|
+
textContents: processedTextStyles,
|
|
482
521
|
align: textOptions.align || 1
|
|
483
522
|
};
|
|
484
523
|
}
|
|
@@ -515,7 +554,55 @@ export class FeishuApiService extends BaseApiService {
|
|
|
515
554
|
};
|
|
516
555
|
}
|
|
517
556
|
break;
|
|
557
|
+
default:
|
|
558
|
+
Logger.warn(`未知的块类型: ${blockType},尝试作为标准类型处理`);
|
|
559
|
+
if ('text' in options) {
|
|
560
|
+
blockConfig.type = BlockType.TEXT;
|
|
561
|
+
const textOptions = options.text;
|
|
562
|
+
// 处理文本样式,应用默认样式
|
|
563
|
+
const textStyles = textOptions.textStyles || [];
|
|
564
|
+
const processedTextStyles = textStyles.map((item) => ({
|
|
565
|
+
text: item.text,
|
|
566
|
+
style: BlockFactory.applyDefaultTextStyle(item.style)
|
|
567
|
+
}));
|
|
568
|
+
blockConfig.options = {
|
|
569
|
+
textContents: processedTextStyles,
|
|
570
|
+
align: textOptions.align || 1
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
else if ('code' in options) {
|
|
574
|
+
blockConfig.type = BlockType.CODE;
|
|
575
|
+
const codeOptions = options.code;
|
|
576
|
+
blockConfig.options = {
|
|
577
|
+
code: codeOptions.code || '',
|
|
578
|
+
language: codeOptions.language === 0 ? 0 : (codeOptions.language || 0),
|
|
579
|
+
wrap: codeOptions.wrap || false
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
else if ('heading' in options) {
|
|
583
|
+
blockConfig.type = BlockType.HEADING;
|
|
584
|
+
const headingOptions = options.heading;
|
|
585
|
+
blockConfig.options = {
|
|
586
|
+
text: headingOptions.content || '',
|
|
587
|
+
level: headingOptions.level || 1,
|
|
588
|
+
align: (headingOptions.align === 1 || headingOptions.align === 2 || headingOptions.align === 3)
|
|
589
|
+
? headingOptions.align : 1
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
else if ('list' in options) {
|
|
593
|
+
blockConfig.type = BlockType.LIST;
|
|
594
|
+
const listOptions = options.list;
|
|
595
|
+
blockConfig.options = {
|
|
596
|
+
text: listOptions.content || '',
|
|
597
|
+
isOrdered: listOptions.isOrdered || false,
|
|
598
|
+
align: (listOptions.align === 1 || listOptions.align === 2 || listOptions.align === 3)
|
|
599
|
+
? listOptions.align : 1
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
break;
|
|
518
603
|
}
|
|
604
|
+
// 记录调试信息
|
|
605
|
+
Logger.debug(`创建块内容: 类型=${blockConfig.type}, 选项=${JSON.stringify(blockConfig.options)}`);
|
|
519
606
|
// 使用BlockFactory创建块
|
|
520
607
|
return this.blockFactory.createBlock(blockConfig.type, blockConfig.options);
|
|
521
608
|
}
|
|
@@ -87,17 +87,18 @@ export const ListBlockSchema = z.object({
|
|
|
87
87
|
align: AlignSchemaWithValidation,
|
|
88
88
|
});
|
|
89
89
|
// 块类型枚举 - 用于批量创建块工具
|
|
90
|
-
export const BlockTypeEnum = z.
|
|
91
|
-
"
|
|
90
|
+
export const BlockTypeEnum = z.string().describe("Block type (required). Supports: 'text', 'code', 'heading', 'list', as well as 'heading1' through 'heading9'. " +
|
|
91
|
+
"For headings, we recommend using 'heading' with level property, but 'heading1'-'heading9' are also supported.");
|
|
92
92
|
// 块配置定义 - 用于批量创建块工具
|
|
93
93
|
export const BlockConfigSchema = z.object({
|
|
94
94
|
blockType: BlockTypeEnum,
|
|
95
95
|
options: z.union([
|
|
96
|
-
z.object({ text: TextStyleBlockSchema }).describe("Text block options.
|
|
97
|
-
z.object({ code: CodeBlockSchema }).describe("Code block options.
|
|
98
|
-
z.object({ heading: HeadingBlockSchema }).describe("Heading block options.
|
|
99
|
-
z.object({ list: ListBlockSchema }).describe("List block options.
|
|
100
|
-
|
|
96
|
+
z.object({ text: TextStyleBlockSchema }).describe("Text block options. Used when blockType is 'text'."),
|
|
97
|
+
z.object({ code: CodeBlockSchema }).describe("Code block options. Used when blockType is 'code'."),
|
|
98
|
+
z.object({ heading: HeadingBlockSchema }).describe("Heading block options. Used with both 'heading' and 'headingN' formats."),
|
|
99
|
+
z.object({ list: ListBlockSchema }).describe("List block options. Used when blockType is 'list'."),
|
|
100
|
+
z.record(z.any()).describe("Fallback for any other block options")
|
|
101
|
+
]).describe('Options for the specific block type. Provide the corresponding options object based on blockType.'),
|
|
101
102
|
});
|
|
102
103
|
// 媒体ID参数定义
|
|
103
104
|
export const MediaIdSchema = z.string().describe('Media ID (required). The unique identifier for a media resource (image, file, etc.) in Feishu. ' +
|
package/dist/utils/logger.js
CHANGED
|
@@ -25,13 +25,28 @@ export class Logger {
|
|
|
25
25
|
static configure(config) {
|
|
26
26
|
this.config = { ...this.config, ...config };
|
|
27
27
|
// 确保日志目录存在
|
|
28
|
-
if (this.config.logToFile) {
|
|
28
|
+
if (this.config.logToFile && this.config.enabled) {
|
|
29
29
|
const logDir = path.dirname(this.config.logFilePath);
|
|
30
30
|
if (!fs.existsSync(logDir)) {
|
|
31
31
|
fs.mkdirSync(logDir, { recursive: true });
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* 设置日志开关
|
|
37
|
+
* @param enabled 是否启用日志
|
|
38
|
+
*/
|
|
39
|
+
static setEnabled(enabled) {
|
|
40
|
+
this.config.enabled = enabled;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* 检查日志是否可输出
|
|
44
|
+
* @param level 日志级别
|
|
45
|
+
* @returns 是否可输出
|
|
46
|
+
*/
|
|
47
|
+
static canLog(level) {
|
|
48
|
+
return this.config.enabled && level >= this.config.minLevel;
|
|
49
|
+
}
|
|
35
50
|
/**
|
|
36
51
|
* 格式化日志消息
|
|
37
52
|
* @param level 日志级别
|
|
@@ -59,7 +74,7 @@ export class Logger {
|
|
|
59
74
|
* @param logParts 日志内容部分
|
|
60
75
|
*/
|
|
61
76
|
static writeToFile(logParts) {
|
|
62
|
-
if (!this.config.logToFile)
|
|
77
|
+
if (!this.config.enabled || !this.config.logToFile)
|
|
63
78
|
return;
|
|
64
79
|
try {
|
|
65
80
|
// 将日志内容转换为字符串
|
|
@@ -143,7 +158,7 @@ export class Logger {
|
|
|
143
158
|
* @param args 日志参数
|
|
144
159
|
*/
|
|
145
160
|
static debug(...args) {
|
|
146
|
-
if (this.
|
|
161
|
+
if (this.canLog(LogLevel.DEBUG)) {
|
|
147
162
|
const formattedMessage = this.formatLogMessage(LogLevel.DEBUG, args);
|
|
148
163
|
console.debug(...formattedMessage);
|
|
149
164
|
this.writeToFile(formattedMessage);
|
|
@@ -154,7 +169,7 @@ export class Logger {
|
|
|
154
169
|
* @param args 日志参数
|
|
155
170
|
*/
|
|
156
171
|
static info(...args) {
|
|
157
|
-
if (this.
|
|
172
|
+
if (this.canLog(LogLevel.INFO)) {
|
|
158
173
|
const formattedMessage = this.formatLogMessage(LogLevel.INFO, args);
|
|
159
174
|
console.info(...formattedMessage);
|
|
160
175
|
this.writeToFile(formattedMessage);
|
|
@@ -165,7 +180,7 @@ export class Logger {
|
|
|
165
180
|
* @param args 日志参数
|
|
166
181
|
*/
|
|
167
182
|
static log(...args) {
|
|
168
|
-
if (this.
|
|
183
|
+
if (this.canLog(LogLevel.LOG)) {
|
|
169
184
|
const formattedMessage = this.formatLogMessage(LogLevel.LOG, args);
|
|
170
185
|
console.log(...formattedMessage);
|
|
171
186
|
this.writeToFile(formattedMessage);
|
|
@@ -176,7 +191,7 @@ export class Logger {
|
|
|
176
191
|
* @param args 日志参数
|
|
177
192
|
*/
|
|
178
193
|
static warn(...args) {
|
|
179
|
-
if (this.
|
|
194
|
+
if (this.canLog(LogLevel.WARN)) {
|
|
180
195
|
const formattedMessage = this.formatLogMessage(LogLevel.WARN, args);
|
|
181
196
|
console.warn(...formattedMessage);
|
|
182
197
|
this.writeToFile(formattedMessage);
|
|
@@ -187,7 +202,7 @@ export class Logger {
|
|
|
187
202
|
* @param args 日志参数
|
|
188
203
|
*/
|
|
189
204
|
static error(...args) {
|
|
190
|
-
if (this.
|
|
205
|
+
if (this.canLog(LogLevel.ERROR)) {
|
|
191
206
|
const formattedMessage = this.formatLogMessage(LogLevel.ERROR, args);
|
|
192
207
|
console.error(...formattedMessage);
|
|
193
208
|
this.writeToFile(formattedMessage);
|
|
@@ -202,7 +217,7 @@ export class Logger {
|
|
|
202
217
|
* @param statusCode 响应状态码
|
|
203
218
|
*/
|
|
204
219
|
static logApiCall(method, url, data, response, statusCode) {
|
|
205
|
-
if (this.
|
|
220
|
+
if (this.canLog(LogLevel.DEBUG)) {
|
|
206
221
|
this.debug('API调用详情:');
|
|
207
222
|
this.debug(`请求: ${method} ${url}`);
|
|
208
223
|
// 简化请求数据记录
|
|
@@ -235,7 +250,7 @@ export class Logger {
|
|
|
235
250
|
this.debug('响应数据: None');
|
|
236
251
|
}
|
|
237
252
|
}
|
|
238
|
-
else {
|
|
253
|
+
else if (this.canLog(LogLevel.INFO)) {
|
|
239
254
|
this.info(`API调用: ${method} ${url} - 状态码: ${statusCode}`);
|
|
240
255
|
}
|
|
241
256
|
}
|
|
@@ -245,13 +260,14 @@ Object.defineProperty(Logger, "config", {
|
|
|
245
260
|
configurable: true,
|
|
246
261
|
writable: true,
|
|
247
262
|
value: {
|
|
263
|
+
enabled: true, // 默认开启日志
|
|
248
264
|
minLevel: LogLevel.DEBUG, // 修改为DEBUG级别,确保捕获所有日志
|
|
249
265
|
showTimestamp: true,
|
|
250
266
|
showLevel: true,
|
|
251
267
|
timestampFormat: 'yyyy-MM-dd HH:mm:ss.SSS',
|
|
252
|
-
logToFile:
|
|
268
|
+
logToFile: true,
|
|
253
269
|
logFilePath: 'log/log.txt',
|
|
254
|
-
maxObjectDepth:
|
|
270
|
+
maxObjectDepth: 4, // 限制对象序列化深度
|
|
255
271
|
maxObjectStringLength: 5000000 // 限制序列化后字符串长度
|
|
256
272
|
}
|
|
257
273
|
});
|