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