Skip to main content
Glama
flyanima

Open Search MCP

by flyanima

search_searx

Perform private web searches using open-source technology with customizable filters for categories, languages, time ranges, and safe search levels.

Instructions

Search using Searx open-source search engine with privacy protection

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
queryYesSearch query
categoriesNoSearch categories: general, images, videos, news, music, files, science, socialgeneral
enginesNoSpecific search engines to use (comma-separated, e.g., "google,bing,duckduckgo")
languageNoSearch language (e.g., "en", "zh", "es", "fr")en
timeRangeNoTime range filter: day, week, month, year
safeSearchNoSafe search level: 0 (off), 1 (moderate), 2 (strict)1
maxResultsNoMaximum number of results to return

Implementation Reference

  • The execute function implementing the core search_searx logic: extracts parameters, calls SearxClient.searchWithFallback with multiple instances and fallback handling, processes and limits results, handles network/TLS errors with detailed responses.
    execute: async (args: any) => {
      const { 
        query, 
        categories = 'general', 
        engines, 
        language = 'en', 
        timeRange, 
        safeSearch = '1',
        maxResults = 10 
      } = args;
    
      try {
        const startTime = Date.now();
        
        const searchResult = await client.searchWithFallback(query, {
          categories,
          engines,
          language,
          timeRange,
          safeSearch
        });
    
        const searchTime = Date.now() - startTime;
        
        // 处理搜索结果
        const results = (searchResult.results || []).slice(0, maxResults).map((result: any) => ({
          title: result.title || 'No title',
          url: result.url || '',
          content: result.content || 'No content available',
          engine: result.engine || 'unknown',
          category: result.category || categories,
          score: result.score || 0,
          publishedDate: result.publishedDate || null,
          thumbnail: result.img_src || null
        }));
    
        return {
          success: true,
          data: {
            source: 'Searx',
            instance: searchResult.instance,
            query: searchResult.query,
            categories,
            language,
            timeRange: timeRange || 'all',
            safeSearch,
            totalResults: searchResult.number_of_results || results.length,
            results,
            suggestions: searchResult.suggestions || [],
            answers: searchResult.answers || [],
            infoboxes: searchResult.infoboxes || [],
            searchTime,
            timestamp: Date.now(),
            metadata: {
              privacy: 'Protected by Searx',
              engines: engines || 'auto-selected',
              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 search failed: ${error.message}`;
        const suggestions = ['Try simpler search terms', 'Try again in a few moments'];
        
        if (isNetworkError) {
          errorMessage = 'Network connection to Searx instances failed. Please check your internet connection.';
          suggestions.push('Check your internet connection', 'Try using a VPN if access is blocked');
        } else if (isTLSError) {
          errorMessage = 'TLS/SSL connection to Searx instances failed. This may be due to certificate issues.';
          suggestions.push('Check if Searx instances are accessible', 'Try again later as this may be temporary');
        }
        
        return {
          success: false,
          error: errorMessage,
          data: {
            source: 'Searx',
            query,
            results: [],
            suggestions,
            troubleshooting: {
              networkError: isNetworkError,
              tlsError: isTLSError,
              errorCode: error.code,
              originalError: error.message
            }
          }
        };
      }
    }
  • Input schema (inputSchema) defining validation for search_searx parameters: query (required), categories, engines, language, timeRange, safeSearch, maxResults.
    inputSchema: {
      type: 'object',
      properties: {
        query: {
          type: 'string',
          description: 'Search query'
        },
        categories: {
          type: 'string',
          description: 'Search categories: general, images, videos, news, music, files, science, social',
          default: 'general',
          enum: ['general', 'images', 'videos', 'news', 'music', 'files', 'science', 'social']
        },
        engines: {
          type: 'string',
          description: 'Specific search engines to use (comma-separated, e.g., "google,bing,duckduckgo")'
        },
        language: {
          type: 'string',
          description: 'Search language (e.g., "en", "zh", "es", "fr")',
          default: 'en'
        },
        timeRange: {
          type: 'string',
          description: 'Time range filter: day, week, month, year',
          enum: ['', 'day', 'week', 'month', 'year']
        },
        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 results to return',
          default: 10,
          minimum: 1,
          maximum: 50
        }
      },
      required: ['query']
    },
  • registry.registerTool call registering the search_searx tool with name, description, inputSchema, and execute handler.
    registry.registerTool({
      name: 'search_searx',
      description: 'Search using Searx open-source search engine with privacy protection',
      category: 'search',
      source: 'Searx',
      inputSchema: {
        type: 'object',
        properties: {
          query: {
            type: 'string',
            description: 'Search query'
          },
          categories: {
            type: 'string',
            description: 'Search categories: general, images, videos, news, music, files, science, social',
            default: 'general',
            enum: ['general', 'images', 'videos', 'news', 'music', 'files', 'science', 'social']
          },
          engines: {
            type: 'string',
            description: 'Specific search engines to use (comma-separated, e.g., "google,bing,duckduckgo")'
          },
          language: {
            type: 'string',
            description: 'Search language (e.g., "en", "zh", "es", "fr")',
            default: 'en'
          },
          timeRange: {
            type: 'string',
            description: 'Time range filter: day, week, month, year',
            enum: ['', 'day', 'week', 'month', 'year']
          },
          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 results to return',
            default: 10,
            minimum: 1,
            maximum: 50
          }
        },
        required: ['query']
      },
      execute: async (args: any) => {
        const { 
          query, 
          categories = 'general', 
          engines, 
          language = 'en', 
          timeRange, 
          safeSearch = '1',
          maxResults = 10 
        } = args;
    
        try {
          const startTime = Date.now();
          
          const searchResult = await client.searchWithFallback(query, {
            categories,
            engines,
            language,
            timeRange,
            safeSearch
          });
    
          const searchTime = Date.now() - startTime;
          
          // 处理搜索结果
          const results = (searchResult.results || []).slice(0, maxResults).map((result: any) => ({
            title: result.title || 'No title',
            url: result.url || '',
            content: result.content || 'No content available',
            engine: result.engine || 'unknown',
            category: result.category || categories,
            score: result.score || 0,
            publishedDate: result.publishedDate || null,
            thumbnail: result.img_src || null
          }));
    
          return {
            success: true,
            data: {
              source: 'Searx',
              instance: searchResult.instance,
              query: searchResult.query,
              categories,
              language,
              timeRange: timeRange || 'all',
              safeSearch,
              totalResults: searchResult.number_of_results || results.length,
              results,
              suggestions: searchResult.suggestions || [],
              answers: searchResult.answers || [],
              infoboxes: searchResult.infoboxes || [],
              searchTime,
              timestamp: Date.now(),
              metadata: {
                privacy: 'Protected by Searx',
                engines: engines || 'auto-selected',
                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 search failed: ${error.message}`;
          const suggestions = ['Try simpler search terms', 'Try again in a few moments'];
          
          if (isNetworkError) {
            errorMessage = 'Network connection to Searx instances failed. Please check your internet connection.';
            suggestions.push('Check your internet connection', 'Try using a VPN if access is blocked');
          } else if (isTLSError) {
            errorMessage = 'TLS/SSL connection to Searx instances failed. This may be due to certificate issues.';
            suggestions.push('Check if Searx instances are accessible', 'Try again later as this may be temporary');
          }
          
          return {
            success: false,
            error: errorMessage,
            data: {
              source: 'Searx',
              query,
              results: [],
              suggestions,
              troubleshooting: {
                networkError: isNetworkError,
                tlsError: isTLSError,
                errorCode: error.code,
                originalError: error.message
              }
            }
          };
        }
      }
    });
  • SearxClient class providing robust search functionality with multiple instance fallback, TLS error handling, retries, and fallback data generation.
    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;
          }
        }
      }
    }
  • src/index.ts:242-242 (registration)
    Top-level call to registerSearxTools(this.toolRegistry) which includes registration of search_searx.
    registerSearxTools(this.toolRegistry);              // 1 tool: search_searx
