UNPKG

vibe-coder-mcp

Version:

Production-ready MCP server with complete agent integration, multi-transport support, and comprehensive development automation tools for AI-assisted workflows.

139 lines (138 loc) 6.45 kB
import fs from 'fs/promises'; import fsSync from 'fs'; import path from 'path'; import logger from '../../logger.js'; import { createSecurePath, isPathWithin, normalizePath } from './pathUtils.js'; import { getBaseOutputDir } from './directoryUtils.js'; export async function readFileSecure(filePath, allowedDirectory, encoding = 'utf-8', allowedOutputDirectory) { const normalizedPath = normalizePath(filePath); const baseOutputDir = getBaseOutputDir(); const isOutputPath = isPathWithin(normalizedPath, baseOutputDir); let securePath; if (isOutputPath && allowedOutputDirectory) { securePath = createSecurePath(filePath, allowedOutputDirectory); logger.debug(`Using output directory validation for reading: ${filePath}`); } else { securePath = createSecurePath(filePath, allowedDirectory); } try { await fs.access(securePath, fsSync.constants.R_OK); const content = await fs.readFile(securePath, { encoding }); logger.debug(`Successfully read file: ${securePath}`); return content; } catch (error) { if (error instanceof Error && 'code' in error) { const fsError = error; if (fsError.code === 'ENOENT') { logger.warn(`File not found: ${securePath}`); throw new Error(`File not found: ${filePath}`); } else if (fsError.code === 'EACCES') { logger.error(`Permission denied for file: ${securePath}`); throw new Error(`Permission denied for file: ${filePath}`); } } const errorMessage = error instanceof Error ? error.message : String(error); logger.error({ err: error, path: securePath }, `Error reading file: ${errorMessage}`); throw new Error(`Could not read file '${filePath}': ${errorMessage}`); } } export async function writeFileSecure(filePath, content, allowedDirectory, encoding = 'utf-8', allowedOutputDirectory) { const normalizedPath = normalizePath(filePath); const baseOutputDir = getBaseOutputDir(); const isOutputPath = isPathWithin(normalizedPath, baseOutputDir); let securePath; if (isOutputPath && allowedOutputDirectory) { securePath = createSecurePath(filePath, allowedOutputDirectory); logger.debug(`Using output directory validation for: ${filePath}`); } else { securePath = createSecurePath(filePath, allowedDirectory); } try { const dirPath = path.dirname(securePath); await fs.mkdir(dirPath, { recursive: true }); await fs.writeFile(securePath, content, { encoding }); logger.debug(`Successfully wrote file: ${securePath}`); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); logger.error({ err: error, path: securePath }, `Error writing file: ${errorMessage}`); throw new Error(`Could not write file '${filePath}': ${errorMessage}`); } } export async function appendToFileSecure(filePath, content, allowedDirectory, encoding = 'utf-8', allowedOutputDirectory) { const normalizedPath = normalizePath(filePath); const baseOutputDir = getBaseOutputDir(); const isOutputPath = isPathWithin(normalizedPath, baseOutputDir); let securePath; if (isOutputPath && allowedOutputDirectory) { securePath = createSecurePath(filePath, allowedOutputDirectory); logger.debug(`Using output directory validation for: ${filePath}`); } else { securePath = createSecurePath(filePath, allowedDirectory); } try { const dirPath = path.dirname(securePath); await fs.mkdir(dirPath, { recursive: true }); await fs.appendFile(securePath, content, { encoding }); logger.debug(`Successfully appended to file: ${securePath}`); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); logger.error({ err: error, path: securePath }, `Error appending to file: ${errorMessage}`); throw new Error(`Could not append to file '${filePath}': ${errorMessage}`); } } export async function readDirSecure(dirPath, allowedDirectory, options) { const securePath = createSecurePath(dirPath, allowedDirectory); try { await fs.access(securePath, fsSync.constants.R_OK); const entries = await fs.readdir(securePath, { ...options, withFileTypes: true }); logger.debug(`Successfully read directory: ${securePath}`); return entries; } catch (error) { if (error instanceof Error && 'code' in error) { const fsError = error; if (fsError.code === 'ENOENT') { logger.warn(`Directory not found: ${securePath}`); throw new Error(`Directory not found: ${dirPath}`); } else if (fsError.code === 'EACCES') { logger.error(`Permission denied for directory: ${securePath}`); throw new Error(`Permission denied for directory: ${dirPath}`); } } const errorMessage = error instanceof Error ? error.message : String(error); logger.error({ err: error, path: securePath }, `Error reading directory: ${errorMessage}`); throw new Error(`Could not read directory '${dirPath}': ${errorMessage}`); } } export async function statSecure(filePath, allowedDirectory) { const securePath = createSecurePath(filePath, allowedDirectory); try { const stats = await fs.stat(securePath); logger.debug(`Successfully got stats for: ${securePath}`); return stats; } catch (error) { if (error instanceof Error && 'code' in error) { const fsError = error; if (fsError.code === 'ENOENT') { logger.warn(`File not found: ${securePath}`); throw new Error(`File not found: ${filePath}`); } else if (fsError.code === 'EACCES') { logger.error(`Permission denied for file: ${securePath}`); throw new Error(`Permission denied for file: ${filePath}`); } } const errorMessage = error instanceof Error ? error.message : String(error); logger.error({ err: error, path: securePath }, `Error getting stats: ${errorMessage}`); throw new Error(`Could not get stats for '${filePath}': ${errorMessage}`); } }