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.
@@ -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: 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: textOptions.textStyles || [],
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.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.");
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. 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.'),
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. ' +
@@ -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.config.minLevel <= LogLevel.DEBUG) {
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.config.minLevel <= LogLevel.INFO) {
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.config.minLevel <= LogLevel.LOG) {
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.config.minLevel <= LogLevel.WARN) {
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.config.minLevel <= LogLevel.ERROR) {
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.config.minLevel <= LogLevel.DEBUG) {
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: false,
268
+ logToFile: true,
253
269
  logFilePath: 'log/log.txt',
254
- maxObjectDepth: 2, // 限制对象序列化深度
270
+ maxObjectDepth: 4, // 限制对象序列化深度
255
271
  maxObjectStringLength: 5000000 // 限制序列化后字符串长度
256
272
  }
257
273
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "feishu-mcp",
3
- "version": "0.0.10",
3
+ "version": "0.0.11",
4
4
  "description": "Model Context Protocol server for Feishu integration",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",