Skip to main content
Glama

Exa MCP Server

by joerup
index.ts12.4 kB
import { z } from 'zod'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import { ExaClient } from '../client.js'; import { formatSearchResults, formatCompanyResults, formatLinkedInResults, formatDeepResearchResults, formatCrawlResults, formatError } from '../formatters.js'; /** * Tool definitions for Exa MCP server */ // Web Search Tool Schema const webSearchSchema = z.object({ query: z.string().describe("Search query for finding information on the web"), num_results: z.number().optional().default(10).describe("Number of results to return (default: 10, max: 100)"), include_domains: z.array(z.string()).optional().describe("Only include results from these domains"), exclude_domains: z.array(z.string()).optional().describe("Exclude results from these domains"), start_crawl_date: z.string().optional().describe("Start date for crawled content (YYYY-MM-DD)"), end_crawl_date: z.string().optional().describe("End date for crawled content (YYYY-MM-DD)"), start_published_date: z.string().optional().describe("Start date for published content (YYYY-MM-DD)"), end_published_date: z.string().optional().describe("End date for published content (YYYY-MM-DD)"), use_autoprompt: z.boolean().optional().default(true).describe("Let Exa AI optimize the search query"), type: z.enum(['keyword', 'neural', 'magic']).optional().default('neural').describe("Search type: 'keyword' for exact match, 'neural' for semantic search, 'magic' for best results"), category: z.string().optional().describe("Filter by content category (news, blog, research, etc.)"), include_text: z.boolean().optional().default(false).describe("Include extracted text content from pages"), include_highlights: z.boolean().optional().default(false).describe("Include highlighted snippets from pages"), include_summary: z.boolean().optional().default(false).describe("Include AI-generated summaries of pages") }); // Company Research Tool Schema const companyResearchSchema = z.object({ company_name: z.string().describe("Name of the company to research"), include_news: z.boolean().optional().default(true).describe("Include recent news and announcements"), include_overview: z.boolean().optional().default(true).describe("Include company overview and description"), include_competitors: z.boolean().optional().default(false).describe("Include information about competitors"), include_reviews: z.boolean().optional().default(false).describe("Include customer reviews and feedback"), max_results_per_category: z.number().optional().default(5).describe("Maximum results per category (default: 5)") }); // Crawling Tool Schema const crawlingSchema = z.object({ urls: z.array(z.string()).describe("List of URLs to crawl and extract content from"), include_text: z.boolean().optional().default(true).describe("Include extracted text content"), include_highlights: z.boolean().optional().default(false).describe("Include key highlights from the content"), include_summary: z.boolean().optional().default(false).describe("Include AI-generated summary"), summary_query: z.string().optional().describe("Custom query for generating summaries") }); // LinkedIn Search Tool Schema const linkedInSearchSchema = z.object({ query: z.string().describe("Search query for LinkedIn profiles or companies"), search_type: z.enum(['people', 'companies', 'both']).optional().default('both').describe("Type of LinkedIn search"), num_results: z.number().optional().default(10).describe("Number of results to return"), include_bios: z.boolean().optional().default(true).describe("Include bio/description text"), include_summaries: z.boolean().optional().default(false).describe("Include AI-generated summaries") }); // Deep Research Start Tool Schema const deepResearchStartSchema = z.object({ topic: z.string().describe("Research topic or question"), research_type: z.enum(['comprehensive', 'news', 'academic', 'market']).optional().default('comprehensive').describe("Type of research to perform"), max_results: z.number().optional().default(50).describe("Maximum total results to collect"), time_range: z.enum(['week', 'month', 'quarter', 'year', 'all']).optional().describe("Time range for research") }); // Deep Research Check Tool Schema const deepResearchCheckSchema = z.object({ task_id: z.string().describe("Task ID returned from deep_researcher_start") }); /** * Get tool definitions based on enabled tools */ export function getToolDefinitions(enabledTools: string[]): Tool[] { const allTools: Tool[] = [ { name: 'web_search_exa', description: 'Search the web using Exa AI for up-to-date information', inputSchema: { type: "object", properties: { query: { type: "string", description: "Search query for finding information on the web" }, num_results: { type: "number", description: "Number of results to return (default: 10, max: 100)", default: 10 }, include_domains: { type: "array", items: { type: "string" }, description: "Only include results from these domains" }, exclude_domains: { type: "array", items: { type: "string" }, description: "Exclude results from these domains" }, start_crawl_date: { type: "string", description: "Start date for crawled content (YYYY-MM-DD)" }, end_crawl_date: { type: "string", description: "End date for crawled content (YYYY-MM-DD)" }, start_published_date: { type: "string", description: "Start date for published content (YYYY-MM-DD)" }, end_published_date: { type: "string", description: "End date for published content (YYYY-MM-DD)" }, use_autoprompt: { type: "boolean", description: "Let Exa AI optimize the search query", default: true }, type: { type: "string", enum: ["keyword", "neural", "magic"], description: "Search type: 'keyword' for exact match, 'neural' for semantic search, 'magic' for best results", default: "neural" }, category: { type: "string", description: "Filter by content category (news, blog, research, etc.)" }, include_text: { type: "boolean", description: "Include extracted text content from pages", default: false }, include_highlights: { type: "boolean", description: "Include highlighted snippets from pages", default: false }, include_summary: { type: "boolean", description: "Include AI-generated summaries of pages", default: false } }, required: ["query"] } }, { name: 'company_research_exa', description: 'Research companies and organizations comprehensively', inputSchema: { type: "object", properties: { company_name: { type: "string", description: "Name of the company to research" }, include_news: { type: "boolean", description: "Include recent news and announcements", default: true }, include_overview: { type: "boolean", description: "Include company overview and description", default: true }, include_competitors: { type: "boolean", description: "Include information about competitors", default: false }, include_reviews: { type: "boolean", description: "Include customer reviews and feedback", default: false }, max_results_per_category: { type: "number", description: "Maximum results per category (default: 5)", default: 5 } }, required: ["company_name"] } } ]; return allTools.filter(tool => enabledTools.includes(tool.name)); } /** * Handle tool calls */ export async function handleToolCall(client: ExaClient, toolName: string, args: any) { try { switch (toolName) { case 'web_search_exa': { const params = webSearchSchema.parse(args); const results = await client.search(params); return { content: [{ type: "text", text: formatSearchResults(results) }] }; } case 'company_research_exa': { const params = companyResearchSchema.parse(args); const results = await client.researchCompany(params); return { content: [{ type: "text", text: formatCompanyResults(results) }] }; } case 'crawling_exa': { const params = crawlingSchema.parse(args); const results = await client.crawl(params); return { content: [{ type: "text", text: formatCrawlResults(results) }] }; } case 'linkedin_search_exa': { const params = linkedInSearchSchema.parse(args); const results = await client.searchLinkedIn(params); return { content: [{ type: "text", text: formatLinkedInResults(results) }] }; } case 'deep_researcher_start': { const params = deepResearchStartSchema.parse(args); const result = await client.startDeepResearch(params); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }; } case 'deep_researcher_check': { const params = deepResearchCheckSchema.parse(args); const result = await client.checkDeepResearch(params.task_id); return { content: [{ type: "text", text: result.results ? formatDeepResearchResults(result.results) : JSON.stringify(result, null, 2) }] }; } default: throw new Error(`Unknown tool: ${toolName}`); } } catch (error) { return { content: [{ type: "text", text: formatError(error) }], isError: true }; } }

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

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