Skip to main content
Glama
portel-dev

NCP - Natural Context Provider

by portel-dev
fuzzy-matcher.ts3.3 kB
/** * Fuzzy matching utility for typo tolerance * Uses Levenshtein distance algorithm to find similar strings * Adapted from Photon's implementation */ export interface FuzzyMatch { text: string; distance: number; } /** * Fuzzy matcher for finding similar strings * Useful for: "Did you mean?" suggestions, typo correction, command aliases */ export class FuzzyMatcher { /** * Find similar matches for a query string * Returns matches sorted by similarity (lowest distance first) */ findSuggestions( query: string, candidates: string[], maxDistance?: number ): string[] { if (!query || candidates.length === 0) { return []; } // Default: allow up to 1/3 of query length as distance // This keeps suggestions reasonable while still being forgiving const threshold = maxDistance !== undefined ? maxDistance : Math.max(1, Math.floor(query.length / 3)); const matches: FuzzyMatch[] = candidates .map(candidate => ({ text: candidate, distance: this.levenshteinDistance(query.toLowerCase(), candidate.toLowerCase()) })) .filter(match => match.distance <= threshold) .sort((a, b) => a.distance - b.distance); return matches.map(m => m.text); } /** * Get the best single match for a query */ findBestMatch(query: string, candidates: string[]): string | undefined { const suggestions = this.findSuggestions(query, candidates); return suggestions.length > 0 ? suggestions[0] : undefined; } /** * Check if two strings are similar enough (for aliasing) */ isSimilar(str1: string, str2: string, threshold: number = 2): boolean { const distance = this.levenshteinDistance( str1.toLowerCase(), str2.toLowerCase() ); return distance <= threshold; } /** * Calculate Levenshtein distance (edit distance) between two strings * Measures minimum edits (insert, delete, substitute) needed to transform one string into another */ private levenshteinDistance(a: string, b: string): number { const aLen = a.length; const bLen = b.length; // Create matrix to store distances const matrix: number[][] = Array(bLen + 1) .fill(null) .map(() => Array(aLen + 1).fill(0)); // Initialize first row and column for (let i = 0; i <= aLen; i++) { matrix[0][i] = i; } for (let j = 0; j <= bLen; j++) { matrix[j][0] = j; } // Fill in the rest of the matrix for (let j = 1; j <= bLen; j++) { for (let i = 1; i <= aLen; i++) { const substitutionCost = a[i - 1] === b[j - 1] ? 0 : 1; matrix[j][i] = Math.min( matrix[j][i - 1] + 1, // deletion matrix[j - 1][i] + 1, // insertion matrix[j - 1][i - 1] + substitutionCost // substitution ); } } return matrix[bLen][aLen]; } /** * Get similarity score (0-1) between two strings * 1.0 = identical, 0.0 = completely different */ similarityScore(str1: string, str2: string): number { const maxLen = Math.max(str1.length, str2.length); if (maxLen === 0) return 1.0; // Both empty = similar const distance = this.levenshteinDistance(str1.toLowerCase(), str2.toLowerCase()); return 1.0 - distance / maxLen; } }

Latest Blog Posts

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/portel-dev/ncp'

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