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
JavaScript
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}`);
}
}