Skip to main content
Glama

Claude MCP Server Integration

by mokemoke0821
file-helper.ts7.6 kB
/** * ファイル操作共通ヘルパー関数 */ import { createHash } from 'crypto'; import { constants, promises as fs } from 'fs'; import { basename, dirname, extname, join, resolve } from 'path'; import { FileInfo, OperationResult } from '../types/index.js'; import { logger } from './logger.js'; /** * ファイルが存在するかチェック */ export async function fileExists(filePath: string): Promise<boolean> { try { await fs.access(filePath, constants.F_OK); return true; } catch { return false; } } /** * ディレクトリが存在するかチェック */ export async function directoryExists(dirPath: string): Promise<boolean> { try { const stats = await fs.stat(dirPath); return stats.isDirectory(); } catch { return false; } } /** * ディレクトリを再帰的に作成 */ export async function ensureDirectory(dirPath: string): Promise<void> { try { await fs.mkdir(dirPath, { recursive: true }); logger.debug(`ディレクトリを作成しました: ${dirPath}`); } catch (error) { logger.error(`ディレクトリ作成に失敗しました: ${dirPath}`, { error }); throw error; } } /** * ファイル情報を取得 */ export async function getFileInfo(filePath: string): Promise<FileInfo> { try { const stats = await fs.stat(filePath); const name = basename(filePath); const extension = extname(filePath); return { path: resolve(filePath), name, size: stats.size, extension, mimeType: getMimeType(extension), created: stats.birthtime, modified: stats.mtime, accessed: stats.atime, isDirectory: stats.isDirectory(), isFile: stats.isFile(), permissions: stats.mode.toString(8) }; } catch (error) { logger.error(`ファイル情報取得に失敗しました: ${filePath}`, { error }); throw error; } } /** * ファイルサイズを人間が読みやすい形式で変換 */ export function formatFileSize(bytes: number): string { if (bytes === 0) return '0 B'; const k = 1024; const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`; } /** * ファイル拡張子からMIMEタイプを推定 */ export function getMimeType(extension: string): string { const mimeTypes: Record<string, string> = { '.txt': 'text/plain', '.md': 'text/markdown', '.html': 'text/html', '.css': 'text/css', '.js': 'application/javascript', '.ts': 'application/typescript', '.json': 'application/json', '.xml': 'application/xml', '.pdf': 'application/pdf', '.doc': 'application/msword', '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', '.xls': 'application/vnd.ms-excel', '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', '.ppt': 'application/vnd.ms-powerpoint', '.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.png': 'image/png', '.gif': 'image/gif', '.svg': 'image/svg+xml', '.webp': 'image/webp', '.mp3': 'audio/mpeg', '.wav': 'audio/wav', '.mp4': 'video/mp4', '.avi': 'video/x-msvideo', '.mov': 'video/quicktime', '.zip': 'application/zip', '.rar': 'application/vnd.rar', '.7z': 'application/x-7z-compressed', '.tar': 'application/x-tar', '.gz': 'application/gzip' }; return mimeTypes[extension.toLowerCase()] || 'application/octet-stream'; } /** * ファイルのハッシュ値を計算 */ export async function calculateFileHash( filePath: string, algorithm: 'md5' | 'sha1' | 'sha256' | 'sha512' = 'sha256' ): Promise<string> { try { const hash = createHash(algorithm); const fileBuffer = await fs.readFile(filePath); hash.update(fileBuffer); return hash.digest('hex'); } catch (error) { logger.error(`ファイルハッシュ計算に失敗しました: ${filePath}`, { error, algorithm }); throw error; } } /** * ディレクトリ内のファイルを再帰的に取得 */ export async function getFilesRecursively( dirPath: string, options: { includeHidden?: boolean; maxDepth?: number; fileFilter?: (filePath: string) => boolean; } = {} ): Promise<string[]> { const { includeHidden = false, maxDepth = Infinity, fileFilter } = options; const files: string[] = []; async function traverse(currentPath: string, depth: number): Promise<void> { if (depth > maxDepth) return; try { const entries = await fs.readdir(currentPath, { withFileTypes: true }); for (const entry of entries) { if (!includeHidden && entry.name.startsWith('.')) continue; const fullPath = join(currentPath, entry.name); if (entry.isDirectory()) { await traverse(fullPath, depth + 1); } else if (entry.isFile()) { if (!fileFilter || fileFilter(fullPath)) { files.push(fullPath); } } } } catch (error) { logger.warn(`ディレクトリ読み取りに失敗しました: ${currentPath}`, { error }); } } await traverse(dirPath, 0); return files; } /** * 安全なファイルパス生成(重複を避ける) */ export async function generateSafeFilePath(originalPath: string, suffix?: string): Promise<string> { const dir = dirname(originalPath); const name = basename(originalPath, extname(originalPath)); const ext = extname(originalPath); const suffixStr = suffix ? `_${suffix}` : ''; let counter = 0; let newPath = join(dir, `${name}${suffixStr}${ext}`); while (await fileExists(newPath)) { counter++; newPath = join(dir, `${name}${suffixStr}_${counter}${ext}`); } return newPath; } /** * ファイル操作結果のラッパー */ export function createOperationResult<T>( success: boolean, message: string, data?: T, error?: Error ): OperationResult<T> { return { success, message, data, error, timestamp: new Date() }; } /** * 成功結果のヘルパー */ export function createSuccessResult<T>( message: string, data: T ): OperationResult<T> { return { success: true, message, data, timestamp: new Date() }; } /** * 失敗結果のヘルパー */ export function createFailureResult<T>( message: string, error?: Error ): OperationResult<T> { return { success: false, message, error, timestamp: new Date() } as OperationResult<T>; } /** * パスの正規化 */ export function normalizePath(filePath: string): string { return resolve(filePath).replace(/\\/g, '/'); } /** * ファイルの読み取り権限チェック */ export async function canReadFile(filePath: string): Promise<boolean> { try { await fs.access(filePath, constants.R_OK); return true; } catch { return false; } } /** * ファイルの書き込み権限チェック */ export async function canWriteFile(filePath: string): Promise<boolean> { try { await fs.access(filePath, constants.W_OK); return true; } catch { return false; } } /** * テンポラリファイルパス生成 */ export function generateTempFilePath(prefix: string = 'enhanced-fs', extension: string = '.tmp'): string { const timestamp = Date.now(); const random = Math.random().toString(36).substring(2, 8); return join(process.env.TEMP || '/tmp', `${prefix}_${timestamp}_${random}${extension}`); }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/mokemoke0821/claude-mcp-integration'

If you have feedback or need assistance with the MCP directory API, please join our Discord server