Skip to main content
Glama
jackedelic

exa-search

web_search_exa

Read-onlyIdempotent

Perform real-time web searches and scrape content from URLs using AI-powered search. Configure result counts and search types to find relevant information.

Instructions

Search the web using Exa AI - performs real-time web searches and can scrape content from specific URLs. Supports configurable result counts and returns the content from the most relevant websites.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
queryYesWebsearch query
numResultsNoNumber of search results to return (default: 8)
livecrawlNoLive crawl mode - 'fallback': use live crawling as backup if cached content unavailable, 'preferred': prioritize live crawling (default: 'fallback')
typeNoSearch type - 'auto': balanced search (default), 'fast': quick results, 'deep': comprehensive search
contextMaxCharactersNoMaximum characters for context string optimized for LLMs (default: 10000)

Implementation Reference

  • Async handler that performs the web search using Exa API: logs the request, sends POST with query and config, processes results as JSON, handles axios and generic errors with appropriate responses.
    handler: async ({ query, numResults }, extra) => {
      const requestId = `web_search_exa-${Date.now()}-${Math.random().toString(36).substring(2, 7)}`;
      const logger = createRequestLogger(requestId, 'web_search_exa');
      
      logger.start(query);
      
      try {
        // Create a fresh axios instance for each request
        const axiosInstance = axios.create({
          baseURL: API_CONFIG.BASE_URL,
          headers: {
            'accept': 'application/json',
            'content-type': 'application/json',
            'x-api-key': process.env.EXA_API_KEY || ''
          },
          timeout: 25000
        });
    
        const searchRequest: ExaSearchRequest = {
          query,
          type: "auto",
          numResults: numResults || API_CONFIG.DEFAULT_NUM_RESULTS,
          contents: {
            text: {
              maxCharacters: API_CONFIG.DEFAULT_MAX_CHARACTERS
            },
            livecrawl: 'always'
          }
        };
        
        logger.log("Sending request to Exa API");
        
        const response = await axiosInstance.post<ExaSearchResponse>(
          API_CONFIG.ENDPOINTS.SEARCH,
          searchRequest,
          { timeout: 25000 }
        );
        
        logger.log("Received response from Exa API");
    
        if (!response.data || !response.data.results) {
          logger.log("Warning: Empty or invalid response from Exa API");
          return {
            content: [{
              type: "text" as const,
              text: "No search results found. Please try a different query."
            }]
          };
        }
    
        logger.log(`Found ${response.data.results.length} results`);
        
        const result = {
          content: [{
            type: "text" as const,
            text: JSON.stringify(response.data, null, 2)
          }]
        };
        
        logger.complete();
        return result;
      } catch (error) {
        logger.error(error);
        
        if (axios.isAxiosError(error)) {
          // Handle Axios errors specifically
          const statusCode = error.response?.status || 'unknown';
          const errorMessage = error.response?.data?.message || error.message;
          
          logger.log(`Axios error (${statusCode}): ${errorMessage}`);
          return {
            content: [{
              type: "text" as const,
              text: `Search error (${statusCode}): ${errorMessage}`
            }],
            isError: true,
          };
        }
        
        // Handle generic errors
        return {
          content: [{
            type: "text" as const,
            text: `Search error: ${error instanceof Error ? error.message : String(error)}`
          }],
          isError: true,
        };
      }
    },
  • Zod input schema defining 'query' as required string and 'numResults' as optional number.
    schema: {
      query: z.string().describe("Search query"),
      numResults: z.number().optional().describe("Number of search results to return (default: 5)")
    },
  • Registers the 'web_search_exa' tool in the toolRegistry with name, description, schema, handler function, and enabled flag.
    toolRegistry["web_search_exa"] = {
      name: "web_search_exa",
      description: "Search the web using Exa AI - performs real-time web searches and can scrape content from specific URLs. Supports configurable result counts and returns the content from the most relevant websites.",
      schema: {
        query: z.string().describe("Search query"),
        numResults: z.number().optional().describe("Number of search results to return (default: 5)")
      },
      handler: async ({ query, numResults }, extra) => {
        const requestId = `web_search_exa-${Date.now()}-${Math.random().toString(36).substring(2, 7)}`;
        const logger = createRequestLogger(requestId, 'web_search_exa');
        
        logger.start(query);
        
        try {
          // Create a fresh axios instance for each request
          const axiosInstance = axios.create({
            baseURL: API_CONFIG.BASE_URL,
            headers: {
              'accept': 'application/json',
              'content-type': 'application/json',
              'x-api-key': process.env.EXA_API_KEY || ''
            },
            timeout: 25000
          });
    
          const searchRequest: ExaSearchRequest = {
            query,
            type: "auto",
            numResults: numResults || API_CONFIG.DEFAULT_NUM_RESULTS,
            contents: {
              text: {
                maxCharacters: API_CONFIG.DEFAULT_MAX_CHARACTERS
              },
              livecrawl: 'always'
            }
          };
          
          logger.log("Sending request to Exa API");
          
          const response = await axiosInstance.post<ExaSearchResponse>(
            API_CONFIG.ENDPOINTS.SEARCH,
            searchRequest,
            { timeout: 25000 }
          );
          
          logger.log("Received response from Exa API");
    
          if (!response.data || !response.data.results) {
            logger.log("Warning: Empty or invalid response from Exa API");
            return {
              content: [{
                type: "text" as const,
                text: "No search results found. Please try a different query."
              }]
            };
          }
    
          logger.log(`Found ${response.data.results.length} results`);
          
          const result = {
            content: [{
              type: "text" as const,
              text: JSON.stringify(response.data, null, 2)
            }]
          };
          
          logger.complete();
          return result;
        } catch (error) {
          logger.error(error);
          
          if (axios.isAxiosError(error)) {
            // Handle Axios errors specifically
            const statusCode = error.response?.status || 'unknown';
            const errorMessage = error.response?.data?.message || error.message;
            
            logger.log(`Axios error (${statusCode}): ${errorMessage}`);
            return {
              content: [{
                type: "text" as const,
                text: `Search error (${statusCode}): ${errorMessage}`
              }],
              isError: true,
            };
          }
          
          // Handle generic errors
          return {
            content: [{
              type: "text" as const,
              text: `Search error: ${error instanceof Error ? error.message : String(error)}`
            }],
            isError: true,
          };
        }
      },
      enabled: true  // Enabled by default
    }; 
