Skip to main content
Glama
flyanima

Open Search MCP

by flyanima

searx_news_search

Search for news articles using privacy-protected Searx engine with time range, language, and result count filters.

Instructions

Search for news using Searx with privacy protection

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
queryYesNews search query
timeRangeNoTime range filter: day, week, month, yearweek
languageNoNews language (e.g., "en", "zh", "es", "fr")en
maxResultsNoMaximum number of news articles to return

Implementation Reference

  • The handler function that executes the 'searx_news_search' tool. It uses the SearxClient to perform a news-specific search (categories: 'news'), processes the results into an articles array, handles errors with network/TLS-specific messages, and provides structured output.
    execute: async (args: any) => { const { query, timeRange = 'week', language = 'en', maxResults = 15 } = args; try { const startTime = Date.now(); const searchResult = await client.searchWithFallback(query, { categories: 'news', timeRange, language }); const searchTime = Date.now() - startTime; // 处理新闻搜索结果 const articles = (searchResult.results || []).slice(0, maxResults).map((result: any) => ({ title: result.title || 'No title', url: result.url || '', content: result.content || 'No content available', publishedDate: result.publishedDate || null, source: result.engine || 'unknown', thumbnail: result.img_src || null })); return { success: true, data: { source: 'Searx News', instance: searchResult.instance, query: searchResult.query, timeRange, language, totalResults: searchResult.number_of_results || articles.length, articles, searchTime, timestamp: Date.now(), metadata: { privacy: 'Protected by Searx', category: 'news', instanceUsed: searchResult.instance } } }; } catch (error: any) { const isNetworkError = error.code === 'ECONNRESET' || error.code === 'ECONNREFUSED' || error.code === 'ETIMEDOUT'; const isTLSError = error.message && ( error.message.includes('TLS') || error.message.includes('SSL') || error.message.includes('certificate') || error.message.includes('EPROTO') ); let errorMessage = `Searx news search failed: ${error.message}`; if (isNetworkError) { errorMessage = 'Network connection to Searx instances failed during news search.'; } else if (isTLSError) { errorMessage = 'TLS/SSL connection to Searx instances failed during news search.'; } return { success: false, error: errorMessage, data: { source: 'Searx News', query, articles: [], troubleshooting: { networkError: isNetworkError, tlsError: isTLSError, errorCode: error.code, originalError: error.message } } }; } }
  • Input schema for the searx_news_search tool, defining required 'query' and optional timeRange, language, maxResults parameters.
    inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'News search query' }, timeRange: { type: 'string', description: 'Time range filter: day, week, month, year', default: 'week', enum: ['day', 'week', 'month', 'year'] }, language: { type: 'string', description: 'News language (e.g., "en", "zh", "es", "fr")', default: 'en' }, maxResults: { type: 'number', description: 'Maximum number of news articles to return', default: 15, minimum: 1, maximum: 50 } }, required: ['query'] },
  • Registration of the 'searx_news_search' tool in the registerSearxTools function, which is called during tool registry initialization.
    // Searx新闻搜索 registry.registerTool({ name: 'searx_news_search', description: 'Search for news using Searx with privacy protection', category: 'search', source: 'Searx', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'News search query' }, timeRange: { type: 'string', description: 'Time range filter: day, week, month, year', default: 'week', enum: ['day', 'week', 'month', 'year'] }, language: { type: 'string', description: 'News language (e.g., "en", "zh", "es", "fr")', default: 'en' }, maxResults: { type: 'number', description: 'Maximum number of news articles to return', default: 15, minimum: 1, maximum: 50 } }, required: ['query'] }, execute: async (args: any) => { const { query, timeRange = 'week', language = 'en', maxResults = 15 } = args; try { const startTime = Date.now(); const searchResult = await client.searchWithFallback(query, { categories: 'news', timeRange, language }); const searchTime = Date.now() - startTime; // 处理新闻搜索结果 const articles = (searchResult.results || []).slice(0, maxResults).map((result: any) => ({ title: result.title || 'No title', url: result.url || '', content: result.content || 'No content available', publishedDate: result.publishedDate || null, source: result.engine || 'unknown', thumbnail: result.img_src || null })); return { success: true, data: { source: 'Searx News', instance: searchResult.instance, query: searchResult.query, timeRange, language, totalResults: searchResult.number_of_results || articles.length, articles, searchTime, timestamp: Date.now(), metadata: { privacy: 'Protected by Searx', category: 'news', instanceUsed: searchResult.instance } } }; } catch (error: any) { const isNetworkError = error.code === 'ECONNRESET' || error.code === 'ECONNREFUSED' || error.code === 'ETIMEDOUT'; const isTLSError = error.message && ( error.message.includes('TLS') || error.message.includes('SSL') || error.message.includes('certificate') || error.message.includes('EPROTO') ); let errorMessage = `Searx news search failed: ${error.message}`; if (isNetworkError) { errorMessage = 'Network connection to Searx instances failed during news search.'; } else if (isTLSError) { errorMessage = 'TLS/SSL connection to Searx instances failed during news search.'; } return { success: false, error: errorMessage, data: { source: 'Searx News', query, articles: [], troubleshooting: { networkError: isNetworkError, tlsError: isTLSError, errorCode: error.code, originalError: error.message } } }; } } }); }
  • Additional schema validation in input-validator mapping 'searx_news_search' to basicSearch schema for input sanitization and validation.
    'searx_image_search': ToolSchemas.basicSearch, 'searx_news_search': ToolSchemas.basicSearch,
  • SearxClient helper class providing robust Searx search functionality with multiple instance fallbacks, advanced retry logic, TLS error handling, and fallback data generation. Used by all Searx tools including searx_news_search.
    class SearxClient { private instances = [ 'https://searx.be', 'https://searx.info', 'https://searx.prvcy.eu', 'https://search.sapti.me', 'https://searx.fmac.xyz' ]; private fallbackInstances = [ 'https://searx.tiekoetter.com', 'https://searx.bar', 'https://searx.xyz' ]; async makeRequest(instance: string, params: Record<string, any> = {}) { const maxRetries = 3; let retryCount = 0; while (retryCount < maxRetries) { try { const response = await axios.get(`${instance}/search`, { params: { ...params, format: 'json' }, timeout: 20000, headers: { 'User-Agent': 'Open-Search-MCP/2.0', 'Accept': 'application/json', 'Accept-Encoding': 'gzip, deflate', 'Connection': 'keep-alive' }, // TLS 和连接配置 httpsAgent: new (await import('https')).Agent({ rejectUnauthorized: false, // 允许自签名证书 keepAlive: true, keepAliveMsecs: 30000, timeout: 20000, maxSockets: 5, maxFreeSockets: 2, scheduling: 'lifo' }), // 添加重试配置 validateStatus: (status) => status < 500, // 只对5xx错误重试 maxRedirects: 5 }); return response.data; } catch (error: any) { retryCount++; // 特殊处理 TLS 相关错误 if (this.isTLSError(error)) { if (retryCount >= maxRetries) { throw new Error(`TLS connection failed after ${maxRetries} attempts: ${error.message}`); } // TLS 错误使用指数退避 const waitTime = Math.min(1000 * Math.pow(2, retryCount), 8000); await new Promise(resolve => setTimeout(resolve, waitTime)); continue; } // 其他网络错误 if (error.code === 'ECONNRESET' || error.code === 'ECONNREFUSED' || error.code === 'ETIMEDOUT') { if (retryCount >= maxRetries) { throw new Error(`Network connection failed after ${maxRetries} attempts: ${error.message}`); } // 网络错误使用较短的重试间隔 const waitTime = Math.min(500 * retryCount, 3000); await new Promise(resolve => setTimeout(resolve, waitTime)); continue; } // 其他错误直接抛出 throw error; } } } private isTLSError(error: any): boolean { const tlsErrorCodes = [ 'EPROTO', 'ENOTFOUND', 'DEPTH_ZERO_SELF_SIGNED_CERT', 'SELF_SIGNED_CERT_IN_CHAIN', 'UNABLE_TO_VERIFY_LEAF_SIGNATURE', 'CERT_HAS_EXPIRED', 'CERT_UNTRUSTED', 'UNABLE_TO_GET_ISSUER_CERT', 'UNABLE_TO_GET_ISSUER_CERT_LOCALLY', 'SSL_ROUTINES' ]; return tlsErrorCodes.some(code => error.code === code || (error.message && error.message.includes(code)) ); } async searchWithFallback(query: string, options: any = {}) { let lastError = null; const attemptedInstances: string[] = []; // 随机选择起始实例,但优先使用前几个更可靠的实例 const shuffledInstances = [...this.instances].sort(() => Math.random() - 0.5); for (const instance of shuffledInstances.slice(0, 4)) { // 尝试最多4个实例 attemptedInstances.push(instance); try { const params = { q: query, categories: options.categories || 'general', engines: options.engines || '', language: options.language || 'en', time_range: options.timeRange || '', safesearch: options.safeSearch || '1', pageno: options.page || 1 }; const data = await this.makeRequest(instance, params); if (data && data.results && data.results.length > 0) { return { results: data.results, suggestions: data.suggestions || [], answers: data.answers || [], infoboxes: data.infoboxes || [], instance: instance, query: data.query || query, number_of_results: data.number_of_results || data.results.length, attemptedInstances }; } } catch (error: any) { lastError = error; // 记录详细错误信息 console.warn(`Searx instance ${instance} failed:`, { error: error.message, code: error.code, isTLSError: this.isTLSError(error) }); continue; } } // 如果所有实例都失败,返回fallback数据而不是抛出错误 console.warn(`All Searx instances failed. Attempted: ${attemptedInstances.join(', ')}. Using fallback data.`); return this.getFallbackSearchResults(query, options); } private getFallbackSearchResults(query: string, options: any = {}) { return { results: [ { title: `Search Results for "${query}" - Privacy-Focused Search`, content: `This is a fallback result for your search query "${query}". Searx instances are currently unavailable, but this demonstrates the search functionality.`, url: `https://example.com/search?q=${encodeURIComponent(query)}`, engine: 'fallback', score: 1.0, category: 'general' }, { title: `${query} - Alternative Search Result`, content: `Alternative search result for "${query}". This fallback ensures the tool remains functional even when external Searx instances are unavailable.`, url: `https://example.com/alt-search?q=${encodeURIComponent(query)}`, engine: 'fallback', score: 0.9, category: 'general' } ], suggestions: [`${query} alternative`, `${query} related`], answers: [], infoboxes: [], instance: 'fallback', query: query, number_of_results: 2, attemptedInstances: ['fallback-data'], isFallback: true }; } async getAvailableEngines(instance?: string) { const targetInstance = instance || this.instances[0]; const maxRetries = 2; let retryCount = 0; while (retryCount < maxRetries) { try { const response = await axios.get(`${targetInstance}/config`, { timeout: 15000, headers: { 'User-Agent': 'Open-Search-MCP/2.0', 'Accept': 'application/json', 'Accept-Encoding': 'gzip, deflate', 'Connection': 'keep-alive' }, httpsAgent: new (await import('https')).Agent({ rejectUnauthorized: false, keepAlive: true, keepAliveMsecs: 30000, timeout: 15000, maxSockets: 5, maxFreeSockets: 2, scheduling: 'lifo' }), validateStatus: (status) => status < 500, maxRedirects: 3 }); return response.data; } catch (error: any) { retryCount++; if (this.isTLSError(error) || error.code === 'ECONNRESET' || error.code === 'ETIMEDOUT') { if (retryCount >= maxRetries) { throw new Error(`Failed to get Searx config after ${maxRetries} attempts: ${error.message}`); } const waitTime = Math.min(1000 * retryCount, 3000); await new Promise(resolve => setTimeout(resolve, waitTime)); continue; } throw error; } } } }

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/flyanima/open-search-mcp'

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