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.

314 lines (313 loc) 14 kB
import fs from 'fs/promises'; import fsSync from 'fs'; import path from 'path'; import logger from '../../logger.js'; import { getCacheDirectory } from './directoryUtils.js'; const DEFAULT_CONFIG = { cache: { enabled: true, maxEntries: 10000, maxAge: 24 * 60 * 60 * 1000, useFileBasedAccess: true, useFileHashes: true, maxCachedFiles: 0, useMemoryCache: false, memoryMaxEntries: 1000, memoryMaxAge: 10 * 60 * 1000, memoryThreshold: 0.8, }, processing: { batchSize: 100, logMemoryUsage: false, maxMemoryUsage: 1024, incremental: true, incrementalConfig: { useFileHashes: true, useFileMetadata: true, saveProcessedFilesList: true } }, output: { format: 'markdown', splitOutput: false, }, featureFlags: { enhancedFunctionDetection: true, contextAnalysis: true, frameworkDetection: true, roleIdentification: true, heuristicNaming: true, memoryOptimization: true, }, importResolver: { enabled: false, useCache: true, cacheSize: 10000, extensions: { javascript: ['.js', '.json', '.node', '.mjs', '.cjs'], typescript: ['.ts', '.tsx', '.js', '.jsx', '.json', '.node'], python: ['.py', '.pyw', '.pyc', '.pyo', '.pyd'], java: ['.java', '.class', '.jar'], csharp: ['.cs', '.dll'], go: ['.go'], ruby: ['.rb', '.rake', '.gemspec'], rust: ['.rs'], php: ['.php'] }, generateImportGraph: false, expandSecurityBoundary: true, enhanceImports: false, importMaxDepth: 3, semgrepTimeout: 30, semgrepMaxMemory: '1GB', disableSemgrepFallback: false }, debug: { showDetailedImports: false, generateASTDebugFiles: false } }; export async function validateCodeMapConfig(config) { const envAllowedDir = process.env.CODE_MAP_ALLOWED_DIR; const allowedMappingDirectory = config.allowedMappingDirectory || envAllowedDir; if (!allowedMappingDirectory) { throw new Error('allowedMappingDirectory is required in the configuration or CODE_MAP_ALLOWED_DIR environment variable'); } await validateAllowedMappingDirectory(allowedMappingDirectory); const validatedConfig = { allowedMappingDirectory, cache: validateCacheConfig(config.cache, allowedMappingDirectory), processing: validateProcessingConfig(config.processing), output: validateOutputConfig(config.output), featureFlags: validateFeatureFlagsConfig(config.featureFlags), importResolver: validateImportResolverConfig(config.importResolver), debug: validateDebugConfig(config.debug), }; return validatedConfig; } export async function validateAllowedMappingDirectory(dirPath) { if (!path.isAbsolute(dirPath)) { throw new Error(`allowedMappingDirectory must be an absolute path. Received: ${dirPath}`); } const normalizedPath = path.resolve(dirPath); try { const stats = await fs.stat(normalizedPath); if (!stats.isDirectory()) { throw new Error(`allowedMappingDirectory must be a directory. Path: ${normalizedPath}`); } } catch (error) { if (error instanceof Error && 'code' in error && error.code === 'ENOENT') { throw new Error(`allowedMappingDirectory does not exist: ${normalizedPath}`); } throw error; } try { await fs.access(normalizedPath, fsSync.constants.R_OK); } catch { throw new Error(`allowedMappingDirectory is not readable: ${normalizedPath}`); } logger.debug(`Validated allowedMappingDirectory: ${normalizedPath}`); } export function validateCacheConfig(config, allowedMappingDirectory) { const defaultCache = DEFAULT_CONFIG.cache; if (!config) { const cacheConfig = { ...defaultCache }; if (allowedMappingDirectory && cacheConfig.enabled) { const tempConfig = { allowedMappingDirectory }; cacheConfig.cacheDir = getCacheDirectory(tempConfig); } return cacheConfig; } if (config.cacheDir && (config.enabled !== false)) { if (!path.isAbsolute(config.cacheDir)) { logger.warn(`cacheDir should be an absolute path. Received: ${config.cacheDir}. Using relative to current working directory.`); } const normalizedCacheDir = path.resolve(config.cacheDir); const parentDir = path.dirname(normalizedCacheDir); try { if (!fsSync.existsSync(parentDir)) { logger.warn(`Parent directory of cacheDir does not exist: ${parentDir}. Cache operations may fail.`); } } catch (error) { logger.warn(`Unable to validate cacheDir parent directory: ${parentDir}. Error: ${error}`); } } const cacheConfig = { enabled: config.enabled !== undefined ? config.enabled : defaultCache.enabled, maxEntries: config.maxEntries || defaultCache.maxEntries, maxAge: config.maxAge || defaultCache.maxAge, cacheDir: config.cacheDir, useFileBasedAccess: config.useFileBasedAccess !== undefined ? config.useFileBasedAccess : defaultCache.useFileBasedAccess, useFileHashes: config.useFileHashes !== undefined ? config.useFileHashes : defaultCache.useFileHashes, maxCachedFiles: config.maxCachedFiles !== undefined ? config.maxCachedFiles : defaultCache.maxCachedFiles, useMemoryCache: config.useMemoryCache !== undefined ? config.useMemoryCache : defaultCache.useMemoryCache, memoryMaxEntries: config.memoryMaxEntries || defaultCache.memoryMaxEntries, memoryMaxAge: config.memoryMaxAge || defaultCache.memoryMaxAge, memoryThreshold: config.memoryThreshold !== undefined ? config.memoryThreshold : defaultCache.memoryThreshold, }; if (!cacheConfig.cacheDir && allowedMappingDirectory && cacheConfig.enabled) { const tempConfig = { allowedMappingDirectory }; cacheConfig.cacheDir = getCacheDirectory(tempConfig); } return cacheConfig; } export function validateProcessingConfig(config) { const defaultProcessing = DEFAULT_CONFIG.processing; if (!config) { return defaultProcessing; } const validatedConfig = { batchSize: config.batchSize || defaultProcessing.batchSize, logMemoryUsage: config.logMemoryUsage !== undefined ? config.logMemoryUsage : defaultProcessing.logMemoryUsage, maxMemoryUsage: config.maxMemoryUsage || defaultProcessing.maxMemoryUsage, incremental: config.incremental !== undefined ? config.incremental : defaultProcessing.incremental, periodicGC: config.periodicGC !== undefined ? config.periodicGC : defaultProcessing.periodicGC, gcInterval: config.gcInterval || defaultProcessing.gcInterval, }; if (config.incrementalConfig || defaultProcessing.incrementalConfig) { validatedConfig.incrementalConfig = { useFileHashes: config.incrementalConfig?.useFileHashes !== undefined ? config.incrementalConfig.useFileHashes : defaultProcessing.incrementalConfig?.useFileHashes, useFileMetadata: config.incrementalConfig?.useFileMetadata !== undefined ? config.incrementalConfig.useFileMetadata : defaultProcessing.incrementalConfig?.useFileMetadata, maxCachedHashes: config.incrementalConfig?.maxCachedHashes || defaultProcessing.incrementalConfig?.maxCachedHashes, maxHashAge: config.incrementalConfig?.maxHashAge || defaultProcessing.incrementalConfig?.maxHashAge, previousFilesListPath: config.incrementalConfig?.previousFilesListPath || defaultProcessing.incrementalConfig?.previousFilesListPath, saveProcessedFilesList: config.incrementalConfig?.saveProcessedFilesList !== undefined ? config.incrementalConfig.saveProcessedFilesList : defaultProcessing.incrementalConfig?.saveProcessedFilesList, }; } return validatedConfig; } export function validateOutputConfig(config) { const defaultOutput = DEFAULT_CONFIG.output; if (!config) { return defaultOutput; } return { outputDir: config.outputDir, format: config.format || defaultOutput.format, splitOutput: config.splitOutput !== undefined ? config.splitOutput : defaultOutput.splitOutput, filePrefix: config.filePrefix, }; } export function validateFeatureFlagsConfig(config) { const defaultFeatureFlags = DEFAULT_CONFIG.featureFlags; if (!config) { return defaultFeatureFlags; } return { enhancedFunctionDetection: config.enhancedFunctionDetection !== undefined ? config.enhancedFunctionDetection : defaultFeatureFlags.enhancedFunctionDetection, contextAnalysis: config.contextAnalysis !== undefined ? config.contextAnalysis : defaultFeatureFlags.contextAnalysis, frameworkDetection: config.frameworkDetection !== undefined ? config.frameworkDetection : defaultFeatureFlags.frameworkDetection, roleIdentification: config.roleIdentification !== undefined ? config.roleIdentification : defaultFeatureFlags.roleIdentification, heuristicNaming: config.heuristicNaming !== undefined ? config.heuristicNaming : defaultFeatureFlags.heuristicNaming, memoryOptimization: config.memoryOptimization !== undefined ? config.memoryOptimization : defaultFeatureFlags.memoryOptimization, }; } export function validateImportResolverConfig(config) { const defaultImportResolver = DEFAULT_CONFIG.importResolver; if (!config) { return defaultImportResolver; } const validatedConfig = { enabled: config.enabled !== undefined ? config.enabled : defaultImportResolver.enabled, useCache: config.useCache !== undefined ? config.useCache : defaultImportResolver.useCache, cacheSize: config.cacheSize || defaultImportResolver.cacheSize, generateImportGraph: config.generateImportGraph !== undefined ? config.generateImportGraph : defaultImportResolver.generateImportGraph, expandSecurityBoundary: config.expandSecurityBoundary !== undefined ? config.expandSecurityBoundary : defaultImportResolver.expandSecurityBoundary, enhanceImports: config.enhanceImports !== undefined ? config.enhanceImports : defaultImportResolver.enhanceImports, importMaxDepth: config.importMaxDepth || defaultImportResolver.importMaxDepth, tsConfig: config.tsConfig, pythonPath: config.pythonPath, pythonVersion: config.pythonVersion, venvPath: config.venvPath, clangdPath: config.clangdPath, compileFlags: config.compileFlags, includePaths: config.includePaths, semgrepPatterns: config.semgrepPatterns, semgrepTimeout: config.semgrepTimeout, semgrepMaxMemory: config.semgrepMaxMemory, disableSemgrepFallback: config.disableSemgrepFallback !== undefined ? config.disableSemgrepFallback : false }; if (config.extensions) { validatedConfig.extensions = { ...defaultImportResolver.extensions, ...config.extensions }; } else { validatedConfig.extensions = defaultImportResolver.extensions; } if (validatedConfig.expandSecurityBoundary) { logger.debug('Import resolver configured with expanded security boundary. This allows resolving imports outside the allowed mapping directory, but file content access is still restricted.'); } return validatedConfig; } export function validateDebugConfig(config) { const defaultDebug = DEFAULT_CONFIG.debug; if (!config) { return defaultDebug; } return { showDetailedImports: config.showDetailedImports !== undefined ? config.showDetailedImports : defaultDebug.showDetailedImports, generateASTDebugFiles: config.generateASTDebugFiles !== undefined ? config.generateASTDebugFiles : defaultDebug.generateASTDebugFiles, }; } export async function extractCodeMapConfig(config) { let codeMapConfig = {}; if (config) { if (config.env) { logger.debug({ configEnv: config.env, hasAllowedDir: Boolean(config.env.CODE_MAP_ALLOWED_DIR), hasOutputDir: Boolean(config.env.VIBE_CODER_OUTPUT_DIR) }, 'Extracting code-map config from MCP client environment variables'); if (config.env.CODE_MAP_ALLOWED_DIR) { codeMapConfig.allowedMappingDirectory = config.env.CODE_MAP_ALLOWED_DIR; } if (config.env.VIBE_CODER_OUTPUT_DIR) { codeMapConfig.output = codeMapConfig.output || {}; codeMapConfig.output.outputDir = path.join(config.env.VIBE_CODER_OUTPUT_DIR, 'code-map-generator'); } } const toolConfig = config.tools?.['map-codebase']; const configSection = config.config?.['map-codebase']; if (toolConfig || configSection) { logger.debug({ toolConfig, configSection }, 'Using legacy tool config extraction as additional config'); codeMapConfig = { ...configSection, ...toolConfig, ...codeMapConfig }; } } logger.debug({ extractedCodeMapConfig: codeMapConfig, hasConfig: Boolean(config), hasEnv: Boolean(config?.env), configKeys: config ? Object.keys(config) : [] }, 'Extracted code-map-generator config'); return validateCodeMapConfig(codeMapConfig); }