@vectorchat/mcp-server
Version:
VectorChat MCP Server - Encrypted AI-to-AI communication with hardware security (YubiKey/TPM). 45+ MCP tools for Windsurf, Claude, and AI assistants. Model-based identity with EMDM encryption. Dynamic AI playbook system, communication zones, message relay
177 lines (154 loc) • 5.58 kB
JavaScript
/**
* VectorChat Daemon Management Module
* Shared functions used by both proxy and CLI
*/
const { exec, spawn } = require('child_process');
const fs = require('fs');
const path = require('path');
const os = require('os');
/**
* Detect mode: embedded (Flutter app) or standalone (system daemon)
*
* Embedded mode triggered by:
* - VECTORCHAT_EMBEDDED=true environment variable
* - VECTORCHAT_MODE=embedded environment variable
* - --embedded command-line flag
*
* Standalone mode (default):
* - No embedded flags/env vars
* - Uses ~/.vectorchat-server/ for configuration
*/
function detectMode() {
// Check environment variables
if (process.env.VECTORCHAT_EMBEDDED === 'true') {
return 'embedded';
}
if (process.env.VECTORCHAT_MODE === 'embedded') {
return 'embedded';
}
// Check command-line arguments
if (process.argv.includes('--embedded')) {
return 'embedded';
}
// Default to standalone
return 'standalone';
}
/**
* Get configuration directory based on mode
*
* Embedded mode (Flutter app):
* - Uses ~/.vectorchat/ (client directory)
* - Port 8767
* - Client identity
*
* Standalone mode (system daemon):
* - Uses ~/.vectorchat-server/ (server directory)
* - Port 8765
* - Server identity
*/
function getConfigDirectory() {
const mode = detectMode();
if (mode === 'embedded') {
// Client directory for embedded mode (Flutter app)
return path.join(os.homedir(), '.vectorchat');
} else {
// Server directory for standalone mode (system daemon)
return path.join(os.homedir(), '.vectorchat-server');
}
}
const MODE = detectMode();
const VECTORCHAT_DIR = getConfigDirectory();
const IDENTITY_FILE = path.join(VECTORCHAT_DIR, 'identity.json');
const AI_AGENTS_DIR = path.join(VECTORCHAT_DIR, 'ai_agents');
const DAEMON_LOG = path.join(VECTORCHAT_DIR, 'daemon.log');
const PACKAGE_ROOT = path.join(__dirname, '..');
const DAEMON_SCRIPT = path.join(PACKAGE_ROOT, 'vectorchat', 'daemon', 'ai_chat_production_v1_0_0.py');
// Log mode detection
if (process.env.DEBUG === 'true') {
console.log(`[Mode Detection]`);
console.log(` Mode: ${MODE}`);
console.log(` Config Dir: ${VECTORCHAT_DIR}`);
console.log(` Identity File: ${IDENTITY_FILE}`);
}
function getIdentity() {
try {
if (fs.existsSync(IDENTITY_FILE)) {
const identity = JSON.parse(fs.readFileSync(IDENTITY_FILE, 'utf8'));
if (identity.userId && !identity.user_id) identity.user_id = identity.userId;
return identity;
}
} catch (error) {
console.error(`Error reading identity: ${error.message}`);
}
return null;
}
function setIdentity(userId) {
const identity = { user_id: userId, created_at: new Date().toISOString() };
const dir = path.dirname(IDENTITY_FILE);
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
fs.writeFileSync(IDENTITY_FILE, JSON.stringify(identity, null, 2));
return identity;
}
function getDaemonStatus() {
return new Promise((resolve) => {
exec('ps aux | grep "ai_chat_production" | grep -v grep', (error, stdout) => {
const running = !error && stdout.trim().length > 0;
const identity = getIdentity();
resolve({ running, identity: identity ? identity.user_id : 'Unknown', log_file: DAEMON_LOG });
});
});
}
function startDaemon(enableIpfs = false) {
return new Promise((resolve, reject) => {
const identity = getIdentity();
if (!identity) return reject(new Error('No identity set. Run setup first.'));
if (!fs.existsSync(DAEMON_SCRIPT)) return reject(new Error(`Daemon script not found: ${DAEMON_SCRIPT}`));
if (!fs.existsSync(VECTORCHAT_DIR)) fs.mkdirSync(VECTORCHAT_DIR, { recursive: true });
// Determine port based on mode
const mode = detectMode();
const port = mode === 'embedded' ? 8767 : 8765;
const args = [DAEMON_SCRIPT, identity.user_id, '--daemon', '--port', port.toString()];
if (enableIpfs) args.push('--enable-ipfs');
console.log(` Mode: ${mode}`);
console.log(` Port: ${port}`);
console.log(` Config: ${VECTORCHAT_DIR}`);
const logStream = fs.openSync(DAEMON_LOG, 'a');
const daemon = spawn('python3', args, { detached: true, stdio: ['ignore', logStream, logStream] });
daemon.unref();
fs.closeSync(logStream);
setTimeout(() => {
getDaemonStatus().then(status => {
if (status.running) resolve({ success: true, message: `✅ Daemon started (${identity.user_id})`, log: DAEMON_LOG });
else reject(new Error('Daemon failed to start'));
});
}, 3000);
});
}
function stopDaemon() {
return new Promise((resolve) => {
exec('pkill -f "ai_chat_production"', () => {
setTimeout(() => {
getDaemonStatus().then(status => {
resolve({ success: !status.running, message: status.running ? '❌ Failed to stop' : '✅ Daemon stopped' });
});
}, 1000);
});
});
}
async function restartDaemon(enableIpfs = false) {
await stopDaemon();
await new Promise(resolve => setTimeout(resolve, 2000));
return await startDaemon(enableIpfs);
}
function readDaemonLogs(lines = 50) {
return new Promise((resolve) => {
exec(`tail -${lines} ${DAEMON_LOG} 2>/dev/null || echo "No logs found"`, (error, stdout) => {
resolve({ logs: stdout });
});
});
}
module.exports = {
MODE, VECTORCHAT_DIR, IDENTITY_FILE, AI_AGENTS_DIR, DAEMON_LOG, DAEMON_SCRIPT, PACKAGE_ROOT,
detectMode, getConfigDirectory,
getIdentity, setIdentity, getDaemonStatus, startDaemon, stopDaemon, restartDaemon, readDaemonLogs
};