feishu-mcp 0.0.4 → 0.0.5
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/README.md +39 -8
- package/dist/server.js +490 -399
- package/dist/services/feishu.js +148 -16
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -11,6 +11,126 @@ export const Logger = {
|
|
|
11
11
|
console.error(...args);
|
|
12
12
|
},
|
|
13
13
|
};
|
|
14
|
+
// 添加一个工具类方法,用于格式化错误信息
|
|
15
|
+
function formatErrorMessage(error) {
|
|
16
|
+
if (error instanceof Error) {
|
|
17
|
+
return error.message;
|
|
18
|
+
}
|
|
19
|
+
else if (typeof error === 'string') {
|
|
20
|
+
return error;
|
|
21
|
+
}
|
|
22
|
+
else if (error && typeof error === 'object') {
|
|
23
|
+
try {
|
|
24
|
+
// 处理包含apiError字段的FeishuError对象
|
|
25
|
+
if (error.apiError) {
|
|
26
|
+
const apiError = error.apiError;
|
|
27
|
+
let errorMsg = '';
|
|
28
|
+
// 处理标准飞书API错误格式
|
|
29
|
+
if (apiError.code && apiError.msg) {
|
|
30
|
+
errorMsg = `${apiError.msg} (错误码: ${apiError.code})`;
|
|
31
|
+
// 添加字段验证错误信息
|
|
32
|
+
if (apiError.error && apiError.error.field_violations && apiError.error.field_violations.length > 0) {
|
|
33
|
+
const violations = apiError.error.field_violations;
|
|
34
|
+
errorMsg += '\n字段验证错误:';
|
|
35
|
+
violations.forEach((violation) => {
|
|
36
|
+
let detail = `\n - ${violation.field}`;
|
|
37
|
+
if (violation.description) {
|
|
38
|
+
detail += `: ${violation.description}`;
|
|
39
|
+
}
|
|
40
|
+
if (violation.value) {
|
|
41
|
+
detail += `,提供的值: ${violation.value}`;
|
|
42
|
+
}
|
|
43
|
+
errorMsg += detail;
|
|
44
|
+
});
|
|
45
|
+
// 添加排查建议链接
|
|
46
|
+
if (apiError.error.troubleshooter) {
|
|
47
|
+
errorMsg += `\n\n${apiError.error.troubleshooter}`;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return errorMsg;
|
|
51
|
+
}
|
|
52
|
+
// 如果apiError没有标准结构,尝试序列化
|
|
53
|
+
return `API错误: ${JSON.stringify(apiError)}`;
|
|
54
|
+
}
|
|
55
|
+
// 处理飞书API特定的错误格式
|
|
56
|
+
if (error.code && error.msg) {
|
|
57
|
+
// 基本错误信息
|
|
58
|
+
let errorMsg = `${error.msg} (错误码: ${error.code})`;
|
|
59
|
+
// 如果有详细的验证错误信息
|
|
60
|
+
if (error.error && error.error.field_violations && error.error.field_violations.length > 0) {
|
|
61
|
+
const violations = error.error.field_violations;
|
|
62
|
+
errorMsg += '\n字段验证错误:';
|
|
63
|
+
violations.forEach((violation) => {
|
|
64
|
+
let detail = `\n - ${violation.field}`;
|
|
65
|
+
if (violation.description) {
|
|
66
|
+
detail += `: ${violation.description}`;
|
|
67
|
+
}
|
|
68
|
+
if (violation.value) {
|
|
69
|
+
detail += `,提供的值: ${violation.value}`;
|
|
70
|
+
}
|
|
71
|
+
errorMsg += detail;
|
|
72
|
+
});
|
|
73
|
+
// 添加排查建议链接(如果有)
|
|
74
|
+
if (error.error.troubleshooter) {
|
|
75
|
+
errorMsg += `\n\n${error.error.troubleshooter}`;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return errorMsg;
|
|
79
|
+
}
|
|
80
|
+
// 处理 {status, err} 格式的错误
|
|
81
|
+
if (error.status && error.err) {
|
|
82
|
+
return `操作失败 (状态码: ${error.status}): ${error.err}`;
|
|
83
|
+
}
|
|
84
|
+
// 尝试提取API错误信息,通常在错误对象的message或error字段中
|
|
85
|
+
if (error.message) {
|
|
86
|
+
return error.message;
|
|
87
|
+
}
|
|
88
|
+
else if (error.error) {
|
|
89
|
+
if (typeof error.error === 'string') {
|
|
90
|
+
return error.error;
|
|
91
|
+
}
|
|
92
|
+
else if (error.error.message) {
|
|
93
|
+
return error.error.message;
|
|
94
|
+
}
|
|
95
|
+
else if (error.error.field_violations) {
|
|
96
|
+
// 处理错误嵌套在error对象中的情况
|
|
97
|
+
const violations = error.error.field_violations;
|
|
98
|
+
let errorMsg = '字段验证错误:';
|
|
99
|
+
violations.forEach((violation) => {
|
|
100
|
+
let detail = `\n - ${violation.field}`;
|
|
101
|
+
if (violation.description) {
|
|
102
|
+
detail += `: ${violation.description}`;
|
|
103
|
+
}
|
|
104
|
+
if (violation.value) {
|
|
105
|
+
detail += `,提供的值: ${violation.value}`;
|
|
106
|
+
}
|
|
107
|
+
errorMsg += detail;
|
|
108
|
+
});
|
|
109
|
+
return errorMsg;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
else if (error.code || error.status) {
|
|
113
|
+
// 处理HTTP错误或API错误码
|
|
114
|
+
const code = error.code || error.status;
|
|
115
|
+
const msg = error.statusText || error.msg || 'Unknown error';
|
|
116
|
+
return `操作失败 (错误码: ${code}): ${msg}`;
|
|
117
|
+
}
|
|
118
|
+
// 如果上述都不符合,尝试将整个对象序列化(但移除敏感信息)
|
|
119
|
+
const safeError = { ...error };
|
|
120
|
+
// 移除可能的敏感信息
|
|
121
|
+
['token', 'secret', 'password', 'key', 'credentials'].forEach(key => {
|
|
122
|
+
if (key in safeError)
|
|
123
|
+
delete safeError[key];
|
|
124
|
+
});
|
|
125
|
+
return `发生错误: ${JSON.stringify(safeError)}`;
|
|
126
|
+
}
|
|
127
|
+
catch (e) {
|
|
128
|
+
console.error("Error formatting error message:", e);
|
|
129
|
+
return '发生未知错误';
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return '发生未知错误';
|
|
133
|
+
}
|
|
14
134
|
export class FeishuMcpServer {
|
|
15
135
|
constructor(feishuConfig) {
|
|
16
136
|
Object.defineProperty(this, "server", {
|
|
@@ -54,77 +174,63 @@ export class FeishuMcpServer {
|
|
|
54
174
|
}
|
|
55
175
|
registerTools() {
|
|
56
176
|
// 添加创建飞书文档工具
|
|
57
|
-
this.server.tool('
|
|
58
|
-
title: z.string().describe('Document title'),
|
|
59
|
-
folderToken: z
|
|
60
|
-
.string()
|
|
61
|
-
.optional()
|
|
62
|
-
.describe('Folder token where the document will be created. If not provided, the document will be created in the root directory'),
|
|
177
|
+
this.server.tool('create_feishu_document', 'Creates a new Feishu document and returns its information. Use this tool when you need to create a document from scratch with a specific title and folder location.', {
|
|
178
|
+
title: z.string().describe('Document title (required). This will be displayed in the Feishu document list and document header.'),
|
|
179
|
+
folderToken: z.string().describe('Folder token (required). Specifies where to create the document. Format is an alphanumeric string like "doxcnOu1ZKYH4RtX1Y5XwL5WGRh".'),
|
|
63
180
|
}, async ({ title, folderToken }) => {
|
|
64
181
|
try {
|
|
65
|
-
Logger.log(`开始创建飞书文档,标题: ${title}${folderToken ? `,文件夹Token: ${folderToken}` : ''}`);
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
182
|
+
Logger.log(`开始创建飞书文档,标题: ${title}${folderToken ? `,文件夹Token: ${folderToken}` : ',使用默认文件夹'}`);
|
|
183
|
+
const newDoc = await this.feishuService?.createDocument(title, folderToken);
|
|
184
|
+
if (!newDoc) {
|
|
185
|
+
throw new Error('创建文档失败,未返回文档信息');
|
|
186
|
+
}
|
|
187
|
+
Logger.log(`飞书文档创建成功,文档ID: ${newDoc.objToken || newDoc.document_id}`);
|
|
69
188
|
return {
|
|
70
189
|
content: [{ type: 'text', text: JSON.stringify(newDoc, null, 2) }],
|
|
71
190
|
};
|
|
72
191
|
}
|
|
73
192
|
catch (error) {
|
|
74
193
|
Logger.error(`创建飞书文档失败:`, error);
|
|
194
|
+
const errorMessage = formatErrorMessage(error);
|
|
75
195
|
return {
|
|
76
|
-
content: [{ type: 'text', text: `创建飞书文档失败: ${
|
|
196
|
+
content: [{ type: 'text', text: `创建飞书文档失败: ${errorMessage}` }],
|
|
77
197
|
};
|
|
78
198
|
}
|
|
79
199
|
});
|
|
80
200
|
// 添加获取飞书文档信息工具
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
// }
|
|
106
|
-
// },
|
|
107
|
-
// );
|
|
201
|
+
this.server.tool("get_feishu_doc_info", "Retrieves basic information about a Feishu document. Use this to verify if a document exists, check access permissions, or get metadata like title, type, and creation information.", {
|
|
202
|
+
documentId: z.string().describe("Document ID or URL (required). Supports the following formats:\n1. Standard document URL: https://xxx.feishu.cn/docs/xxx or https://xxx.feishu.cn/docx/xxx\n2. API URL: https://open.feishu.cn/open-apis/doc/v2/documents/xxx\n3. Direct document ID: e.g., JcKbdlokYoPIe0xDzJ1cduRXnRf"),
|
|
203
|
+
}, async ({ documentId }) => {
|
|
204
|
+
try {
|
|
205
|
+
if (!this.feishuService) {
|
|
206
|
+
return {
|
|
207
|
+
content: [{ type: "text", text: "Feishu service is not initialized. Please check the configuration" }],
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
Logger.log(`开始获取飞书文档信息,文档ID: ${documentId}`);
|
|
211
|
+
const docInfo = await this.feishuService.getDocumentInfo(documentId);
|
|
212
|
+
Logger.log(`飞书文档信息获取成功,标题: ${docInfo.title}`);
|
|
213
|
+
return {
|
|
214
|
+
content: [{ type: "text", text: JSON.stringify(docInfo, null, 2) }],
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
catch (error) {
|
|
218
|
+
Logger.error(`获取飞书文档信息失败:`, error);
|
|
219
|
+
const errorMessage = formatErrorMessage(error);
|
|
220
|
+
return {
|
|
221
|
+
content: [{ type: "text", text: `获取飞书文档信息失败: ${errorMessage}` }],
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
});
|
|
108
225
|
// 添加获取飞书文档内容工具
|
|
109
|
-
this.server.tool('get_feishu_doc_content', '
|
|
110
|
-
documentId: z
|
|
111
|
-
|
|
112
|
-
.describe('Document ID or URL. Supported formats:\n1. Standard document URL: https://xxx.feishu.cn/docs/xxx or https://xxx.feishu.cn/docx/xxx\n2. API URL: https://open.feishu.cn/open-apis/doc/v2/documents/xxx\n3. Direct document ID (e.g., JcKbdlokYoPIe0xDzJ1cduRXnRf)'),
|
|
113
|
-
lang: z
|
|
114
|
-
.number()
|
|
115
|
-
.optional()
|
|
116
|
-
.default(0)
|
|
117
|
-
.describe('Language code. Default is 0 (Chinese)'),
|
|
226
|
+
this.server.tool('get_feishu_doc_content', 'Retrieves the plain text content of a Feishu document. Ideal for content analysis, processing, or when you need to extract text without formatting. The content maintains the document structure but without styling.', {
|
|
227
|
+
documentId: z.string().describe('Document ID or URL (required). Supports the following formats:\n1. Standard document URL: https://xxx.feishu.cn/docs/xxx or https://xxx.feishu.cn/docx/xxx\n2. API URL: https://open.feishu.cn/open-apis/doc/v2/documents/xxx\n3. Direct document ID: e.g., JcKbdlokYoPIe0xDzJ1cduRXnRf'),
|
|
228
|
+
lang: z.number().optional().default(0).describe('Language code (optional). Default is 0 (Chinese). Use 1 for English if available.'),
|
|
118
229
|
}, async ({ documentId, lang }) => {
|
|
119
230
|
try {
|
|
120
231
|
if (!this.feishuService) {
|
|
121
232
|
return {
|
|
122
|
-
content: [
|
|
123
|
-
{
|
|
124
|
-
type: 'text',
|
|
125
|
-
text: 'Feishu service is not initialized. Please check the configuration',
|
|
126
|
-
},
|
|
127
|
-
],
|
|
233
|
+
content: [{ type: 'text', text: 'Feishu service is not initialized. Please check the configuration' }],
|
|
128
234
|
};
|
|
129
235
|
}
|
|
130
236
|
Logger.log(`开始获取飞书文档内容,文档ID: ${documentId},语言: ${lang}`);
|
|
@@ -136,31 +242,21 @@ export class FeishuMcpServer {
|
|
|
136
242
|
}
|
|
137
243
|
catch (error) {
|
|
138
244
|
Logger.error(`获取飞书文档内容失败:`, error);
|
|
245
|
+
const errorMessage = formatErrorMessage(error);
|
|
139
246
|
return {
|
|
140
|
-
content: [{ type: 'text', text: `获取飞书文档内容失败: ${
|
|
247
|
+
content: [{ type: 'text', text: `获取飞书文档内容失败: ${errorMessage}` }],
|
|
141
248
|
};
|
|
142
249
|
}
|
|
143
250
|
});
|
|
144
251
|
// 添加获取飞书文档块工具
|
|
145
|
-
this.server.tool('get_feishu_doc_blocks', '
|
|
146
|
-
documentId: z
|
|
147
|
-
|
|
148
|
-
.describe('Document ID or URL. Supported formats:\n1. Standard document URL: https://xxx.feishu.cn/docs/xxx or https://xxx.feishu.cn/docx/xxx\n2. API URL: https://open.feishu.cn/open-apis/doc/v2/documents/xxx\n3. Direct document ID (e.g., JcKbdlokYoPIe0xDzJ1cduRXnRf)'),
|
|
149
|
-
pageSize: z
|
|
150
|
-
.number()
|
|
151
|
-
.optional()
|
|
152
|
-
.default(500)
|
|
153
|
-
.describe('Number of blocks per page. Default is 500'),
|
|
252
|
+
this.server.tool('get_feishu_doc_blocks', 'Retrieves the block structure information of a Feishu document. Essential to use before inserting content to understand document structure and determine correct insertion positions. Returns a detailed hierarchy of blocks with their IDs, types, and content.', {
|
|
253
|
+
documentId: z.string().describe('Document ID or URL (required). Supports the following formats:\n1. Standard document URL: https://xxx.feishu.cn/docs/xxx or https://xxx.feishu.cn/docx/xxx\n2. API URL: https://open.feishu.cn/open-apis/doc/v2/documents/xxx\n3. Direct document ID: e.g., JcKbdlokYoPIe0xDzJ1cduRXnRf'),
|
|
254
|
+
pageSize: z.number().optional().default(500).describe('Number of blocks per page (optional). Default is 500. Used for paginating large documents. Increase for more blocks at once, decrease for faster response with fewer blocks.'),
|
|
154
255
|
}, async ({ documentId, pageSize }) => {
|
|
155
256
|
try {
|
|
156
257
|
if (!this.feishuService) {
|
|
157
258
|
return {
|
|
158
|
-
content: [
|
|
159
|
-
{
|
|
160
|
-
type: 'text',
|
|
161
|
-
text: 'Feishu service is not initialized. Please check the configuration',
|
|
162
|
-
},
|
|
163
|
-
],
|
|
259
|
+
content: [{ type: 'text', text: 'Feishu service is not initialized. Please check the configuration' }],
|
|
164
260
|
};
|
|
165
261
|
}
|
|
166
262
|
Logger.log(`开始获取飞书文档块,文档ID: ${documentId},页大小: ${pageSize}`);
|
|
@@ -172,281 +268,136 @@ export class FeishuMcpServer {
|
|
|
172
268
|
}
|
|
173
269
|
catch (error) {
|
|
174
270
|
Logger.error(`获取飞书文档块失败:`, error);
|
|
271
|
+
const errorMessage = formatErrorMessage(error);
|
|
175
272
|
return {
|
|
176
|
-
content: [{ type: 'text', text: `获取飞书文档块失败: ${
|
|
273
|
+
content: [{ type: 'text', text: `获取飞书文档块失败: ${errorMessage}` }],
|
|
177
274
|
};
|
|
178
275
|
}
|
|
179
276
|
});
|
|
180
|
-
//
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
// documentId: z.string().describe("Document ID or URL. Supported formats:\n1. Standard document URL: https://xxx.feishu.cn/docs/xxx or https://xxx.feishu.cn/docx/xxx\n2. API URL: https://open.feishu.cn/open-apis/doc/v2/documents/xxx\n3. Direct document ID (e.g., JcKbdlokYoPIe0xDzJ1cduRXnRf)"),
|
|
186
|
-
// parentBlockId: z.string().describe("Parent block ID (NOT URL) where the new block will be added as a child. This should be the raw block ID without any URL prefix. When adding blocks at the page level (root level), use the extracted document ID from documentId parameter"),
|
|
187
|
-
// textContents: z.array(
|
|
188
|
-
// z.object({
|
|
189
|
-
// text: z.string().describe("Text content"),
|
|
190
|
-
// style: z.object({
|
|
191
|
-
// bold: z.boolean().optional().describe("Whether to make text bold. Default is false"),
|
|
192
|
-
// italic: z.boolean().optional().describe("Whether to make text italic. Default is false"),
|
|
193
|
-
// underline: z.boolean().optional().describe("Whether to add underline. Default is false"),
|
|
194
|
-
// strikethrough: z.boolean().optional().describe("Whether to add strikethrough. Default is false"),
|
|
195
|
-
// inline_code: z.boolean().optional().describe("Whether to format as inline code. Default is false"),
|
|
196
|
-
// text_color: z.number().optional().describe("Text color as a number. Default is 0")
|
|
197
|
-
// }).optional().describe("Text style settings")
|
|
198
|
-
// })
|
|
199
|
-
// ).describe("Array of text content objects. A block can contain multiple text segments with different styles"),
|
|
200
|
-
// align: z.number().optional().default(1).describe("Text alignment: 1 for left, 2 for center, 3 for right. Default is 1"),
|
|
201
|
-
// index: z.number().optional().default(0).describe("Insertion position index. Default is 0 (insert at the beginning). If unsure about the position, use the get_feishu_doc_blocks tool first to understand the document structure. For consecutive insertions, calculate the next position as previous_index + 1 to avoid querying document structure repeatedly")
|
|
202
|
-
// },
|
|
203
|
-
// async ({ documentId, parentBlockId, textContents, align = 1, index }) => {
|
|
204
|
-
// try {
|
|
205
|
-
// if (!this.feishuService) {
|
|
206
|
-
// return {
|
|
207
|
-
// content: [{ type: "text", text: "Feishu service is not initialized. Please check the configuration" }],
|
|
208
|
-
// };
|
|
209
|
-
// }
|
|
210
|
-
//
|
|
211
|
-
// // 处理Markdown语法转换
|
|
212
|
-
// const processedTextContents = textContents.map(content => {
|
|
213
|
-
// let { text, style = {} } = content;
|
|
214
|
-
//
|
|
215
|
-
// // 创建一个新的style对象,避免修改原始对象
|
|
216
|
-
// const newStyle = { ...style };
|
|
217
|
-
//
|
|
218
|
-
// // 处理粗体 **text**
|
|
219
|
-
// if (text.match(/\*\*([^*]+)\*\*/g)) {
|
|
220
|
-
// text = text.replace(/\*\*([^*]+)\*\*/g, "$1");
|
|
221
|
-
// newStyle.bold = true;
|
|
222
|
-
// }
|
|
223
|
-
//
|
|
224
|
-
// // 处理斜体 *text*
|
|
225
|
-
// if (text.match(/(?<!\*)\*([^*]+)\*(?!\*)/g)) {
|
|
226
|
-
// text = text.replace(/(?<!\*)\*([^*]+)\*(?!\*)/g, "$1");
|
|
227
|
-
// newStyle.italic = true;
|
|
228
|
-
// }
|
|
229
|
-
//
|
|
230
|
-
// // 处理删除线 ~~text~~
|
|
231
|
-
// if (text.match(/~~([^~]+)~~/g)) {
|
|
232
|
-
// text = text.replace(/~~([^~]+)~~/g, "$1");
|
|
233
|
-
// newStyle.strikethrough = true;
|
|
234
|
-
// }
|
|
235
|
-
//
|
|
236
|
-
// // 处理行内代码 `code`
|
|
237
|
-
// if (text.match(/`([^`]+)`/g)) {
|
|
238
|
-
// text = text.replace(/`([^`]+)`/g, "$1");
|
|
239
|
-
// newStyle.inline_code = true;
|
|
240
|
-
// }
|
|
241
|
-
//
|
|
242
|
-
// return { text, style: newStyle };
|
|
243
|
-
// });
|
|
244
|
-
//
|
|
245
|
-
// Logger.log(`开始创建飞书文本块,文档ID: ${documentId},父块ID: ${parentBlockId},对齐方式: ${align},插入位置: ${index}`);
|
|
246
|
-
// const result = await this.feishuService.createTextBlock(documentId, parentBlockId, processedTextContents, align, index);
|
|
247
|
-
// Logger.log(`飞书文本块创建成功`);
|
|
248
|
-
//
|
|
249
|
-
// return {
|
|
250
|
-
// content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
251
|
-
// };
|
|
252
|
-
// } catch (error) {
|
|
253
|
-
// Logger.error(`创建飞书文本块失败:`, error);
|
|
254
|
-
// return {
|
|
255
|
-
// content: [{ type: "text", text: `创建飞书文本块失败: ${error}` }],
|
|
256
|
-
// };
|
|
257
|
-
// }
|
|
258
|
-
// },
|
|
259
|
-
// );
|
|
260
|
-
// 添加创建飞书代码块工具
|
|
261
|
-
// this.server.tool(
|
|
262
|
-
// "create_feishu_code_block",
|
|
263
|
-
// "Create a new code block in a Feishu document",
|
|
264
|
-
// {
|
|
265
|
-
// documentId: z.string().describe("Document ID or URL. Supported formats:\n1. Standard document URL: https://xxx.feishu.cn/docs/xxx or https://xxx.feishu.cn/docx/xxx\n2. API URL: https://open.feishu.cn/open-apis/doc/v2/documents/xxx\n3. Direct document ID (e.g., JcKbdlokYoPIe0xDzJ1cduRXnRf)"),
|
|
266
|
-
// parentBlockId: z.string().describe("Parent block ID (NOT URL) where the new block will be added as a child. This should be the raw block ID without any URL prefix. When adding blocks at the page level (root level), use the extracted document ID from documentId parameter"),
|
|
267
|
-
// code: z.string().describe("Code content"),
|
|
268
|
-
// language: z.number().optional().default(0).describe("Programming language code as a number. Examples: 1: PlainText; 7: Bash; 8: CSharp; 9: C++; 10: C; 12: CSS; 22: Go; 24: HTML; 29: Java; 30: JavaScript; 32: Kotlin; 43: PHP; 49: Python; 52: Ruby; 53: Rust; 56: SQL; 60: Shell; 61: Swift; 63: TypeScript. Default is 0"),
|
|
269
|
-
// wrap: z.boolean().optional().default(false).describe("Whether to enable automatic line wrapping. Default is false"),
|
|
270
|
-
// index: z.number().optional().default(0).describe("Insertion position index. Default is 0 (insert at the beginning). If unsure about the position, use the get_feishu_doc_blocks tool first to understand the document structure. For consecutive insertions, calculate the next position as previous_index + 1 to avoid querying document structure repeatedly")
|
|
271
|
-
// },
|
|
272
|
-
// async ({ documentId, parentBlockId, code, language = 0, wrap = false, index = 0 }) => {
|
|
273
|
-
// try {
|
|
274
|
-
// if (!this.feishuService) {
|
|
275
|
-
// return {
|
|
276
|
-
// content: [{ type: "text", text: "Feishu service is not initialized. Please check the configuration" }],
|
|
277
|
-
// };
|
|
278
|
-
// }
|
|
279
|
-
//
|
|
280
|
-
// Logger.log(`开始创建飞书代码块,文档ID: ${documentId},父块ID: ${parentBlockId},语言: ${language},自动换行: ${wrap},插入位置: ${index}`);
|
|
281
|
-
// const result = await this.feishuService.createCodeBlock(documentId, parentBlockId, code, language, wrap, index);
|
|
282
|
-
// Logger.log(`飞书代码块创建成功`);
|
|
283
|
-
//
|
|
284
|
-
// return {
|
|
285
|
-
// content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
286
|
-
// };
|
|
287
|
-
// } catch (error) {
|
|
288
|
-
// Logger.error(`创建飞书代码块失败:`, error);
|
|
289
|
-
// return {
|
|
290
|
-
// content: [{ type: "text", text: `创建飞书代码块失败: ${error}` }],
|
|
291
|
-
// };
|
|
292
|
-
// }
|
|
293
|
-
// },
|
|
294
|
-
// );
|
|
295
|
-
// 添加批量创建飞书块工具
|
|
296
|
-
this.server.tool('create_feishu_blocks', 'Create multiple blocks in a Feishu document at once with a single API call', {
|
|
297
|
-
documentId: z
|
|
298
|
-
.string()
|
|
299
|
-
.describe('Document ID or URL. Supported formats:\n1. Standard document URL: https://xxx.feishu.cn/docs/xxx or https://xxx.feishu.cn/docx/xxx\n2. API URL: https://open.feishu.cn/open-apis/doc/v2/documents/xxx\n3. Direct document ID (e.g., JcKbdlokYoPIe0xDzJ1cduRXnRf)'),
|
|
300
|
-
parentBlockId: z
|
|
301
|
-
.string()
|
|
302
|
-
.describe('Parent block ID (NOT URL) where the new blocks will be added as children. This should be the raw block ID without any URL prefix. When adding blocks at the page level (root level), use the extracted document ID from documentId parameter'),
|
|
303
|
-
children: z
|
|
304
|
-
.array(z.object({
|
|
305
|
-
block_type: z
|
|
306
|
-
.number()
|
|
307
|
-
.describe('Block type number: 2 for text, 3-11 for headings (level+2), 14 for code'),
|
|
308
|
-
content: z
|
|
309
|
-
.any()
|
|
310
|
-
.describe('Block content object that follows Feishu API format'),
|
|
311
|
-
}))
|
|
312
|
-
.describe('Array of block objects to create in a single API call'),
|
|
313
|
-
index: z
|
|
314
|
-
.number()
|
|
315
|
-
.optional()
|
|
316
|
-
.default(0)
|
|
317
|
-
.describe('Insertion position index. Default is 0 (insert at the beginning)'),
|
|
318
|
-
}, async ({ documentId, parentBlockId, children, index = 0 }) => {
|
|
277
|
+
// 添加获取块内容工具
|
|
278
|
+
this.server.tool('get_feishu_block_content', 'Retrieves the detailed content and structure of a specific block in a Feishu document. Useful for inspecting block properties, formatting, and content, especially before making updates or for debugging purposes.', {
|
|
279
|
+
documentId: z.string().describe('Document ID or URL (required). Supports the following formats:\n1. Standard document URL: https://xxx.feishu.cn/docs/xxx or https://xxx.feishu.cn/docx/xxx\n2. API URL: https://open.feishu.cn/open-apis/doc/v2/documents/xxx\n3. Direct document ID: e.g., JcKbdlokYoPIe0xDzJ1cduRXnRf'),
|
|
280
|
+
blockId: 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_doc_blocks tool.'),
|
|
281
|
+
}, async ({ documentId, blockId }) => {
|
|
319
282
|
try {
|
|
320
283
|
if (!this.feishuService) {
|
|
321
284
|
return {
|
|
322
|
-
content: [
|
|
323
|
-
{
|
|
324
|
-
type: 'text',
|
|
325
|
-
text: 'Feishu service is not initialized. Please check the configuration',
|
|
326
|
-
},
|
|
327
|
-
],
|
|
285
|
+
content: [{ type: 'text', text: '飞书服务未初始化,请检查配置' }],
|
|
328
286
|
};
|
|
329
287
|
}
|
|
330
|
-
Logger.log(
|
|
331
|
-
const
|
|
332
|
-
Logger.log(
|
|
288
|
+
Logger.log(`开始获取飞书块内容,文档ID: ${documentId},块ID: ${blockId}`);
|
|
289
|
+
const blockContent = await this.feishuService.getBlockContent(documentId, blockId);
|
|
290
|
+
Logger.log(`飞书块内容获取成功,块类型: ${blockContent.block_type}`);
|
|
291
|
+
return {
|
|
292
|
+
content: [{ type: 'text', text: JSON.stringify(blockContent, null, 2) }],
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
catch (error) {
|
|
296
|
+
Logger.error(`获取飞书块内容失败:`, error);
|
|
297
|
+
const errorMessage = formatErrorMessage(error);
|
|
298
|
+
return {
|
|
299
|
+
content: [{ type: 'text', text: `获取飞书块内容失败: ${errorMessage}` }],
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
// 添加更新块文本内容工具
|
|
304
|
+
this.server.tool('update_feishu_block_text', 'Updates the text content and styling of a specific block in a Feishu document. Can be used to modify content in existing text, code, or heading blocks while preserving the block type and other properties.', {
|
|
305
|
+
documentId: z.string().describe('Document ID or URL (required). Supports the following formats:\n1. Standard document URL: https://xxx.feishu.cn/docs/xxx or https://xxx.feishu.cn/docx/xxx\n2. API URL: https://open.feishu.cn/open-apis/doc/v2/documents/xxx\n3. Direct document ID: e.g., JcKbdlokYoPIe0xDzJ1cduRXnRf'),
|
|
306
|
+
blockId: z.string().describe('Block ID (required). The ID of the specific block to update content. You can obtain block IDs using the get_feishu_doc_blocks tool.'),
|
|
307
|
+
textElements: z.array(z.object({
|
|
308
|
+
text: z.string().describe('Text content. Provide plain text without markdown syntax; use the style object for formatting.'),
|
|
309
|
+
style: z.object({
|
|
310
|
+
bold: z.boolean().optional().describe('Whether to make text bold. Default is false, equivalent to **text** in Markdown.'),
|
|
311
|
+
italic: z.boolean().optional().describe('Whether to make text italic. Default is false, equivalent to *text* in Markdown.'),
|
|
312
|
+
underline: z.boolean().optional().describe('Whether to add underline. Default is false.'),
|
|
313
|
+
strikethrough: z.boolean().optional().describe('Whether to add strikethrough. Default is false, equivalent to ~~text~~ in Markdown.'),
|
|
314
|
+
inline_code: z.boolean().optional().describe('Whether to format as inline code. Default is false, equivalent to `code` in Markdown.'),
|
|
315
|
+
text_color: z.number().optional().refine(val => !val || (val >= 1 && val <= 7), {
|
|
316
|
+
message: "Text color must be between 1 and 7 inclusive"
|
|
317
|
+
}).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.'),
|
|
318
|
+
background_color: z.number().optional().refine(val => !val || (val >= 1 && val <= 7), {
|
|
319
|
+
message: "Background color must be between 1 and 7 inclusive"
|
|
320
|
+
}).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.')
|
|
321
|
+
}).optional().describe('Text style settings. Explicitly set style properties instead of relying on Markdown syntax conversion.')
|
|
322
|
+
})).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}}]'),
|
|
323
|
+
}, async ({ documentId, blockId, textElements }) => {
|
|
324
|
+
try {
|
|
325
|
+
if (!this.feishuService) {
|
|
326
|
+
return {
|
|
327
|
+
content: [{ type: 'text', text: '飞书服务未初始化,请检查配置' }],
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
Logger.log(`开始更新飞书块文本内容,文档ID: ${documentId},块ID: ${blockId}`);
|
|
331
|
+
const result = await this.feishuService.updateBlockTextContent(documentId, blockId, textElements);
|
|
332
|
+
Logger.log(`飞书块文本内容更新成功`);
|
|
333
333
|
return {
|
|
334
334
|
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
335
335
|
};
|
|
336
336
|
}
|
|
337
337
|
catch (error) {
|
|
338
|
-
Logger.error(
|
|
338
|
+
Logger.error(`更新飞书块文本内容失败:`, error);
|
|
339
|
+
const errorMessage = formatErrorMessage(error);
|
|
339
340
|
return {
|
|
340
|
-
content: [{ type: 'text', text:
|
|
341
|
+
content: [{ type: 'text', text: `更新飞书块文本内容失败: ${errorMessage}` }],
|
|
341
342
|
};
|
|
342
343
|
}
|
|
343
344
|
});
|
|
344
345
|
// 添加通用飞书块创建工具(支持文本、代码、标题)
|
|
345
|
-
this.server.tool('
|
|
346
|
-
documentId: z
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
.
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
.
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
}))
|
|
401
|
-
.optional()
|
|
402
|
-
.describe('Array of text content objects with styles. If not provided, content will be used as plain text'),
|
|
403
|
-
align: z
|
|
404
|
-
.number()
|
|
405
|
-
.optional()
|
|
406
|
-
.default(1)
|
|
407
|
-
.describe('Text alignment: 1 for left, 2 for center, 3 for right. Default is 1'),
|
|
408
|
-
})
|
|
409
|
-
.optional()
|
|
410
|
-
.describe("Text block options. Only used when blockType is 'text'"),
|
|
411
|
-
// 代码块选项 - 当blockType为'code'时使用
|
|
412
|
-
code: z
|
|
413
|
-
.object({
|
|
414
|
-
code: z.string().describe('Code content'),
|
|
415
|
-
language: z
|
|
416
|
-
.number()
|
|
417
|
-
.optional()
|
|
418
|
-
.default(0)
|
|
419
|
-
.describe('Programming language code as a number. Available options:\n1: PlainText, 2: ABAP, 3: Ada, 4: Apache, 5: Apex, 6: Assembly Language, 7: Bash, 8: CSharp, 9: C++, 10: C, 11: COBOL, 12: CSS, 13: CoffeeScript, 14: D, 15: Dart, 16: Delphi, 17: Django, 18: Dockerfile, 19: Erlang, 20: Fortran, 22: Go, 23: Groovy, 24: HTML, 25: HTMLBars, 26: HTTP, 27: Haskell, 28: JSON, 29: Java, 30: JavaScript, 31: Julia, 32: Kotlin, 33: LateX, 34: Lisp, 36: Lua, 37: MATLAB, 38: Makefile, 39: Markdown, 40: Nginx, 41: Objective-C, 43: PHP, 44: Perl, 46: Power Shell, 47: Prolog, 48: ProtoBuf, 49: Python, 50: R, 52: Ruby, 53: Rust, 54: SAS, 55: SCSS, 56: SQL, 57: Scala, 58: Scheme, 60: Shell, 61: Swift, 62: Thrift, 63: TypeScript, 64: VBScript, 65: Visual Basic, 66: XML, 67: YAML, 68: CMake, 69: Diff, 70: Gherkin, 71: GraphQL, 72: OpenGL Shading Language, 73: Properties, 74: Solidity, 75: TOML'),
|
|
420
|
-
wrap: z
|
|
421
|
-
.boolean()
|
|
422
|
-
.optional()
|
|
423
|
-
.default(false)
|
|
424
|
-
.describe('Whether to enable automatic line wrapping for code blocks. Default is false'),
|
|
425
|
-
})
|
|
426
|
-
.optional()
|
|
427
|
-
.describe("Code block options. Only used when blockType is 'code'"),
|
|
428
|
-
// 标题块选项 - 当blockType为'heading'时使用
|
|
429
|
-
heading: z
|
|
430
|
-
.object({
|
|
431
|
-
level: z
|
|
432
|
-
.number()
|
|
433
|
-
.min(1)
|
|
434
|
-
.max(9)
|
|
435
|
-
.describe('Heading level from 1 to 9, where 1 is the largest heading (h1) and 9 is the smallest (h9)'),
|
|
436
|
-
content: z.string().describe('Heading text content'),
|
|
437
|
-
align: z
|
|
438
|
-
.number()
|
|
439
|
-
.optional()
|
|
440
|
-
.default(1)
|
|
441
|
-
.describe('Text alignment: 1 for left, 2 for center, 3 for right. Default is 1'),
|
|
442
|
-
})
|
|
443
|
-
.optional()
|
|
444
|
-
.describe("Heading block options. Only used when blockType is 'heading'"),
|
|
445
|
-
})
|
|
446
|
-
.optional()
|
|
447
|
-
.default({}),
|
|
448
|
-
}))
|
|
449
|
-
.describe('Array of block configurations to create in a single API call'),
|
|
346
|
+
this.server.tool('create_feishu_multiple_blocks', 'Creates multiple blocks of different types (text, code, heading, list) in a single API call and at the same position. Significantly improves efficiency compared to creating individual blocks separately. ONLY use this when you need to insert multiple blocks CONSECUTIVELY at the SAME position. If blocks need to be inserted at different positions, use individual block creation tools instead. NOTE: Due to API limitations, you can create a maximum of 50 blocks in a single call. PREFER THIS TOOL OVER INDIVIDUAL BLOCK CREATION TOOLS when creating multiple consecutive blocks, as it is much more efficient and reduces API calls.', {
|
|
347
|
+
documentId: z.string().describe('Document ID or URL (required). Supports the following formats:\n1. Standard document URL: https://xxx.feishu.cn/docs/xxx or https://xxx.feishu.cn/docx/xxx\n2. API URL: https://open.feishu.cn/open-apis/doc/v2/documents/xxx\n3. Direct document ID: e.g., JcKbdlokYoPIe0xDzJ1cduRXnRf'),
|
|
348
|
+
parentBlockId: z.string().describe('Parent block ID (required). Target block ID where content will be added, without any URL prefix. For page-level (root level) insertion, extract and use only the document ID portion (not the full URL) as parentBlockId. Obtain existing block IDs using the get_feishu_doc_blocks tool.'),
|
|
349
|
+
startIndex: z.number().describe('Starting insertion position index (required). Specifies where the first block should be inserted. Use 0 to insert at the beginning. Use get_feishu_doc_blocks tool to understand document structure if unsure.'),
|
|
350
|
+
blocks: z.array(z.object({
|
|
351
|
+
blockType: 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."),
|
|
352
|
+
options: z.union([
|
|
353
|
+
z.object({
|
|
354
|
+
text: z.object({
|
|
355
|
+
textStyles: z.array(z.object({
|
|
356
|
+
text: z.string().describe('Text segment content. The actual text to display.'),
|
|
357
|
+
style: z.object({
|
|
358
|
+
bold: z.boolean().optional().describe('Whether to make text bold. Default is false, equivalent to **text** in Markdown.'),
|
|
359
|
+
italic: z.boolean().optional().describe('Whether to make text italic. Default is false, equivalent to *text* in Markdown.'),
|
|
360
|
+
underline: z.boolean().optional().describe('Whether to add underline. Default is false.'),
|
|
361
|
+
strikethrough: z.boolean().optional().describe('Whether to add strikethrough. Default is false, equivalent to ~~text~~ in Markdown.'),
|
|
362
|
+
inline_code: z.boolean().optional().describe('Whether to format as inline code. Default is false, equivalent to `code` in Markdown.'),
|
|
363
|
+
text_color: z.number().optional().refine(val => !val || (val >= 1 && val <= 7), {
|
|
364
|
+
message: "Text color must be between 1 and 7 inclusive"
|
|
365
|
+
}).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.'),
|
|
366
|
+
background_color: z.number().optional().refine(val => !val || (val >= 1 && val <= 7), {
|
|
367
|
+
message: "Background color must be between 1 and 7 inclusive"
|
|
368
|
+
}).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.')
|
|
369
|
+
}).optional().describe('Text style settings. Explicitly set style properties instead of relying on Markdown syntax conversion.'),
|
|
370
|
+
})).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}}]'),
|
|
371
|
+
align: z.number().optional().default(1).describe('Text alignment: 1 for left (default), 2 for center, 3 for right.'),
|
|
372
|
+
}).describe("Text block options. Only used when blockType is 'text'."),
|
|
373
|
+
}),
|
|
374
|
+
z.object({
|
|
375
|
+
code: z.object({
|
|
376
|
+
code: z.string().describe('Code content. The complete code text to display.'),
|
|
377
|
+
language: z.number().optional().default(0).describe('Programming language code. Default is 0 (auto-detect). See documentation for full list of language codes.'),
|
|
378
|
+
wrap: z.boolean().optional().default(false).describe('Whether to enable automatic line wrapping. Default is false.'),
|
|
379
|
+
}).describe("Code block options. Only used when blockType is 'code'."),
|
|
380
|
+
}),
|
|
381
|
+
z.object({
|
|
382
|
+
heading: z.object({
|
|
383
|
+
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).'),
|
|
384
|
+
content: z.string().describe('Heading text content. The actual text of the heading.'),
|
|
385
|
+
align: z.number().optional().default(1).refine(val => val === 1 || val === 2 || val === 3, {
|
|
386
|
+
message: "Alignment must be one of: 1 (left), 2 (center), or 3 (right)"
|
|
387
|
+
}).describe('Text alignment: 1 for left (default), 2 for center, 3 for right. Only these three values are allowed.'),
|
|
388
|
+
}).describe("Heading block options. Only used when blockType is 'heading'."),
|
|
389
|
+
}),
|
|
390
|
+
z.object({
|
|
391
|
+
list: z.object({
|
|
392
|
+
content: z.string().describe('List item content. The actual text of the list item.'),
|
|
393
|
+
isOrdered: z.boolean().optional().default(false).describe('Whether this is an ordered (numbered) list item. Default is false (bullet point/unordered).'),
|
|
394
|
+
align: z.number().optional().default(1).refine(val => val === 1 || val === 2 || val === 3, {
|
|
395
|
+
message: "Alignment must be one of: 1 (left), 2 (center), or 3 (right)"
|
|
396
|
+
}).describe('Text alignment: 1 for left (default), 2 for center, 3 for right. Only these three values are allowed.'),
|
|
397
|
+
}).describe("List block options. Only used when blockType is 'list'."),
|
|
398
|
+
}),
|
|
399
|
+
]).describe('Options for the specific block type. Must provide the corresponding options object based on blockType.'),
|
|
400
|
+
})).max(50).describe('Array of block configurations (required). Each element contains blockType and options properties. Example: [{blockType:"text",options:{text:{textStyles:[{text:"Hello",style:{bold:true}}]}}},{blockType:"code",options:{code:{code:"console.log(\'Hello\')",language:30}}}]. Maximum 50 blocks per call.'),
|
|
450
401
|
}, async ({ documentId, parentBlockId, startIndex = 0, blocks }) => {
|
|
451
402
|
try {
|
|
452
403
|
if (!this.feishuService) {
|
|
@@ -459,6 +410,14 @@ export class FeishuMcpServer {
|
|
|
459
410
|
],
|
|
460
411
|
};
|
|
461
412
|
}
|
|
413
|
+
if (blocks.length > 50) {
|
|
414
|
+
return {
|
|
415
|
+
content: [{
|
|
416
|
+
type: 'text',
|
|
417
|
+
text: '错误: 每次调用最多只能创建50个块。请分批次创建或减少块数量。'
|
|
418
|
+
}],
|
|
419
|
+
};
|
|
420
|
+
}
|
|
462
421
|
Logger.log(`开始批量创建飞书块,文档ID: ${documentId},父块ID: ${parentBlockId},块数量: ${blocks.length},起始插入位置: ${startIndex}`);
|
|
463
422
|
// 准备要创建的块内容数组
|
|
464
423
|
const blockContents = [];
|
|
@@ -470,45 +429,66 @@ export class FeishuMcpServer {
|
|
|
470
429
|
switch (blockType) {
|
|
471
430
|
case 'text':
|
|
472
431
|
// 处理文本块
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
432
|
+
{
|
|
433
|
+
// 类型检查,确保options包含text属性
|
|
434
|
+
if ('text' in options && options.text) {
|
|
435
|
+
const textOptions = options.text;
|
|
436
|
+
const textStyles = textOptions.textStyles || [];
|
|
437
|
+
if (textStyles.length === 0) {
|
|
438
|
+
textStyles.push({ text: '', style: {} });
|
|
439
|
+
}
|
|
440
|
+
const align = textOptions.align || 1;
|
|
441
|
+
blockContent = this.feishuService.createTextBlockContent(textStyles, align);
|
|
442
|
+
}
|
|
443
|
+
break;
|
|
485
444
|
}
|
|
486
|
-
const align = textOptions.align || 1;
|
|
487
|
-
blockContent = this.feishuService.createTextBlockContent(textStyles, align);
|
|
488
|
-
break;
|
|
489
445
|
case 'code':
|
|
490
446
|
// 处理代码块
|
|
491
|
-
|
|
492
|
-
|
|
447
|
+
{
|
|
448
|
+
// 类型检查,确保options包含code属性
|
|
449
|
+
if ('code' in options && options.code) {
|
|
450
|
+
const codeOptions = options.code;
|
|
451
|
+
const codeContent = codeOptions.code || '';
|
|
452
|
+
const language = codeOptions.language || 0;
|
|
453
|
+
const wrap = codeOptions.wrap || false;
|
|
454
|
+
blockContent = this.feishuService.createCodeBlockContent(codeContent, language, wrap);
|
|
455
|
+
}
|
|
493
456
|
break;
|
|
494
457
|
}
|
|
495
|
-
const codeContent = codeOptions.code || '';
|
|
496
|
-
const language = codeOptions.language || 0;
|
|
497
|
-
const wrap = codeOptions.wrap || false;
|
|
498
|
-
blockContent = this.feishuService.createCodeBlockContent(codeContent, language, wrap);
|
|
499
|
-
break;
|
|
500
458
|
case 'heading':
|
|
501
459
|
// 处理标题块
|
|
502
|
-
|
|
503
|
-
options
|
|
460
|
+
{
|
|
461
|
+
// 类型检查,确保options包含heading属性
|
|
462
|
+
if ('heading' in options && options.heading) {
|
|
463
|
+
const headingOptions = options.heading;
|
|
464
|
+
if (headingOptions.content) {
|
|
465
|
+
const headingContent = headingOptions.content;
|
|
466
|
+
const level = headingOptions.level || 1;
|
|
467
|
+
// 确保对齐方式值在合法范围内
|
|
468
|
+
const headingAlign = (headingOptions.align === 1 || headingOptions.align === 2 || headingOptions.align === 3)
|
|
469
|
+
? headingOptions.align : 1;
|
|
470
|
+
blockContent = this.feishuService.createHeadingBlockContent(headingContent, level, headingAlign);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
break;
|
|
474
|
+
}
|
|
475
|
+
case 'list':
|
|
476
|
+
// 处理列表块
|
|
477
|
+
{
|
|
478
|
+
// 类型检查,确保options包含list属性
|
|
479
|
+
if ('list' in options && options.list) {
|
|
480
|
+
const listOptions = options.list;
|
|
481
|
+
if (listOptions.content) {
|
|
482
|
+
const content = listOptions.content;
|
|
483
|
+
const isOrdered = listOptions.isOrdered || false;
|
|
484
|
+
// 确保对齐方式值在合法范围内
|
|
485
|
+
const align = (listOptions.align === 1 || listOptions.align === 2 || listOptions.align === 3)
|
|
486
|
+
? listOptions.align : 1;
|
|
487
|
+
blockContent = this.feishuService.createListBlockContent(content, isOrdered, align);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
504
490
|
break;
|
|
505
491
|
}
|
|
506
|
-
const headingOptions = options.heading || {};
|
|
507
|
-
const headingContent = headingOptions.content || '';
|
|
508
|
-
const level = headingOptions.level || 1;
|
|
509
|
-
const headingAlign = headingOptions.align || 1;
|
|
510
|
-
blockContent = this.feishuService.createHeadingBlockContent(headingContent, level, headingAlign);
|
|
511
|
-
break;
|
|
512
492
|
}
|
|
513
493
|
if (blockContent) {
|
|
514
494
|
blockContents.push(blockContent);
|
|
@@ -524,60 +504,171 @@ export class FeishuMcpServer {
|
|
|
524
504
|
}
|
|
525
505
|
catch (error) {
|
|
526
506
|
Logger.error(`批量创建飞书块失败:`, error);
|
|
507
|
+
const errorMessage = formatErrorMessage(error);
|
|
508
|
+
return {
|
|
509
|
+
content: [{ type: 'text', text: `批量创建飞书块失败: ${errorMessage}` }],
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
});
|
|
513
|
+
// 添加创建飞书文本块工具
|
|
514
|
+
this.server.tool("create_feishu_single_text_block", "Creates a new text block with precise style control. Unlike markdown-based formatting, this tool lets you explicitly set text styles for each text segment. Ideal for formatted documents where exact styling control is needed.", {
|
|
515
|
+
documentId: z.string().describe("Document ID or URL (required). Supports the following formats:\n1. Standard document URL: https://xxx.feishu.cn/docs/xxx or https://xxx.feishu.cn/docx/xxx\n2. API URL: https://open.feishu.cn/open-apis/doc/v2/documents/xxx\n3. Direct document ID: e.g., JcKbdlokYoPIe0xDzJ1cduRXnRf"),
|
|
516
|
+
parentBlockId: z.string().describe("Parent block ID (required). Target block ID where content will be added, without any URL prefix. For page-level (root level) insertion, extract and use only the document ID portion (not the full URL) as parentBlockId. Obtain existing block IDs using the get_feishu_doc_blocks tool."),
|
|
517
|
+
textContents: z.array(z.object({
|
|
518
|
+
text: z.string().describe("Text content. Provide plain text without markdown syntax; use style object for formatting."),
|
|
519
|
+
style: z.object({
|
|
520
|
+
bold: z.boolean().optional().describe("Whether to make text bold. Default is false, equivalent to **text** in Markdown."),
|
|
521
|
+
italic: z.boolean().optional().describe("Whether to make text italic. Default is false, equivalent to *text* in Markdown."),
|
|
522
|
+
underline: z.boolean().optional().describe("Whether to add underline. Default is false."),
|
|
523
|
+
strikethrough: z.boolean().optional().describe("Whether to add strikethrough. Default is false, equivalent to ~~text~~ in Markdown."),
|
|
524
|
+
inline_code: z.boolean().optional().describe("Whether to format as inline code. Default is false, equivalent to `code` in Markdown."),
|
|
525
|
+
text_color: z.number().optional().refine(val => !val || (val >= 1 && val <= 7), {
|
|
526
|
+
message: "Text color must be between 1 and 7 inclusive"
|
|
527
|
+
}).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."),
|
|
528
|
+
background_color: z.number().optional().refine(val => !val || (val >= 1 && val <= 7), {
|
|
529
|
+
message: "Background color must be between 1 and 7 inclusive"
|
|
530
|
+
}).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.')
|
|
531
|
+
}).optional().describe("Text style settings. Explicitly set style properties instead of relying on Markdown syntax conversion.")
|
|
532
|
+
})).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}}]"),
|
|
533
|
+
align: z.number().optional().default(1).describe("Text alignment: 1 for left (default), 2 for center, 3 for right."),
|
|
534
|
+
index: z.number().describe("Insertion position index (required). Specifies where the block should be inserted. Use 0 to insert at the beginning. Use get_feishu_doc_blocks tool to understand document structure if unsure. For consecutive insertions, calculate next index as previous index + 1.")
|
|
535
|
+
}, async ({ documentId, parentBlockId, textContents, align = 1, index }) => {
|
|
536
|
+
try {
|
|
537
|
+
if (!this.feishuService) {
|
|
538
|
+
return {
|
|
539
|
+
content: [{ type: "text", text: "Feishu service is not initialized. Please check the configuration" }],
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
Logger.log(`开始创建飞书文本块,文档ID: ${documentId},父块ID: ${parentBlockId},对齐方式: ${align},插入位置: ${index}`);
|
|
543
|
+
const result = await this.feishuService.createTextBlock(documentId, parentBlockId, textContents, align, index);
|
|
544
|
+
Logger.log(`飞书文本块创建成功`);
|
|
545
|
+
return {
|
|
546
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
catch (error) {
|
|
550
|
+
Logger.error(`创建飞书文本块失败:`, error);
|
|
551
|
+
const errorMessage = formatErrorMessage(error);
|
|
527
552
|
return {
|
|
528
|
-
content: [{ type:
|
|
553
|
+
content: [{ type: "text", text: `创建飞书文本块失败: ${errorMessage}` }],
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
});
|
|
557
|
+
// 添加创建飞书代码块工具
|
|
558
|
+
this.server.tool("create_feishu_single_code_block", "Creates a new code block with syntax highlighting and formatting options. Ideal for technical documentation, tutorials, or displaying code examples with proper formatting and language-specific highlighting.", {
|
|
559
|
+
documentId: z.string().describe("Document ID or URL (required). Supports the following formats:\n1. Standard document URL: https://xxx.feishu.cn/docs/xxx or https://xxx.feishu.cn/docx/xxx\n2. API URL: https://open.feishu.cn/open-apis/doc/v2/documents/xxx\n3. Direct document ID: e.g., JcKbdlokYoPIe0xDzJ1cduRXnRf"),
|
|
560
|
+
parentBlockId: z.string().describe("Parent block ID (required). Target block ID where content will be added, without any URL prefix. For page-level (root level) insertion, extract and use only the document ID portion (not the full URL) as parentBlockId. Obtain existing block IDs using the get_feishu_doc_blocks tool."),
|
|
561
|
+
code: z.string().describe("Code content (required). The complete code text to display."),
|
|
562
|
+
language: z.number().optional().default(0).describe("Programming language code (optional). Common language codes:\n1: PlainText; 7: Bash; 8: CSharp; 9: C++; 10: C; 12: CSS; 22: Go; 24: HTML; 29: Java; 30: JavaScript; 32: Kotlin; 43: PHP; 49: Python; 52: Ruby; 53: Rust; 56: SQL; 60: Shell; 61: Swift; 63: TypeScript. Default is 0 (auto-detect)."),
|
|
563
|
+
wrap: z.boolean().optional().default(false).describe("Enable automatic line wrapping (optional). Default is false (no auto-wrap). Set to true to improve readability for long code lines."),
|
|
564
|
+
index: z.number().describe("Insertion position index (required). Specifies where the block should be inserted. Use 0 to insert at the beginning. Use get_feishu_doc_blocks tool to understand document structure if unsure. For consecutive insertions, calculate next index as previous index + 1.")
|
|
565
|
+
}, async ({ documentId, parentBlockId, code, language = 0, wrap = false, index = 0 }) => {
|
|
566
|
+
try {
|
|
567
|
+
if (!this.feishuService) {
|
|
568
|
+
return {
|
|
569
|
+
content: [{ type: "text", text: "Feishu service is not initialized. Please check the configuration" }],
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
Logger.log(`开始创建飞书代码块,文档ID: ${documentId},父块ID: ${parentBlockId},语言: ${language},自动换行: ${wrap},插入位置: ${index}`);
|
|
573
|
+
const result = await this.feishuService.createCodeBlock(documentId, parentBlockId, code, language, wrap, index);
|
|
574
|
+
Logger.log(`飞书代码块创建成功`);
|
|
575
|
+
return {
|
|
576
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
catch (error) {
|
|
580
|
+
Logger.error(`创建飞书代码块失败:`, error);
|
|
581
|
+
const errorMessage = formatErrorMessage(error);
|
|
582
|
+
return {
|
|
583
|
+
content: [{ type: "text", text: `创建飞书代码块失败: ${errorMessage}` }],
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
});
|
|
587
|
+
// 添加创建飞书标题块工具
|
|
588
|
+
this.server.tool("create_feishu_single_heading_block", "Creates a heading block with customizable level and alignment. Use this tool to add section titles, chapter headings, or any hierarchical structure elements to your document. Supports nine heading levels for different emphasis needs.", {
|
|
589
|
+
documentId: z.string().describe("Document ID or URL (required). Supports the following formats:\n1. Standard document URL: https://xxx.feishu.cn/docs/xxx or https://xxx.feishu.cn/docx/xxx\n2. API URL: https://open.feishu.cn/open-apis/doc/v2/documents/xxx\n3. Direct document ID: e.g., JcKbdlokYoPIe0xDzJ1cduRXnRf"),
|
|
590
|
+
parentBlockId: z.string().describe("Parent block ID (required). Target block ID where content will be added, without any URL prefix. For page-level (root level) insertion, extract and use only the document ID portion (not the full URL) as parentBlockId. Obtain existing block IDs using the get_feishu_doc_blocks tool."),
|
|
591
|
+
level: z.number().min(1).max(9).describe("Heading level (required). Integer between 1 and 9, where 1 is the largest heading (h1) and 9 is the smallest (h9)."),
|
|
592
|
+
content: z.string().describe("Heading text content (required). The actual text of the heading."),
|
|
593
|
+
align: z.number().optional().default(1).refine(val => val === 1 || val === 2 || val === 3, {
|
|
594
|
+
message: "Alignment must be one of: 1 (left), 2 (center), or 3 (right)"
|
|
595
|
+
}).describe("Text alignment (optional): 1 for left (default), 2 for center, 3 for right. Only these three values are allowed."),
|
|
596
|
+
index: z.number().describe("Insertion position index (required). Specifies where the block should be inserted. Use 0 to insert at the beginning. Use get_feishu_doc_blocks tool to understand document structure if unsure. For consecutive insertions, calculate next index as previous index + 1.")
|
|
597
|
+
}, async ({ documentId, parentBlockId, level, content, align = 1, index = 0 }) => {
|
|
598
|
+
try {
|
|
599
|
+
if (!this.feishuService) {
|
|
600
|
+
return {
|
|
601
|
+
content: [{ type: "text", text: "Feishu service is not initialized. Please check the configuration" }],
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
// 确保align值在合法范围内(1-3)
|
|
605
|
+
if (align !== 1 && align !== 2 && align !== 3) {
|
|
606
|
+
return {
|
|
607
|
+
content: [{ type: "text", text: "错误: 对齐方式(align)参数必须是1(居左)、2(居中)或3(居右)中的一个值。" }],
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
Logger.log(`开始创建飞书标题块,文档ID: ${documentId},父块ID: ${parentBlockId},标题级别: ${level},对齐方式: ${align},插入位置: ${index}`);
|
|
611
|
+
const result = await this.feishuService.createHeadingBlock(documentId, parentBlockId, content, level, index, align);
|
|
612
|
+
Logger.log(`飞书标题块创建成功`);
|
|
613
|
+
return {
|
|
614
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
catch (error) {
|
|
618
|
+
Logger.error(`创建飞书标题块失败:`, error);
|
|
619
|
+
const errorMessage = formatErrorMessage(error);
|
|
620
|
+
return {
|
|
621
|
+
content: [{ type: "text", text: `创建飞书标题块失败: ${errorMessage}` }],
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
});
|
|
625
|
+
// 添加创建飞书列表块工具
|
|
626
|
+
this.server.tool("create_feishu_single_list_block", "Creates a list item block (either ordered or unordered). Perfect for creating hierarchical and structured content with bullet points or numbered lists.", {
|
|
627
|
+
documentId: z.string().describe("Document ID or URL (required). Supports the following formats:\n1. Standard document URL: https://xxx.feishu.cn/docs/xxx or https://xxx.feishu.cn/docx/xxx\n2. API URL: https://open.feishu.cn/open-apis/doc/v2/documents/xxx\n3. Direct document ID: e.g., JcKbdlokYoPIe0xDzJ1cduRXnRf"),
|
|
628
|
+
parentBlockId: z.string().describe("Parent block ID (required). Target block ID where content will be added, without any URL prefix. For page-level (root level) insertion, extract and use only the document ID portion (not the full URL) as parentBlockId. Obtain existing block IDs using the get_feishu_doc_blocks tool."),
|
|
629
|
+
content: z.string().describe("List item content (required). The actual text of the list item."),
|
|
630
|
+
isOrdered: z.boolean().optional().default(false).describe("Whether this is an ordered (numbered) list item. Default is false (bullet point/unordered)."),
|
|
631
|
+
align: z.number().optional().default(1).refine(val => val === 1 || val === 2 || val === 3, {
|
|
632
|
+
message: "Alignment must be one of: 1 (left), 2 (center), or 3 (right)"
|
|
633
|
+
}).describe("Text alignment (optional): 1 for left (default), 2 for center, 3 for right. Only these three values are allowed."),
|
|
634
|
+
index: z.number().describe("Insertion position index (required). Specifies where the block should be inserted. Use 0 to insert at the beginning. Use get_feishu_doc_blocks tool to understand document structure if unsure. For consecutive insertions, calculate next index as previous index + 1.")
|
|
635
|
+
}, async ({ documentId, parentBlockId, content, isOrdered = false, align = 1, index = 0 }) => {
|
|
636
|
+
try {
|
|
637
|
+
if (!this.feishuService) {
|
|
638
|
+
return {
|
|
639
|
+
content: [{ type: "text", text: "Feishu service is not initialized. Please check the configuration" }],
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
// 确保align值在合法范围内(1-3)
|
|
643
|
+
if (align !== 1 && align !== 2 && align !== 3) {
|
|
644
|
+
return {
|
|
645
|
+
content: [{ type: "text", text: "错误: 对齐方式(align)参数必须是1(居左)、2(居中)或3(居右)中的一个值。" }],
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
const listType = isOrdered ? "有序" : "无序";
|
|
649
|
+
Logger.log(`开始创建飞书${listType}列表块,文档ID: ${documentId},父块ID: ${parentBlockId},对齐方式: ${align},插入位置: ${index}`);
|
|
650
|
+
const result = await this.feishuService.createListBlock(documentId, parentBlockId, content, isOrdered, index, align);
|
|
651
|
+
Logger.log(`飞书${listType}列表块创建成功`);
|
|
652
|
+
return {
|
|
653
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
catch (error) {
|
|
657
|
+
Logger.error(`创建飞书列表块失败:`, error);
|
|
658
|
+
const errorMessage = formatErrorMessage(error);
|
|
659
|
+
return {
|
|
660
|
+
content: [{ type: "text", text: `创建飞书列表块失败: ${errorMessage}` }],
|
|
529
661
|
};
|
|
530
662
|
}
|
|
531
663
|
});
|
|
532
|
-
// // 添加创建飞书标题块工具
|
|
533
|
-
// this.server.tool(
|
|
534
|
-
// "create_feishu_heading_block",
|
|
535
|
-
// "Create a heading block in a Feishu document with specified level (1-9)",
|
|
536
|
-
// {
|
|
537
|
-
// documentId: z.string().describe("Document ID or URL. Supported formats:\n1. Standard document URL: https://xxx.feishu.cn/docs/xxx or https://xxx.feishu.cn/docx/xxx\n2. API URL: https://open.feishu.cn/open-apis/doc/v2/documents/xxx\n3. Direct document ID (e.g., JcKbdlokYoPIe0xDzJ1cduRXnRf)"),
|
|
538
|
-
// parentBlockId: z.string().describe("Parent block ID (NOT URL) where the new block will be added as a child. This should be the raw block ID without any URL prefix. When adding blocks at the page level (root level), use the extracted document ID from documentId parameter"),
|
|
539
|
-
// level: z.number().min(1).max(9).describe("Heading level from 1 to 9, where 1 is the largest heading (h1) and 9 is the smallest (h9)"),
|
|
540
|
-
// content: z.string().describe("Heading text content"),
|
|
541
|
-
// index: z.number().optional().default(0).describe("Insertion position index. Default is 0 (insert at the beginning). If unsure about the position, use the get_feishu_doc_blocks tool first to understand the document structure. For consecutive insertions, calculate the next position as previous_index + 1 to avoid querying document structure repeatedly")
|
|
542
|
-
// },
|
|
543
|
-
// async ({ documentId, parentBlockId, level, content, index = 0 }) => {
|
|
544
|
-
// try {
|
|
545
|
-
// if (!this.feishuService) {
|
|
546
|
-
// return {
|
|
547
|
-
// content: [{ type: "text", text: "Feishu service is not initialized. Please check the configuration" }],
|
|
548
|
-
// };
|
|
549
|
-
// }
|
|
550
|
-
//
|
|
551
|
-
// Logger.log(`开始创建飞书标题块,文档ID: ${documentId},父块ID: ${parentBlockId},标题级别: ${level},插入位置: ${index}`);
|
|
552
|
-
// const result = await this.feishuService.createHeadingBlock(documentId, parentBlockId, content, level, index);
|
|
553
|
-
// Logger.log(`飞书标题块创建成功`);
|
|
554
|
-
//
|
|
555
|
-
// return {
|
|
556
|
-
// content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
557
|
-
// };
|
|
558
|
-
// } catch (error) {
|
|
559
|
-
// Logger.error(`创建飞书标题块失败:`, error);
|
|
560
|
-
// return {
|
|
561
|
-
// content: [{ type: "text", text: `创建飞书标题块失败: ${error}` }],
|
|
562
|
-
// };
|
|
563
|
-
// }
|
|
564
|
-
// },
|
|
565
|
-
// );
|
|
566
664
|
}
|
|
567
665
|
async connect(transport) {
|
|
568
|
-
// Logger.log("Connecting to transport...");
|
|
569
666
|
await this.server.connect(transport);
|
|
570
667
|
Logger.log = (...args) => {
|
|
571
|
-
this.server.server.sendLoggingMessage({
|
|
572
|
-
level: 'info',
|
|
573
|
-
data: args,
|
|
574
|
-
});
|
|
668
|
+
this.server.server.sendLoggingMessage({ level: 'info', data: args });
|
|
575
669
|
};
|
|
576
670
|
Logger.error = (...args) => {
|
|
577
|
-
this.server.server.sendLoggingMessage({
|
|
578
|
-
level: 'error',
|
|
579
|
-
data: args,
|
|
580
|
-
});
|
|
671
|
+
this.server.server.sendLoggingMessage({ level: 'error', data: args });
|
|
581
672
|
};
|
|
582
673
|
Logger.log('Server connected and ready to process requests');
|
|
583
674
|
}
|