exa-mcp-server
Version:
A Model Context Protocol server with Exa for web search, academic paper search, and Twitter/X.com search. Provides real-time web searches with configurable tool selection, allowing users to enable or disable specific search capabilities. Supports customiz
90 lines (89 loc) • 4.32 kB
JavaScript
import { z } from "zod";
import axios from "axios";
import { toolRegistry, API_CONFIG } from "./config.js";
import { createRequestLogger } from "../utils/logger.js";
// Register the research paper search tool
toolRegistry["research_paper_search"] = {
name: "research_paper_search",
description: "Search across 100M+ research papers with full text access using Exa AI - performs targeted academic paper searches with deep research content coverage. Returns detailed information about relevant academic papers including titles, authors, publication dates, and full text excerpts. Control the number of results and character counts returned to balance comprehensiveness with conciseness based on your task requirements.",
schema: {
query: z.string().describe("Research topic or keyword to search for"),
numResults: z.number().optional().describe("Number of research papers to return (default: 5)"),
maxCharacters: z.number().optional().describe("Maximum number of characters to return for each result's text content (Default: 3000)")
},
handler: async ({ query, numResults, maxCharacters }, extra) => {
const requestId = `research_paper-${Date.now()}-${Math.random().toString(36).substring(2, 7)}`;
const logger = createRequestLogger(requestId, 'research_paper_search');
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 = {
query,
category: "research paper",
type: "auto",
numResults: numResults || API_CONFIG.DEFAULT_NUM_RESULTS,
contents: {
text: {
maxCharacters: maxCharacters || API_CONFIG.DEFAULT_MAX_CHARACTERS
},
livecrawl: 'fallback'
}
};
logger.log("Sending research paper request to Exa API");
const response = await axiosInstance.post(API_CONFIG.ENDPOINTS.SEARCH, searchRequest, { timeout: 25000 });
logger.log("Received research paper response from Exa API");
if (!response.data || !response.data.results) {
logger.log("Warning: Empty or invalid response from Exa API for research papers");
return {
content: [{
type: "text",
text: "No research papers found. Please try a different query."
}]
};
}
logger.log(`Found ${response.data.results.length} research papers`);
const result = {
content: [{
type: "text",
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",
text: `Research paper search error (${statusCode}): ${errorMessage}`
}],
isError: true,
};
}
// Handle generic errors
return {
content: [{
type: "text",
text: `Research paper search error: ${error instanceof Error ? error.message : String(error)}`
}],
isError: true,
};
}
},
enabled: false // disabled by default
};