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.
236 lines ⢠9.3 kB
JavaScript
import chalk from 'chalk';
import { existsSync, readFileSync, statSync } from 'fs';
import { join } from 'path';
import { exec } from 'child_process';
import { promisify } from 'util';
const execAsync = promisify(exec);
export async function statusCommand(options) {
if (!options.json) {
console.log(chalk.bold.blue('š Twenty MCP Server Status'));
console.log(chalk.gray('Checking server status and configuration\n'));
}
try {
const status = await gatherStatus(options.verbose || false);
if (options.json) {
console.log(JSON.stringify(status, null, 2));
}
else {
displayStatus(status, options.verbose || false);
}
}
catch (error) {
if (options.json) {
console.log(JSON.stringify({ error: error instanceof Error ? error.message : error }, null, 2));
}
else {
console.error(chalk.red('ā Failed to get status:'), error instanceof Error ? error.message : error);
}
process.exit(1);
}
}
async function gatherStatus(verbose) {
const status = {
installation: {
projectBuilt: false,
nodeModulesInstalled: false,
configurationExists: false,
},
configuration: {
twentyApiKey: false,
twentyBaseUrl: null,
authEnabled: false,
ipProtectionEnabled: false,
environment: 'development',
},
server: {
httpServerRunning: false,
},
validation: {
configValid: false,
apiConnectionWorking: false,
},
};
// Check installation
await checkInstallation(status);
// Check configuration
await checkConfiguration(status);
// Check server status
await checkServerStatus(status);
// Check validation (if verbose)
if (verbose) {
await checkValidation(status);
}
return status;
}
async function checkInstallation(status) {
// Check if project is built
const distPath = join(process.cwd(), 'dist/index.js');
status.installation.projectBuilt = existsSync(distPath);
if (status.installation.projectBuilt) {
const stats = statSync(distPath);
status.installation.lastBuilt = stats.mtime.toISOString();
}
// Check node modules
const nodeModulesPath = join(process.cwd(), 'node_modules');
status.installation.nodeModulesInstalled = existsSync(nodeModulesPath);
// Check configuration file
const envPath = join(process.cwd(), '.env');
status.installation.configurationExists = existsSync(envPath);
}
async function checkConfiguration(status) {
const envPath = join(process.cwd(), '.env');
if (!existsSync(envPath)) {
return;
}
const envContent = readFileSync(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]);
}
});
// Check Twenty configuration
status.configuration.twentyApiKey = envVars.has('TWENTY_API_KEY') && !!envVars.get('TWENTY_API_KEY');
status.configuration.twentyBaseUrl = envVars.get('TWENTY_BASE_URL') || null;
// Check auth configuration
status.configuration.authEnabled = envVars.get('AUTH_ENABLED') === 'true';
// Check IP protection
status.configuration.ipProtectionEnabled = envVars.get('IP_PROTECTION_ENABLED') === 'true';
// Determine environment
if (envVars.has('NODE_ENV')) {
status.configuration.environment = envVars.get('NODE_ENV') || 'development';
}
}
async function checkServerStatus(status) {
try {
// Try to connect to HTTP server on default port
const response = await checkHttpServer(3000);
if (response) {
status.server.httpServerRunning = true;
status.server.port = 3000;
}
}
catch {
// Server not running or not accessible
status.server.httpServerRunning = false;
}
// Try to find running Node.js processes
try {
const { stdout } = await execAsync('pgrep -f "twenty-mcp"');
const pids = stdout.trim().split('\n').filter(pid => pid);
if (pids.length > 0) {
status.server.processId = parseInt(pids[0], 10);
}
}
catch {
// No process found or command failed
}
}
async function checkHttpServer(port) {
return new Promise((resolve) => {
const http = require('http');
const req = http.request({
hostname: 'localhost',
port: port,
path: '/health',
method: 'GET',
timeout: 2000,
}, (res) => {
resolve(res.statusCode === 200);
});
req.on('error', () => resolve(false));
req.on('timeout', () => {
req.destroy();
resolve(false);
});
req.end();
});
}
async function checkValidation(status) {
try {
// Run validation script
const { stdout, stderr } = await execAsync('npm run validate');
status.validation.configValid = !stderr && stdout.includes('SUCCESS');
status.validation.lastValidated = new Date().toISOString();
// Basic API connection test
if (status.configuration.twentyApiKey && status.configuration.twentyBaseUrl) {
status.validation.apiConnectionWorking = await testApiConnection(status.configuration.twentyBaseUrl);
}
}
catch {
status.validation.configValid = false;
status.validation.apiConnectionWorking = false;
}
}
async function testApiConnection(baseUrl) {
try {
const response = await fetch(`${baseUrl}/graphql`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.TWENTY_API_KEY}`,
},
body: JSON.stringify({
query: '{ __typename }',
}),
});
return response.ok;
}
catch {
return false;
}
}
function displayStatus(status, verbose) {
// Installation Status
console.log(chalk.bold.yellow('šļø Installation:'));
console.log(` Project Built: ${status.installation.projectBuilt ? chalk.green('ā
Yes') : chalk.red('ā No')}`);
console.log(` Dependencies: ${status.installation.nodeModulesInstalled ? chalk.green('ā
Installed') : chalk.red('ā Missing')}`);
console.log(` Configuration: ${status.installation.configurationExists ? chalk.green('ā
Found') : chalk.red('ā Missing')}`);
if (verbose && status.installation.lastBuilt) {
console.log(` Last Built: ${chalk.gray(new Date(status.installation.lastBuilt).toLocaleString())}`);
}
// Configuration Status
console.log(chalk.bold.yellow('\nāļø Configuration:'));
console.log(` Twenty API Key: ${status.configuration.twentyApiKey ? chalk.green('ā
Set') : chalk.red('ā Missing')}`);
console.log(` Twenty Base URL: ${status.configuration.twentyBaseUrl ? chalk.green(status.configuration.twentyBaseUrl) : chalk.red('ā Not set')}`);
console.log(` OAuth Auth: ${status.configuration.authEnabled ? chalk.green('ā
Enabled') : chalk.gray('ā Disabled')}`);
console.log(` IP Protection: ${status.configuration.ipProtectionEnabled ? chalk.green('ā
Enabled') : chalk.gray('ā Disabled')}`);
console.log(` Environment: ${chalk.blue(status.configuration.environment)}`);
// Server Status
console.log(chalk.bold.yellow('\nš Server:'));
console.log(` HTTP Server: ${status.server.httpServerRunning ? chalk.green('ā
Running') : chalk.red('ā Not running')}`);
if (status.server.port) {
console.log(` Port: ${chalk.cyan(status.server.port)}`);
}
if (status.server.processId) {
console.log(` Process ID: ${chalk.gray(status.server.processId)}`);
}
// Validation Status (if verbose)
if (verbose) {
console.log(chalk.bold.yellow('\nš Validation:'));
console.log(` Config Valid: ${status.validation.configValid ? chalk.green('ā
Yes') : chalk.red('ā No')}`);
console.log(` API Connection: ${status.validation.apiConnectionWorking ? chalk.green('ā
Working') : chalk.red('ā Failed')}`);
if (status.validation.lastValidated) {
console.log(` Last Checked: ${chalk.gray(new Date(status.validation.lastValidated).toLocaleString())}`);
}
}
// Overall Status Summary
console.log(chalk.bold.yellow('\nš Summary:'));
const isHealthy = status.installation.projectBuilt &&
status.installation.nodeModulesInstalled &&
status.installation.configurationExists &&
status.configuration.twentyApiKey;
if (isHealthy) {
console.log(chalk.green('ā
Server is ready to use'));
if (!status.server.httpServerRunning) {
console.log(chalk.blue('š” Run "twenty-mcp start" to start the server'));
}
}
else {
console.log(chalk.red('ā Server needs configuration'));
console.log(chalk.blue('š” Run "twenty-mcp setup" to configure'));
}
console.log('');
}
//# sourceMappingURL=status.js.map