Skip to main content
Glama

web_search_exa

Read-onlyIdempotent

Perform real-time web searches and scrape content from URLs to find relevant information for research tasks. Supports configurable result counts and search modes.

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

  • The asynchronous handler function that implements the core logic of the web_search_exa tool. It creates an axios instance, sends a POST request to the Exa API search endpoint with the query and configuration, processes the results or handles errors, and returns markdown-formatted content.
    async ({ query, numResults }) => {
      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': config?.exaApiKey || 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: 'preferred'
          }
        };
        
        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 parameters: required 'query' (string) and optional 'numResults' (number, default 5).
    {
      query: z.string().describe("Search query"),
      numResults: z.number().optional().describe("Number of search results to return (default: 5)")
    },
  • The registerWebSearchTool export function that registers the web_search_exa tool on the McpServer instance using server.tool(), providing name, description, input schema, and the handler function.
    export function registerWebSearchTool(server: McpServer, config?: { exaApiKey?: string }): void {
      server.tool(
        "web_search_exa",
        "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.",
        {
          query: z.string().describe("Search query"),
          numResults: z.number().optional().describe("Number of search results to return (default: 5)")
        },
        async ({ query, numResults }) => {
          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': config?.exaApiKey || 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: 'preferred'
              }
            };
            
            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,
            };
          }
        }
      );
  • src/index.ts:86-88 (registration)
    Higher-level conditional registration: calls registerWebSearchTool if the tool is enabled via configuration.
    if (shouldRegisterTool('web_search_exa')) {
      registerWebSearchTool(server, config);
      registeredTools.push('web_search_exa');
Behavior4/5

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

The description adds valuable behavioral context beyond what annotations provide. While annotations indicate read-only, idempotent, and non-destructive operations, the description specifies real-time capabilities, scraping functionality, configurable result counts, and relevance-based content return. This enhances the agent's understanding of the tool's operational characteristics.

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 efficiently structured in two sentences that front-load the core functionality. Every phrase adds value: the first sentence establishes the primary action and key capabilities, while the second sentence highlights configurable aspects and return behavior. There's no wasted language 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?

Given the tool's moderate complexity (5 parameters, real-time operations), the description provides good context about what the tool does and key capabilities. While there's no output schema, the description mentions what gets returned ('content from the most relevant websites'). The combination of good annotations and descriptive text makes this reasonably complete, though more detail about result format would enhance it further.

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?

With 100% schema description coverage, the input schema already documents all 5 parameters thoroughly. The description mentions 'configurable result counts' which aligns with the numResults parameter, but doesn't add significant semantic value beyond what's already in the structured schema. The baseline of 3 is appropriate when schema coverage is complete.

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 from specific URLs') and resources ('using Exa AI', 'websites'). It distinguishes itself 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 about when to use this tool ('performs real-time web searches', 'can scrape content from specific URLs'), but doesn't explicitly mention when NOT to use it or provide specific alternatives. The sibling tool name suggests a more specialized code-focused search, but this distinction isn't articulated in the description.

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/dsouza-anush/exa-mcp-server-heroku'

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