#!/usr/bin/env node
/**
* Agentic Control Framework (ACF) - MCP Server Wrapper
*
* @author Abhilash Chadhar (FutureAtoms)
* @description Wrapper script for the ACF MCP server
* @version 0.1.1
*
* This script handles launching the server and maintaining workspace paths between restarts
*/
const path = require('path');
const fs = require('fs');
const { spawn } = require('child_process');
// Get project root with improved environment variable support
// Order of precedence:
// 1. ACF_PATH environment variable
// 2. Path relative to this script
const projectRoot = process.env.ACF_PATH || path.resolve(__dirname, '..');
// Set up a place to store last used workspace
const tmpDir = path.resolve(projectRoot, '.tmp');
const workspacePathFile = path.join(tmpDir, 'last-workspace.txt');
// Helper to get command line arguments
const getArg = (flag) => {
const index = process.argv.findIndex(arg => arg === flag);
return index !== -1 && index < process.argv.length - 1 ? process.argv[index + 1] : null;
};
// Create tmp directory if it doesn't exist
try {
if (!fs.existsSync(tmpDir)) {
fs.mkdirSync(tmpDir, { recursive: true });
}
} catch (err) {
console.error('[ERROR] Error creating tmp directory:', err);
}
// Determine the workspace root with correct precedence.
let initialWorkspaceRoot = getArg('--workspaceRoot');
if (initialWorkspaceRoot) {
// If --workspaceRoot is provided, use it and save it. This is the highest priority.
try {
fs.writeFileSync(workspacePathFile, initialWorkspaceRoot);
} catch (err) {
console.error('[ERROR] Error saving workspace path:', err);
}
} else if (process.env.WORKSPACE_ROOT) {
// Otherwise, check for the environment variable.
initialWorkspaceRoot = process.env.WORKSPACE_ROOT;
} else {
// Otherwise, try to read from the saved file.
try {
if (fs.existsSync(workspacePathFile)) {
const savedPath = fs.readFileSync(workspacePathFile, 'utf8').trim();
if (savedPath && fs.existsSync(savedPath)) {
initialWorkspaceRoot = savedPath;
}
}
} catch (err) {
console.error('[ERROR] Error reading saved workspace path:', err);
}
}
// If still no workspace root, default to the current working directory.
if (!initialWorkspaceRoot) {
initialWorkspaceRoot = process.cwd();
}
// Save the determined workspace path for future runs if it's not the default
try {
if (initialWorkspaceRoot !== process.cwd()) {
fs.writeFileSync(workspacePathFile, initialWorkspaceRoot);
}
} catch (err) {
console.error('[ERROR] Error saving workspace path:', err);
}
// Debug info
if (process.env.DEBUG === 'true') {
console.error('[DEBUG] Project root:', projectRoot);
console.error('[DEBUG] Initial workspace root:', initialWorkspaceRoot);
console.error('[DEBUG] Tmp dir:', tmpDir);
console.error('[DEBUG] Workspace path file:', workspacePathFile);
}
// MCP server path (unified v2)
const mcpServerPath = path.join(projectRoot, 'src', 'mcp', 'server.js');
// Launch the server with the initial workspace root
const serverProcess = spawn('node', [
mcpServerPath,
'--workspaceRoot',
initialWorkspaceRoot
], {
stdio: ['inherit', 'pipe', 'pipe'],
env: {
...process.env,
WORKSPACE_ROOT: initialWorkspaceRoot,
ACF_PATH: projectRoot
}
});
// Handle process errors
serverProcess.on('error', (err) => {
console.error(`[ERROR] Failed to start MCP server process: ${err.message}`);
process.exit(1);
});
// Process server output
serverProcess.stdout.on('data', (data) => {
const output = data.toString();
// Only write to stdout for JSON-RPC responses, which is the primary communication channel
process.stdout.write(output);
});
serverProcess.stdout.on('error', (err) => {
console.error(`[ERROR] Error in stdout stream: ${err.message}`);
});
// Handle errors and logging from stderr
serverProcess.stderr.on('data', (data) => {
const output = data.toString();
// All stderr from MCP server gets written to stderr
process.stderr.write(output);
// Check for workspace path changes in stderr
const workspaceMatch = output.match(/\[INFO\] Workspace root set to: (.+)/);
if (workspaceMatch && workspaceMatch[1]) {
const newWorkspacePath = workspaceMatch[1].trim();
// Save the new workspace path for future sessions
try {
fs.writeFileSync(workspacePathFile, newWorkspacePath);
console.error(`[INFO] Saved workspace path: ${newWorkspacePath}`);
} catch (err) {
console.error('[ERROR] Error saving workspace path:', err);
}
}
});
serverProcess.stderr.on('error', (err) => {
console.error(`[ERROR] Error in stderr stream: ${err.message}`);
});
// Handle server exit
serverProcess.on('exit', (code) => {
if (code !== 0) {
console.error(`[ERROR] MCP server exited with code ${code}`);
} else {
console.error('[INFO] MCP server exited normally');
}
process.exit(code);
});
// Handle process termination
process.on('SIGINT', () => {
console.error('[INFO] Received SIGINT, shutting down...');
serverProcess.kill('SIGINT');
});
process.on('SIGTERM', () => {
console.error('[INFO] Received SIGTERM, shutting down...');
serverProcess.kill('SIGTERM');
});