Behavior2/5

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

No annotations are provided, so the description carries the full burden of behavioral disclosure. It mentions 'privacy protection', which adds some context about the tool's nature, but fails to describe key behaviors: it doesn't specify the output format (e.g., list of results, structured data), pagination, rate limits, authentication needs, or error handling. For a search tool with 7 parameters and no output schema, this leaves significant gaps in understanding how the tool behaves.

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: 'Search using Searx open-source search engine with privacy protection'. It's front-loaded with the core purpose and includes a distinguishing feature ('privacy protection') without unnecessary words. Every part of the sentence adds value, making it appropriately sized for the tool's complexity.

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

Completeness2/5

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

Given the tool's complexity (7 parameters, no annotations, no output schema), the description is incomplete. It lacks information on output format, behavioral traits (e.g., rate limits, errors), and usage guidelines relative to siblings. While concise, it doesn't provide enough context for an AI agent to fully understand how to invoke and interpret results from this tool, especially with no output schema to fall back on.

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?

The input schema has 100% description coverage, with detailed parameter descriptions and enums for many. The description adds no parameter-specific information beyond what's in the schema. According to the rules, with high schema coverage (>80%), the baseline is 3 even with no param info in the description, which applies here as the description doesn't compensate or add meaning.

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 tool's purpose: 'Search using Searx open-source search engine with privacy protection'. It specifies the verb ('search') and resource ('Searx open-source search engine'), and distinguishes it from siblings by mentioning 'privacy protection', which is unique among the many search tools listed. However, it doesn't explicitly differentiate from other Searx tools like 'searx_image_search' or 'searx_news_search' beyond the general nature.

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. It doesn't mention any specific contexts, prerequisites, or exclusions, and with many sibling search tools (e.g., 'search_arxiv', 'search_pubmed', 'search_brave'), there's no indication of when Searx is preferred over others. The phrase 'with privacy protection' hints at a use case but isn't explicit enough to guide selection.

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