Skip to main content
Glama

DuckDuckGo Search MCP

by OEvortex
search_felo.js6.98 kB
import axios from 'axios'; import { v4 as uuidv4 } from 'uuid'; import https from 'https'; // Rotating User Agents const USER_AGENTS = [ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Edge/120.0.0.0', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2.1 Safari/605.1.15', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:122.0) Gecko/20100101 Firefox/122.0', 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36' ]; // Cache results to avoid repeated requests const resultsCache = new Map(); const CACHE_DURATION = 5 * 60 * 1000; // 5 minutes // HTTPS agent configuration to handle certificate chain issues const httpsAgent = new https.Agent({ rejectUnauthorized: true, // Keep security enabled keepAlive: true, timeout: 30000, // Provide fallback for certificate issues while maintaining security secureProtocol: 'TLSv1_2_method' }); // Create a persistent axios instance to maintain session state const feloSession = axios.create({ timeout: 30000, httpsAgent: httpsAgent, headers: { 'accept': '*/*', 'accept-encoding': 'gzip, deflate, br, zstd', 'accept-language': 'en-US,en;q=0.9,en-IN;q=0.8', 'content-type': 'application/json', 'dnt': '1', 'origin': 'https://felo.ai', 'referer': 'https://felo.ai/', 'sec-ch-ua': '"Not)A;Brand";v="99", "Microsoft Edge";v="127", "Chromium";v="127"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': '"Windows"', 'sec-fetch-dest': 'empty', 'sec-fetch-mode': 'cors', 'sec-fetch-site': 'same-site', 'user-agent': getRandomUserAgent() } }); /** * Response class for Felo API responses */ class Response { /** * Create a new Response * @param {string} text - The text content of the response */ constructor(text) { this.text = text; } /** * String representation of the response * @returns {string} The text content */ toString() { return this.text; } } /** * Get a random user agent from the list * @returns {string} A random user agent string */ function getRandomUserAgent() { return USER_AGENTS[Math.floor(Math.random() * USER_AGENTS.length)]; } /** * Generate a cache key for a search query * @param {string} query - The search query * @returns {string} The cache key */ function getCacheKey(query) { return `felo-${query}`; } /** * Clear old entries from the cache */ function clearOldCache() { const now = Date.now(); for (const [key, value] of resultsCache.entries()) { if (now - value.timestamp > CACHE_DURATION) { resultsCache.delete(key); } } } /** * Search using the Felo AI API * @param {string} prompt - The search query or prompt * @param {boolean} stream - If true, yields response chunks as they arrive * @param {boolean} raw - If true, returns raw response dictionaries * @returns {Promise<string|AsyncGenerator<string>>} The search results */ async function searchFelo(prompt, stream = false, raw = false) { // Clear old cache entries clearOldCache(); // Check cache first if not streaming if (!stream) { const cacheKey = getCacheKey(prompt); const cachedResults = resultsCache.get(cacheKey); if (cachedResults && Date.now() - cachedResults.timestamp < CACHE_DURATION) { return cachedResults.results; } } // Create payload for Felo API with proper structure from reference const payload = { query: prompt, search_uuid: uuidv4().replace(/-/g, ''), // Remove dashes like in reference lang: "", agent_lang: "en", search_options: { langcode: "en-US", search_image: true, search_video: true }, search_video: true, model: "", contexts_from: "google", auto_routing: true }; // Update user agent for this request feloSession.defaults.headers['user-agent'] = getRandomUserAgent(); // Define the streaming function async function* streamFunction() { try { const response = await feloSession.post('https://api.felo.ai/search/threads', payload, { responseType: 'stream' }); // Check for HTTP errors if (response.status !== 200) { throw new Error(`Failed to generate response - (${response.status}, ${response.statusText}) - ${response.data}`); } let streamingText = ''; let buffer = ''; // Process the stream as it comes in for await (const chunk of response.data) { buffer += chunk.toString(); const lines = buffer.split('\n'); buffer = lines.pop() || ''; // Keep the last (potentially incomplete) line in the buffer for (const line of lines) { if (line.startsWith('data:')) { try { const dataStr = line.substring(5).trim(); if (dataStr) { const data = JSON.parse(dataStr); if (data.type === 'answer' && 'text' in data.data) { const newText = data.data.text; if (newText.length > streamingText.length) { const delta = newText.substring(streamingText.length); streamingText = newText; if (raw) { yield { text: delta }; } else { yield new Response(delta).toString(); } } } } } catch (error) { // Ignore JSON parse errors and continue console.debug('JSON parse error:', error.message); } } } } // Cache the complete response if (streamingText) { resultsCache.set(getCacheKey(prompt), { results: streamingText, timestamp: Date.now() }); } } catch (error) { console.error('Error searching Felo:', error.message); // Handle specific API errors if (error.response) { const status = error.response.status; const statusText = error.response.statusText; const data = error.response.data; throw new Error(`Felo API error: ${status} ${statusText} - ${data}`); } throw new Error(`Failed to search Felo: ${error.message}`); } } // If streaming is requested, return the generator if (stream) { return streamFunction(); } // For non-streaming, collect all chunks and return as a single string let fullResponse = ''; try { for await (const chunk of streamFunction()) { if (raw) { fullResponse += chunk.text; } else { fullResponse += chunk; } } return fullResponse; } catch (error) { console.error('Error in non-streaming Felo search:', error.message); throw error; } } export { searchFelo };

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/OEvortex/ddg_search'

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