atlas-mcp-server
Version:
ATLAS (Adaptive Task & Logic Automation System): An MCP server enabling LLM agents to manage projects, tasks, and knowledge via a Neo4j-backed, three-tier architecture. Facilitates complex workflow automation and project management through LLM Agents.
121 lines (120 loc) • 5.77 kB
JavaScript
import { existsSync, lstatSync } from "fs";
import path from "path";
import { fileURLToPath } from "url";
import { importDatabase } from "../index.js"; // Adjusted path
import { closeNeo4jConnection } from "../../index.js"; // Adjusted path
import { logger, requestContextService } from "../../../../utils/index.js"; // Adjusted path
import { config } from "../../../../config/index.js"; // Added config import
/**
* DB Import Script
* ================
*
* Description:
* Imports data from a specified backup directory into the Neo4j database,
* overwriting existing data. Validates paths for security.
*
* Usage:
* - Update package.json script for db:import to point to the new path.
* - Run directly: npm run db:import <path_to_backup_directory>
* - Example: npm run db:import ./backups/atlas-backup-20230101120000
*/
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Project root is now 5 levels up from src/services/neo4j/backupRestoreService/scripts/
const projectRoot = path.resolve(__dirname, "../../../../../");
/**
* Validates the provided backup directory path.
* Ensures the path is within the project root and is a directory.
* @param backupDir Path to the backup directory.
* @returns True if valid, false otherwise.
*/
const isValidBackupDir = (backupDir) => {
const resolvedBackupDir = path.resolve(backupDir);
const reqContext = requestContextService.createRequestContext({
operation: "isValidBackupDir.validation",
backupDir: resolvedBackupDir,
projectRoot,
});
if (!resolvedBackupDir.startsWith(projectRoot + path.sep) &&
resolvedBackupDir !== projectRoot) {
logger.error(`Invalid backup directory: Path is outside the project boundary.`, new Error("Path security violation: outside project boundary."), { ...reqContext, pathChecked: resolvedBackupDir });
return false;
}
if (!existsSync(resolvedBackupDir) ||
!lstatSync(resolvedBackupDir).isDirectory()) {
logger.error(`Invalid backup directory: Path does not exist or is not a directory.`, new Error("Path validation failed: not a directory or does not exist."), { ...reqContext, pathChecked: resolvedBackupDir });
return false;
}
const expectedFiles = [
"projects.json",
"tasks.json",
"knowledge.json",
"relationships.json",
"full-export.json", // Check for full-export as well, though import logic handles its absence
];
let foundAtLeastOne = false;
for (const file of expectedFiles) {
const filePath = path.join(resolvedBackupDir, file);
const resolvedFilePath = path.resolve(filePath);
if (!resolvedFilePath.startsWith(resolvedBackupDir + path.sep) &&
resolvedFilePath !== resolvedBackupDir) {
logger.warning(`Skipping check for potentially unsafe file path: ${resolvedFilePath} (outside ${resolvedBackupDir})`, { ...reqContext, filePath: resolvedFilePath });
continue;
}
if (existsSync(resolvedFilePath)) {
foundAtLeastOne = true;
// For full-export, its presence is enough. For others, we just note if they are missing.
if (file !== "full-export.json" && !existsSync(resolvedFilePath)) {
logger.warning(`Expected backup file not found: ${resolvedFilePath}. Import might be incomplete if not using full-export.json.`, { ...reqContext, missingFile: resolvedFilePath });
}
}
else if (file !== "full-export.json") {
// Only warn if individual files are missing and full-export isn't the one being checked
logger.warning(`Expected backup file not found: ${resolvedFilePath}. Import might be incomplete if not using full-export.json.`, { ...reqContext, missingFile: resolvedFilePath });
}
}
// If neither full-export.json nor any of the individual main files are found, it's likely not a valid backup.
// However, the import logic itself checks for full-export first, then individual files.
// This validation is more of a sanity check.
return true;
};
/**
* Manual import script entry point.
*/
const runManualImport = async () => {
await logger.initialize(config.logLevel);
const args = process.argv.slice(2);
if (args.length !== 1) {
logger.error("Usage: npm run db:import <path_to_backup_directory>", new Error("Invalid arguments"), requestContextService.createRequestContext({
operation: "runManualImport.argCheck",
}));
process.exit(1);
}
const userInputPath = args[0];
const resolvedBackupDir = path.resolve(userInputPath);
logger.info(`Attempting manual database import from: ${resolvedBackupDir}`);
logger.warning("!!! THIS WILL OVERWRITE ALL EXISTING DATA IN THE DATABASE !!!");
if (!isValidBackupDir(resolvedBackupDir)) {
process.exit(1);
}
try {
await importDatabase(resolvedBackupDir); // importDatabase itself uses validatedBackupRoot internally
logger.info(`Manual import from ${resolvedBackupDir} completed successfully.`);
}
catch (error) {
const reqContext = requestContextService.createRequestContext({
operation: "runManualImport.catch",
backupDir: resolvedBackupDir,
});
const errorToLog = error instanceof Error ? error : new Error(JSON.stringify(error));
logger.error("Manual database import failed:", errorToLog, reqContext);
process.exitCode = 1;
}
finally {
logger.info("Closing Neo4j connection...");
await closeNeo4jConnection();
logger.info("Neo4j connection closed.");
}
};
runManualImport();