Skip to main content
Glama
ParameterNormalizer.ts6.5 kB
export class ParameterNormalizer { public static normalizeDestinationName(destination: string): string { if (!destination || typeof destination !== 'string') { return destination; } // Remove extra whitespace and normalize case let normalized = destination.trim(); // Common destination name variations const destinationMappings: Record<string, string> = { // iPhone variants 'iphone': 'iPhone', 'iphone-15': 'iPhone 15', 'iphone-15-pro': 'iPhone 15 Pro', 'iphone-15-pro-max': 'iPhone 15 Pro Max', 'iphone-16': 'iPhone 16', 'iphone-16-pro': 'iPhone 16 Pro', 'iphone-16-pro-max': 'iPhone 16 Pro Max', 'iphone-14': 'iPhone 14', 'iphone-14-pro': 'iPhone 14 Pro', 'iphone-14-pro-max': 'iPhone 14 Pro Max', 'iphone-13': 'iPhone 13', 'iphone-13-pro': 'iPhone 13 Pro', 'iphone-13-pro-max': 'iPhone 13 Pro Max', // iPad variants 'ipad': 'iPad', 'ipad-air': 'iPad Air', 'ipad-pro': 'iPad Pro', 'ipad-mini': 'iPad mini', // Simulator variants 'simulator': 'Simulator', 'sim': 'Simulator', // Mac variants 'mac': 'Mac', 'my-mac': 'My Mac', 'mymac': 'My Mac', }; // Try exact mapping first const lowerNormalized = normalized.toLowerCase(); if (destinationMappings[lowerNormalized]) { return destinationMappings[lowerNormalized]; } // Try pattern matching for simulator names if (lowerNormalized.includes('simulator')) { // Handle "iPhone 15 Simulator" -> keep as is but normalize spacing normalized = normalized.replace(/\s+/g, ' ').trim(); return normalized; } // Handle dash/underscore to space conversion for device names if (lowerNormalized.includes('-') || lowerNormalized.includes('_')) { normalized = normalized .replace(/[-_]/g, ' ') .replace(/\s+/g, ' ') .trim(); // Capitalize words appropriately normalized = this._capitalizeDeviceName(normalized); } return normalized; } public static normalizeSchemeName(schemeName: string): string { if (!schemeName || typeof schemeName !== 'string') { return schemeName; } // Remove extra whitespace let normalized = schemeName.trim(); // Common scheme name patterns const schemeMappings: Record<string, string> = { // Test scheme variants 'test': 'Tests', 'tests': 'Tests', 'unit-test': 'UnitTests', 'unit-tests': 'UnitTests', 'unittests': 'UnitTests', 'integration-test': 'IntegrationTests', 'integration-tests': 'IntegrationTests', 'integrationtests': 'IntegrationTests', // Debug/Release variants 'debug': 'Debug', 'release': 'Release', 'prod': 'Release', 'production': 'Release', 'dev': 'Debug', 'development': 'Debug', }; const lowerNormalized = normalized.toLowerCase(); if (schemeMappings[lowerNormalized]) { return schemeMappings[lowerNormalized]; } // Handle dash/underscore to space conversion, but preserve original casing if (normalized.includes('-') || normalized.includes('_')) { // Don't modify case for scheme names as they're often project-specific normalized = normalized.replace(/[-_]/g, ' ').replace(/\s+/g, ' ').trim(); } return normalized; } private static _capitalizeDeviceName(name: string): string { const words = name.split(' '); return words.map(word => { const lower = word.toLowerCase(); // Special cases if (lower === 'iphone') return 'iPhone'; if (lower === 'ipad') return 'iPad'; if (lower === 'mac') return 'Mac'; if (lower === 'pro') return 'Pro'; if (lower === 'max') return 'Max'; if (lower === 'mini') return 'mini'; if (lower === 'air') return 'Air'; if (lower === 'simulator') return 'Simulator'; // Numbers stay as-is if (/^\d+$/.test(word)) return word; // Default capitalization return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(); }).join(' '); } // Fuzzy matching for when exact normalization doesn't work public static findBestMatch(input: string, availableOptions: string[]): string | null { if (!input || !availableOptions || !Array.isArray(availableOptions)) { return null; } const normalized = input.toLowerCase().trim(); // Try exact match first const exactMatch = availableOptions.find(option => option.toLowerCase() === normalized ); if (exactMatch) return exactMatch; // Try partial match const partialMatches = availableOptions.filter(option => option.toLowerCase().includes(normalized) || normalized.includes(option.toLowerCase()) ); if (partialMatches.length === 1) { return partialMatches[0] || null; } // Try fuzzy matching for common typos const fuzzyMatches = availableOptions.filter(option => { const optionLower = option.toLowerCase(); return this._calculateSimilarity(normalized, optionLower) > 0.7; }); if (fuzzyMatches.length === 1) { return fuzzyMatches[0] || null; } return null; } private static _calculateSimilarity(str1: string, str2: string): number { const longer = str1.length > str2.length ? str1 : str2; const shorter = str1.length > str2.length ? str2 : str1; if (longer.length === 0) { return 1.0; } const editDistance = this._levenshteinDistance(longer, shorter); return (longer.length - editDistance) / longer.length; } private static _levenshteinDistance(str1: string, str2: string): number { const matrix: number[][] = []; for (let i = 0; i <= str2.length; i++) { matrix[i] = [i]; } for (let j = 0; j <= str1.length; j++) { matrix[0]![j] = j; } for (let i = 1; i <= str2.length; i++) { for (let j = 1; j <= str1.length; j++) { if (str2.charAt(i - 1) === str1.charAt(j - 1)) { matrix[i]![j] = matrix[i - 1]![j - 1]!; } else { matrix[i]![j] = Math.min( matrix[i - 1]![j - 1]! + 1, matrix[i]![j - 1]! + 1, matrix[i - 1]![j]! + 1 ); } } } return matrix[str2.length]![str1.length]!; } }

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/lapfelix/XcodeMCP'

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