UNPKG

@yinzhouzhi/mcp-filesystem-server

Version:

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

975 lines (846 loc) 27.9 kB
/** * 文件操作工具模块 * 实现文件的读写、删除、复制、移动等操作 */ const fs = require('fs'); const path = require('path'); const { promisify } = require('util'); const logger = require('../utils/logging'); const pathUtils = require('../utils/path-utils'); const streamUtils = require('../utils/stream-utils'); const securityUtils = require('../config/security'); const config = require('../config'); const { pipeline } = require('stream/promises'); const zlib = require('zlib'); const watchTools = require('./watch-tools'); // 将回调API转换为Promise const fsCopyFile = promisify(fs.copyFile); const unlink = promisify(fs.unlink); const rename = promisify(fs.rename); // 文件缓存配置 const FILE_CACHE = { enabled: false, // 默认禁用缓存 items: new Map(), // 缓存项 size: 0, // 当前缓存大小 maxSize: 50 * 1024 * 1024, // 最大缓存大小 (50MB) maxAge: 60 * 1000, // 缓存项最大存活时间 (60秒) hits: 0, // 缓存命中次数 misses: 0, // 缓存未命中次数 evictions: 0, // 缓存项被清除次数 monitoring: { enabled: false, // 默认禁用缓存监控 watchers: new Map(), // 文件监控器 monitoredPaths: new Set() // 已监控的路径 } }; // 文件缓存监控状态 const CACHE_MONITORS = { enabled: config?.fileOperations?.cacheMonitoring?.enabled || false, watchers: new Map(), monitoredPaths: new Set() }; // 大文件的阈值 const LARGE_FILE_THRESHOLD = { read: 10 * 1024 * 1024, // 10MB write: 5 * 1024 * 1024 // 5MB }; /** * 初始化缓存监控 */ function initCacheMonitoring() { if (CACHE_MONITORS.enabled) return; // 已初始化 CACHE_MONITORS.enabled = true; logger.info('初始化文件缓存监控系统'); // 当进程退出时清理所有监控 process.on('exit', () => { // 关闭所有监控器 for (const watcher of CACHE_MONITORS.watchers.values()) { if (watcher && typeof watcher.close === 'function') { watcher.close(); } } }); } /** * 为缓存的文件添加监控 * @param {string} filePath 文件路径 */ function monitorFileForCache(filePath) { if (!FILE_CACHE.enabled || !config?.fileOperations?.cacheMonitoring?.enabled) { return; } if (!CACHE_MONITORS.enabled) { initCacheMonitoring(); } // 已监控的路径不重复添加 if (CACHE_MONITORS.monitoredPaths.has(filePath)) { return; } try { // 监控配置选项 const watchOptions = config?.fileOperations?.cacheMonitoring?.watchOptions || { ignoreInitial: true, awaitWriteFinish: { stabilityThreshold: 1000, pollInterval: 100 } }; // 创建监控并设置回调 const watcher = watchTools.setFileChangeCallback( filePath, (event, changedPath) => { if (event === 'change' || event === 'unlink') { // 文件被修改或删除,从缓存中移除 if (FILE_CACHE.items.has(filePath)) { logger.debug(`文件变更,从缓存移除: ${filePath}, 事件: ${event}`); FILE_CACHE.items.delete(filePath); // 同时通知任何依赖该文件的目录缓存 const dirPath = path.dirname(filePath); try { // 导入目录工具模块(避免循环依赖) const dirTools = require('./dir-tools'); if (typeof dirTools.invalidateDirCache === 'function') { dirTools.invalidateDirCache(dirPath); } } catch (err) { // 忽略错误 } } } }, watchOptions ); if (watcher) { // 保存监控器引用 CACHE_MONITORS.watchers.set(filePath, watcher); CACHE_MONITORS.monitoredPaths.add(filePath); logger.debug(`已添加文件缓存监控: ${filePath}`); } } catch (error) { logger.warn(`添加文件缓存监控失败: ${filePath}`, { error }); } } /** * 停止文件缓存监控 * @param {string} filePath 文件路径 */ function stopMonitoringFile(filePath) { if (!CACHE_MONITORS.enabled || !CACHE_MONITORS.monitoredPaths.has(filePath)) { return; } try { const watcher = CACHE_MONITORS.watchers.get(filePath); if (watcher) { // 使用watchTools的closeWatcher函数关闭监控器 watchTools.closeWatcher(watcher); CACHE_MONITORS.watchers.delete(filePath); CACHE_MONITORS.monitoredPaths.delete(filePath); logger.debug(`已停止文件缓存监控: ${filePath}`); } } catch (error) { logger.warn(`停止文件缓存监控失败: ${filePath}`, { error }); } } /** * 清理所有文件监控器 */ function clearAllFileMonitors() { try { // 关闭所有监控器 for (const [filePath, watcher] of FILE_CACHE.monitoring.watchers.entries()) { try { watcher.close(); logger.debug(`清理文件监控器: ${filePath}`); } catch (err) { logger.error(`关闭文件监控器失败: ${filePath}`, { error: err }); } } // 清空集合 FILE_CACHE.monitoring.watchers.clear(); FILE_CACHE.monitoring.monitoredPaths.clear(); logger.info(`已清理所有文件缓存监控器: ${FILE_CACHE.monitoring.watchers.size}`); } catch (error) { logger.error('清理文件监控器失败', { error }); } } /** * 读取文件内容 * @param {Object} params 参数 * @param {string} params.path 文件路径 * @param {string} params.encoding 编码方式,默认utf-8 * @returns {Promise<string>} 文件内容 */ async function readFile(params) { const { path: filePath, encoding = 'utf8' } = params; // 检查权限 if (!securityUtils.validateToolCall('read_file', params)) { throw new Error('没有权限读取文件'); } try { // 检查路径是否存在 if (!fs.existsSync(filePath)) { return { success: false, error: `文件不存在: ${filePath}` }; } // 检查缓存 if (FILE_CACHE.enabled) { const cachedContent = getFromCache(filePath); if (cachedContent) { FILE_CACHE.hits++; return { success: true, content: cachedContent, encoding }; } FILE_CACHE.misses++; } // 获取文件状态 const stats = fs.statSync(filePath); // 检查文件大小,大文件使用流处理 if (stats.size > (config?.fileOperations?.streamThresholds?.read || LARGE_FILE_THRESHOLD.read)) { return readLargeFile(filePath, encoding); } // 读取文件内容 const content = fs.readFileSync(filePath, encoding); // 添加到缓存 if (FILE_CACHE.enabled && stats.size < 1024 * 1024) { // 只缓存1MB以下的文件 addToCache(filePath, content); } return { success: true, content, encoding }; } catch (error) { logger.error(`读取文件失败: ${error.message}`, { path: filePath, error }); return { success: false, error: error.message }; } } // 使用流读取大文件 function readLargeFile(filePath, encoding) { try { // 创建可读流 const readStream = fs.createReadStream(filePath, { encoding }); // 收集数据 let content = ''; return new Promise((resolve) => { readStream.on('data', (chunk) => { content += chunk; }); readStream.on('end', () => { resolve({ success: true, content, encoding, streamed: true }); }); readStream.on('error', (error) => { resolve({ success: false, error: error.message }); }); }); } catch (error) { return { success: false, error: error.message }; } } /** * 写入文件内容 * @param {Object} params 参数 * @param {string} params.path 文件路径 * @param {string} params.content 文件内容 * @param {string} params.encoding 编码方式,默认utf-8 * @returns {Promise<Object>} 写入结果 */ async function writeFile(params) { const { path: filePath, content, encoding = 'utf8' } = params; // 检查权限 if (!securityUtils.validateToolCall('write_file', params)) { throw new Error('没有权限写入文件'); } try { // 确保目标目录存在 const dir = path.dirname(filePath); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } // 检查文件内容大小,大文件使用流处理 if (content.length > (config?.fileOperations?.streamThresholds?.write || LARGE_FILE_THRESHOLD.write)) { return writeLargeFile(filePath, content, encoding); } // 写入文件内容 fs.writeFileSync(filePath, content, encoding); // 如果内容在缓存中,更新缓存 if (FILE_CACHE.enabled && FILE_CACHE.items.has(filePath)) { addToCache(filePath, content); } return { success: true, path: filePath }; } catch (error) { logger.error(`写入文件失败: ${error.message}`, { path: filePath, error }); throw error; } } // 使用流写入大文件 function writeLargeFile(filePath, content, encoding) { return new Promise((resolve) => { try { // 创建可写流 const writeStream = fs.createWriteStream(filePath, { encoding }); // 写入数据 writeStream.write(content); writeStream.end(); writeStream.on('finish', () => { resolve({ success: true, path: filePath, streamed: true }); }); writeStream.on('error', (error) => { resolve({ success: false, error: error.message }); }); } catch (error) { resolve({ success: false, error: error.message }); } }); } /** * 追加内容到文件 * @param {Object} params 参数 * @param {string} params.path 文件路径 * @param {string} params.content 追加的内容 * @param {string} params.encoding 编码方式,默认utf-8 * @returns {Promise<Object>} 追加结果 */ async function appendFile(params) { const { path: filePath, content, encoding = 'utf8' } = params; // 检查权限 if (!securityUtils.validateToolCall('append_file', params)) { throw new Error('没有权限追加文件内容'); } try { // 确保目标目录存在 const dir = path.dirname(filePath); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } // 检查文件内容大小,大文件使用流处理 if (content.length > (config?.fileOperations?.streamThresholds?.append || LARGE_FILE_THRESHOLD.write)) { return appendLargeFile(filePath, content, encoding); } // 追加文件内容 fs.appendFileSync(filePath, content, encoding); // 如果内容在缓存中,移除缓存 if (FILE_CACHE.enabled && FILE_CACHE.items.has(filePath)) { FILE_CACHE.items.delete(filePath); } return { success: true, path: filePath }; } catch (error) { logger.error(`追加文件内容失败: ${error.message}`, { path: filePath, error }); throw error; } } // 使用流追加大文件 function appendLargeFile(filePath, content, encoding) { return new Promise((resolve) => { try { // 创建可写流 const writeStream = fs.createWriteStream(filePath, { encoding, flags: 'a' }); // 写入数据 writeStream.write(content); writeStream.end(); writeStream.on('finish', () => { resolve({ success: true, path: filePath, streamed: true }); }); writeStream.on('error', (error) => { resolve({ success: false, error: error.message }); }); } catch (error) { resolve({ success: false, error: error.message }); } }); } /** * 删除文件 * @param {Object} params 参数 * @param {string} params.path 文件路径 * @returns {Promise<Object>} 删除结果 */ async function deleteFile(params) { const { path: filePath } = params; // 检查权限 if (!securityUtils.validateToolCall('delete_file', params)) { throw new Error('没有权限删除文件'); } try { // 检查文件是否存在 if (!fs.existsSync(filePath)) { return { success: true, deleted: false, path: filePath }; } // 删除文件 logger.info(`删除文件: ${filePath}`); fs.unlinkSync(filePath); // 从缓存中删除 if (FILE_CACHE.enabled && FILE_CACHE.items.has(filePath)) { FILE_CACHE.items.delete(filePath); } return { success: true, deleted: true, path: filePath }; } catch (error) { logger.error(`删除文件失败: ${error.message}`, { path: filePath, error }); throw error; } } /** * 复制文件 * @param {Object} params 参数 * @param {string} params.source 源文件路径 * @param {string} params.destination 目标文件路径 * @param {boolean} params.overwrite 是否覆盖目标文件,默认false * @returns {Promise<Object>} 复制结果 */ async function copyFile(params) { const { source, destination, overwrite = false } = params; // 检查权限 if (!securityUtils.validateToolCall('copy_file', params)) { throw new Error('没有权限复制文件'); } try { // 检查源文件是否存在 if (!fs.existsSync(source)) { return { success: false, error: `源文件不存在: ${source}` }; } // 确保目标目录存在 const destDir = path.dirname(destination); if (!fs.existsSync(destDir)) { fs.mkdirSync(destDir, { recursive: true }); } // 获取文件状态 const stats = fs.statSync(source); // 检查文件大小,大文件使用流处理 if (stats.size > (config?.fileOperations?.streamThresholds?.copy || LARGE_FILE_THRESHOLD.read)) { return copyLargeFile(source, destination, overwrite); } // 使用标准 fs.copyFile 复制文件 let copyFlag = 0; if (!overwrite) { copyFlag = fs.constants.COPYFILE_EXCL; } await fsCopyFile(source, destination, copyFlag); return { success: true, source, destination }; } catch (error) { // 处理特定错误:文件已存在 if (error.code === 'EEXIST') { return { success: false, error: `目标文件已存在: ${destination}。如需覆盖,请设置 overwrite 为 true` }; } logger.error(`复制文件失败: ${error.message}`, { source, destination, error }); return { success: false, error: error.message }; } } // 使用流复制大文件 async function copyLargeFile(source, destination, overwrite) { try { // 检查目标文件是否已存在 if (!overwrite && fs.existsSync(destination)) { return { success: false, error: `目标文件已存在: ${destination}。如需覆盖,请设置 overwrite 为 true` }; } // 创建目标文件的写入流 const writeStream = fs.createWriteStream(destination); // 使用pipeline进行流复制 await pipeline( fs.createReadStream(source), writeStream ); return { success: true, source, destination, streamed: true }; } catch (error) { logger.error(`大文件复制失败: ${error.message}`, { source, destination, error }); return { success: false, error: error.message }; } } // 移动文件 async function moveFile(params) { const { source, destination } = params; // 检查权限 if (!securityUtils.validateToolCall('move_file', params)) { throw new Error('没有权限移动文件'); } try { // 尝试使用重命名(通常更快,但只在同一文件系统上工作) try { // 检查目标文件是否已存在 if (fs.existsSync(destination)) { return { success: false, error: `目标文件已存在,未指定覆盖: ${destination}` }; } // 确保目标目录存在 const dir = path.dirname(destination); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } // 尝试直接重命名 fs.renameSync(source, destination); // 更新缓存 if (FILE_CACHE.enabled) { if (FILE_CACHE.items.has(source)) { const item = FILE_CACHE.items.get(source); FILE_CACHE.items.set(destination, item); FILE_CACHE.items.delete(source); } } return { success: true, source, destination }; } catch (renameError) { // 在不同文件系统上可能失败,尝试复制然后删除 const copyResult = await copyFile({ source, destination, overwrite: true }); if (copyResult.success) { fs.unlinkSync(source); // 更新缓存 if (FILE_CACHE.enabled && FILE_CACHE.items.has(source)) { FILE_CACHE.items.delete(source); } return { success: true, source, destination, method: 'copy-delete' }; } else { return copyResult; } } } catch (error) { logger.error(`移动文件失败: ${error.message}`, { source, destination, error }); throw error; } } /** * 获取文件信息 * @param {Object} params 参数 * @param {string} params.path 文件路径 * @returns {Promise<Object>} 文件信息 */ async function getFileInfo(params) { const { path: filePath } = params; // 检查权限 if (!securityUtils.validateToolCall('get_file_info', params)) { throw new Error('没有权限获取文件信息'); } try { // 检查文件是否存在 if (!fs.existsSync(filePath)) { return { success: true, path: filePath, exists: false }; } // 获取文件信息 const stats = fs.statSync(filePath); logger.info(`获取文件信息: ${filePath}`); return { success: true, path: filePath, exists: true, isFile: stats.isFile(), isDirectory: stats.isDirectory(), isSymbolicLink: stats.isSymbolicLink(), size: stats.size, created: stats.birthtime, modified: stats.mtime, accessed: stats.atime, permissions: stats.mode, inCache: FILE_CACHE.enabled && FILE_CACHE.items.has(filePath) }; } catch (error) { logger.error(`获取文件信息失败: ${error.message}`, { path: filePath, error }); throw error; } } /** * 检查文件是否存在 * @param {Object} params 参数 * @param {string} params.path 文件路径 * @returns {Promise<Object>} 检查结果 */ async function fileExists(params) { const { path: filePath } = params; // 检查权限 if (!securityUtils.validateToolCall('file_exists', params)) { throw new Error('没有权限检查文件存在'); } try { // 检查路径是否存在 let exists = false; let isFile = false; try { const stats = fs.statSync(filePath); exists = true; isFile = stats.isFile(); } catch (error) { if (error.code !== 'ENOENT') { throw error; } // 文件不存在,继续 } logger.info(`检查文件存在: ${filePath} => ${exists && isFile}`); return { success: true, path: filePath, exists: exists && isFile }; } catch (error) { logger.error(`检查文件存在失败: ${error.message}`, { path: filePath, error }); throw error; } } // 从缓存获取文件内容 function getFromCache(filePath) { if (!FILE_CACHE.enabled) { return null; } const cacheItem = FILE_CACHE.items.get(filePath); if (!cacheItem) { FILE_CACHE.misses++; return null; } // 检查缓存项是否过期 if (Date.now() - cacheItem.timestamp > FILE_CACHE.maxAge) { // 过期了,从缓存中移除 logger.debug(`缓存项过期: ${filePath}`); if (FILE_CACHE.monitoring.enabled) { stopMonitoringFile(filePath); } FILE_CACHE.items.delete(filePath); FILE_CACHE.size -= cacheItem.content.length; FILE_CACHE.evictions++; return null; } // 缓存命中 FILE_CACHE.hits++; // 更新访问时间 cacheItem.lastAccess = Date.now(); return cacheItem.content; } // 添加文件内容到缓存 function addToCache(filePath, content) { if (!FILE_CACHE.enabled || !content) { return; } // 如果内容太大,不缓存 if (content.length > LARGE_FILE_THRESHOLD.read) { logger.debug(`文件太大,不缓存: ${filePath} (${content.length} bytes)`); return; } const cacheItem = { content, timestamp: Date.now(), lastAccess: Date.now() }; // 已经在缓存中,更新内容 if (FILE_CACHE.items.has(filePath)) { const oldItem = FILE_CACHE.items.get(filePath); FILE_CACHE.size -= oldItem.content.length; } // 尝试进行缓存清理,确保有足够空间 ensureCacheSpace(content.length); // 添加到缓存 FILE_CACHE.items.set(filePath, cacheItem); FILE_CACHE.size += content.length; // 如果启用了监控,为文件设置监控 if (FILE_CACHE.monitoring.enabled) { monitorFileForCache(filePath); } logger.debug(`文件已缓存: ${filePath} (${content.length} bytes)`); } // 确保缓存有足够空间 function ensureCacheSpace(requiredSpace) { if (FILE_CACHE.size + requiredSpace <= FILE_CACHE.maxSize) { return; // 空间足够 } // 按最后访问时间排序 const cacheItems = Array.from(FILE_CACHE.items.entries()) .sort((a, b) => a[1].lastAccess - b[1].lastAccess); // 从最久未访问的开始删除 for (const [key, item] of cacheItems) { if (FILE_CACHE.monitoring.enabled) { stopMonitoringFile(key); } FILE_CACHE.items.delete(key); FILE_CACHE.size -= item.content.length; FILE_CACHE.evictions++; logger.debug(`缓存清理: ${key} (${item.content.length} bytes)`); // 检查是否已经有足够空间 if (FILE_CACHE.size + requiredSpace <= FILE_CACHE.maxSize) { break; } } } // 清理缓存 function clearCache() { // 停止所有文件监控 for (const filePath of CACHE_MONITORS.monitoredPaths) { stopMonitoringFile(filePath); } FILE_CACHE.items.clear(); FILE_CACHE.size = 0; return { success: true, message: '文件缓存已清理', stats: { enabled: FILE_CACHE.enabled, monitoringEnabled: FILE_CACHE.monitoring.enabled, itemCount: FILE_CACHE.items.size, size: FILE_CACHE.size, maxSize: FILE_CACHE.maxSize, maxAge: FILE_CACHE.maxAge, hits: FILE_CACHE.hits, misses: FILE_CACHE.misses, evictions: FILE_CACHE.evictions, monitoredFiles: FILE_CACHE.monitoring.monitoredPaths.size } }; } /** * 获取缓存统计信息 * @returns {Object} 缓存统计信息 */ function getCacheStats() { return { enabled: FILE_CACHE.enabled, size: FILE_CACHE.items.size, maxSize: FILE_CACHE.maxSize, maxAge: FILE_CACHE.maxAge, monitoring: { enabled: CACHE_MONITORS.enabled, count: CACHE_MONITORS.monitoredPaths.size }, stats: { hits: FILE_CACHE.hits, misses: FILE_CACHE.misses, evictions: FILE_CACHE.evictions } }; } // 文件压缩操作 async function compressFile({ source, destination, level = config?.fileOperations?.compression?.defaultLevel || 6 }) { try { // 检查源文件是否存在 if (!fs.existsSync(source)) { return { success: false, error: `源文件不存在: ${source}` }; } // 如果未指定目标文件,默认添加.gz后缀 if (!destination) { destination = `${source}.gz`; } // 确保目标目录存在 const dir = path.dirname(destination); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } try { // 使用管道流压缩 await pipeline( fs.createReadStream(source), zlib.createGzip({ level }), fs.createWriteStream(destination) ); return { success: true, source, destination }; } catch (error) { return { success: false, error: error.message }; } } catch (error) { return { success: false, error: error.message }; } } // 文件解压操作 async function decompressFile({ source, destination }) { try { // 检查源文件是否存在 if (!fs.existsSync(source)) { return { success: false, error: `源文件不存在: ${source}` }; } // 如果未指定目标文件,默认去除.gz后缀 if (!destination) { if (source.endsWith('.gz')) { destination = source.slice(0, -3); } else { destination = `${source}.uncompressed`; } } // 确保目标目录存在 const dir = path.dirname(destination); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } try { // 使用管道流解压 await pipeline( fs.createReadStream(source), zlib.createGunzip(), fs.createWriteStream(destination) ); return { success: true, source, destination }; } catch (error) { return { success: false, error: error.message }; } } catch (error) { return { success: false, error: error.message }; } } module.exports = { readFile, writeFile, appendFile, deleteFile, copyFile, moveFile, getFileInfo, fileExists, getCacheStats, clearCache, compressFile, decompressFile, monitorFileForCache, stopMonitoringFile, setConfig: function(config) { if (config.hasOwnProperty('cacheEnabled')) { FILE_CACHE.enabled = !!config.cacheEnabled; // 如果禁用缓存,清理所有监控 if (!FILE_CACHE.enabled) { clearCache(); } } if (config.maxCacheSize && typeof config.maxCacheSize === 'number') { FILE_CACHE.maxSize = config.maxCacheSize; } if (config.maxCacheAge && typeof config.maxCacheAge === 'number') { FILE_CACHE.maxAge = config.maxCacheAge; } if (config.hasOwnProperty('monitoringEnabled')) { FILE_CACHE.monitoring.enabled = !!config.monitoringEnabled; // 如果禁用监控,清理所有监控 if (!FILE_CACHE.monitoring.enabled) { clearAllFileMonitors(); } else if (FILE_CACHE.enabled) { // 如果启用监控,为所有缓存项添加监控 for (const filePath of FILE_CACHE.items.keys()) { monitorFileForCache(filePath); } } } return { success: true, message: '文件工具配置已更新', config: { cacheEnabled: FILE_CACHE.enabled, maxCacheSize: FILE_CACHE.maxSize, maxCacheAge: FILE_CACHE.maxAge, monitoringEnabled: FILE_CACHE.monitoring.enabled } }; } };