UNPKG

@cequenceai/mcp-cli

Version:

Cequence MCP CLI - Command-line tool for setting up Cequence MCP servers with AI clients

341 lines (296 loc) • 12.7 kB
#!/usr/bin/env node import { Command } from 'commander'; import chalk from 'chalk'; import ora from 'ora'; import { setupCursor } from './clients/cursor'; import { setupClaude } from './clients/claude'; import { setupWindsurf } from './clients/windsurf'; import { validateMcpUrl } from './utils/validation'; const program = new Command(); program .name('cequence-mcp') .description('Cequence MCP CLI - Command-line tool for setting up Cequence MCP servers with AI clients') .version('1.3.0'); program .command('cursor') .description('Setup Cequence MCP server with Cursor IDE') .requiredOption('-u, --url <url>', 'Cequence MCP server URL') .option('-n, --name <name>', 'MCP server name') .option('-k, --api-key <key>', 'API key for authentication (if required)') .option('--no-refresh', 'Disable automatic token refresh (requires manual login when token expires)') .action(async (options) => { const spinner = ora('Setting up Cequence MCP server with Cursor IDE...').start(); try { // Validate URL if (!validateMcpUrl(options.url)) { spinner.fail('Invalid Cequence MCP server URL'); process.exit(1); } await setupCursor({ url: options.url, name: options.name || 'Cequence MCP Gateway', apiKey: options.apiKey, noRefresh: options.noRefresh }); spinner.succeed(chalk.green('āœ… Cequence MCP server successfully configured for Cursor IDE!')); console.log(chalk.cyan('\nšŸ“ Next steps:')); console.log(' 1. Restart Cursor IDE'); console.log(' 2. Open the command palette (Cmd/Ctrl + Shift + P)'); console.log(' 3. Look for MCP-related commands'); console.log(' 4. Your Cequence-secured APIs are now available as MCP tools'); } catch (error) { spinner.fail('Failed to setup Cequence MCP server'); console.error(chalk.red('Error:'), error instanceof Error ? error.message : String(error)); process.exit(1); } }); program .command('claude') .description('Setup Cequence MCP server with Claude AI') .requiredOption('-u, --url <url>', 'Cequence MCP server URL') .option('-n, --name <name>', 'MCP server name') .option('-k, --api-key <key>', 'API key for authentication (if required)') .option('--no-refresh', 'Disable automatic token refresh (requires manual login when token expires)') .action(async (options) => { const spinner = ora('Setting up Cequence MCP server with Claude AI...').start(); try { // Validate URL if (!validateMcpUrl(options.url)) { spinner.fail('Invalid Cequence MCP server URL'); process.exit(1); } await setupClaude({ url: options.url, name: options.name || 'Cequence MCP Gateway', apiKey: options.apiKey, noRefresh: options.noRefresh }); spinner.succeed(chalk.green('āœ… Cequence MCP server successfully configured for Claude AI!')); console.log(chalk.cyan('\nšŸ“ Next steps:')); console.log(' 1. Restart Claude AI application'); console.log(' 2. The Cequence MCP server should be available in your conversation'); console.log(' 3. Start using your secured APIs through Claude\'s interface'); } catch (error) { spinner.fail('Failed to setup Cequence MCP server'); console.error(chalk.red('Error:'), error instanceof Error ? error.message : String(error)); process.exit(1); } }); program .command('windsurf') .description('Setup Cequence MCP server with Windsurf IDE') .requiredOption('-u, --url <url>', 'Cequence MCP server URL') .option('-n, --name <name>', 'MCP server name') .option('-k, --api-key <key>', 'API key for authentication (if required)') .option('--no-refresh', 'Disable automatic token refresh (requires manual login when token expires)') .action(async (options) => { const spinner = ora('Setting up Cequence MCP server with Windsurf IDE...').start(); try { // Validate URL if (!validateMcpUrl(options.url)) { spinner.fail('Invalid Cequence MCP server URL'); process.exit(1); } const result = await setupWindsurf({ url: options.url, name: options.name || 'Cequence MCP Gateway', apiKey: options.apiKey, noRefresh: options.noRefresh }); if (!result.success) { spinner.fail('Failed to setup Cequence MCP server'); console.error(chalk.red('Error:'), result.message); process.exit(1); } spinner.succeed(chalk.green('āœ… Cequence MCP server successfully configured for Windsurf IDE!')); console.log(chalk.cyan(`\nšŸ“ Configuration written to: ${result.configPath}`)); console.log(chalk.cyan('\nšŸ“ Next steps:')); console.log(' 1. Restart Windsurf IDE'); console.log(' 2. Open the command palette (Cmd/Ctrl + Shift + P)'); console.log(' 3. Look for MCP-related commands'); console.log(' 4. Your Cequence-secured APIs are now available as MCP tools'); } catch (error) { spinner.fail('Failed to setup Cequence MCP server'); console.error(chalk.red('Error:'), error instanceof Error ? error.message : String(error)); process.exit(1); } }); program .command('serve') .description('Start MCP bridge server (internal use by Claude AI)') .requiredOption('-u, --url <url>', 'HTTP MCP server URL to bridge') .option('-n, --name <name>', 'MCP server name') .option('-k, --api-key <key>', 'API key for authentication (if required)') .action(async (options) => { // This command creates a stdio-based MCP server that bridges to HTTP const http = require('http'); const https = require('https'); const { URL } = require('url'); try { const serverUrl = new URL(options.url); const client = serverUrl.protocol === 'https:' ? https : http; // Set up stdio for MCP protocol process.stdin.setEncoding('utf8'); process.stdin.resume(); let buffer = ''; process.stdin.on('data', async (chunk) => { buffer += chunk; // Process complete JSON-RPC messages (each ends with newline) let newlineIndex; while ((newlineIndex = buffer.indexOf('\n')) >= 0) { const line = buffer.slice(0, newlineIndex).trim(); buffer = buffer.slice(newlineIndex + 1); if (line) { try { const request = JSON.parse(line); // Validate JSON-RPC request structure if (!request.jsonrpc || !request.method) { const errorResponse = { jsonrpc: '2.0', id: request.id || null, error: { code: -32600, message: 'Invalid Request: Missing jsonrpc or method' } }; process.stdout.write(JSON.stringify(errorResponse) + '\n'); continue; } // Forward request to HTTP MCP server const postData = JSON.stringify(request); const headers: Record<string, string | number> = { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(postData), 'Accept': 'application/json, text/event-stream' }; // Add API key header if provided if (options.apiKey) { headers['Authorization'] = `Bearer ${options.apiKey}`; } const requestOptions = { hostname: serverUrl.hostname, port: serverUrl.port, path: serverUrl.pathname, method: 'POST', headers, timeout: 30000 // 30 second timeout }; const httpReq = client.request(requestOptions, (httpRes: any) => { let responseData = ''; httpRes.on('data', (chunk: any) => { responseData += chunk; }); httpRes.on('end', () => { try { if (responseData.trim()) { const response = JSON.parse(responseData); // Ensure response has proper JSON-RPC structure if (!response.jsonrpc) { response.jsonrpc = '2.0'; } if (response.id === undefined) { response.id = request.id; } process.stdout.write(JSON.stringify(response) + '\n'); } else { // Empty response - send error const errorResponse = { jsonrpc: '2.0', id: request.id || null, error: { code: -32603, message: 'Empty response from HTTP server' } }; process.stdout.write(JSON.stringify(errorResponse) + '\n'); } } catch (parseError: any) { // Invalid JSON response const errorResponse = { jsonrpc: '2.0', id: request.id || null, error: { code: -32603, message: `Invalid JSON response from HTTP server: ${parseError?.message || 'Unknown error'}` } }; process.stdout.write(JSON.stringify(errorResponse) + '\n'); } }); }); httpReq.on('timeout', () => { httpReq.destroy(); const errorResponse = { jsonrpc: '2.0', id: request.id || null, error: { code: -32603, message: 'Request timeout' } }; process.stdout.write(JSON.stringify(errorResponse) + '\n'); }); httpReq.on('error', (error: any) => { const errorResponse = { jsonrpc: '2.0', id: request.id || null, error: { code: -32603, message: `HTTP request error: ${error?.message || 'Unknown error'}` } }; process.stdout.write(JSON.stringify(errorResponse) + '\n'); }); httpReq.write(postData); httpReq.end(); } catch (parseError: any) { // Invalid JSON input const errorResponse = { jsonrpc: '2.0', id: null, error: { code: -32700, message: `Parse error: ${parseError?.message || 'Unknown error'}` } }; process.stdout.write(JSON.stringify(errorResponse) + '\n'); } } } }); process.stdin.on('end', () => { process.exit(0); }); process.stdin.on('error', (error) => { console.error('Stdin error:', error); process.exit(1); }); } catch (error) { console.error('Bridge server error:', error); process.exit(1); } }); program .command('list') .description('List configured Cequence MCP servers') .action(async () => { console.log(chalk.blue('šŸ“‹ Configured Cequence MCP Servers:')); // TODO: Implement listing functionality console.log(chalk.yellow(' Feature coming soon...')); }); // Handle unknown commands program.on('command:*', () => { console.error(chalk.red('Invalid command:'), program.args.join(' ')); console.log(chalk.cyan('Available commands:')); console.log(' cursor - Setup Cequence MCP server with Cursor IDE'); console.log(' claude - Setup Cequence MCP server with Claude AI'); console.log(' windsurf - Setup Cequence MCP server with Windsurf IDE'); console.log(' serve - Start MCP bridge server (internal use)'); console.log(' list - List configured Cequence MCP servers'); process.exit(1); }); // Show help when no command is provided if (!process.argv.slice(2).length) { program.outputHelp(); } program.parse(process.argv);