@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
169 lines (162 loc) • 6.67 kB
JavaScript
import * as yaml from 'js-yaml';
import { OpenAI } from 'openai';
import * as dotenv from 'dotenv';
dotenv.config();
const openai = process.env.OPENAI_API_KEY ? new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
}) : null;
const smitheryAnalysisPrompt = `You are an expert at analyzing MCP server configurations from smithery.yml files.
Your task is to:
1. Identify all required and optional arguments
2. Identify all required and optional environment variables
3. Understand the configuration schema
4. Suggest safe default values
5. Extract or infer usage examples
6. Identify security considerations
Return a JSON object following the SmitheryAnalysis interface.`;
export async function analyzeSmitheryConfig(smitheryContent, readmeContent) {
if (!openai) {
// Fallback to basic parsing without AI
return basicSmitheryParsing(smitheryContent);
}
try {
const prompt = `Analyze this smithery.yml configuration:
\`\`\`yaml
${smitheryContent}
\`\`\`
${readmeContent ? `\nREADME content for additional context:\n${readmeContent.substring(0, 3000)}` : ''}
Provide a comprehensive analysis including:
1. What arguments are required vs optional
2. What environment variables are needed
3. Safe default values for a typical user
4. Usage examples
5. Security considerations
Focus on practical, user-friendly defaults.`;
const response = await openai.chat.completions.create({
model: 'gpt-4o-mini',
messages: [
{ role: 'system', content: smitheryAnalysisPrompt },
{ role: 'user', content: prompt }
],
temperature: 0.2,
max_tokens: 1500,
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 smithery config with AI:', error);
// Fallback to basic parsing
return basicSmitheryParsing(smitheryContent);
}
}
function basicSmitheryParsing(smitheryContent) {
try {
const config = yaml.load(smitheryContent);
const analysis = {
requiredArgs: [],
optionalArgs: [],
requiredEnvs: [],
optionalEnvs: [],
suggestedDefaults: {},
usageExamples: [],
securityConsiderations: []
};
// Parse start command configuration
if (config.startCommand) {
const { args, env, configSchema } = config.startCommand;
// Extract arguments
if (args) {
analysis.requiredArgs = args;
}
// Extract environment variables
if (env) {
analysis.requiredEnvs = Object.keys(env);
analysis.suggestedDefaults = { ...env };
}
// Parse config schema for more details
if (configSchema?.properties) {
analysis.configSchema = configSchema;
for (const [key, schema] of Object.entries(configSchema.properties)) {
if (configSchema.required?.includes(key)) {
if (!analysis.requiredEnvs.includes(key)) {
analysis.requiredEnvs.push(key);
}
}
else {
if (!analysis.optionalEnvs.includes(key)) {
analysis.optionalEnvs.push(key);
}
}
// Extract defaults from schema
if (schema.default !== undefined) {
analysis.suggestedDefaults[key] = schema.default;
}
else if (schema.example !== undefined) {
analysis.suggestedDefaults[key] = schema.example;
}
// Add security considerations for sensitive fields
if (key.toLowerCase().includes('key') ||
key.toLowerCase().includes('token') ||
key.toLowerCase().includes('secret')) {
analysis.securityConsiderations.push(`${key} appears to be a sensitive value - ensure it's kept secure`);
}
}
}
}
// Extract examples from config if available
if (config.examples) {
analysis.usageExamples = Array.isArray(config.examples)
? config.examples
: [config.examples];
}
return analysis;
}
catch (error) {
console.error('Error parsing smithery config:', error);
return null;
}
}
// Enhanced configuration suggestion based on smithery analysis
export async function suggestConfigFromSmithery(serverName, smitheryAnalysis, userContext) {
const args = [...smitheryAnalysis.requiredArgs];
const envVars = {};
// Start with suggested defaults
Object.assign(envVars, smitheryAnalysis.suggestedDefaults);
// Customize based on user context
if (userContext) {
// Replace placeholders with user-specific values
for (const [key, value] of Object.entries(envVars)) {
if (typeof value === 'string') {
envVars[key] = value
.replace('$HOME', userContext.homeDir)
.replace('~', userContext.homeDir)
.replace('$USER', userContext.username || 'user');
}
}
// For filesystem-related servers, suggest safe paths
if (serverName.toLowerCase().includes('filesystem') ||
serverName.toLowerCase().includes('file')) {
if (args.length === 0 || !args.some(arg => arg.includes('/'))) {
args.push(`${userContext.homeDir}/Desktop`);
}
}
}
// Ensure sensitive values are clearly marked
for (const key of smitheryAnalysis.requiredEnvs) {
if (!envVars[key] &&
(key.toLowerCase().includes('key') ||
key.toLowerCase().includes('token') ||
key.toLowerCase().includes('secret'))) {
envVars[key] = `YOUR_${key.toUpperCase()}_HERE`;
}
}
const explanation = `Configuration based on smithery.yml analysis. ` +
`Required args: ${smitheryAnalysis.requiredArgs.join(', ') || 'none'}. ` +
`Required environment variables: ${smitheryAnalysis.requiredEnvs.join(', ') || 'none'}.`;
return { args, envVars, explanation };
}