twenty-mcp-server
Version:
Easy-to-install Model Context Protocol server for Twenty CRM. Try instantly with 'npx twenty-mcp-server setup' or install globally for permanent use.
283 lines • 10.2 kB
JavaScript
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
import { join } from 'path';
import chalk from 'chalk';
import { getPlatformConfigDir } from './platform-utils.js';
export class ConfigManager {
configDir;
configPath;
envPath;
constructor(projectPath) {
if (projectPath) {
// Local project configuration
this.configDir = projectPath;
this.configPath = join(projectPath, '.twenty-mcp.json');
this.envPath = join(projectPath, '.env');
}
else {
// Global user configuration - platform-specific
const platformConfigDir = getPlatformConfigDir();
this.configDir = join(platformConfigDir, 'twenty-mcp');
this.configPath = join(this.configDir, 'config.json');
this.envPath = join(this.configDir, '.env');
}
}
ensureConfigDir() {
if (!existsSync(this.configDir)) {
mkdirSync(this.configDir, { recursive: true });
}
}
getDefaultConfig() {
return {
version: '1.1.0',
installation: {
installPath: process.cwd(),
installedAt: new Date().toISOString(),
},
twenty: {},
auth: {
enabled: false,
requireAuth: false,
},
ipProtection: {
enabled: false,
allowlist: ['127.0.0.1'],
trustedProxies: [],
blockUnknownIPs: true,
},
server: {
port: 3000,
mode: 'http',
verbose: false,
},
preferences: {
autoStart: false,
checkUpdates: true,
telemetry: false,
},
};
}
load() {
if (!existsSync(this.configPath)) {
return this.getDefaultConfig();
}
try {
const configData = readFileSync(this.configPath, 'utf8');
const config = JSON.parse(configData);
// Merge with defaults to ensure all fields exist
const defaultConfig = this.getDefaultConfig();
return this.mergeConfigs(defaultConfig, config);
}
catch (error) {
console.warn(chalk.yellow('⚠️ Invalid config file, using defaults'));
return this.getDefaultConfig();
}
}
mergeConfigs(defaultConfig, userConfig) {
return {
version: userConfig.version || defaultConfig.version,
installation: {
...defaultConfig.installation,
...userConfig.installation,
},
twenty: {
...defaultConfig.twenty,
...userConfig.twenty,
},
auth: {
...defaultConfig.auth,
...userConfig.auth,
},
ipProtection: {
...defaultConfig.ipProtection,
...userConfig.ipProtection,
},
server: {
...defaultConfig.server,
...userConfig.server,
},
preferences: {
...defaultConfig.preferences,
...userConfig.preferences,
},
};
}
save(config) {
this.ensureConfigDir();
// Update last updated timestamp
config.installation.lastUpdated = new Date().toISOString();
try {
const configData = JSON.stringify(config, null, 2);
writeFileSync(this.configPath, configData, 'utf8');
}
catch (error) {
throw new Error(`Failed to save config: ${error instanceof Error ? error.message : error}`);
}
}
updateTwentyConfig(apiKey, baseUrl) {
const config = this.load();
config.twenty.apiKey = apiKey;
config.twenty.baseUrl = baseUrl;
this.save(config);
this.syncToEnv(config);
}
updateAuthConfig(authConfig) {
const config = this.load();
config.auth = { ...config.auth, ...authConfig };
this.save(config);
this.syncToEnv(config);
}
updateIPProtectionConfig(ipConfig) {
const config = this.load();
config.ipProtection = { ...config.ipProtection, ...ipConfig };
this.save(config);
this.syncToEnv(config);
}
updateServerConfig(serverConfig) {
const config = this.load();
config.server = { ...config.server, ...serverConfig };
this.save(config);
}
updatePreferences(preferences) {
const config = this.load();
config.preferences = { ...config.preferences, ...preferences };
this.save(config);
}
// Sync configuration to .env file for backward compatibility
syncToEnv(config) {
const configData = config || this.load();
const envVars = new Map();
// Twenty CRM configuration
if (configData.twenty.apiKey) {
envVars.set('TWENTY_API_KEY', configData.twenty.apiKey);
}
if (configData.twenty.baseUrl) {
envVars.set('TWENTY_BASE_URL', configData.twenty.baseUrl);
}
// Auth configuration
envVars.set('AUTH_ENABLED', configData.auth.enabled.toString());
envVars.set('REQUIRE_AUTH', configData.auth.requireAuth.toString());
if (configData.auth.provider) {
envVars.set('AUTH_PROVIDER', configData.auth.provider);
}
if (configData.auth.clerkPublishableKey) {
envVars.set('CLERK_PUBLISHABLE_KEY', configData.auth.clerkPublishableKey);
}
if (configData.auth.clerkSecretKey) {
envVars.set('CLERK_SECRET_KEY', configData.auth.clerkSecretKey);
}
if (configData.auth.clerkDomain) {
envVars.set('CLERK_DOMAIN', configData.auth.clerkDomain);
}
if (configData.auth.encryptionSecret) {
envVars.set('API_KEY_ENCRYPTION_SECRET', configData.auth.encryptionSecret);
}
// IP Protection configuration
envVars.set('IP_PROTECTION_ENABLED', configData.ipProtection.enabled.toString());
if (configData.ipProtection.allowlist.length > 0) {
envVars.set('IP_ALLOWLIST', configData.ipProtection.allowlist.join(','));
}
if (configData.ipProtection.trustedProxies.length > 0) {
envVars.set('TRUSTED_PROXIES', configData.ipProtection.trustedProxies.join(','));
}
envVars.set('IP_BLOCK_UNKNOWN', configData.ipProtection.blockUnknownIPs.toString());
// Server configuration
envVars.set('MCP_SERVER_URL', `http://localhost:${configData.server.port}`);
// Write to .env file
let envContent = '';
envVars.forEach((value, key) => {
envContent += `${key}=${value}\n`;
});
try {
writeFileSync(this.envPath, envContent, 'utf8');
}
catch (error) {
console.warn(chalk.yellow('⚠️ Could not write .env file'));
}
}
// Load environment variables into current process
loadEnv() {
if (!existsSync(this.envPath)) {
return;
}
try {
const envContent = readFileSync(this.envPath, 'utf8');
envContent.split('\n').forEach(line => {
const match = line.match(/^([A-Z_]+)=(.*)$/);
if (match) {
process.env[match[1]] = match[2];
}
});
}
catch (error) {
console.warn(chalk.yellow('⚠️ Could not load .env file'));
}
}
getConfigPath() {
return this.configPath;
}
getEnvPath() {
return this.envPath;
}
exists() {
return existsSync(this.configPath);
}
reset() {
const defaultConfig = this.getDefaultConfig();
this.save(defaultConfig);
this.syncToEnv(defaultConfig);
}
// Import from existing .env file
importFromEnv() {
const config = this.getDefaultConfig();
if (!existsSync(this.envPath)) {
return config;
}
try {
const envContent = readFileSync(this.envPath, 'utf8');
const envVars = new Map();
envContent.split('\n').forEach(line => {
const match = line.match(/^([A-Z_]+)=(.*)$/);
if (match) {
envVars.set(match[1], match[2]);
}
});
// Import Twenty configuration
if (envVars.has('TWENTY_API_KEY')) {
config.twenty.apiKey = envVars.get('TWENTY_API_KEY');
}
if (envVars.has('TWENTY_BASE_URL')) {
config.twenty.baseUrl = envVars.get('TWENTY_BASE_URL');
}
// Import auth configuration
if (envVars.has('AUTH_ENABLED')) {
config.auth.enabled = envVars.get('AUTH_ENABLED') === 'true';
}
if (envVars.has('REQUIRE_AUTH')) {
config.auth.requireAuth = envVars.get('REQUIRE_AUTH') === 'true';
}
if (envVars.has('CLERK_PUBLISHABLE_KEY')) {
config.auth.clerkPublishableKey = envVars.get('CLERK_PUBLISHABLE_KEY');
}
if (envVars.has('CLERK_SECRET_KEY')) {
config.auth.clerkSecretKey = envVars.get('CLERK_SECRET_KEY');
}
// Import IP protection configuration
if (envVars.has('IP_PROTECTION_ENABLED')) {
config.ipProtection.enabled = envVars.get('IP_PROTECTION_ENABLED') === 'true';
}
if (envVars.has('IP_ALLOWLIST')) {
const allowlist = envVars.get('IP_ALLOWLIST');
if (allowlist) {
config.ipProtection.allowlist = allowlist.split(',').map(ip => ip.trim());
}
}
this.save(config);
return config;
}
catch (error) {
console.warn(chalk.yellow('⚠️ Could not import from .env file'));
return config;
}
}
}
//# sourceMappingURL=config-manager.js.map