Skip to main content
Glama
flyanima

Open Search MCP

by flyanima

searx_image_search

Search for images using privacy-focused Searx engine with customizable safe search filters and result limits.

Instructions

Search for images using Searx with privacy protection

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
queryYesImage search query
safeSearchNoSafe search level: 0 (off), 1 (moderate), 2 (strict)1
maxResultsNoMaximum number of images to return

Implementation Reference

  • Core handler function that executes the searx_image_search tool. Performs image search using SearxClient, processes results, formats image data, and handles errors with network and TLS troubleshooting.
    execute: async (args: any) => { const { query, safeSearch = '1', maxResults = 20 } = args; try { const startTime = Date.now(); const searchResult = await client.searchWithFallback(query, { categories: 'images', safeSearch }); const searchTime = Date.now() - startTime; // 处理图片搜索结果 const images = (searchResult.results || []).slice(0, maxResults).map((result: any) => ({ title: result.title || 'No title', url: result.url || '', thumbnailUrl: result.thumbnail_src || result.img_src || '', imageUrl: result.img_src || result.url || '', width: result.resolution ? result.resolution.split('x')[0] : null, height: result.resolution ? result.resolution.split('x')[1] : null, source: result.engine || 'unknown', publishedDate: result.publishedDate || null })); return { success: true, data: { source: 'Searx Images', instance: searchResult.instance, query: searchResult.query, safeSearch, totalResults: searchResult.number_of_results || images.length, images, searchTime, timestamp: Date.now(), metadata: { privacy: 'Protected by Searx', category: 'images', 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 image search failed: ${error.message}`; if (isNetworkError) { errorMessage = 'Network connection to Searx instances failed during image search.'; } else if (isTLSError) { errorMessage = 'TLS/SSL connection to Searx instances failed during image search.'; } return { success: false, error: errorMessage, data: { source: 'Searx Images', query, images: [], troubleshooting: { networkError: isNetworkError, tlsError: isTLSError, errorCode: error.code, originalError: error.message } } }; } }
  • Tool registration block that defines and registers the 'searx_image_search' tool including name, description, input schema, and execute handler.
    registry.registerTool({ name: 'searx_image_search', description: 'Search for images using Searx with privacy protection', category: 'search', source: 'Searx', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Image search query' }, safeSearch: { type: 'string', description: 'Safe search level: 0 (off), 1 (moderate), 2 (strict)', default: '1', enum: ['0', '1', '2'] }, maxResults: { type: 'number', description: 'Maximum number of images to return', default: 20, minimum: 1, maximum: 50 } }, required: ['query'] }, execute: async (args: any) => { const { query, safeSearch = '1', maxResults = 20 } = args; try { const startTime = Date.now(); const searchResult = await client.searchWithFallback(query, { categories: 'images', safeSearch }); const searchTime = Date.now() - startTime; // 处理图片搜索结果 const images = (searchResult.results || []).slice(0, maxResults).map((result: any) => ({ title: result.title || 'No title', url: result.url || '', thumbnailUrl: result.thumbnail_src || result.img_src || '', imageUrl: result.img_src || result.url || '', width: result.resolution ? result.resolution.split('x')[0] : null, height: result.resolution ? result.resolution.split('x')[1] : null, source: result.engine || 'unknown', publishedDate: result.publishedDate || null })); return { success: true, data: { source: 'Searx Images', instance: searchResult.instance, query: searchResult.query, safeSearch, totalResults: searchResult.number_of_results || images.length, images, searchTime, timestamp: Date.now(), metadata: { privacy: 'Protected by Searx', category: 'images', 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 image search failed: ${error.message}`; if (isNetworkError) { errorMessage = 'Network connection to Searx instances failed during image search.'; } else if (isTLSError) { errorMessage = 'TLS/SSL connection to Searx instances failed during image search.'; } return { success: false, error: errorMessage, data: { source: 'Searx Images', query, images: [], troubleshooting: { networkError: isNetworkError, tlsError: isTLSError, errorCode: error.code, originalError: error.message } } }; } } });
  • Input schema definition for the searx_image_search tool, specifying parameters like query, safeSearch, and maxResults.
    inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Image search query' }, safeSearch: { type: 'string', description: 'Safe search level: 0 (off), 1 (moderate), 2 (strict)', default: '1', enum: ['0', '1', '2'] }, maxResults: { type: 'number', description: 'Maximum number of images to return', default: 20, minimum: 1, maximum: 50 } }, required: ['query'] },
  • Additional schema mapping in input validator that associates 'searx_image_search' with ToolSchemas.basicSearch for validation.
    'searx_search': ToolSchemas.basicSearch, 'searx_image_search': ToolSchemas.basicSearch, 'searx_news_search': ToolSchemas.basicSearch,
  • SearxClient helper class that provides the searchWithFallback method used by the tool handler, with fallback instances, retry logic, TLS handling, and mock fallback data.
    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