Skip to main content
Glama

MCP API Server

by fikri2992
curl-analyzer.ts9.06 kB
import { OpenAIClient, APISpec, ParsingContext } from './openai-client.js'; export interface CurlCommand { raw: string; method?: string; url?: string; headers?: Record<string, string>; body?: string; options?: string[]; } export interface CurlAnalysisResult { apiSpec: APISpec; confidence: number; warnings: string[]; extractedElements: { method: boolean; url: boolean; headers: boolean; body: boolean; parameters: boolean; }; } export class CurlAnalyzer { private openaiClient: OpenAIClient; constructor(openaiClient: OpenAIClient) { this.openaiClient = openaiClient; } /** * Extract curl commands from markdown content */ extractCurlCommands(markdown: string): CurlCommand[] { const curlCommands: CurlCommand[] = []; // Match code blocks that contain curl commands const codeBlockRegex = /```(?:bash|shell|sh)?\s*\n([\s\S]*?)\n```/g; let match; while ((match = codeBlockRegex.exec(markdown)) !== null) { const codeBlock = match[1]; // Look for curl commands in the code block const curlMatches = this.findCurlInBlock(codeBlock); curlCommands.push(...curlMatches); } // Also look for inline curl commands (less common but possible) const inlineCurlRegex = /`curl[^`]+`/g; let inlineMatch; while ((inlineMatch = inlineCurlRegex.exec(markdown)) !== null) { const curlCommand = inlineMatch[0].slice(1, -1); // Remove backticks curlCommands.push({ raw: curlCommand }); } return curlCommands; } /** * Analyze a single curl command using AI */ async analyzeCurlCommand( curlCommand: CurlCommand, context?: ParsingContext ): Promise<CurlAnalysisResult> { try { // First, try basic parsing to extract what we can const basicParsed = this.basicCurlParse(curlCommand.raw); // Use AI to get a complete API specification const apiSpec = await this.openaiClient.analyzeCurlCommand(curlCommand.raw, context); // Calculate confidence based on what we could extract const extractedElements = { method: !!basicParsed.method || !!apiSpec.method, url: !!basicParsed.url || !!apiSpec.url, headers: Object.keys(basicParsed.headers || {}).length > 0 || Object.keys(apiSpec.headers || {}).length > 0, body: !!basicParsed.body || !!apiSpec.body, parameters: (apiSpec.parameters?.length || 0) > 0 }; const confidence = this.calculateConfidence(extractedElements, curlCommand.raw); const warnings = this.generateWarnings(extractedElements, curlCommand.raw); return { apiSpec, confidence, warnings, extractedElements }; } catch (error) { throw new Error(`Failed to analyze curl command: ${error instanceof Error ? error.message : 'Unknown error'}`); } } /** * Analyze multiple curl commands in batch */ async analyzeCurlCommands( curlCommands: CurlCommand[], context?: ParsingContext ): Promise<CurlAnalysisResult[]> { const results: CurlAnalysisResult[] = []; for (const curlCommand of curlCommands) { try { const result = await this.analyzeCurlCommand(curlCommand, context); results.push(result); } catch (error) { // Create a failed result with low confidence results.push({ apiSpec: { name: 'Failed to parse', method: 'GET', url: '', curlCommand: curlCommand.raw }, confidence: 0, warnings: [`Failed to parse curl command: ${error instanceof Error ? error.message : 'Unknown error'}`], extractedElements: { method: false, url: false, headers: false, body: false, parameters: false } }); } } return results; } /** * Find curl commands within a code block */ private findCurlInBlock(codeBlock: string): CurlCommand[] { const commands: CurlCommand[] = []; const lines = codeBlock.split('\n'); let currentCommand = ''; let inCommand = false; for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); if (line.startsWith('curl')) { if (currentCommand && inCommand) { // Save previous command commands.push({ raw: currentCommand.trim() }); } currentCommand = line; inCommand = true; } else if (inCommand) { if (line.endsWith('\\')) { // Line continuation currentCommand += ' ' + line.slice(0, -1).trim(); } else if (line.length > 0) { // End of command currentCommand += ' ' + line; commands.push({ raw: currentCommand.trim() }); currentCommand = ''; inCommand = false; } else { // Empty line might end command if (currentCommand) { commands.push({ raw: currentCommand.trim() }); currentCommand = ''; inCommand = false; } } } } // Handle case where command is at end of block if (currentCommand && inCommand) { commands.push({ raw: currentCommand.trim() }); } return commands; } /** * Basic curl parsing without AI (fallback/validation) */ private basicCurlParse(curlCommand: string): Partial<CurlCommand> { const result: Partial<CurlCommand> = { raw: curlCommand, headers: {}, options: [] }; // Extract HTTP method const methodMatch = curlCommand.match(/-X\s+(\w+)/i); if (methodMatch) { result.method = methodMatch[1].toUpperCase(); } else { // Default to GET if no method specified result.method = 'GET'; } // Extract URL (look for quoted or unquoted URLs) const urlMatches = [ curlCommand.match(/"(https?:\/\/[^"]+)"/), curlCommand.match(/'(https?:\/\/[^']+)'/), curlCommand.match(/curl\s+(?:-\w+\s+)*([^\s-][^\s]*)/), ]; for (const match of urlMatches) { if (match && match[1]) { result.url = match[1]; break; } } // Extract headers const headerRegex = /-H\s+["']([^"']+)["']/g; let headerMatch; while ((headerMatch = headerRegex.exec(curlCommand)) !== null) { const headerString = headerMatch[1]; const colonIndex = headerString.indexOf(':'); if (colonIndex > 0) { const key = headerString.substring(0, colonIndex).trim(); const value = headerString.substring(colonIndex + 1).trim(); result.headers![key] = value; } } // Extract body data const bodyMatches = [ curlCommand.match(/-d\s+["']([^"']+)["']/), curlCommand.match(/--data\s+["']([^"']+)["']/), curlCommand.match(/--data-raw\s+["']([^"']+)["']/), ]; for (const match of bodyMatches) { if (match && match[1]) { result.body = match[1]; break; } } return result; } /** * Calculate confidence score based on extracted elements */ private calculateConfidence(extractedElements: any, curlCommand: string): number { let score = 0; let maxScore = 0; // Method extraction (20 points) maxScore += 20; if (extractedElements.method) score += 20; // URL extraction (30 points - most important) maxScore += 30; if (extractedElements.url) score += 30; // Headers extraction (20 points) maxScore += 20; if (extractedElements.headers) score += 20; // Body extraction (15 points) maxScore += 15; if (extractedElements.body) score += 15; // Parameters extraction (15 points) maxScore += 15; if (extractedElements.parameters) score += 15; // Bonus for well-formed curl command if (curlCommand.includes('curl') && curlCommand.length > 10) { score += 5; maxScore += 5; } return Math.min(1, score / maxScore); } /** * Generate warnings based on analysis */ private generateWarnings(extractedElements: any, curlCommand: string): string[] { const warnings: string[] = []; if (!extractedElements.method) { warnings.push('HTTP method could not be determined'); } if (!extractedElements.url) { warnings.push('URL could not be extracted'); } if (!extractedElements.headers && curlCommand.includes('-H')) { warnings.push('Headers were present but could not be parsed'); } if (!extractedElements.body && (curlCommand.includes('-d') || curlCommand.includes('--data'))) { warnings.push('Request body was present but could not be parsed'); } if (!extractedElements.parameters) { warnings.push('No parameters could be inferred from the curl command'); } if (curlCommand.length < 20) { warnings.push('Curl command appears to be incomplete or truncated'); } if (!curlCommand.includes('http')) { warnings.push('No HTTP URL found in curl command'); } return warnings; } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/fikri2992/mcp0'

If you have feedback or need assistance with the MCP directory API, please join our Discord server