@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
JavaScript
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
};
}