Skip to main content
Glama
waldzellai

Exa Websets MCP Server

by waldzellai

web_search_exa

Perform real-time web searches and scrape content from specific URLs using AI-powered search capabilities for research and data analysis.

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
queryYesSearch query
numResultsNoNumber of search results to return (default: 5)

Implementation Reference

  • The main execution logic for the web_search_exa tool. Handles input parameters, constructs Exa API request, performs HTTP POST to search endpoint, processes results or errors, logs the operation, and returns structured content.
    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: EXA_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 || EXA_API_CONFIG.DEFAULT_NUM_RESULTS, contents: { text: { maxCharacters: EXA_API_CONFIG.DEFAULT_MAX_CHARACTERS }, livecrawl: 'always' } }; logger.log("Sending request to Exa API"); const response = await axiosInstance.post<ExaSearchResponse>( EXA_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 schema defining the input parameters for the tool: required 'query' string and optional 'numResults' 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 into the internal toolRegistry with name, description, schema, category, service, handler, and enabled status.
    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)") }, category: ToolCategory.SEARCH, service: ServiceType.EXA_SEARCH, 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: EXA_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 || EXA_API_CONFIG.DEFAULT_NUM_RESULTS, contents: { text: { maxCharacters: EXA_API_CONFIG.DEFAULT_MAX_CHARACTERS }, livecrawl: 'always' } }; logger.log("Sending request to Exa API"); const response = await axiosInstance.post<ExaSearchResponse>( EXA_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 };
  • src/index.ts:124-144 (registration)
    Registers the web_search_exa tool (along with others) from toolRegistry to the MCP server using server.tool() method.
    private registerTools(): void { // Create our simplified tool registry with three tools const simplifiedRegistry = { web_search_exa: toolRegistry["web_search_exa"], websets_manager: toolRegistry["websets_manager"], websets_guide: websetsGuideTool, knowledge_graph: toolRegistry["knowledge_graph"], }; // Register our tools Object.values(simplifiedRegistry).forEach(tool => { if (tool) { this.server.tool( tool.name, tool.description, tool.schema, tool.handler ); } }); }
  • TypeScript interfaces defining the structure of ExaSearchRequest and ExaSearchResponse used in the tool handler for type safety.
    export interface ExaSearchRequest { query: string; type: string; category?: string; includeDomains?: string[]; excludeDomains?: string[]; startPublishedDate?: string; endPublishedDate?: string; numResults: number; contents: { text: { maxCharacters?: number; } | boolean; livecrawl?: 'always' | 'fallback'; subpages?: number; subpageTarget?: string[]; }; } export interface ExaCrawlRequest { ids: string[]; text: boolean; livecrawl?: 'always' | 'fallback'; } export interface ExaSearchResult { id: string; title: string; url: string; publishedDate: string; author: string; text: string; image?: string; favicon?: string; score?: number; } export interface ExaSearchResponse { requestId: string; autopromptString: string; resolvedSearchType: string; results: ExaSearchResult[]; }

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

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