UNPKG

@pluggedin/pluggedin-mcp-proxy

Version:

Unified MCP proxy that aggregates all your MCP servers (STDIO, SSE, Streamable HTTP) into one powerful interface. Access any tool through a single connection, search across unified documents with built-in RAG, and receive notifications from any model. Tes

375 lines (374 loc) 14.3 kB
import { Router } from 'express'; import { suggestServerConfiguration, analyzeServerPatterns, validateConfiguration } from '../services/ai-config-assistant'; import { db } from '../db'; import { mcpServers } from '../schema'; import { eq, inArray, isNull, or } from 'drizzle-orm'; const router = Router(); // Suggest configuration for a specific server router.post('/suggest/:serverId', async (req, res) => { try { const { serverId } = req.params; const { userSystem, installedServers, preferredPaths } = req.body; // Get server details const server = await db.query.mcpServers.findFirst({ where: eq(mcpServers.id, serverId) }); if (!server) { return res.status(404).json({ error: 'Server not found' }); } if (!server.command || !server.args) { return res.status(400).json({ error: 'Server missing command information', message: 'This server does not have installation instructions yet' }); } const suggestion = await suggestServerConfiguration({ serverName: server.name, serverType: server.serverType, command: server.command, args: server.args || [], envs: server.envs || [], repositoryUrl: server.repositoryUrl || undefined, description: server.description, userSystem, installedServers, preferredPaths }); if (!suggestion) { return res.status(503).json({ error: 'AI service unavailable', message: 'Could not generate configuration suggestion' }); } // Validate the suggestion const validation = validateConfiguration(suggestion, server.serverType || 'STDIO'); res.json({ server: { id: server.id, name: server.name, command: server.command, serverType: server.serverType }, suggestion, validation, needsUserInput: validation.issues.some(issue => issue.includes('needs a real value') || issue.includes('PLACEHOLDER')) }); } catch (error) { console.error('Error suggesting configuration:', error); res.status(500).json({ error: 'Internal server error' }); } }); // Batch configuration suggestions for multiple servers router.post('/suggest-batch', async (req, res) => { try { const { serverIds, userSystem } = req.body; if (!Array.isArray(serverIds) || serverIds.length === 0) { return res.status(400).json({ error: 'Server IDs array required' }); } // Limit batch size if (serverIds.length > 10) { return res.status(400).json({ error: 'Too many servers', message: 'Maximum 10 servers per batch' }); } // Get all servers const servers = await db.query.mcpServers.findMany({ where: (mcpServers, { inArray }) => inArray(mcpServers.id, serverIds) }); const suggestions = await Promise.all(servers .filter(server => server.command && server.args) .map(async (server) => { const suggestion = await suggestServerConfiguration({ serverName: server.name, serverType: server.serverType, command: server.command, args: server.args || [], envs: server.envs || [], repositoryUrl: server.repositoryUrl || undefined, description: server.description, userSystem }); if (!suggestion) return null; const validation = validateConfiguration(suggestion, server.serverType || 'STDIO'); return { serverId: server.id, serverName: server.name, suggestion, validation }; })); res.json({ suggestions: suggestions.filter(Boolean), failed: servers.length - suggestions.filter(Boolean).length }); } catch (error) { console.error('Error batch suggesting:', error); res.status(500).json({ error: 'Internal server error' }); } }); // Analyze patterns across servers router.post('/analyze-patterns', async (req, res) => { try { const { category } = req.body; let servers; if (category) { servers = await db.query.mcpServers.findMany({ where: eq(mcpServers.category, category), limit: 20 }); } else { servers = await db.query.mcpServers.findMany({ limit: 20 }); } const serversWithCommands = servers .filter(s => s.command && s.args) .map(s => ({ name: s.name, command: s.command, args: s.args || [], envs: s.envs || [] })); if (serversWithCommands.length === 0) { return res.json({ commonPatterns: [], recommendations: ['No servers with commands found for analysis'] }); } const analysis = await analyzeServerPatterns(serversWithCommands); res.json(analysis || { commonPatterns: [], recommendations: ['AI analysis unavailable'] }); } catch (error) { console.error('Error analyzing patterns:', error); res.status(500).json({ error: 'Internal server error' }); } }); // Check if AI service is available router.get('/status', async (_req, res) => { const hasApiKey = !!process.env.OPENAI_API_KEY; res.json({ available: hasApiKey, model: 'gpt-4o-mini', features: hasApiKey ? [ 'configuration_suggestions', 'batch_suggestions', 'pattern_analysis', 'fix_server', 'batch_fix' ] : [] }); }); // Fix a server's configuration with AI router.post('/fix-server', async (req, res) => { try { const { serverData, customDirectives, userSystem } = req.body; if (!serverData || !serverData.name) { return res.status(400).json({ error: 'Invalid server data', message: 'Server name is required' }); } // Build the configuration request const configRequest = { serverName: serverData.name, serverType: serverData.type || 'STDIO', command: serverData.command || 'npx', args: Array.isArray(serverData.args) ? serverData.args : (serverData.args ? serverData.args.split(' ').filter(Boolean) : []), envs: serverData.envs || [], repositoryUrl: serverData.url, description: serverData.description, userSystem }; // Get AI suggestion const suggestion = await suggestServerConfiguration(configRequest); if (!suggestion) { return res.status(503).json({ error: 'AI service unavailable', message: 'Could not generate configuration suggestion' }); } // Apply custom directives if provided let finalSuggestion = suggestion; if (customDirectives && customDirectives.trim()) { // Re-run with custom directives const enhancedRequest = { ...configRequest, description: `${configRequest.description}\n\nCustom requirements: ${customDirectives}` }; const customSuggestion = await suggestServerConfiguration(enhancedRequest); if (customSuggestion) { finalSuggestion = customSuggestion; finalSuggestion.explanation += `\n\nApplied custom directives: ${customDirectives}`; } } // Validate the suggestion const validation = validateConfiguration(finalSuggestion, serverData.type || 'STDIO'); res.json({ fixed: true, original: { command: serverData.command, args: serverData.args, envs: serverData.envs }, suggestion: finalSuggestion, validation, needsUserInput: validation.issues.some(issue => issue.includes('needs a real value') || issue.includes('PLACEHOLDER')) }); } catch (error) { console.error('Error fixing server configuration:', error); res.status(500).json({ error: 'Internal server error' }); } }); // Batch fix multiple servers router.post('/batch-fix', async (req, res) => { try { const { serverIds, customDirectives, userSystem } = req.body; if (!Array.isArray(serverIds) || serverIds.length === 0) { return res.status(400).json({ error: 'Invalid request', message: 'Server IDs array is required' }); } // Limit batch size if (serverIds.length > 20) { return res.status(400).json({ error: 'Too many servers', message: 'Maximum 20 servers per batch' }); } // Get servers that need fixing const servers = await db.select() .from(mcpServers) .where(inArray(mcpServers.id, serverIds)); // Filter servers that likely need configuration const serversNeedingFix = servers.filter(server => { if (!server.command) return true; if (!server.args || server.args.length === 0) return true; if (server.args.length === 1 && !server.args[0].includes('/')) { // Likely missing directory or configuration return true; } return false; }); const results = []; let successCount = 0; let failureCount = 0; for (const server of serversNeedingFix) { try { const suggestion = await suggestServerConfiguration({ serverName: server.name, serverType: server.serverType, command: server.command || 'npx', args: server.args || [], envs: server.envs || [], repositoryUrl: server.repositoryUrl || undefined, description: server.description, userSystem }); if (suggestion) { const validation = validateConfiguration(suggestion, server.serverType || 'STDIO'); results.push({ serverId: server.id, serverName: server.name, status: 'success', suggestion, validation }); successCount++; } else { results.push({ serverId: server.id, serverName: server.name, status: 'failed', error: 'Could not generate suggestion' }); failureCount++; } } catch (error) { results.push({ serverId: server.id, serverName: server.name, status: 'error', error: error instanceof Error ? error.message : 'Unknown error' }); failureCount++; } // Small delay to avoid rate limits await new Promise(resolve => setTimeout(resolve, 100)); } res.json({ totalServers: serverIds.length, processedServers: serversNeedingFix.length, skippedServers: serverIds.length - serversNeedingFix.length, successCount, failureCount, results }); } catch (error) { console.error('Error batch fixing servers:', error); res.status(500).json({ error: 'Internal server error' }); } }); // Get servers that need configuration fixes router.get('/servers-needing-fix', async (req, res) => { try { const limit = Math.min(parseInt(req.query.limit) || 50, 100); // Find servers that likely need configuration const servers = await db.select() .from(mcpServers) .where(or(isNull(mcpServers.command), isNull(mcpServers.args))) .limit(limit); // Also find servers with potentially incomplete args const additionalServers = await db.select() .from(mcpServers) .where(eq(mcpServers.serverType, 'STDIO')) .limit(limit); const serversNeedingFix = [ ...servers, ...additionalServers.filter(server => { if (!server.args || server.args.length === 0) return true; if (server.args.length === 1 && !server.args[0].includes('/')) { // Likely missing directory or configuration return true; } return false; }) ]; // Remove duplicates const uniqueServers = Array.from(new Map(serversNeedingFix.map(s => [s.id, s])).values()); res.json({ count: uniqueServers.length, servers: uniqueServers.map(server => ({ id: server.id, name: server.name, command: server.command, args: server.args, needsFix: !server.command || !server.args || server.args.length === 0 || (server.args.length === 1 && !server.args[0].includes('/')) })) }); } catch (error) { console.error('Error finding servers needing fix:', error); res.status(500).json({ error: 'Internal server error' }); } }); export default router;