import express from 'express'; import { promises as fs } from 'fs'; import path from 'path'; import os from 'os'; import { fileURLToPath } from 'url'; import { dirname } from 'path'; import { spawn } from 'child_process'; const router = express.Router(); const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // Claude CLI command routes // GET /api/mcp/cli/list - List MCP servers using Claude CLI router.get('/cli/list', async (req, res) => { try { console.log('📋 Listing MCP servers using Claude CLI'); const { spawn } = await import('child_process'); const { promisify } = await import('util'); const exec = promisify(spawn); const process = spawn('claude', ['mcp', 'list', '-s', 'user'], { stdio: ['pipe', 'pipe', 'pipe'] }); let stdout = ''; let stderr = ''; process.stdout.on('data', (data) => { stdout += data.toString(); }); process.stderr.on('data', (data) => { stderr += data.toString(); }); process.on('close', (code) => { if (code === 0) { res.json({ success: true, output: stdout, servers: parseClaudeListOutput(stdout) }); } else { console.error('Claude CLI error:', stderr); res.status(500).json({ error: 'Claude CLI command failed', details: stderr }); } }); process.on('error', (error) => { console.error('Error running Claude CLI:', error); res.status(500).json({ error: 'Failed to run Claude CLI', details: error.message }); }); } catch (error) { console.error('Error listing MCP servers via CLI:', error); res.status(500).json({ error: 'Failed to list MCP servers', details: error.message }); } }); // POST /api/mcp/cli/add - Add MCP server using Claude CLI router.post('/cli/add', async (req, res) => { try { const { name, type = 'stdio', command, args = [], url, headers = {}, env = {} } = req.body; console.log('➕ Adding MCP server using Claude CLI:', name); const { spawn } = await import('child_process'); let cliArgs = ['mcp', 'add']; if (type === 'http') { cliArgs.push('--transport', 'http', name, '-s', 'user', url); // Add headers if provided Object.entries(headers).forEach(([key, value]) => { cliArgs.push('--header', `${key}: ${value}`); }); } else if (type === 'sse') { cliArgs.push('--transport', 'sse', name, '-s', 'user', url); // Add headers if provided Object.entries(headers).forEach(([key, value]) => { cliArgs.push('--header', `${key}: ${value}`); }); } else { // stdio (default): claude mcp add -s user [args...] cliArgs.push(name, '-s', 'user'); // Add environment variables Object.entries(env).forEach(([key, value]) => { cliArgs.push('-e', `${key}=${value}`); }); cliArgs.push(command); if (args && args.length > 0) { cliArgs.push(...args); } } console.log('🔧 Running Claude CLI command:', 'claude', cliArgs.join(' ')); const process = spawn('claude', cliArgs, { stdio: ['pipe', 'pipe', 'pipe'] }); let stdout = ''; let stderr = ''; process.stdout.on('data', (data) => { stdout += data.toString(); }); process.stderr.on('data', (data) => { stderr += data.toString(); }); process.on('close', (code) => { if (code === 0) { res.json({ success: true, output: stdout, message: `MCP server "${name}" added successfully` }); } else { console.error('Claude CLI error:', stderr); res.status(400).json({ error: 'Claude CLI command failed', details: stderr }); } }); process.on('error', (error) => { console.error('Error running Claude CLI:', error); res.status(500).json({ error: 'Failed to run Claude CLI', details: error.message }); }); } catch (error) { console.error('Error adding MCP server via CLI:', error); res.status(500).json({ error: 'Failed to add MCP server', details: error.message }); } }); // DELETE /api/mcp/cli/remove/:name - Remove MCP server using Claude CLI router.delete('/cli/remove/:name', async (req, res) => { try { const { name } = req.params; console.log('🗑️ Removing MCP server using Claude CLI:', name); const { spawn } = await import('child_process'); const process = spawn('claude', ['mcp', 'remove', '-s', 'user', name], { stdio: ['pipe', 'pipe', 'pipe'] }); let stdout = ''; let stderr = ''; process.stdout.on('data', (data) => { stdout += data.toString(); }); process.stderr.on('data', (data) => { stderr += data.toString(); }); process.on('close', (code) => { if (code === 0) { res.json({ success: true, output: stdout, message: `MCP server "${name}" removed successfully` }); } else { console.error('Claude CLI error:', stderr); res.status(400).json({ error: 'Claude CLI command failed', details: stderr }); } }); process.on('error', (error) => { console.error('Error running Claude CLI:', error); res.status(500).json({ error: 'Failed to run Claude CLI', details: error.message }); }); } catch (error) { console.error('Error removing MCP server via CLI:', error); res.status(500).json({ error: 'Failed to remove MCP server', details: error.message }); } }); // GET /api/mcp/cli/get/:name - Get MCP server details using Claude CLI router.get('/cli/get/:name', async (req, res) => { try { const { name } = req.params; console.log('📄 Getting MCP server details using Claude CLI:', name); const { spawn } = await import('child_process'); const process = spawn('claude', ['mcp', 'get', '-s', 'user', name], { stdio: ['pipe', 'pipe', 'pipe'] }); let stdout = ''; let stderr = ''; process.stdout.on('data', (data) => { stdout += data.toString(); }); process.stderr.on('data', (data) => { stderr += data.toString(); }); process.on('close', (code) => { if (code === 0) { res.json({ success: true, output: stdout, server: parseClaudeGetOutput(stdout) }); } else { console.error('Claude CLI error:', stderr); res.status(404).json({ error: 'Claude CLI command failed', details: stderr }); } }); process.on('error', (error) => { console.error('Error running Claude CLI:', error); res.status(500).json({ error: 'Failed to run Claude CLI', details: error.message }); }); } catch (error) { console.error('Error getting MCP server details via CLI:', error); res.status(500).json({ error: 'Failed to get MCP server details', details: error.message }); } }); // Helper functions to parse Claude CLI output function parseClaudeListOutput(output) { // Parse the output from 'claude mcp list' command // Format: "name: command/url" or "name: url (TYPE)" const servers = []; const lines = output.split('\n').filter(line => line.trim()); for (const line of lines) { if (line.includes(':')) { const colonIndex = line.indexOf(':'); const name = line.substring(0, colonIndex).trim(); const rest = line.substring(colonIndex + 1).trim(); let type = 'stdio'; // default type // Check if it has transport type in parentheses like "(SSE)" or "(HTTP)" const typeMatch = rest.match(/\((\w+)\)\s*$/); if (typeMatch) { type = typeMatch[1].toLowerCase(); } else if (rest.startsWith('http://') || rest.startsWith('https://')) { // If it's a URL but no explicit type, assume HTTP type = 'http'; } if (name) { servers.push({ name, type, status: 'active' }); } } } console.log('🔍 Parsed Claude CLI servers:', servers); return servers; } function parseClaudeGetOutput(output) { // Parse the output from 'claude mcp get ' command // This is a simple parser - might need adjustment based on actual output format try { // Try to extract JSON if present const jsonMatch = output.match(/\{[\s\S]*\}/); if (jsonMatch) { return JSON.parse(jsonMatch[0]); } // Otherwise, parse as text const server = { raw_output: output }; const lines = output.split('\n'); for (const line of lines) { if (line.includes('Name:')) { server.name = line.split(':')[1]?.trim(); } else if (line.includes('Type:')) { server.type = line.split(':')[1]?.trim(); } else if (line.includes('Command:')) { server.command = line.split(':')[1]?.trim(); } else if (line.includes('URL:')) { server.url = line.split(':')[1]?.trim(); } } return server; } catch (error) { return { raw_output: output, parse_error: error.message }; } } export default router;