UNPKG

@cequenceai/mcp-cli

Version:

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

446 lines (387 loc) • 17.2 kB
#!/usr/bin/env node import { Command } from 'commander'; import chalk from 'chalk'; import ora from 'ora'; import * as fs from 'fs'; import * as path from 'path'; import { setupCursor } from './clients/cursor'; import { setupClaude } from './clients/claude'; import { setupWindsurf } from './clients/windsurf'; import { setupClaudeCode } from './clients/claudecode'; import { setupVSCode } from './clients/vscode'; import { validateMcpUrl } from './utils/validation'; // Read version from package.json const packageJsonPath = path.join(__dirname, '..', 'package.json'); const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')); 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(packageJson.version); 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('claudecode') .description('Setup Cequence MCP server with Claude Code') .requiredOption('-u, --url <url>', 'Cequence MCP server URL') .option('-n, --name <name>', 'MCP server name') .option('-t, --transport <transport>', 'Transport type (http or sse)', 'http') .option('-k, --api-key <key>', 'API key for authentication (if required)') .action(async (options) => { const spinner = ora('Setting up Cequence MCP server with Claude Code...').start(); try { // Validate URL if (!validateMcpUrl(options.url)) { spinner.fail('Invalid Cequence MCP server URL'); process.exit(1); } // Validate transport const transport = options.transport?.toLowerCase(); if (transport !== 'http' && transport !== 'sse') { spinner.fail('Invalid transport type. Use "http" or "sse"'); process.exit(1); } const result = await setupClaudeCode({ url: options.url, name: options.name || 'Cequence MCP Gateway', apiKey: options.apiKey }, transport as 'http' | 'sse'); 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 Claude Code!')); console.log(chalk.cyan('\nšŸ“ Next steps:')); console.log(' 1. Claude Code will automatically detect the new MCP server'); console.log(' 2. You can verify with: claude mcp list'); console.log(' 3. Start using your secured APIs through Claude Code'); } 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('vscode') .description('Setup Cequence MCP server with VS Code') .requiredOption('-u, --url <url>', 'Cequence MCP server URL') .option('-n, --name <name>', 'MCP server name') .option('-g, --global', 'Configure globally for all workspaces (default: workspace-specific)') .option('-w, --workspace <path>', 'Workspace path for project-specific config') .option('-k, --api-key <key>', 'API key for authentication (if required)') .action(async (options) => { const configType = options.global ? 'global' : 'workspace'; const spinner = ora(`Setting up Cequence MCP server with VS Code (${configType})...`).start(); try { // Validate URL if (!validateMcpUrl(options.url)) { spinner.fail('Invalid Cequence MCP server URL'); process.exit(1); } const result = await setupVSCode({ url: options.url, name: options.name || 'Cequence MCP Gateway', apiKey: options.apiKey }, options.global || false, options.workspace); 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 VS Code!')); console.log(chalk.cyan(`\nšŸ“ Configuration written to: ${result.configPath}`)); console.log(chalk.cyan('\nšŸ“ Next steps:')); console.log(' 1. Reload VS Code (Cmd/Ctrl + Shift + P → "Developer: Reload Window")'); console.log(' 2. Ensure GitHub Copilot is installed and activated'); console.log(' 3. The MCP server should appear in your Copilot tools'); 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 Desktop'); console.log(' claudecode - Setup Cequence MCP server with Claude Code'); console.log(' vscode - Setup Cequence MCP server with VS Code'); 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);