| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242 |
- #!/usr/bin/env node
- import { Server } from "@modelcontextprotocol/sdk/server/index.js";
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
- import {
- CallToolRequestSchema,
- ListToolsRequestSchema,
- McpError,
- ErrorCode,
- } from "@modelcontextprotocol/sdk/types.js";
- import path from "node:path";
- import { fileURLToPath } from "node:url";
- import {
- FeishuClient,
- inferExportType,
- resolveWorkspacePath,
- } from "./feishu-client.js";
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
- const WORKSPACE_ROOT =
- process.env.FEISHU_WORKSPACE_ROOT ||
- path.resolve(__dirname, "..", "..");
- const DEFAULT_FOLDER = process.env.FEISHU_DEFAULT_FOLDER_TOKEN || "";
- let client;
- try {
- client = FeishuClient.fromEnv();
- } catch (error) {
- console.error("[feishu-drive-mcp] 初始化失败:", error.message);
- process.exit(1);
- }
- function textResult(obj) {
- return {
- content: [{ type: "text", text: JSON.stringify(obj, null, 2) }],
- };
- }
- function toolError(message) {
- return {
- isError: true,
- content: [{ type: "text", text: message }],
- };
- }
- const server = new Server(
- { name: "feishu-drive-mcp", version: "0.1.0" },
- { capabilities: { tools: {} } }
- );
- server.setRequestHandler(ListToolsRequestSchema, async () => ({
- tools: [
- {
- name: "feishu_list_folder",
- description:
- "列出飞书云盘文件夹内容。返回 file_token、名称、类型(docx/sheet/file/folder 等)。folder_token 不传则用 FEISHU_DEFAULT_FOLDER_TOKEN。",
- inputSchema: {
- type: "object",
- properties: {
- folder_token: {
- type: "string",
- description: "文件夹 token,如链接 Ks1RfygMblbsNEdB3JdcxHCnnFb",
- },
- page_token: { type: "string", description: "分页 token" },
- },
- required: [],
- },
- },
- {
- name: "feishu_get_file_meta",
- description: "获取云盘文件或在线文档的元信息(名称、类型、父文件夹等)",
- inputSchema: {
- type: "object",
- properties: {
- file_token: { type: "string", description: "文件 token" },
- doc_type: {
- type: "string",
- description: "可选:file/docx/doc/sheet/bitable/slides/folder",
- },
- },
- required: ["file_token"],
- },
- },
- {
- name: "feishu_download_file",
- description:
- "下载云盘中的 Office 文件(docx/xlsx/pptx 等 file 类型)到工作区相对路径或绝对路径",
- inputSchema: {
- type: "object",
- properties: {
- file_token: { type: "string" },
- save_path: {
- type: "string",
- description: "保存路径,相对仓库根或绝对路径",
- },
- },
- required: ["file_token", "save_path"],
- },
- },
- {
- name: "feishu_upload_file",
- description:
- "上传本地 Office 文件到飞书云盘文件夹;可选 replace_file_token 覆盖旧文件",
- inputSchema: {
- type: "object",
- properties: {
- local_path: { type: "string", description: "本地文件路径" },
- folder_token: {
- type: "string",
- description: "目标文件夹 token,覆盖时可省略",
- },
- file_name: { type: "string", description: "上传后的文件名" },
- replace_file_token: {
- type: "string",
- description: "若提供,上传到同目录后删除此旧文件",
- },
- },
- required: ["local_path"],
- },
- },
- {
- name: "feishu_export_online_doc",
- description:
- "将飞书在线文档/表格/幻灯片导出为 Office 文件并下载(docx/xlsx/pptx/pdf 等)",
- inputSchema: {
- type: "object",
- properties: {
- file_token: { type: "string", description: "在线文档 token" },
- file_extension: {
- type: "string",
- description: "导出格式:docx/pdf/xlsx/csv/pptx 等",
- },
- save_path: { type: "string", description: "保存路径" },
- },
- required: ["file_token", "file_extension", "save_path"],
- },
- },
- {
- name: "feishu_delete_file",
- description: "删除云盘文件(慎用)",
- inputSchema: {
- type: "object",
- properties: {
- file_token: { type: "string" },
- },
- required: ["file_token"],
- },
- },
- ],
- }));
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
- const { name, arguments: args } = request.params;
- try {
- switch (name) {
- case "feishu_list_folder": {
- const folderToken = args?.folder_token || DEFAULT_FOLDER;
- if (!folderToken) {
- throw new McpError(
- ErrorCode.InvalidParams,
- "请提供 folder_token 或配置 FEISHU_DEFAULT_FOLDER_TOKEN"
- );
- }
- const data = await client.listFolder(folderToken, args?.page_token);
- return textResult(data);
- }
- case "feishu_get_file_meta": {
- const data = await client.getFileMeta(
- args.file_token,
- args.doc_type
- );
- return textResult(data);
- }
- case "feishu_download_file": {
- const savePath = resolveWorkspacePath(args.save_path, WORKSPACE_ROOT);
- const data = await client.downloadFile(args.file_token, savePath);
- return textResult(data);
- }
- case "feishu_upload_file": {
- const localPath = resolveWorkspacePath(args.local_path, WORKSPACE_ROOT);
- const folderToken = args.folder_token || DEFAULT_FOLDER || undefined;
- if (!folderToken && !args.replace_file_token) {
- throw new McpError(
- ErrorCode.InvalidParams,
- "请提供 folder_token、replace_file_token 或配置 FEISHU_DEFAULT_FOLDER_TOKEN"
- );
- }
- const data = await client.uploadFile({
- folderToken,
- localPath,
- fileName: args.file_name,
- replaceFileToken: args.replace_file_token,
- });
- return textResult(data);
- }
- case "feishu_export_online_doc": {
- const meta = await client.getFileMeta(args.file_token);
- const hint = inferExportType(meta.type);
- if (!hint) {
- throw new McpError(
- ErrorCode.InvalidParams,
- `类型 ${meta.type} 不支持导出,请用 feishu_download_file 或官方 lark-mcp 在线编辑`
- );
- }
- if (!hint.extensions.includes(args.file_extension)) {
- throw new McpError(
- ErrorCode.InvalidParams,
- `${meta.type} 支持的导出格式: ${hint.extensions.join(", ")}`
- );
- }
- const savePath = resolveWorkspacePath(args.save_path, WORKSPACE_ROOT);
- const data = await client.exportAndDownload({
- token: args.file_token,
- type: hint.type,
- fileExtension: args.file_extension,
- savePath,
- });
- return textResult({ ...data, source_type: meta.type, name: meta.name });
- }
- case "feishu_delete_file": {
- await client.deleteFile(args.file_token);
- return textResult({ deleted: args.file_token });
- }
- default:
- throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
- }
- } catch (error) {
- if (error instanceof McpError) {
- throw error;
- }
- return toolError(error.message || String(error));
- }
- });
- async function main() {
- const transport = new StdioServerTransport();
- await server.connect(transport);
- }
- main().catch((error) => {
- console.error("[feishu-drive-mcp] fatal:", error);
- process.exit(1);
- });
|