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

198 lines (186 loc) 9.24 kB
import { OpenAI } from 'openai'; import * as dotenv from 'dotenv'; import { analyzeGitHubRepo } from '../utils/github-analyzer'; import { analyzeSmitheryConfig, suggestConfigFromSmithery } from './smithery-analyzer'; dotenv.config(); if (!process.env.OPENAI_API_KEY) { console.warn('OPENAI_API_KEY not found in environment variables'); } const openai = process.env.OPENAI_API_KEY ? new OpenAI({ apiKey: process.env.OPENAI_API_KEY, }) : null; const systemPrompt = `You are an AI assistant that helps configure MCP (Model Context Protocol) servers. Your task is to suggest reasonable default configurations based on the server requirements and user's system. Key principles: 1. Security first - never suggest paths that expose sensitive data 2. Use reasonable defaults that work for most users 3. Suggest user-specific paths (like Desktop, Documents) when appropriate 4. For filesystem servers, suggest safe directories like Desktop, Documents, or Downloads 5. For API-based servers, use placeholder values that clearly indicate what's needed 6. Always explain why you chose specific configurations Respond with a JSON object following the ServerConfigSuggestion interface.`; export async function suggestServerConfiguration(request) { if (!openai) { console.error('OpenAI client not initialized'); return null; } try { // Fetch additional context from GitHub if available let githubContext = ''; let smitheryConfig = null; let smitheryAnalysis = null; if (request.repositoryUrl && request.repositoryUrl.includes('github.com')) { try { const repoInfo = await analyzeGitHubRepo(request.repositoryUrl); if (repoInfo) { if (repoInfo.smitheryConfig) { smitheryConfig = repoInfo.smitheryConfig; githubContext += `\nSmithery Configuration Found:\n${JSON.stringify(smitheryConfig, null, 2)}\n`; // Get AI analysis of smithery config const smitheryYaml = JSON.stringify(smitheryConfig); smitheryAnalysis = await analyzeSmitheryConfig(smitheryYaml, repoInfo.readmeContent); if (smitheryAnalysis) { // Use smithery analysis for initial suggestion const smitherySuggestion = await suggestConfigFromSmithery(request.serverName, smitheryAnalysis, request.userSystem); // Override request args/envs with smithery suggestions if they're more complete if (smitherySuggestion.args.length > request.args.length) { request.args = smitherySuggestion.args; } githubContext += `\nSmithery Analysis:\n`; githubContext += `- Required args: ${smitheryAnalysis.requiredArgs.join(', ') || 'none'}\n`; githubContext += `- Required envs: ${smitheryAnalysis.requiredEnvs.join(', ') || 'none'}\n`; githubContext += `- Suggested defaults: ${JSON.stringify(smitheryAnalysis.suggestedDefaults)}\n`; if (smitheryAnalysis.securityConsiderations.length > 0) { githubContext += `- Security considerations: ${smitheryAnalysis.securityConsiderations.join('; ')}\n`; } } } if (repoInfo.readmeContent) { // Extract relevant sections from README const readmeLower = repoInfo.readmeContent.toLowerCase(); const configStart = Math.max(readmeLower.indexOf('## configuration'), readmeLower.indexOf('## setup'), readmeLower.indexOf('## installation'), readmeLower.indexOf('## usage')); if (configStart > -1) { const configSection = repoInfo.readmeContent.substring(configStart, Math.min(configStart + 2000, repoInfo.readmeContent.length)); githubContext += `\nRelevant README Section:\n${configSection}\n`; } } if (repoInfo.packageJson) { githubContext += `\nPackage.json info:\n- Name: ${repoInfo.packageJson.name}\n`; if (repoInfo.packageJson.bin) { githubContext += `- Binary: ${JSON.stringify(repoInfo.packageJson.bin)}\n`; } } } } catch (error) { console.warn('Failed to fetch GitHub context:', error); } } const userContext = request.userSystem ? ` User System Information: - OS: ${request.userSystem.os} - Platform: ${request.userSystem.platform} - Home Directory: ${request.userSystem.homeDir} ${request.userSystem.username ? `- Username: ${request.userSystem.username}` : ''} ${request.installedServers?.length ? `- Already installed servers: ${request.installedServers.join(', ')}` : ''} ${request.preferredPaths?.length ? `- User's preferred paths: ${request.preferredPaths.join(', ')}` : ''} ` : ''; const prompt = `Configure the following MCP server: Server: ${request.serverName} Type: ${request.serverType} Command: ${request.command} Current Args: ${JSON.stringify(request.args)} Required Environment Variables: ${JSON.stringify(request.envs)} ${request.description ? `Description: ${request.description}` : ''} ${request.repositoryUrl ? `Repository: ${request.repositoryUrl}` : ''} ${githubContext} ${userContext} Suggest a reasonable default configuration for this server. Consider: 1. What directories/paths would be safe and useful for this server? 2. What environment variable values would work for most users? 3. Are there any security concerns to warn about? 4. Could there be alternative configurations for different use cases? For filesystem-type servers, suggest paths like: - Desktop for general file access - Documents for document management - Downloads for downloaded files - Project-specific directories if relevant Return a JSON object with the configuration suggestion.`; const response = await openai.chat.completions.create({ model: 'gpt-4o-mini', messages: [ { role: 'system', content: systemPrompt }, { role: 'user', content: prompt } ], temperature: 0.3, max_tokens: 1000, response_format: { type: 'json_object' } }); const content = response.choices[0]?.message?.content; if (!content) { throw new Error('No response from OpenAI'); } return JSON.parse(content); } catch (error) { console.error('Error suggesting configuration:', error); return null; } } // Helper to analyze multiple servers and find patterns export async function analyzeServerPatterns(servers) { if (!openai) { return null; } try { const prompt = `Analyze these MCP servers and identify common configuration patterns: ${servers.map(s => `- ${s.name}: ${s.command} ${s.args.join(' ')} (envs: ${s.envs.join(', ')})`).join('\n')} Identify: 1. Common argument patterns (like directory paths, API keys, etc.) 2. Common environment variable naming conventions 3. General recommendations for configuring these types of servers Return a JSON object with commonPatterns and recommendations arrays.`; const response = await openai.chat.completions.create({ model: 'gpt-4o-mini', messages: [ { role: 'user', content: prompt } ], temperature: 0.3, max_tokens: 500, response_format: { type: 'json_object' } }); const content = response.choices[0]?.message?.content; if (!content) { throw new Error('No response from OpenAI'); } return JSON.parse(content); } catch (error) { console.error('Error analyzing patterns:', error); return null; } } // Validate configuration suggestions export function validateConfiguration(suggestion, serverType) { const issues = []; // Check for potentially dangerous paths const dangerousPaths = ['/', '/etc', '/usr', '/bin', '/sbin', '/root', 'C:\\Windows', 'C:\\Program Files']; suggestion.args.forEach(arg => { if (dangerousPaths.some(path => arg.startsWith(path))) { issues.push(`Potentially dangerous path: ${arg}`); } }); // Check for missing required values Object.entries(suggestion.envVars).forEach(([key, value]) => { if (value.includes('YOUR_') || value.includes('PLACEHOLDER')) { issues.push(`Environment variable ${key} needs a real value`); } }); // Validate based on server type if (serverType === 'STDIO' && suggestion.args.length === 0) { issues.push('STDIO servers typically need at least one argument'); } return { isValid: issues.length === 0, issues }; }