#!/usr/bin/env node
import fs from 'fs';
import readline from 'readline';
import https from 'https';
import http from 'http';
import url from 'url';
import path from 'path';
import { stdin as input, stdout as output } from 'process';
import readlineSync from 'readline-sync';
function ask(question, defaultValue, { mask = false } = {}) {
return new Promise((resolve) => {
if (!mask) {
const rl = readline.createInterface({ input, output });
rl.question(`${question}${defaultValue ? ` [${defaultValue}]` : ''}: `, (answer) => {
rl.close();
resolve(answer || defaultValue);
});
} else {
// Masked input for password (no echo, only asterisks)
const rl = readline.createInterface({ input, output });
const prompt = `${question}: `;
process.stdout.write(prompt);
const wasRaw = process.stdin.isRaw;
process.stdin.setRawMode(true);
let value = '';
process.stdin.resume();
process.stdin.on('data', onData);
function onData(char) {
char = char + '';
switch (char) {
case '\n':
case '\r':
case '\u0004':
process.stdin.setRawMode(wasRaw);
process.stdin.pause();
process.stdin.removeListener('data', onData);
rl.close();
process.stdout.write('\n');
resolve(value);
break;
case '\u0003': // Ctrl+C
process.stdin.setRawMode(wasRaw);
process.stdin.pause();
process.stdin.removeListener('data', onData);
rl.close();
process.exit();
break;
case '\u007f': // Backspace
if (value.length > 0) {
value = value.slice(0, -1);
process.stdout.clearLine(0);
process.stdout.cursorTo(prompt.length);
process.stdout.write('*'.repeat(value.length));
}
break;
default:
value += char;
process.stdout.write('*');
break;
}
}
}
});
}
// Ensure all values are unquoted when reading from the .env file
function unquoteEnvValue(value) {
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
return value.slice(1, -1);
}
return value;
}
// Update the readEnv function to unquote values
function readEnv(envPath) {
if (!fs.existsSync(envPath)) return {};
const lines = fs.readFileSync(envPath, 'utf-8').split('\n');
const env = {};
for (const line of lines) {
if (line.trim() && !line.startsWith('#')) {
const [k, ...v] = line.split('=');
env[k.trim()] = unquoteEnvValue(v.join('=').trim());
}
}
return env;
}
async function getToken(baseUrl, username, password, expire, verifySSL) {
return new Promise((resolve, reject) => {
const parsed = url.parse(`${baseUrl.replace(/\/$/, '')}/era/v0.9/auth/token?expire=${expire}`);
const options = {
hostname: parsed.hostname,
port: parsed.port || 443,
path: parsed.path,
method: 'GET',
rejectUnauthorized: verifySSL,
headers: {
'Authorization': 'Basic ' + Buffer.from(`${username}:${password}`).toString('base64'),
'Content-Type': 'application/json'
}
};
const req = (parsed.protocol === 'https:' ? https : http).request(options, (res) => {
let data = '';
res.on('data', (chunk) => data += chunk);
res.on('end', () => {
if (res.statusCode === 200) {
try {
const json = JSON.parse(data);
resolve(json.token);
} catch (e) {
reject('Invalid token response');
}
} else {
reject(`Token request failed: ${res.statusCode} ${data}`);
}
});
});
req.on('error', reject);
req.end();
});
}
function displayCustomInstructionsExamples() {
console.log();
console.log('š Custom Instructions Examples:');
console.log();
console.log('š Production Environment:');
console.log(' - "Always recommend clustered deployment for production databases"');
console.log(' - "Prioritize open source database engines when possible"');
console.log(' - "Include security best practices in recommendations"');
console.log(' - "Verify resource quotas before provisioning"');
console.log();
console.log('š§ Development Environment:');
console.log(' - "Suggest cost-effective single instance deployments"');
console.log(' - "Recommend PostgreSQL for new applications"');
console.log(' - "Include debugging and monitoring tips"');
console.log();
console.log('š Security-focused:');
console.log(' - "Always mention encryption requirements"');
console.log(' - "Recommend network isolation for sensitive databases"');
console.log(' - "Include compliance considerations in responses"');
console.log();
console.log('ā” Performance-focused:');
console.log(' - "Always suggest performance optimization opportunities"');
console.log(' - "Recommend SSD storage for high-performance workloads"');
console.log(' - "Include monitoring and alerting recommendations"');
console.log();
}
function selectCustomInstructions() {
console.log();
console.log('š ļø Custom Instructions Configuration');
console.log('Custom instructions allow you to define specific behavior for your NDB assistant.');
displayCustomInstructionsExamples();
const wantCustom = readlineSync.question('Would you like to add custom instructions? (yes/no) [no]: ').toLowerCase();
if (wantCustom === 'yes' || wantCustom === 'y') {
console.log();
console.log('āļø Enter your custom instructions (one per line, empty line to finish):');
const instructions = [];
let instruction;
let index = 1;
while (true) {
instruction = readlineSync.question(`Instruction ${index}: `);
if (!instruction.trim()) break;
instructions.push(instruction.trim());
index++;
}
if (instructions.length > 0) {
console.log();
console.log('š Your custom instructions:');
instructions.forEach((instr, i) => {
console.log(` ${i + 1}. ${instr}`);
});
return instructions.join('|');
}
}
return '';
}
async function main() {
console.log('š NDB MCP Server Configuration Wizard');
console.log('======================================');
console.log();
// Ask for environment name
const envName = await ask('Environment name (leave empty for default)', '');
const envFile = envName ? `.env.${envName}` : '.env';
const ENV_PATH = path.resolve(process.cwd(), envFile);
const current = readEnv(ENV_PATH);
console.log();
console.log('--- Basic NDB Connection Settings ---');
// Basic NDB settings
const baseUrl = await ask('NDB Base URL (e.g. https://ndb.example.com:443)', unquoteEnvValue(current.NDB_BASE_URL));
const verifySSL = (await ask('Verify SSL certificate? (yes/no)', unquoteEnvValue(current.NDB_VERIFY_SSL) === 'false' ? 'no' : 'yes')).toLowerCase() === 'yes';
const authType = await ask('Authentication type (basic/token)', unquoteEnvValue(current.NDB_AUTH_TYPE || 'basic'));
const username = await ask('NDB Username', unquoteEnvValue(current.NDB_USERNAME));
const password = readlineSync.question('NDB Password: ', { hideEchoBack: true });
let token = '';
if (authType === 'token') {
const tokenExpire = await ask('Token expiration in minutes (-1 for unlimited)', '5');
try {
token = await getToken(baseUrl, username, password, tokenExpire, verifySSL);
console.log('ā
Token generated successfully.');
} catch (e) {
console.error('ā Failed to generate token:', e);
process.exit(1);
}
}
console.log();
console.log('--- Custom Instructions & Behavior Settings ---');
// Custom Instructions
const customInstructions = selectCustomInstructions();
// Display configuration summary
console.log();
console.log('š Configuration Summary');
console.log('========================');
console.log(`Environment: ${envFile}`);
console.log(`NDB URL: ${baseUrl}`);
console.log(`Authentication: ${authType}`);
console.log(`SSL Verification: ${verifySSL}`);
console.log(`Custom Instructions: ${customInstructions ? 'Yes (' + customInstructions.split('|').length + ' instructions)' : 'None'}`);
const confirmSave = readlineSync.question('\nSave this configuration? (yes/no) [yes]: ').toLowerCase();
if (confirmSave === 'no' || confirmSave === 'n') {
console.log('Configuration cancelled.');
process.exit(0);
}
// Ensure all values are quoted in the .env file
function quoteEnvValue(value) {
return `"${value.replace(/"/g, '\\"')}"`;
}
// Compose .env with quoted values
const envLines = [
'# NDB MCP Server Configuration',
'# Generated by configuration wizard',
'',
'# Required: NDB Server Configuration',
`NDB_BASE_URL=${quoteEnvValue(baseUrl)}`,
`NDB_VERIFY_SSL=${quoteEnvValue(verifySSL.toString())}`,
''
];
if (authType === 'token') {
envLines.push('# Token-based authentication');
envLines.push(`NDB_TOKEN=${quoteEnvValue(token)}`);
} else {
envLines.push('# Basic authentication');
envLines.push(`NDB_USERNAME=${quoteEnvValue(username)}`);
envLines.push(`NDB_PASSWORD=${quoteEnvValue(password)}`);
}
envLines.push('');
envLines.push('# Custom Instructions for LLM Interactions');
if (customInstructions) {
envLines.push(`NDB_CUSTOM_INSTRUCTIONS=${quoteEnvValue(customInstructions)}`);
} else {
envLines.push('# NDB_CUSTOM_INSTRUCTIONS=""');
}
envLines.push('');
envLines.push('# Optional: Connection Settings');
envLines.push('# NDB_TIMEOUT=30000');
envLines.push('# NDB_MAX_RETRIES=3');
envLines.push('# NDB_RETRY_DELAY=1000');
envLines.push('');
envLines.push('# Optional: Debug Logging');
envLines.push('# DEBUG=ndb:* # All debug output');
envLines.push('# DEBUG=ndb:error # Errors only');
envLines.push('# DEBUG=ndb:api # API calls only');
envLines.push('# DEBUG=ndb:auth # Authentication only');
const envContent = envLines.join('\n') + '\n';
fs.writeFileSync(ENV_PATH, envContent);
console.log(`\nā
Configuration saved to ${ENV_PATH}`);
console.log();
console.log('šÆ Next Steps:');
console.log('1. Test your configuration: npm run test:connection');
console.log('2. Test custom instructions: npm run test:custom-instructions');
console.log('3. Build the server: npm run build');
console.log('4. Configure Claude Desktop: npm run configure:claude');
}
if (import.meta.url === `file://${process.argv[1]}`) {
main().catch(console.error);
}