UNPKG

fast-filesystem-mcp

Version:

Fast Filesystem MCP Server - Advanced file operations with Auto-Chunking, Sequential Reading, complex file operations (copy, move, delete, batch, compress), optimized for Claude Desktop

332 lines 13.2 kB
#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js'; import { promises as fs } from 'fs'; import path from 'path'; import { exec } from 'child_process'; import { promisify } from 'util'; import { SafeLargeFileWriter } from './checkpoint-writer.js'; const execAsync = promisify(exec); // Claude 최적화 설정 const CLAUDE_MAX_RESPONSE_SIZE = 5 * 1024 * 1024; // 5MB const CLAUDE_MAX_CHUNK_SIZE = 2 * 1024 * 1024; // 2MB const CLAUDE_MAX_LINES = 2000; // 최대 2000줄 const CLAUDE_MAX_DIR_ITEMS = 1000; // 디렉토리 항목 최대 1000개 // 기본 허용 디렉토리들 const DEFAULT_ALLOWED_DIRECTORIES = [ process.env.HOME || '/home', '/tmp', '/Users', '/home' ]; // 기본 제외 패턴 (보안 및 성능) const DEFAULT_EXCLUDE_PATTERNS = [ '.venv', 'venv', 'node_modules', '.git', '.svn', '.hg', '__pycache__', '.pytest_cache', '.mypy_cache', '.coverage', 'dist', 'build', 'target', 'bin', 'obj', '.vs', '.vscode', '*.pyc', '*.pyo', '*.pyd', '.DS_Store', 'Thumbs.db' ]; // 이모지 제거 함수 function removeEmojis(text, enableEmojis = false) { if (enableEmojis) return text; // 이모지 패턴 정규식 const emojiRegex = /[\u{1F600}-\u{1F64F}]|[\u{1F300}-\u{1F5FF}]|[\u{1F680}-\u{1F6FF}]|[\u{1F700}-\u{1F77F}]|[\u{1F780}-\u{1F7FF}]|[\u{1F800}-\u{1F8FF}]|[\u{1F900}-\u{1F9FF}]|[\u{1FA00}-\u{1FA6F}]|[\u{1FA70}-\u{1FAFF}]|[\u{2600}-\u{26FF}]|[\u{2700}-\u{27BF}]/gu; return text.replace(emojiRegex, ''); } // 유틸리티 함수들 function isPathAllowed(targetPath) { const absolutePath = path.resolve(targetPath); return DEFAULT_ALLOWED_DIRECTORIES.some(allowedDir => absolutePath.startsWith(path.resolve(allowedDir))); } function safePath(inputPath) { if (!isPathAllowed(inputPath)) { throw new Error(`Access denied to path: ${inputPath}`); } return path.resolve(inputPath); } function formatSize(bytes) { const units = ['B', 'KB', 'MB', 'GB', 'TB']; let size = bytes; let unitIndex = 0; while (size >= 1024 && unitIndex < units.length - 1) { size /= 1024; unitIndex++; } return `${size.toFixed(2)} ${units[unitIndex]}`; } function shouldExcludePath(targetPath, excludePatterns = []) { const patterns = [...DEFAULT_EXCLUDE_PATTERNS, ...excludePatterns]; const pathName = path.basename(targetPath).toLowerCase(); const pathParts = targetPath.split(path.sep); return patterns.some(pattern => { const patternLower = pattern.toLowerCase(); if (pattern.includes('*') || pattern.includes('?')) { const regex = new RegExp(pattern.replace(/\\*/g, '.*').replace(/\\?/g, '.')); return regex.test(pathName); } return pathParts.some(part => part.toLowerCase() === patternLower) || pathName === patternLower; }); } function truncateContent(content, maxSize = CLAUDE_MAX_RESPONSE_SIZE) { const contentBytes = Buffer.byteLength(content, 'utf8'); if (contentBytes <= maxSize) { return { content, truncated: false }; } let truncated = content; while (Buffer.byteLength(truncated, 'utf8') > maxSize) { truncated = truncated.slice(0, -1); } return { content: truncated, truncated: true, original_size: contentBytes, truncated_size: Buffer.byteLength(truncated, 'utf8') }; } // MCP 서버 생성 const server = new Server({ name: 'fast-filesystem', version: '2.5.0', }, { capabilities: { tools: {}, }, }); // 툴 목록 정의 server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ // ... 기존 툴들 ... { name: 'fast_checkpoint_write_file', description: '체크포인트 기반 대용량 파일 안전 작성 (중단 복구 가능, 이모지 제거 옵션)', inputSchema: { type: 'object', properties: { path: { type: 'string', description: '파일 경로' }, sections: { type: 'array', description: '섹션 배열', items: { type: 'object', properties: { id: { type: 'string', description: '섹션 ID' }, name: { type: 'string', description: '섹션 이름' }, content: { type: 'string', description: '섹션 내용' } }, required: ['id', 'name', 'content'] } }, options: { type: 'object', description: '작성 옵션', properties: { enable_emojis: { type: 'boolean', description: '이모지 허용', default: false }, checkpoint_interval: { type: 'number', description: '체크포인트 간격', default: 1 }, auto_backup: { type: 'boolean', description: '자동 백업', default: true }, verify_write: { type: 'boolean', description: '작성 검증', default: true }, max_retries: { type: 'number', description: '최대 재시도', default: 3 }, mode: { type: 'string', enum: ['write', 'append'], description: '작성 모드', default: 'write' } } } }, required: ['path', 'sections'] } }, { name: 'fast_checkpoint_status', description: '체크포인트 상태 확인', inputSchema: { type: 'object', properties: { path: { type: 'string', description: '파일 경로' } }, required: ['path'] } }, { name: 'fast_checkpoint_continue', description: '체크포인트에서 이어서 작성', inputSchema: { type: 'object', properties: { path: { type: 'string', description: '파일 경로' } }, required: ['path'] } }, { name: 'fast_checkpoint_reset', description: '체크포인트 초기화', inputSchema: { type: 'object', properties: { path: { type: 'string', description: '파일 경로' } }, required: ['path'] } } ], }; }); // 체크포인트 핸들러들 async function handleCheckpointWriteFile(args) { const { path: filePath, sections, options = {} } = args; const safeFilePath = safePath(filePath); const writer = new SafeLargeFileWriter(safeFilePath, { enableEmojis: options.enable_emojis || false, checkpointInterval: options.checkpoint_interval || 1, autoBackup: options.auto_backup !== false, verifyWrite: options.verify_write !== false, maxRetries: options.max_retries || 3 }); // 섹션들 추가 for (const section of sections) { writer.addSection(section.id, section.name, section.content); } const success = await writer.writeSafely(options.mode || 'write'); return { success, path: safeFilePath, total_sections: writer.getSections().length, total_size: writer.getTotalSize(), total_size_readable: formatSize(writer.getTotalSize()), total_lines: writer.getTotalLines(), checkpoint_path: safeFilePath + '.checkpoint.json', backup_path: safeFilePath + '.backup', timestamp: new Date().toISOString() }; } async function handleCheckpointStatus(args) { const { path: filePath } = args; const safeFilePath = safePath(filePath); const writer = new SafeLargeFileWriter(safeFilePath); // 상태 정보를 문자열로 캡처 let statusOutput = ''; const originalLog = console.log; console.log = (message) => { statusOutput += message + '\n'; }; await writer.getStatus(); console.log = originalLog; return { path: safeFilePath, status_output: statusOutput, checkpoint_exists: await fs.access(safeFilePath + '.checkpoint.json').then(() => true).catch(() => false), file_exists: await fs.access(safeFilePath).then(() => true).catch(() => false), backup_exists: await fs.access(safeFilePath + '.backup').then(() => true).catch(() => false), timestamp: new Date().toISOString() }; } async function handleCheckpointContinue(args) { const { path: filePath } = args; const safeFilePath = safePath(filePath); const writer = new SafeLargeFileWriter(safeFilePath); const success = await writer.continueFromCheckpoint(); return { success, path: safeFilePath, message: success ? '체크포인트에서 성공적으로 이어서 작성 완료' : '체크포인트에서 이어서 작성 실패', timestamp: new Date().toISOString() }; } async function handleCheckpointReset(args) { const { path: filePath } = args; const safeFilePath = safePath(filePath); const writer = new SafeLargeFileWriter(safeFilePath); await writer.resetCheckpoint(); return { path: safeFilePath, message: '체크포인트 초기화 완료', timestamp: new Date().toISOString() }; } // 기존 write_file 함수에 이모지 제거 기능 추가 async function handleWriteFile(args) { const { path: filePath, content, encoding = 'utf-8', create_dirs = true, append = false, enable_emojis = false } = args; let targetPath; if (path.isAbsolute(filePath)) { targetPath = filePath; } else { targetPath = path.join(process.cwd(), filePath); } if (!isPathAllowed(targetPath)) { throw new Error(`Access denied to path: ${targetPath}`); } const resolvedPath = path.resolve(targetPath); if (create_dirs) { const dir = path.dirname(resolvedPath); await fs.mkdir(dir, { recursive: true }); } // 이모지 제거 적용 const cleanContent = removeEmojis(content, enable_emojis); if (append) { await fs.appendFile(resolvedPath, cleanContent, encoding); } else { await fs.writeFile(resolvedPath, cleanContent, encoding); } const stats = await fs.stat(resolvedPath); return { message: `File ${append ? 'appended' : 'written'} successfully` + (!enable_emojis ? ' (emojis removed)' : ''), path: resolvedPath, size: stats.size, size_readable: formatSize(stats.size), encoding: encoding, mode: append ? 'append' : 'write', emojis_removed: !enable_emojis, timestamp: new Date().toISOString() }; } // 툴 호출 핸들러에 체크포인트 케이스 추가 server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { let result; switch (name) { case 'fast_checkpoint_write_file': result = await handleCheckpointWriteFile(args); break; case 'fast_checkpoint_status': result = await handleCheckpointStatus(args); break; case 'fast_checkpoint_continue': result = await handleCheckpointContinue(args); break; case 'fast_checkpoint_reset': result = await handleCheckpointReset(args); break; case 'fast_write_file': result = await handleWriteFile(args); break; // ... 기존 케이스들 ... default: throw new Error(`Tool not implemented: ${name}`); } return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (error) { throw new Error(error instanceof Error ? error.message : 'Unknown error'); } }); // 서버 시작 async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error('Fast Filesystem MCP Server v2.5.0 running on stdio (with checkpoint system)'); } main().catch((error) => { console.error('Server failed to start:', error); process.exit(1); }); //# sourceMappingURL=index-new.js.map