Skip to main content
Glama

mcp-omnisearch

utils.ts5.69 kB
// Common utility functions for the MCP Omnisearch server import { ErrorType, ProviderError } from './types.js'; const normalize_api_key = (raw: string): string => { // Trim whitespace and strip a single pair of wrapping quotes if present const trimmed = raw.trim(); return trimmed.replace(/^(['"])(.*)\1$/, '$2'); }; export const validate_api_key = ( key: string | undefined, provider: string, ): string => { if (!key) { throw new ProviderError( ErrorType.INVALID_INPUT, `API key not found for ${provider}`, provider, ); } return normalize_api_key(key); }; /** * Checks if an API key exists and is potentially valid without throwing an error * @param key The API key to check * @param provider The name of the provider (for logging purposes) * @returns boolean indicating if the key exists and is not empty */ export const is_api_key_valid = ( key: string | undefined, provider: string, ): boolean => { if (!key || key.trim() === '') { console.warn(`API key not found or empty for ${provider}`); return false; } return true; }; export const handle_rate_limit = ( provider: string, reset_time?: Date, ): never => { throw new ProviderError( ErrorType.RATE_LIMIT, `Rate limit exceeded for ${provider}${ reset_time ? `. Reset at ${reset_time.toISOString()}` : '' }`, provider, { reset_time }, ); }; export const sanitize_query = (query: string): string => { return query.trim().replace(/[\n\r]+/g, ' '); }; export const create_error_response = ( error: Error, ): { error: string } => { if (error instanceof ProviderError) { return { error: `${error.provider} error: ${error.message}`, }; } return { error: `Unexpected error: ${error.message}`, }; }; export const merge_search_results = <T extends { score?: number }>( results: T[], limit?: number, ): T[] => { const sorted = [...results].sort((a, b) => { const score_a = a.score ?? 0; const score_b = b.score ?? 0; return score_b - score_a; }); return limit ? sorted.slice(0, limit) : sorted; }; export const extract_domain = (url: string): string => { try { const domain = new URL(url).hostname; return domain.startsWith('www.') ? domain.slice(4) : domain; } catch { return ''; } }; export const is_valid_url = (url: string): boolean => { try { new URL(url); return true; } catch { return false; } }; export const delay = (ms: number): Promise<void> => { return new Promise((resolve) => setTimeout(resolve, ms)); }; export const retry_with_backoff = async <T>( fn: () => Promise<T>, max_retries: number = 3, initial_delay: number = 1000, ): Promise<T> => { let retries = 0; while (true) { try { return await fn(); } catch (error) { if (retries >= max_retries) { throw error; } const delay_time = initial_delay * Math.pow(2, retries); await delay(delay_time); retries++; } } }; export interface SearchOperator { type: | 'site' | 'exclude_site' | 'filetype' | 'intitle' | 'inurl' | 'before' | 'after' | 'exact' | 'boolean'; value: string; original_text: string; } export interface ParsedQuery { base_query: string; operators: SearchOperator[]; } const operator_patterns = { site: /site:([^\s]+)/g, exclude_site: /-site:([^\s]+)/g, filetype: /filetype:([^\s]+)/g, intitle: /intitle:([^\s]+)/g, inurl: /inurl:([^\s]+)/g, before: /before:(\d{4}(?:-\d{2}(?:-\d{2})?)?)/g, after: /after:(\d{4}(?:-\d{2}(?:-\d{2})?)?)/g, exact: /"([^"]+)"/g, boolean: /\b(AND|OR|NOT)\b/g, }; export const parse_search_operators = ( query: string, ): ParsedQuery => { const operators: SearchOperator[] = []; let modified_query = query; // Extract operators Object.entries(operator_patterns).forEach(([type, pattern]) => { modified_query = modified_query.replace( pattern, (match, value) => { operators.push({ type: type as SearchOperator['type'], value: value, original_text: match, }); return ''; }, ); }); // Clean up the base query const base_query = modified_query.replace(/\s+/g, ' ').trim(); return { base_query, operators, }; }; export interface SearchParams { query: string; include_domains?: string[]; exclude_domains?: string[]; file_type?: string; title_filter?: string; url_filter?: string; date_before?: string; date_after?: string; exact_phrases?: string[]; boolean_operators?: { type: 'AND' | 'OR' | 'NOT'; terms: string[]; }[]; } export const apply_search_operators = ( parsed_query: ParsedQuery, ): SearchParams => { const params: SearchParams = { query: parsed_query.base_query, }; for (const operator of parsed_query.operators) { switch (operator.type) { case 'site': params.include_domains = [ ...(params.include_domains || []), operator.value, ]; break; case 'exclude_site': params.exclude_domains = [ ...(params.exclude_domains || []), operator.value, ]; break; case 'filetype': params.file_type = operator.value; break; case 'intitle': params.title_filter = operator.value; break; case 'inurl': params.url_filter = operator.value; break; case 'before': params.date_before = operator.value; break; case 'after': params.date_after = operator.value; break; case 'exact': params.exact_phrases = [ ...(params.exact_phrases || []), operator.value, ]; break; case 'boolean': // Handle boolean operators in the query string if (!params.boolean_operators) { params.boolean_operators = []; } params.boolean_operators.push({ type: operator.value as 'AND' | 'OR' | 'NOT', terms: [], }); break; } } return params; };

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/spences10/mcp-omnisearch'

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