UNPKG

@gabrielmaialva33/mcp-filesystem

Version:
146 lines 4.48 kB
import { exec } from 'node:child_process'; import { promisify } from 'node:util'; import { z } from 'zod'; import { logger } from '../../logger/index.js'; import { FileSystemError } from '../../errors/index.js'; import { validatePath } from '../path.js'; const execPromise = promisify(exec); const SAFE_COMMAND_REGEX = /^[a-zA-Z0-9_\-./\s:;,|><&{}()\[\]'"$%+*!?~=]+$/; const DEV_COMMANDS = [ 'npm', 'pnpm', 'yarn', 'npx', 'node', 'ts-node', 'tsc', 'eslint', 'prettier', 'jest', 'vitest', 'mocha', 'git', 'find', 'grep', 'sed', 'awk', 'cat', 'ls', 'cd', 'cp', 'mv', ]; const FORBIDDEN_COMMANDS = [ 'rm -rf', 'rm -rf /', 'rm -rf /*', 'rm -r /', 'rmdir', 'dd', 'mkfs', 'format', 'wget', 'curl -O', 'curl --output', 'chmod 777', 'chmod -R 777', 'sudo', 'su', 'doas', ':(){:|:&};:', ]; export const BashExecuteArgsSchema = z.object({ command: z.string().describe('The bash command to execute'), workingDir: z.string().optional().describe('Working directory for command execution'), timeout: z .number() .int() .positive() .max(60000) .optional() .default(30000) .describe('Maximum execution time in milliseconds (max 60s)'), env: z.record(z.string()).optional().describe('Additional environment variables for the command'), }); export const BashPipeArgsSchema = z.object({ commands: z.array(z.string()).min(1).describe('Array of commands to pipe together'), workingDir: z.string().optional().describe('Working directory for command execution'), timeout: z .number() .int() .positive() .max(60000) .optional() .default(30000) .describe('Maximum execution time in milliseconds (max 60s)'), env: z.record(z.string()).optional().describe('Additional environment variables for the command'), }); function validateCommand(command) { if (FORBIDDEN_COMMANDS.some((forbidden) => command.includes(forbidden))) { throw new FileSystemError(`Command contains forbidden operations`, 'FORBIDDEN_COMMAND', undefined, { command }); } const baseCommand = command.split(' ')[0].trim(); const isDevCommand = DEV_COMMANDS.some((cmd) => baseCommand === cmd || baseCommand.endsWith(`/${cmd}`)); if (isDevCommand) { return true; } if (!SAFE_COMMAND_REGEX.test(command)) { throw new FileSystemError(`Command contains potentially unsafe characters`, 'UNSAFE_COMMAND', undefined, { command }); } return true; } export async function bashExecute(args, config) { validateCommand(args.command); const cwd = args.workingDir ? await validatePath(args.workingDir, config) : process.cwd(); const options = { cwd, timeout: args.timeout || 30000, env: args.env ? { ...process.env, ...args.env } : process.env, encoding: 'utf8', maxBuffer: 10 * 1024 * 1024, }; try { await logger.debug(`Executing bash command: ${args.command}`, { workingDir: cwd, timeout: options.timeout, }); const { stdout, stderr } = await execPromise(args.command, options); await logger.debug(`Command executed successfully: ${args.command}`, { exitCode: 0, stdoutPreview: stdout.substring(0, 100) + (stdout.length > 100 ? '...' : ''), }); return { stdout, stderr, exitCode: 0, }; } catch (error) { const stderr = error.stderr || ''; const stdout = error.stdout || ''; const exitCode = error.code || 1; await logger.warn(`Command execution failed: ${args.command}`, { exitCode, stderr: stderr.substring(0, 100) + (stderr.length > 100 ? '...' : ''), }); return { stdout, stderr, exitCode, }; } } export async function bashPipe(args, config) { for (const command of args.commands) { validateCommand(command); } args.workingDir ? await validatePath(args.workingDir, config) : process.cwd(); const pipedCommand = args.commands.join(' | '); return bashExecute({ command: pipedCommand, workingDir: args.workingDir, timeout: args.timeout, env: args.env, }, config); } //# sourceMappingURL=bash_tools.js.map