UNPKG

@yinzhouzhi/mcp-filesystem-server

Version:

本地文件系统操作服务器 - 基于STDIO通信的MCP服务,支持文件和目录操作

1,093 lines (1,032 loc) 33.8 kB
/** * 本地文件系统操作服务器 - MCP规范实现 * 基于Model Context Protocol SDK */ const { McpServer } = require('@modelcontextprotocol/sdk/server/mcp.js'); const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js'); const { z } = require('zod'); const path = require('path'); const fs = require('fs'); const fileTools = require('./tools/file-tools'); const dirTools = require('./tools/dir-tools'); const lineTools = require('./tools/line-tools'); const watchTools = require('./tools/watch-tools'); const officeTools = require('./tools/office-tools'); const logger = require('./utils/logging'); // 初始化环境 const isDebug = process.env.DEBUG_MODE === 'true'; const logLevel = process.env.LOG_LEVEL || (isDebug ? 'debug' : 'info'); // 配置日志 logger.initialize({ level: logLevel, logToFile: true, logFilePath: path.join(process.cwd(), 'logs', 'filesystem-server.log') }); logger.info('MCP文件系统服务器启动中...'); // 创建MCP服务器实例 const server = new McpServer( { name: 'filesystem-server', version: '1.0.0', description: '本地文件系统操作服务' }, { capabilities: { tools: {} // 启用工具功能 } } ); // 注册文件操作工具 function registerFileTools() { // 读取文件工具 server.tool( 'read_file', '读取文件内容', { path: z.string().describe('文件路径'), encoding: z.string().optional().describe('编码方式,默认utf-8') }, async (params) => { try { // 直接使用本地fs读取文件,以确保正确获取内容 const filePath = params.path; const encoding = params.encoding || 'utf8'; // 检查文件是否存在 if (!fs.existsSync(filePath)) { return { content: [{ type: 'text', text: `文件不存在: ${filePath}` }], isError: true }; } // 读取文件内容 const content = fs.readFileSync(filePath, { encoding }); return { content: [{ type: 'text', text: content }] }; } catch (error) { logger.error(`读取文件工具执行失败: ${error.message}`, { error }); return { content: [{ type: 'text', text: `读取文件失败: ${error.message}` }], isError: true }; } } ); // 写入文件工具 server.tool( 'write_file', '写入文件内容', { path: z.string().describe('文件路径'), content: z.string().describe('文件内容'), encoding: z.string().optional().describe('编码方式,默认utf-8') }, async (params) => { try { const result = await fileTools.writeFile(params); return { content: [{ type: 'text', text: JSON.stringify({ success: true, path: result.path }) }] }; } catch (error) { logger.error(`写入文件工具执行失败: ${error.message}`, { error }); return { content: [{ type: 'text', text: `写入文件失败: ${error.message}` }], isError: true }; } } ); // 追加文件工具 server.tool( 'append_file', '追加文件内容', { path: z.string().describe('文件路径'), content: z.string().describe('追加内容'), encoding: z.string().optional().describe('编码方式,默认utf-8') }, async (params) => { try { const result = await fileTools.appendFile(params); return { content: [{ type: 'text', text: JSON.stringify({ success: true, path: result.path }) }] }; } catch (error) { logger.error(`追加文件工具执行失败: ${error.message}`, { error }); return { content: [{ type: 'text', text: `追加文件失败: ${error.message}` }], isError: true }; } } ); // 删除文件工具 server.tool( 'delete_file', '删除文件', { path: z.string().describe('文件路径') }, async (params) => { try { const result = await fileTools.deleteFile(params); return { content: [{ type: 'text', text: JSON.stringify({ success: true, path: result.path }) }] }; } catch (error) { logger.error(`删除文件工具执行失败: ${error.message}`, { error }); return { content: [{ type: 'text', text: `删除文件失败: ${error.message}` }], isError: true }; } }, { description: '删除文件' } ); // 复制文件工具 server.tool( 'copy_file', '复制文件', { source: z.string().describe('源文件路径'), destination: z.string().describe('目标文件路径'), overwrite: z.boolean().optional().describe('如果目标文件存在是否覆盖') }, async (params) => { try { const result = await fileTools.copyFile(params); return { content: [{ type: 'text', text: JSON.stringify({ success: true, source: result.source, destination: result.destination }) }] }; } catch (error) { logger.error(`复制文件工具执行失败: ${error.message}`, { error }); return { content: [{ type: 'text', text: `复制文件失败: ${error.message}` }], isError: true }; } } ); // 移动文件工具 server.tool( 'move_file', '移动文件', { source: z.string().describe('源文件路径'), destination: z.string().describe('目标文件路径'), overwrite: z.boolean().optional().describe('如果目标文件存在是否覆盖') }, async (params) => { try { const result = await fileTools.moveFile(params); return { content: [{ type: 'text', text: JSON.stringify({ success: true, source: result.source, destination: result.destination }) }] }; } catch (error) { logger.error(`移动文件工具执行失败: ${error.message}`, { error }); return { content: [{ type: 'text', text: `移动文件失败: ${error.message}` }], isError: true }; } } ); // 获取文件信息工具 server.tool( 'get_file_info', '获取文件信息', { path: z.string().describe('文件路径') }, async (params) => { try { const result = await fileTools.getFileInfo(params); return { content: [{ type: 'text', text: JSON.stringify(result) }] }; } catch (error) { logger.error(`获取文件信息工具执行失败: ${error.message}`, { error }); return { content: [{ type: 'text', text: `获取文件信息失败: ${error.message}` }], isError: true }; } } ); // 检查文件是否存在工具 server.tool( 'file_exists', '检查文件是否存在', { path: z.string().describe('文件路径') }, async (params) => { try { const result = await fileTools.fileExists(params); return { content: [{ type: 'text', text: JSON.stringify({ success: true, exists: result.exists, isFile: result.isFile }) }] }; } catch (error) { logger.error(`检查文件存在工具执行失败: ${error.message}`, { error }); return { content: [{ type: 'text', text: `检查文件是否存在失败: ${error.message}` }], isError: true }; } } ); } // 注册目录操作工具 function registerDirectoryTools() { // 列出文件工具 server.tool( 'list_files', '列出目录内容', { path: z.string().describe('目录路径'), recursive: z.boolean().optional().describe('是否递归列出子目录内容') }, async (params) => { try { const result = await dirTools.listFiles(params); return { content: [{ type: 'text', text: JSON.stringify(result) }] }; } catch (error) { logger.error(`列出文件工具执行失败: ${error.message}`, { error }); return { content: [{ type: 'text', text: `列出目录内容失败: ${error.message}` }], isError: true }; } } ); // 创建目录工具 server.tool( 'create_directory', '创建目录', { path: z.string().describe('目录路径'), recursive: z.boolean().optional().describe('是否递归创建父目录') }, async (params) => { try { const result = await dirTools.createDirectory(params); return { content: [{ type: 'text', text: JSON.stringify({ success: true, path: result.path, created: result.created }) }] }; } catch (error) { logger.error(`创建目录工具执行失败: ${error.message}`, { error }); return { content: [{ type: 'text', text: `创建目录失败: ${error.message}` }], isError: true }; } } ); // 删除目录工具 server.tool( 'delete_directory', '删除目录', { path: z.string().describe('目录路径'), recursive: z.boolean().optional().describe('是否递归删除子目录和文件') }, async (params) => { try { const result = await dirTools.deleteDirectory(params); return { content: [{ type: 'text', text: JSON.stringify({ success: true, path: result.path, deleted: result.deleted }) }] }; } catch (error) { logger.error(`删除目录工具执行失败: ${error.message}`, { error }); return { content: [{ type: 'text', text: `删除目录失败: ${error.message}` }], isError: true }; } }, '删除目录' ); // 检查目录是否存在工具 server.tool( 'directory_exists', '检查目录是否存在', { path: z.string().describe('目录路径') }, async (params) => { try { const result = await dirTools.directoryExists(params); return { content: [{ type: 'text', text: JSON.stringify({ success: true, exists: result.exists, isDirectory: result.isDirectory }) }] }; } catch (error) { logger.error(`检查目录存在工具执行失败: ${error.message}`, { error }); return { content: [{ type: 'text', text: `检查目录是否存在失败: ${error.message}` }], isError: true }; } } ); // 获取目录信息工具 server.tool( 'directory_info', '获取目录信息', { path: z.string().describe('目录路径') }, async (params) => { try { const result = await dirTools.getDirectoryInfo(params); return { content: [{ type: 'text', text: JSON.stringify(result) }] }; } catch (error) { logger.error(`获取目录信息工具执行失败: ${error.message}`, { error }); return { content: [{ type: 'text', text: `获取目录信息失败: ${error.message}` }], isError: true }; } } ); } // 注册文件行操作工具 function registerLineTools() { // 读取文件行工具 server.tool( 'read_file_lines', '读取文件行', { path: z.string().describe('文件路径'), start: z.number().int().describe('起始行号(从1开始)'), end: z.number().int().describe('结束行号'), encoding: z.string().optional().describe('编码方式,默认utf-8') }, async (params) => { try { const result = await lineTools.readFileLines(params); return { content: [{ type: 'text', text: JSON.stringify(result) }] }; } catch (error) { logger.error(`读取文件行工具执行失败: ${error.message}`, { error }); return { content: [{ type: 'text', text: `读取文件行失败: ${error.message}` }], isError: true }; } } ); // 统计文件行数工具 server.tool( 'count_file_lines', '统计文件行数', { path: z.string().describe('文件路径') }, async (params) => { try { const result = await lineTools.countFileLines(params); return { content: [{ type: 'text', text: JSON.stringify(result) }] }; } catch (error) { logger.error(`统计文件行数工具执行失败: ${error.message}`, { error }); return { content: [{ type: 'text', text: `统计文件行数失败: ${error.message}` }], isError: true }; } } ); // 搜索文件内容工具 server.tool( 'search_file_content', '搜索文件内容', { path: z.string().describe('文件路径'), pattern: z.string().describe('搜索模式'), regex: z.boolean().optional().describe('是否使用正则表达式'), ignoreCase: z.boolean().optional().describe('是否忽略大小写'), encoding: z.string().optional().describe('编码方式,默认utf-8') }, async (params) => { try { const result = await lineTools.searchFileContent(params); return { content: [{ type: 'text', text: JSON.stringify(result) }] }; } catch (error) { logger.error(`搜索文件内容工具执行失败: ${error.message}`, { error }); return { content: [{ type: 'text', text: `搜索文件内容失败: ${error.message}` }], isError: true }; } } ); } // 注册文件监控工具 function registerWatchTools() { // 监控文件或目录变更工具 server.tool( 'watch_path', '监控文件或目录变更', { path: z.string().describe('要监控的文件或目录路径'), recursive: z.boolean().optional().describe('是否递归监控子目录'), events: z.string().optional().describe('监控的事件类型,逗号分隔') }, async (params) => { try { const result = await watchTools.watchPath(params); return { content: [{ type: 'text', text: JSON.stringify(result) }] }; } catch (error) { logger.error(`监控文件工具执行失败: ${error.message}`, { error }); return { content: [{ type: 'text', text: `监控文件失败: ${error.message}` }], isError: true }; } } ); // 停止监控工具 server.tool( 'stop_watch', '停止文件或目录监控', { watcherId: z.string().describe('监控ID') }, async (params) => { try { const result = await watchTools.stopWatch(params); return { content: [{ type: 'text', text: JSON.stringify(result) }] }; } catch (error) { logger.error(`停止监控工具执行失败: ${error.message}`, { error }); return { content: [{ type: 'text', text: `停止监控失败: ${error.message}` }], isError: true }; } } ); // 获取监控列表工具 server.tool( 'list_watchers', '获取当前活跃的监控列表', {}, async () => { try { const result = await watchTools.listWatchers(); return { content: [{ type: 'text', text: JSON.stringify(result) }] }; } catch (error) { logger.error(`获取监控列表工具执行失败: ${error.message}`, { error }); return { content: [{ type: 'text', text: `获取监控列表失败: ${error.message}` }], isError: true }; } } ); } // 注册Office文件操作工具 function registerOfficeTools() { // Word文档读取工具 server.tool( 'read_word_document', '读取Word文档内容并转换为文本', { path: z.string().describe('Word文件路径(.doc或.docx格式)'), outputFormat: z.enum(['text', 'html']).optional().describe('输出格式,支持text和html,默认text'), extractImages: z.boolean().optional().describe('是否提取图片信息,默认false'), includeStyles: z.boolean().optional().describe('是否包含样式信息,默认false'), pagination: z.object({ pageSize: z.number().int().positive().optional().describe('每页字符数'), pageNum: z.number().int().positive().optional().describe('页码(从1开始)') }).optional().describe('分页参数,用于大文档分页读取'), range: z.object({ startLine: z.number().int().positive().optional().describe('起始行号(从1开始)'), endLine: z.number().int().positive().optional().describe('结束行号') }).optional().describe('读取范围,指定起始行和结束行'), splitByParagraphs: z.boolean().optional().describe('是否按段落拆分返回,默认false') }, async (params) => { try { const result = await officeTools.readWordDocument(params); // 根据输出格式返回不同结构 if (params.outputFormat === 'html') { // office-tools已经转换了内容为HTML格式,直接使用 return { content: [{ type: 'html', text: result.content }] }; } else { // 构建响应内容 const responseContent = [{ type: 'text', text: result.content }]; // 添加分页信息 if (result.pagination) { responseContent.push({ type: 'text', text: `\n\n页码信息: 第${result.pagination.pageNum}页/共${result.pagination.totalPages}页,每页${result.pagination.pageSize}字符` }); } // 添加范围信息 if (result.range) { responseContent.push({ type: 'text', text: `\n\n行范围信息: 第${result.range.startLine}行-第${result.range.endLine}行,总行数${result.range.totalLines}` }); } // 添加段落信息 if (result.paragraphs) { responseContent.push({ type: 'text', text: `\n\n文档共${result.paragraphs.length}个段落` }); } // 添加图片信息 if (result.images && result.images.length > 0) { responseContent.push({ type: 'text', text: `\n\n图片数量: ${result.images.length}` }); } return { content: responseContent }; } } catch (error) { logger.error(`读取Word文档工具执行失败: ${error.message}`, { error }); return { content: [{ type: 'text', text: `读取Word文档失败: ${error.message}` }], isError: true }; } } ); // Excel文件读取工具 server.tool( 'read_excel_file', '读取Excel文件内容', { path: z.string().describe('Excel文件路径(.xlsx、.xls或.csv格式)'), sheet: z.union([ z.string(), z.number() ]).optional().describe('要读取的工作表名称或索引,默认读取第一个工作表'), outputFormat: z.enum(['json', 'csv', 'array']).optional().describe('输出格式,支持json、csv和array,默认array'), range: z.object({ startRow: z.number().optional(), endRow: z.number().optional(), startCol: z.number().optional(), endCol: z.number().optional() }).optional().describe('读取范围,例如{startRow:1, endRow:10, startCol:1, endCol:5}'), includeFormulas: z.boolean().optional().describe('是否包含公式,默认false'), headerRow: z.boolean().optional().describe('是否将第一行作为表头,默认false') }, async (params) => { try { const result = await officeTools.readExcelFile(params); // 根据输出格式返回不同结构 if (params.outputFormat === 'csv') { return { content: [{ type: 'text', text: result.data }] }; } else { return { content: [{ type: 'text', text: JSON.stringify({ sheet: result.sheet.name, rowCount: result.range.rowCount, colCount: result.range.colCount, headers: result.headers, data: result.data.slice(0, 10), // 只返回前10行用于预览 totalRows: result.data.length, availableSheets: result.availableSheets }, null, 2) }] }; } } catch (error) { logger.error(`读取Excel文件工具执行失败: ${error.message}`, { error }); return { content: [{ type: 'text', text: `读取Excel文件失败: ${error.message}` }], isError: true }; } } ); } // 注册服务器状态工具 server.tool( 'get_server_status', '获取服务器状态', {}, async () => { try { const packageJson = require('../package.json'); const status = { status: 'running', version: packageJson.version, pid: process.pid, uptime: process.uptime(), memoryUsage: process.memoryUsage(), isDebug }; return { content: [{ type: 'text', text: JSON.stringify(status) }] }; } catch (error) { logger.error(`获取服务器状态失败: ${error.message}`, { error }); return { content: [{ type: 'text', text: `获取服务器状态失败: ${error.message}` }], isError: true }; } } ); // 注册文件缓存工具 function registerCacheTools() { // 获取缓存统计信息 server.tool( 'get_cache_stats', '获取文件缓存统计数据', {}, async () => { try { const stats = fileTools.getCacheStats(); return { content: [{ type: 'text', text: JSON.stringify(stats) }] }; } catch (error) { logger.error(`获取缓存统计工具执行失败: ${error.message}`, { error }); return { content: [{ type: 'text', text: `获取缓存统计失败: ${error.message}` }], isError: true }; } } ); // 设置文件缓存配置 server.tool( 'set_file_cache_config', '设置文件缓存配置', { enabled: z.boolean().optional().describe('是否启用缓存'), maxSize: z.number().optional().describe('最大缓存数量'), maxAge: z.number().optional().describe('缓存过期时间(毫秒)') }, async (params) => { try { const result = fileTools.setConfig(params); return { content: [{ type: 'text', text: JSON.stringify(result) }] }; } catch (error) { logger.error(`设置缓存配置工具执行失败: ${error.message}`, { error }); return { content: [{ type: 'text', text: `设置缓存配置失败: ${error.message}` }], isError: true }; } } ); // 清除文件缓存 server.tool( 'clear_cache', '清除文件缓存', {}, async () => { try { const result = fileTools.clearCache(); return { content: [{ type: 'text', text: JSON.stringify(result) }] }; } catch (error) { logger.error(`清除缓存工具执行失败: ${error.message}`, { error }); return { content: [{ type: 'text', text: `清除缓存失败: ${error.message}` }], isError: true }; } } ); } // 注册监控回调相关工具 function registerMonitorCallbackTools() { // 设置文件变更回调 server.tool( 'set_file_change_callback', '设置文件变更回调', { filePath: z.string().describe('要监控的文件路径'), eventTypes: z.string().optional().describe('要监控的事件类型,逗号分隔,如:change,unlink') }, async (params) => { try { const result = await watchTools.setFileChangeCallback( params.filePath, (event, path) => { logger.info(`文件变更事件: ${event}, 路径: ${path}`); // 这里可以添加更多回调处理逻辑 }, { events: params.eventTypes ? params.eventTypes.split(',') : ['change'], ignoreInitial: true } ); if (!result) { return { content: [{ type: 'text', text: JSON.stringify({ success: false, message: '设置文件变更回调失败' }) }], isError: true }; } return { content: [{ type: 'text', text: JSON.stringify({ success: true, watcherId: result._poolId || 'unknown', message: `正在监控文件: ${params.filePath}` }) }] }; } catch (error) { logger.error(`设置文件变更回调工具执行失败: ${error.message}`, { error }); return { content: [{ type: 'text', text: `设置文件变更回调失败: ${error.message}` }], isError: true }; } } ); // 设置目录变更回调 server.tool( 'set_dir_change_callback', '设置目录变更回调', { dirPath: z.string().describe('要监控的目录路径'), eventTypes: z.string().optional().describe('要监控的事件类型,逗号分隔,如:add,change,unlink,addDir,unlinkDir'), recursive: z.boolean().optional().describe('是否递归监控子目录') }, async (params) => { try { const result = await watchTools.setDirChangeCallback( params.dirPath, (event, path) => { logger.info(`目录变更事件: ${event}, 路径: ${path}`); // 这里可以添加更多回调处理逻辑 }, { events: params.eventTypes ? params.eventTypes.split(',') : ['add', 'change', 'unlink'], ignoreInitial: false, recursive: params.recursive !== false } ); if (!result) { return { content: [{ type: 'text', text: JSON.stringify({ success: false, message: '设置目录变更回调失败' }) }], isError: true }; } return { content: [{ type: 'text', text: JSON.stringify({ success: true, watcherId: result._poolId || 'unknown', message: `正在监控目录: ${params.dirPath}` }) }] }; } catch (error) { logger.error(`设置目录变更回调工具执行失败: ${error.message}`, { error }); return { content: [{ type: 'text', text: `设置目录变更回调失败: ${error.message}` }], isError: true }; } } ); // 关闭所有监控器 server.tool( 'close_all_watchers', '关闭所有监控器', {}, async () => { try { const closedCount = watchTools.closeAllWatchers(); return { content: [{ type: 'text', text: JSON.stringify({ success: true, closed: closedCount, message: `已关闭所有监控器: ${closedCount}个` }) }] }; } catch (error) { logger.error(`关闭所有监控器工具执行失败: ${error.message}`, { error }); return { content: [{ type: 'text', text: `关闭所有监控器失败: ${error.message}` }], isError: true }; } } ); // 获取监控池状态 server.tool( 'get_monitor_pool_status', '获取监控池状态', {}, async () => { try { const status = watchTools.getMonitorPoolStatus(); return { content: [{ type: 'text', text: JSON.stringify(status) }] }; } catch (error) { logger.error(`获取监控池状态工具执行失败: ${error.message}`, { error }); return { content: [{ type: 'text', text: `获取监控池状态失败: ${error.message}` }], isError: true }; } } ); } // 启动MCP服务器的主函数 async function startServer(options = {}) { try { // 确保传入的日志路径是正确的文件路径 let logConfig = { level: options.logLevel || logLevel, logToFile: true, // 如果传入的是logDir,则使用它来构建日志文件路径;如果传入的是logPath,也兼容处理 logFilePath: options.logDir ? path.join(options.logDir, 'filesystem-server.log') : (options.logPath ? (fs.statSync(options.logPath).isDirectory() ? path.join(options.logPath, 'filesystem-server.log') : options.logPath) : path.join(process.cwd(), 'logs', 'filesystem-server.log')), rotateLogFiles: true, maxLogFileSize: 10 * 1024 * 1024, // 10MB maxLogFiles: 5, compressRotatedLogs: true, rotationType: 'size' }; // 确保日志目录存在 const logDir = path.dirname(logConfig.logFilePath); if (!fs.existsSync(logDir)) { fs.mkdirSync(logDir, { recursive: true }); } // 配置日志 logger.initialize(logConfig); // 记录启动信息 logger.info('启动文件系统MCP服务器', { options, logConfig, pid: process.pid, platform: process.platform, nodeVersion: process.version }); // 注册所有工具 registerFileTools(); registerDirectoryTools(); registerLineTools(); registerWatchTools(); registerOfficeTools(); registerCacheTools(); registerMonitorCallbackTools(); // 创建STDIO传输层 const transport = new StdioServerTransport(); // 连接传输层 await server.connect(transport); logger.info('MCP文件系统服务器已启动,等待命令...'); // 注册进程退出处理 process.on('SIGINT', async () => { logger.info('收到退出信号,正在关闭服务器...'); try { // 安全关闭服务器连接 if (server && typeof server.disconnect === 'function') { await server.disconnect(); } // 关闭日志系统 logger.shutdown(); } catch (e) { logger.error(`关闭服务器时出错: ${e.message}`); } process.exit(0); }); process.on('SIGTERM', async () => { logger.info('收到终止信号,正在关闭服务器...'); try { // 安全关闭服务器连接 if (server && typeof server.disconnect === 'function') { await server.disconnect(); } // 关闭日志系统 logger.shutdown(); } catch (e) { logger.error(`关闭服务器时出错: ${e.message}`); } process.exit(0); }); // 注册未捕获的异常处理 process.on('uncaughtException', (error) => { logger.error(`未捕获的异常,关闭所有监控器`, { error }); // 尝试关闭所有监控器 try { watchTools.closeAllWatchers(); } catch (e) { logger.error(`关闭监控器时出错: ${e.message}`); } // 关闭日志 logger.shutdown(); console.error('MCP服务器已关闭'); process.exit(1); }); return { success: true, server }; } catch (error) { logger.error(`服务器启动失败: ${error.message}`, { error, stack: error.stack }); throw error; } } // 如果直接运行此文件则启动服务器 if (require.main === module) { startServer().catch(() => { process.exit(1); }); } // 导出startServer函数 module.exports = { startServer };