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;
          }
        }
      }
    }
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries full burden for behavioral disclosure. It mentions 'privacy protection' as a trait, but fails to describe other critical behaviors like rate limits, authentication needs, response format, or error handling. For a search tool with zero annotation coverage, this is a significant gap.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, efficient sentence with zero waste. It's appropriately sized and front-loaded, clearly stating the core functionality without unnecessary elaboration.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given no annotations, no output schema, and 100% schema coverage, the description is minimally adequate but lacks completeness. It doesn't explain return values, error cases, or behavioral constraints that would be needed for robust tool selection and invocation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema fully documents all three parameters. The description adds no additional parameter semantics beyond what's in the schema, but meets the baseline of 3 since the schema does the heavy lifting.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the verb ('search for images') and resource ('using Searx'), and mentions the privacy protection feature. However, it doesn't explicitly differentiate from sibling tools like 'search_searx' or 'searx_news_search', which would require a more specific comparison.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus alternatives like 'search_searx' (general search) or 'searx_news_search'. It lacks explicit when/when-not instructions or named alternatives, leaving usage context implied at best.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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