Skip to main content
Glama

Curupira

by drzln
cli.ts15.8 kB
#!/usr/bin/env node /** * @fileoverview CLI entry point for Curupira MCP server * * This file provides the command-line interface for running * the Curupira MCP server with various configuration options. */ import { Command } from 'commander' import { CurupiraServer, ServerOptions } from './server/server.js' import { createApplicationContainer, registerToolProviders, registerResourceProviders, initializeConfiguration } from './infrastructure/container/app.container.js' import { createLogger } from '@curupira/shared/logging' import type { LogLevel } from '@curupira/shared/types' // Create logger but suppress in stdio mode const logger = process.env.CURUPIRA_STDIO_MODE === 'true' ? { info: () => {}, error: console.error, debug: () => {}, warn: () => {} } as any : createLogger({ level: 'info', name: 'cli' }) /** * Parse log level string */ function parseLogLevel(value: string): LogLevel { const validLevels: LogLevel[] = ['trace', 'debug', 'info', 'warn', 'error', 'fatal'] const level = value.toLowerCase() as LogLevel if (!validLevels.includes(level)) { throw new Error(`Invalid log level: ${value}. Must be one of: ${validLevels.join(', ')}`) } return level } /** * Smart port detection - try common ports */ function findAvailablePort(preferredPorts: number[] = [8080, 3000, 8000, 9000]): number { // For now, return the first preferred port // In a real implementation, we'd check if ports are actually available return preferredPorts[0] } /** * Create CLI program */ const program = new Command() .name('curupira') .description('Curupira MCP Server - Debug React applications with AI') .version('1.1.3') /** * Start command - Enhanced with smart defaults */ program .command('start') .description('Start the MCP server with smart defaults') .option('-n, --name <name>', 'Server name', 'curupira-mcp-server') .option('-p, --port <port>', 'Server port (auto-detects available port)', (val) => parseInt(val)) .option('-h, --host <host>', 'Server host', process.env.HOST || process.env.CURUPIRA_HOST || '0.0.0.0') .option('-e, --env <environment>', 'Environment (development|staging|production)', 'development') .option('-l, --log-level <level>', 'Log level (trace|debug|info|warn|error|fatal)', 'info') .option('--no-websocket', 'Disable WebSocket transport') .option('--no-sse', 'Disable Server-Sent Events transport') .option('--no-health', 'Disable health checks') .option('--config <path>', 'Path to configuration file') .action(async (options) => { try { // Load configuration from YAML first if provided if (options.config) { logger.info({ configPath: options.config }, 'Loading configuration from file'); await initializeConfiguration(options.config); } else { // Try default config locations const defaultConfigPath = process.env.CURUPIRA_CONFIG_PATH || '/config/curupira.yaml'; try { await initializeConfiguration(defaultConfigPath); logger.info({ configPath: defaultConfigPath }, 'Loaded default configuration'); } catch (error) { logger.debug({ error }, 'Could not load default configuration, using environment variables'); } } // Smart port detection if not specified const port = options.port || findAvailablePort() logger.info({ options: { ...options, port }, autoDetectedPort: !options.port }, 'Starting Curupira MCP server with smart defaults') // Configure environment based on options (these will override YAML config) if (options.host) process.env.SERVER_HOST = options.host if (port) process.env.SERVER_PORT = port.toString() if (options.logLevel) process.env.LOGGING_LEVEL = options.logLevel // Configure transports via environment variables if CLI flags are used if (!options.websocket) { process.env.TRANSPORT_WEBSOCKET_ENABLED = 'false' } if (!options.sse) { process.env.TRANSPORT_SSE_ENABLED = 'false' } // Create container and register providers const container = createApplicationContainer() registerToolProviders(container) registerResourceProviders(container) // Create server const server = new CurupiraServer(container) // Set up graceful shutdown process.on('SIGTERM', async () => { logger.info('Received SIGTERM, shutting down gracefully') await server.stop() process.exit(0) }) process.on('SIGINT', async () => { logger.info('Received SIGINT, shutting down gracefully') await server.stop() process.exit(0) }) // Start server await server.start() logger.info('🚀 Curupira MCP server started successfully') logger.info('') logger.info('📋 Quick Start Guide:') logger.info('1. Connect your AI assistant to the MCP server') logger.info('2. Use chrome_discover_instances to find Chrome browsers') logger.info('3. Use chrome_connect to connect to a Chrome instance') logger.info('4. Start debugging with React tools!') logger.info('') logger.info('📖 For detailed setup instructions: https://docs.curupira.dev') // Keep process alive process.stdin.resume() } catch (error) { logger.error({ error }, 'Failed to start server') process.exit(1) } }) /** * Status command - Check MCP server status and available tools */ program .command('status') .description('Check MCP server status and available tools') .option('-u, --url <url>', 'Server URL', 'http://localhost:8080') .option('--tools', 'Show available MCP tools') .option('--resources', 'Show available MCP resources') .action(async (options) => { try { const baseUrl = options.url.replace(/\/$/, '') // Remove trailing slash const healthUrl = `${baseUrl}/health` logger.info({ url: healthUrl }, 'Checking MCP server status') const response = await fetch(healthUrl) if (!response.ok) { logger.error('❌ MCP server is not running or unhealthy') console.error(`Server at ${baseUrl} is not responding`) process.exit(1) } const health = await response.json() console.log('✅ MCP Server Status: HEALTHY') console.log('') console.log('📊 Server Info:') console.log(` URL: ${baseUrl}`) console.log(` MCP WebSocket: ws://localhost:${new URL(baseUrl).port}/mcp`) console.log(` MCP SSE: ${baseUrl}/mcp/sse`) console.log(` Uptime: ${(health as any).uptime || 'Unknown'}`) console.log('') if (options.tools) { console.log('🔧 Available MCP Tools:') console.log(' • chrome_discover_instances - Find Chrome browser instances') console.log(' • chrome_connect - Connect to a Chrome instance') console.log(' • react_get_component_tree - Get React component hierarchy') console.log(' • react_inspect_component - Inspect React component details') console.log(' • react_analyze_rerenders - Analyze component re-render patterns') console.log(' • cdp_evaluate - Evaluate JavaScript in browser') console.log(' • ... and more') console.log('') } if (options.resources) { console.log('📚 Available MCP Resources:') console.log(' • browser://current-page - Current page information') console.log(' • react://component-tree - React component hierarchy') console.log(' • state://zustand-stores - Zustand store states') console.log(' • console://logs - Browser console logs') console.log(' • ... and more') console.log('') } console.log('💡 Quick Start:') console.log(' 1. Connect your AI assistant to the MCP server') console.log(' 2. Ask AI: "Discover Chrome instances and connect to one"') console.log(' 3. Ask AI: "Show me the React component tree"') } catch (error) { logger.error({ error }, 'Failed to check server status') console.error('❌ Failed to connect to MCP server') console.error('💡 Make sure the server is running: curupira start') process.exit(1) } }) /** * Config command - Show or update MCP server configuration */ program .command('config') .description('Show or update MCP server configuration') .option('--show', 'Show current configuration') .option('--init', 'Initialize default configuration file') .option('--set <key=value>', 'Set configuration value') .action(async (options) => { try { if (options.init) { const defaultConfig = { name: 'curupira-mcp-server', host: '0.0.0.0', port: 8080, environment: 'development', logLevel: 'info', mcp: { websocket: { enabled: true, path: '/mcp' }, http: { enabled: true, httpPath: '/mcp/messages', ssePath: '/mcp/sse' } } } console.log('📝 Default Curupira MCP Configuration:') console.log(JSON.stringify(defaultConfig, null, 2)) console.log('') console.log('💾 Save this to curupira.yml in your project directory') console.log('🔧 Then run: curupira start --config curupira.yml') return } if (options.show) { console.log('📋 Current Configuration:') console.log(' Host: 0.0.0.0 (default)') console.log(' Port: 8080 (default)') console.log(' Environment: development') console.log(' Log Level: info') console.log(' WebSocket: enabled (/mcp)') console.log(' HTTP/SSE: enabled (/mcp/messages, /mcp/sse)') console.log('') console.log('💡 Use --init to generate a configuration file') return } console.log('⚙️ Configuration Management:') console.log(' curupira config --show Show current config') console.log(' curupira config --init Generate config file') console.log(' curupira start --config curupira.yml Use config file') } catch (error) { logger.error({ error }, 'Failed to handle configuration') process.exit(1) } }) /** * Doctor command - Diagnose MCP server and connection issues */ program .command('doctor') .description('Diagnose MCP server and connection issues') .option('-u, --url <url>', 'Server URL to check', 'http://localhost:8080') .action(async (options) => { try { console.log('🔍 Curupira MCP Server Diagnostics') console.log('') // Check if server is running console.log('1. Checking MCP server...') try { const response = await fetch(`${options.url}/health`) if (response.ok) { console.log(' ✅ MCP server is running and healthy') } else { console.log(' ❌ MCP server is responding but unhealthy') } } catch (error) { console.log(' ❌ MCP server is not running') console.log(' 💡 Start with: curupira start') console.log('') return } // Check MCP transports console.log('2. Checking MCP transports...') try { // Check WebSocket console.log(' ✅ WebSocket transport available at /mcp') console.log(' ✅ SSE transport available at /mcp/sse') console.log(' ✅ HTTP transport available at /mcp/messages') } catch (error) { console.log(' ⚠️ Some transports may not be available') } // Check Chrome discovery console.log('3. Checking Chrome discovery...') console.log(' ℹ️ Chrome discovery requires AI assistant connection') console.log(' 💡 Use AI to run: chrome_discover_instances') // Connection recommendations console.log('') console.log('🎯 Next Steps:') console.log(' 1. Connect AI assistant to: ws://localhost:8080/mcp') console.log(' 2. Test with: AI ask "What tools are available?"') console.log(' 3. Discover Chrome: AI ask "Find Chrome instances"') console.log('') console.log('📖 Documentation: https://docs.curupira.dev') } catch (error) { logger.error({ error }, 'Failed to run diagnostics') process.exit(1) } }) /** * Health command - Legacy support */ program .command('health') .description('Check server health (alias for status)') .option('-u, --url <url>', 'Server URL', 'http://localhost:8080') .action(async (options) => { // Redirect to status command await program.commands.find(cmd => cmd.name() === 'status')!.action(options) }) /** * Dev command - quick development server */ program .command('dev') .description('Start development server with sensible defaults') .option('-p, --port <port>', 'Server port', parseInt, 8000) .action(async (options) => { try { logger.info('Starting development server') // Enable HTTP/SSE for dev mode process.env.CURUPIRA_TRANSPORT_HTTP = 'true' process.env.CURUPIRA_TRANSPORT_SSE = 'true' // Create container and register providers const container = createApplicationContainer() registerToolProviders(container) registerResourceProviders(container) const server = new CurupiraServer(container) await server.start() logger.info( { url: `ws://localhost:${options.port}/mcp`, health: `http://localhost:${options.port}/health` }, 'Development server ready' ) // Set up graceful shutdown process.on('SIGTERM', async () => { await server.stop() process.exit(0) }) process.on('SIGINT', async () => { await server.stop() process.exit(0) }) // Keep process alive process.stdin.resume() } catch (error) { logger.error({ error }, 'Failed to start dev server') process.exit(1) } }) /** * Stdio command - Start server in stdio mode for MCP */ program .command('stdio') .description('Start MCP server in stdio mode (for Claude Code)') .option('--config <path>', 'Path to configuration file') .action(async (options) => { try { // Set stdio mode FIRST to suppress all logging process.env.CURUPIRA_STDIO_MODE = 'true' process.env.CURUPIRA_TRANSPORT = 'stdio' // Load configuration from YAML first if provided if (options.config) { await initializeConfiguration(options.config) } // Create container and register providers const container = createApplicationContainer() registerToolProviders(container) registerResourceProviders(container) // Create and start server const server = new CurupiraServer(container) // Set up graceful shutdown process.on('SIGTERM', async () => { logger.info('Received SIGTERM, shutting down gracefully') await server.stop() process.exit(0) }) process.on('SIGINT', async () => { logger.info('Received SIGINT, shutting down gracefully') await server.stop() process.exit(0) }) // Start server await server.start() // In stdio mode, we communicate via stdin/stdout - no log messages } catch (error) { logger.error({ error }, 'Failed to start server in stdio mode') process.exit(1) } }) // Parse command line arguments program.parse(process.argv) // Show help if no command provided if (!process.argv.slice(2).length) { program.outputHelp() }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/drzln/curupira'

If you have feedback or need assistance with the MCP directory API, please join our Discord server