Behavior4/5

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

Annotations already declare readOnlyHint=true, idempotentHint=true, and destructiveHint=false, covering safety and idempotency. The description adds valuable behavioral context beyond annotations: it specifies real-time capability, scraping functionality, configurable result counts, and that it returns content from relevant websites. No contradiction with annotations exists.

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 front-loaded with the core purpose in the first sentence, followed by supporting details. Every sentence adds value: the first establishes the main function, the second explains scraping capability, and the third covers configurability and return behavior. No wasted words or redundancy.

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

Completeness4/5

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

For a search tool with rich annotations (covering safety and idempotency) and 100% schema coverage, the description provides good contextual completeness. It explains the tool's real-time nature, scraping capability, and return behavior. The main gap is the absence of an output schema, but the description partially compensates by mentioning what gets returned ('content from the most relevant websites').

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%, providing detailed documentation for all 5 parameters. The description mentions 'configurable result counts' which aligns with the 'numResults' parameter, but doesn't add significant meaning beyond what the schema already specifies. Baseline 3 is appropriate given the comprehensive schema coverage.

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

Purpose5/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 with specific verbs ('search the web', 'scrape content') and resources ('Exa AI', 'web', 'specific URLs'). It distinguishes from the sibling tool 'get_code_context_exa' by focusing on general web search rather than code-specific context retrieval.

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

Usage Guidelines4/5

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

The description provides clear context for when to use this tool ('performs real-time web searches', 'can scrape content from specific URLs'), but doesn't explicitly state when not to use it or mention alternatives beyond the implied distinction from the sibling tool. It lacks explicit exclusions or comparison to other search methods.

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/jackedelic/exa-mcp-server'

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