#
tokens: 14171/50000 21/22 files (page 1/2)
lines: off (toggle) GitHub
raw markdown copy
This is page 1 of 2. Use http://codebase.md/exa-labs/exa-mcp-server?lines=false&page={x} to view the full context.

# Directory Structure

```
├── .gitignore
├── .npmignore
├── Dockerfile
├── LICENSE
├── llm_mcp_docs.txt
├── mcp_publishing_steps_on_mcpregistry.md
├── package-lock.json
├── package.json
├── README.md
├── server.json
├── smithery-example.json
├── smithery.yaml
├── src
│   ├── index.ts
│   ├── tools
│   │   ├── companyResearch.ts
│   │   ├── config.ts
│   │   ├── crawling.ts
│   │   ├── deepResearchCheck.ts
│   │   ├── deepResearchStart.ts
│   │   ├── exaCode.ts
│   │   ├── linkedInSearch.ts
│   │   └── webSearch.ts
│   ├── types.ts
│   └── utils
│       └── logger.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
node_modules/
build/
*.log
.env*
.smithery/
```

--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------

```
src/
tests/
.github/
.gitignore
.npmignore
tsconfig.json
*.log
.env* 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
# Exa MCP Server 🔍
[![npm version](https://badge.fury.io/js/exa-mcp-server.svg)](https://www.npmjs.com/package/exa-mcp-server)
[![smithery badge](https://smithery.ai/badge/exa)](https://smithery.ai/server/exa)

## 🆕 `exa-code`: fast, efficient web context for coding agents

Vibe coding should never have a bad vibe. `exa-code` is a huge step towards coding agents that never hallucinate.

When your coding agent makes a search query, `exa-code` searches over billions
of Github repos, docs pages, Stackoverflow posts, and more, to find the perfect, token-efficient context that the agent needs to code correctly. It's powered by the Exa search engine.

Examples of queries you can make with `exa-code`:
* use Exa search in python and make sure content is always livecrawled
* use correct syntax for vercel ai sdk to call gpt-5 nano asking it how are you
* how to set up a reproducible Nix Rust development environment

**✨ Works with Cursor and Claude Code!** Use the HTTP-based configuration format:

```json
{
  "mcpServers": {
    "exa": {
      "type": "http",
      "url": "https://mcp.exa.ai/mcp",
      "headers": {
        "Remove-Me": "Disable web_search_exa tool if you're just coding. To 100% call exa-code, say 'use exa-code'."
      }
    }
  }
}
```

You may include your exa api key in the url like this:
```
https://mcp.exa.ai/mcp?exaApiKey=YOUREXAKEY
```

You may whitelist specific tools in the url with the `enabledTools` parameter which expects a url encoded array strings like this:
```
https://mcp.exa.ai/mcp?exaApiKey=YOUREXAKEY&enabledTools=%5B%22crawling_exa%ss%5D
```

You can also use `exa-code` through [Smithery](https://smithery.ai/server/exa) without an Exa API key.

---

A Model Context Protocol (MCP) server that connects AI assistants like Claude to Exa AI's search capabilities, including web search, research tools, and our new code search feature.

## Remote Exa MCP 🌐

Connect directly to Exa's hosted MCP server (instead of running it locally).

### Remote Exa MCP URL

```
https://mcp.exa.ai/mcp
```

### Claude Desktop Configuration for Remote MCP

Add this to your Claude Desktop configuration file:

```json
{
  "mcpServers": {
    "exa": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-remote",
        "https://mcp.exa.ai/mcp"
      ]
    }
  }
}
```

### Cursor and Claude Code Configuration for Remote MCP

For Cursor and Claude Code, use this HTTP-based configuration format:

```json
{
  "mcpServers": {
    "exa": {
      "type": "http",
      "url": "https://mcp.exa.ai/mcp",
      "headers": {}
    }
  }
}
```

### NPM Installation

```bash
npm install -g exa-mcp-server
```

### Using Claude Code

```bash
claude mcp add exa -e EXA_API_KEY=YOUR_API_KEY -- npx -y exa-mcp-server
```

### Using Exa MCP through Smithery

To install the Exa MCP server via [Smithery](https://smithery.ai/server/exa), head over to:

[smithery.ai/server/exa](https://smithery.ai/server/exa)


## Configuration ⚙️

### 1. Configure Claude Desktop to recognize the Exa MCP server

You can find claude_desktop_config.json inside the settings of Claude Desktop app:

Open the Claude Desktop app and enable Developer Mode from the top-left menu bar. 

Once enabled, open Settings (also from the top-left menu bar) and navigate to the Developer Option, where you'll find the Edit Config button. Clicking it will open the claude_desktop_config.json file, allowing you to make the necessary edits. 

OR (if you want to open claude_desktop_config.json from terminal)

#### For macOS:

1. Open your Claude Desktop configuration:

```bash
code ~/Library/Application\ Support/Claude/claude_desktop_config.json
```

#### For Windows:

1. Open your Claude Desktop configuration:

```powershell
code %APPDATA%\Claude\claude_desktop_config.json
```

### 2. Add the Exa server configuration:

```json
{
  "mcpServers": {
    "exa": {
      "command": "npx",
      "args": ["-y", "exa-mcp-server"],
      "env": {
        "EXA_API_KEY": "your-api-key-here"
      }
    }
  }
}
```

Replace `your-api-key-here` with your actual Exa API key from [dashboard.exa.ai/api-keys](https://dashboard.exa.ai/api-keys).

### 3. Available Tools & Tool Selection

The Exa MCP server includes powerful tools for developers and researchers:

#### 🔥 **Featured: Code Search Tool**
- **get_code_context_exa**: 🆕 **NEW!** Search and get relevant code snippets, examples, and documentation from open source libraries, GitHub repositories, and programming frameworks. Perfect for finding up-to-date code documentation, implementation examples, API usage patterns, and best practices from real codebases.

#### 🌐 **Other Available Tools**
- **web_search_exa**: Performs real-time web searches with optimized results and content extraction.
- **company_research**: Comprehensive company research tool that crawls company websites to gather detailed information about businesses.
- **crawling**: Extracts content from specific URLs, useful for reading articles, PDFs, or any web page when you have the exact URL.
- **linkedin_search**: Search LinkedIn for companies and people using Exa AI. Simply include company names, person names, or specific LinkedIn URLs in your query.
- **deep_researcher_start**: Start a smart AI researcher for complex questions. The AI will search the web, read many sources, and think deeply about your question to create a detailed research report.
- **deep_researcher_check**: Check if your research is ready and get the results. Use this after starting a research task to see if it's done and get your comprehensive report.

You can choose which tools to enable by adding the `--tools` parameter to your Claude Desktop configuration:

#### 💻 **Setup for Code Search Only** (Recommended for Developers)

```json
{
  "mcpServers": {
    "exa": {
      "command": "npx",
      "args": [
        "-y",
        "exa-mcp-server",
        "--tools=get_code_context_exa,web_search_exa"
      ],
      "env": {
        "EXA_API_KEY": "your-api-key-here"
      }
    }
  }
}
```

#### Specify which tools to enable:

```json
{
  "mcpServers": {
    "exa": {
      "command": "npx",
      "args": [
        "-y",
        "exa-mcp-server",
        "--tools=get_code_context_exa,web_search_exa,company_research,crawling,linkedin_search,deep_researcher_start,deep_researcher_check"
      ],
      "env": {
        "EXA_API_KEY": "your-api-key-here"
      }
    }
  }
}
```

For enabling multiple tools, use a comma-separated list:

```json
{
  "mcpServers": {
    "exa": {
      "command": "npx",
      "args": [
        "-y",
        "exa-mcp-server",
        "--tools=get_code_context_exa,web_search_exa,company_research,crawling,linkedin_search,deep_researcher_start,deep_researcher_check"
      ],
      "env": {
        "EXA_API_KEY": "your-api-key-here"
      }
    }
  }
}
```

If you don't specify any tools, all tools enabled by default will be used.

### 4. Restart Claude Desktop

For the changes to take effect:

1. Completely quit Claude Desktop (not just close the window)
2. Start Claude Desktop again
3. Look for the icon to verify the Exa server is connected

## Using via NPX

If you prefer to run the server directly, you can use npx:

```bash
# Run with all tools enabled by default
npx exa-mcp-server

# Enable specific tools only
npx exa-mcp-server --tools=web_search_exa

# Enable multiple tools
npx exa-mcp-server --tools=web_search_exa,get_code_context_exa

# List all available tools
npx exa-mcp-server --list-tools
```

---

Built with ❤️ by team Exa

```

--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------

```yaml
runtime: typescript 
```

--------------------------------------------------------------------------------
/smithery-example.json:
--------------------------------------------------------------------------------

```json
{
  "exaApiKey": "your-exa-api-key-here",
  "enabledTools": [
    "web_search_exa",
    "company_research_exa",
    "crawling_exa",
    "linkedin_search_exa",
    "deep_researcher_start",
    "deep_researcher_check"
  ],
  "debug": false
} 
```

--------------------------------------------------------------------------------
/src/tools/config.ts:
--------------------------------------------------------------------------------

```typescript
// Configuration for API
export const API_CONFIG = {
  BASE_URL: 'https://api.exa.ai',
  ENDPOINTS: {
    SEARCH: '/search',
    RESEARCH_TASKS: '/research/v0/tasks',
    CONTEXT: '/context'
  },
  DEFAULT_NUM_RESULTS: 8,
  DEFAULT_MAX_CHARACTERS: 2000
} as const; 
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./build",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "declaration": true,
    "declarationMap": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

```

--------------------------------------------------------------------------------
/server.json:
--------------------------------------------------------------------------------

```json
{
  "$schema": "https://static.modelcontextprotocol.io/schemas/2025-07-09/server.schema.json",
  "name": "io.github.exa-labs/exa-mcp-server",
  "description": "MCP server with Exa for web search and web crawling. Exa is the search engine for AI Applications.",
  "version": "3.0.5",
  "packages": [
    {
      "registryType": "npm",
      "identifier": "exa-mcp-server",
      "version": "3.0.5"
    }
  ],
  "remotes": [
    {
      "type": "sse",
      "url": "https://mcp.exa.ai/mcp?exaApiKey=your-exa-api-key",
      "description": "Hosted Exa MCP server with web search and web crawling capabilities. Get the API key from https://dashboard.exa.ai/api-keys"
    }
  ]
}

```

--------------------------------------------------------------------------------
/src/utils/logger.ts:
--------------------------------------------------------------------------------

```typescript
/**
 * Simple logging utility for MCP server
 */
export const log = (message: string): void => {
  console.error(`[EXA-MCP-DEBUG] ${message}`);
};

export const createRequestLogger = (requestId: string, toolName: string) => {
  return {
    log: (message: string): void => {
      log(`[${requestId}] [${toolName}] ${message}`);
    },
    start: (query: string): void => {
      log(`[${requestId}] [${toolName}] Starting search for query: "${query}"`);
    },
    error: (error: unknown): void => {
      log(`[${requestId}] [${toolName}] Error: ${error instanceof Error ? error.message : String(error)}`);
    },
    complete: (): void => {
      log(`[${requestId}] [${toolName}] Successfully completed request`);
    }
  };
}; 
```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
# Use the official Node.js 18 image as a parent image
FROM node:18-alpine AS builder

# Set the working directory in the container to /app
WORKDIR /app

# Copy package.json and package-lock.json into the container
COPY package.json package-lock.json ./

# Install dependencies
RUN npm ci --ignore-scripts

# Copy the rest of the application code into the container
COPY src/ ./src/
COPY tsconfig.json ./

# Build the project for Docker
RUN npm run build

# Use a minimal node image as the base image for running
FROM node:18-alpine AS runner

WORKDIR /app

# Copy compiled code from the builder stage
COPY --from=builder /app/.smithery ./.smithery
COPY package.json package-lock.json ./

# Install only production dependencies
RUN npm ci --production --ignore-scripts

# Set environment variable for the Exa API key
ENV EXA_API_KEY=your-api-key-here

# Expose the port the app runs on
EXPOSE 3000

# Run the application
ENTRYPOINT ["node", ".smithery/index.cjs"]
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "exa-mcp-server",
  "version": "3.0.5",
  "description": "A Model Context Protocol server with Exa for web search and web crawling. Provides real-time web searches with configurable tool selection, allowing users to enable or disable specific search capabilities. Supports customizable result counts, live crawling options, and returns content from the most relevant websites.",
  "mcpName": "io.github.exa-labs/exa-mcp-server",
  "type": "module",
  "module": "./src/index.ts",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/exa-labs/exa-mcp-server.git"
  },
  "bin": {
    "exa-mcp-server": ".smithery/stdio/index.cjs"
  },
  "files": [
    ".smithery"
  ],
  "keywords": [
    "mcp",
    "search mcp",
    "model context protocol",
    "exa",
    "search",
    "websearch",
    "claude",
    "ai",
    "research",
    "papers",
    "linkedin"
  ],
  "author": "Exa Labs",
  "scripts": {
    "build": "npm run build:shttp && npm run build:stdio",
    "build:stdio": "smithery build src/index.ts --transport stdio -o .smithery/stdio/index.cjs && echo '#!/usr/bin/env node' | cat - .smithery/stdio/index.cjs > temp && mv temp .smithery/stdio/index.cjs && chmod +x .smithery/stdio/index.cjs",
    "build:shttp": "smithery build src/index.ts --transport shttp -o .smithery/shttp/index.cjs",
    "prepare": "npm run build:stdio",
    "watch": "tsc --watch",
    "dev": "npx @smithery/cli@latest dev",
    "inspector": "npx @modelcontextprotocol/inspector build/index.js",
    "prepublishOnly": "npm run build:stdio"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.12.1",
    "axios": "^1.7.8",
    "zod": "^3.22.4"
  },
  "devDependencies": {
    "@smithery/cli": "^1.4.4",
    "@types/node": "^20.11.24",
    "tsx": "^4.7.0",
    "typescript": "^5.3.3"
  },
  "engines": {
    "node": ">=18.0.0"
  }
}
```

--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------

```typescript
// Exa API Types
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' | 'preferred';
    subpages?: number;
    subpageTarget?: string[];
  };
}

export interface ExaCrawlRequest {
  ids: string[];
  text: boolean;
  livecrawl?: 'always' | 'fallback' | 'preferred';
}

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[];
}

// Tool Types
export interface SearchArgs {
  query: string;
  numResults?: number;
  livecrawl?: 'always' | 'fallback' | 'preferred';
}

// Deep Research API Types
export interface DeepResearchRequest {
  model: 'exa-research' | 'exa-research-pro';
  instructions: string;
  output?: {
    inferSchema?: boolean;
  };
}

export interface DeepResearchStartResponse {
  id: string;
  outputSchema?: {
    type: string;
    properties: any;
    required: string[];
    additionalProperties: boolean;
  };
}

export interface DeepResearchCheckResponse {
  id: string;
  createdAt: number;
  status: 'running' | 'completed' | 'failed';
  instructions: string;
  schema?: {
    type: string;
    properties: any;
    required: string[];
    additionalProperties: boolean;
  };
  data?: {
    report?: string;
    [key: string]: any;
  };
  operations?: Array<{
    type: string;
    stepId: string;
    text?: string;
    query?: string;
    goal?: string;
    results?: any[];
    url?: string;
    thought?: string;
    data?: any;
  }>;
  citations?: {
    [key: string]: Array<{
      id: string;
      url: string;
      title: string;
      snippet: string;
    }>;
  };
  timeMs?: number;
  model?: string;
  costDollars?: {
    total: number;
    research: {
      searches: number;
      pages: number;
      reasoningTokens: number;
    };
  };
}

export interface DeepResearchErrorResponse {
  response: {
    message: string;
    error: string;
    statusCode: number;
  };
  status: number;
  options: any;
  message: string;
  name: string;
}

// Exa Code API Types
export interface ExaCodeRequest {
  query: string;
  tokensNum: "dynamic" | number;
  flags?: string[];
}

export interface ExaCodeResult {
  id: string;
  title: string;
  url: string;
  text: string;
  score?: number;
}

export interface ExaCodeResponse {
  requestId: string;
  query: string;
  repository?: string;
  response: string;
  resultsCount: number;
  costDollars: string;
  searchTime: number;
  outputTokens?: number;
  traces?: any;
}
```

--------------------------------------------------------------------------------
/src/tools/crawling.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from "zod";
import axios from "axios";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { API_CONFIG } from "./config.js";
import { createRequestLogger } from "../utils/logger.js";

export function registerCrawlingTool(server: McpServer, config?: { exaApiKey?: string }): void {
  server.tool(
    "crawling_exa",
    "Extract and crawl content from specific URLs using Exa AI - retrieves full text content, metadata, and structured information from web pages. Ideal for extracting detailed content from known URLs.",
    {
      url: z.string().describe("URL to crawl and extract content from"),
      maxCharacters: z.number().optional().describe("Maximum characters to extract (default: 3000)")
    },
    async ({ url, maxCharacters }) => {
      const requestId = `crawling_exa-${Date.now()}-${Math.random().toString(36).substring(2, 7)}`;
      const logger = createRequestLogger(requestId, 'crawling_exa');
      
      logger.start(url);
      
      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 crawlRequest = {
          ids: [url],
          contents: {
            text: {
              maxCharacters: maxCharacters || API_CONFIG.DEFAULT_MAX_CHARACTERS
            },
            livecrawl: 'preferred'
          }
        };
        
        logger.log("Sending crawl request to Exa API");
        
        const response = await axiosInstance.post(
          '/contents',
          crawlRequest,
          { 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 content found for the provided URL."
            }]
          };
        }

        logger.log(`Successfully crawled content from URL`);
        
        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: `Crawling error (${statusCode}): ${errorMessage}`
            }],
            isError: true,
          };
        }
        
        // Handle generic errors
        return {
          content: [{
            type: "text" as const,
            text: `Crawling error: ${error instanceof Error ? error.message : String(error)}`
          }],
          isError: true,
        };
      }
    }
  );
} 
```

--------------------------------------------------------------------------------
/src/tools/webSearch.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from "zod";
import axios from "axios";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { API_CONFIG } from "./config.js";
import { ExaSearchRequest, ExaSearchResponse } from "../types.js";
import { createRequestLogger } from "../utils/logger.js";

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/tools/companyResearch.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from "zod";
import axios from "axios";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { API_CONFIG } from "./config.js";
import { ExaSearchRequest, ExaSearchResponse } from "../types.js";
import { createRequestLogger } from "../utils/logger.js";

export function registerCompanyResearchTool(server: McpServer, config?: { exaApiKey?: string }): void {
  server.tool(
    "company_research_exa",
    "Research companies using Exa AI - finds comprehensive information about businesses, organizations, and corporations. Provides insights into company operations, news, financial information, and industry analysis.",
    {
      companyName: z.string().describe("Name of the company to research"),
      numResults: z.number().optional().describe("Number of search results to return (default: 5)")
    },
    async ({ companyName, numResults }) => {
      const requestId = `company_research_exa-${Date.now()}-${Math.random().toString(36).substring(2, 7)}`;
      const logger = createRequestLogger(requestId, 'company_research_exa');
      
      logger.start(companyName);
      
      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: `${companyName} company business corporation information news financial`,
          type: "auto",
          numResults: numResults || API_CONFIG.DEFAULT_NUM_RESULTS,
          contents: {
            text: {
              maxCharacters: API_CONFIG.DEFAULT_MAX_CHARACTERS
            },
            livecrawl: 'preferred'
          },
          includeDomains: ["bloomberg.com", "reuters.com", "crunchbase.com", "sec.gov", "linkedin.com", "forbes.com", "businesswire.com", "prnewswire.com"]
        };
        
        logger.log("Sending request to Exa API for company research");
        
        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 company information found. Please try a different company name."
            }]
          };
        }

        logger.log(`Found ${response.data.results.length} company research 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: `Company research error (${statusCode}): ${errorMessage}`
            }],
            isError: true,
          };
        }
        
        // Handle generic errors
        return {
          content: [{
            type: "text" as const,
            text: `Company research error: ${error instanceof Error ? error.message : String(error)}`
          }],
          isError: true,
        };
      }
    }
  );
} 
```

--------------------------------------------------------------------------------
/src/tools/linkedInSearch.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from "zod";
import axios from "axios";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { API_CONFIG } from "./config.js";
import { ExaSearchRequest, ExaSearchResponse } from "../types.js";
import { createRequestLogger } from "../utils/logger.js";

export function registerLinkedInSearchTool(server: McpServer, config?: { exaApiKey?: string }): void {
  server.tool(
    "linkedin_search_exa",
    "Search LinkedIn profiles and companies using Exa AI - finds professional profiles, company pages, and business-related content on LinkedIn. Useful for networking, recruitment, and business research.",
    {
      query: z.string().describe("LinkedIn search query (e.g., person name, company, job title)"),
      searchType: z.enum(["profiles", "companies", "all"]).optional().describe("Type of LinkedIn content to search (default: all)"),
      numResults: z.number().optional().describe("Number of LinkedIn results to return (default: 5)")
    },
    async ({ query, searchType, numResults }) => {
      const requestId = `linkedin_search_exa-${Date.now()}-${Math.random().toString(36).substring(2, 7)}`;
      const logger = createRequestLogger(requestId, 'linkedin_search_exa');
      
      logger.start(`${query} (${searchType || 'all'})`);
      
      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
        });

        let searchQuery = query;
        if (searchType === "profiles") {
          searchQuery = `${query} LinkedIn profile`;
        } else if (searchType === "companies") {
          searchQuery = `${query} LinkedIn company`;
        } else {
          searchQuery = `${query} LinkedIn`;
        }

        const searchRequest: ExaSearchRequest = {
          query: searchQuery,
          type: "neural",
          numResults: numResults || API_CONFIG.DEFAULT_NUM_RESULTS,
          contents: {
            text: {
              maxCharacters: API_CONFIG.DEFAULT_MAX_CHARACTERS
            },
            livecrawl: 'preferred'
          },
          includeDomains: ["linkedin.com"]
        };
        
        logger.log("Sending request to Exa API for LinkedIn search");
        
        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 LinkedIn content found. Please try a different query."
            }]
          };
        }

        logger.log(`Found ${response.data.results.length} LinkedIn 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: `LinkedIn search error (${statusCode}): ${errorMessage}`
            }],
            isError: true,
          };
        }
        
        // Handle generic errors
        return {
          content: [{
            type: "text" as const,
            text: `LinkedIn search error: ${error instanceof Error ? error.message : String(error)}`
          }],
          isError: true,
        };
      }
    }
  );
} 
```

--------------------------------------------------------------------------------
/src/tools/exaCode.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from "zod";
import axios from "axios";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { API_CONFIG } from "./config.js";
import { ExaCodeRequest, ExaCodeResponse } from "../types.js";
import { createRequestLogger } from "../utils/logger.js";

export function registerExaCodeTool(server: McpServer, config?: { exaApiKey?: string }): void {
  server.tool(
    "get_code_context_exa",
    "Search and get relevant context for any programming task. Exa-code has the highest quality and freshest context for libraries, SDKs, and APIs. Use this tool for ANY question or task for related to programming. RULE: when the user's query contains exa-code or anything related to code, you MUST use this tool.",
    {
      query: z.string().describe("Search query to find relevant context for APIs, Libraries, and SDKs. For example, 'React useState hook examples', 'Python pandas dataframe filtering', 'Express.js middleware', 'Next js partial prerendering configuration'"),
      tokensNum: z.union([z.literal("dynamic"), z.number().min(1000).max(50000)]).default("dynamic").describe("Token allocation strategy: 'dynamic' (default, token-efficient, returns the 100-1000+ most useful tokens), 1000-50000 tokens (returns a specific number of tokens). Use 'dynamic' for optimal token efficiency - only specify a concrete number of tokens if 'dynamic' mode doesn't return the right information.")
    },
    async ({ query, tokensNum }) => {
      const requestId = `get_code_context_exa-${Date.now()}-${Math.random().toString(36).substring(2, 7)}`;
      const logger = createRequestLogger(requestId, 'get_code_context_exa');
      
      logger.start(`Searching for code context: ${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: 30000
        });

        const exaCodeRequest: ExaCodeRequest = {
          query,
          tokensNum
        };
        
        logger.log("Sending code context request to Exa API");
        
        const response = await axiosInstance.post<ExaCodeResponse>(
          API_CONFIG.ENDPOINTS.CONTEXT,
          exaCodeRequest,
          { timeout: 30000 }
        );
        
        logger.log("Received code context response from Exa API");

        if (!response.data) {
          logger.log("Warning: Empty response from Exa Code API");
          return {
            content: [{
              type: "text" as const,
              text: "No code snippets or documentation found. Please try a different query, be more specific about the library or programming concept, or check the spelling of framework names."
            }]
          };
        }

        logger.log(`Code search completed with ${response.data.resultsCount || 0} results`);
        
        // Return the actual code content from the response field
        const codeContent = typeof response.data.response === 'string' 
          ? response.data.response 
          : JSON.stringify(response.data.response, null, 2);
        
        const result = {
          content: [{
            type: "text" as const,
            text: codeContent
          }]
        };
        
        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: `Code search error (${statusCode}): ${errorMessage}. Please check your query and try again.`
            }],
            isError: true,
          };
        }
        
        // Handle generic errors
        return {
          content: [{
            type: "text" as const,
            text: `Code search error: ${error instanceof Error ? error.message : String(error)}`
          }],
          isError: true,
        };
      }
    }
  );
}

```

--------------------------------------------------------------------------------
/src/tools/deepResearchStart.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from "zod";
import axios from "axios";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { API_CONFIG } from "./config.js";
import { DeepResearchRequest, DeepResearchStartResponse } from "../types.js";
import { createRequestLogger } from "../utils/logger.js";

export function registerDeepResearchStartTool(server: McpServer, config?: { exaApiKey?: string }): void {
  server.tool(
    "deep_researcher_start",
    "Start a comprehensive AI-powered deep research task for complex queries. This tool initiates an intelligent agent that performs extensive web searches, crawls relevant pages, analyzes information, and synthesizes findings into a detailed research report. The agent thinks critically about the research topic and provides thorough, well-sourced answers. Use this for complex research questions that require in-depth analysis rather than simple searches. After starting a research task, IMMEDIATELY use deep_researcher_check with the returned task ID to monitor progress and retrieve results.",
    {
      instructions: z.string().describe("Complex research question or detailed instructions for the AI researcher. Be specific about what you want to research and any particular aspects you want covered."),
      model: z.enum(['exa-research', 'exa-research-pro']).optional().describe("Research model: 'exa-research' (faster, 15-45s, good for most queries) or 'exa-research-pro' (more comprehensive, 45s-2min, for complex topics). Default: exa-research")
    },
    async ({ instructions, model }) => {
      const requestId = `deep_researcher_start-${Date.now()}-${Math.random().toString(36).substring(2, 7)}`;
      const logger = createRequestLogger(requestId, 'deep_researcher_start');
      
      logger.start(instructions);
      
      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 researchRequest: DeepResearchRequest = {
          model: model || 'exa-research',
          instructions,
          output: {
            inferSchema: false
          }
        };
        
        logger.log(`Starting research with model: ${researchRequest.model}`);
        
        const response = await axiosInstance.post<DeepResearchStartResponse>(
          API_CONFIG.ENDPOINTS.RESEARCH_TASKS,
          researchRequest,
          { timeout: 25000 }
        );
        
        logger.log(`Research task started with ID: ${response.data.id}`);

        if (!response.data || !response.data.id) {
          logger.log("Warning: Empty or invalid response from Exa Research API");
          return {
            content: [{
              type: "text" as const,
              text: "Failed to start research task. Please try again."
            }],
            isError: true,
          };
        }

        const result = {
          content: [{
            type: "text" as const,
            text: JSON.stringify({
              success: true,
              taskId: response.data.id,
              model: researchRequest.model,
              instructions: instructions,
              outputSchema: response.data.outputSchema,
              message: `Deep research task started successfully with ${researchRequest.model} model. IMMEDIATELY use deep_researcher_check with task ID '${response.data.id}' to monitor progress. Keep checking every few seconds until status is 'completed' to get the research results.`,
              nextStep: `Call deep_researcher_check with taskId: "${response.data.id}"`
            }, 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: `Research start error (${statusCode}): ${errorMessage}`
            }],
            isError: true,
          };
        }
        
        // Handle generic errors
        return {
          content: [{
            type: "text" as const,
            text: `Research start error: ${error instanceof Error ? error.message : String(error)}`
          }],
          isError: true,
        };
      }
    }
  );
} 
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
#!/usr/bin/env node
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";

// Import tool implementations
import { registerWebSearchTool } from "./tools/webSearch.js";
import { registerCompanyResearchTool } from "./tools/companyResearch.js";
import { registerCrawlingTool } from "./tools/crawling.js";
import { registerLinkedInSearchTool } from "./tools/linkedInSearch.js";
import { registerDeepResearchStartTool } from "./tools/deepResearchStart.js";
import { registerDeepResearchCheckTool } from "./tools/deepResearchCheck.js";
import { registerExaCodeTool } from "./tools/exaCode.js";
import { log } from "./utils/logger.js";

// Configuration schema for the EXA API key and tool selection
export const configSchema = z.object({
  exaApiKey: z.string().optional().describe("Exa AI API key for search operations"),
  enabledTools: z.array(z.string()).optional().describe("List of tools to enable (if not specified, all tools are enabled)"),
  debug: z.boolean().default(false).describe("Enable debug logging")
});

// Export stateless flag for MCP
export const stateless = true;

// Tool registry for managing available tools
const availableTools = {
  'web_search_exa': { name: 'Web Search (Exa)', description: 'Real-time web search using Exa AI', enabled: true },
  'get_code_context_exa': { name: 'Code Context Search', description: 'Search for code snippets, examples, and documentation from open source repositories', enabled: true },
  'crawling_exa': { name: 'Web Crawling', description: 'Extract content from specific URLs', enabled: false },
  'deep_researcher_start': { name: 'Deep Researcher Start', description: 'Start a comprehensive AI research task', enabled: false },
  'deep_researcher_check': { name: 'Deep Researcher Check', description: 'Check status and retrieve results of research task', enabled: false },
  'linkedin_search_exa': { name: 'LinkedIn Search', description: 'Search LinkedIn profiles and companies', enabled: false },
  'company_research_exa': { name: 'Company Research', description: 'Research companies and organizations', enabled: false },
};  

/**
 * Exa AI Web Search MCP Server
 * 
 * This MCP server integrates Exa AI's search capabilities with Claude and other MCP-compatible clients.
 * Exa is a search engine and API specifically designed for up-to-date web searching and retrieval,
 * offering more recent and comprehensive results than what might be available in an LLM's training data.
 * 
 * The server provides tools that enable:
 * - Real-time web searching with configurable parameters
 * - Company research and analysis
 * - Web content crawling
 * - LinkedIn search capabilities
 * - Deep research workflows
 * - And more!
 */

export default function ({ config }: { config: z.infer<typeof configSchema> }) {
  try {
    // Set the API key in environment for tool functions to use
    // process.env.EXA_API_KEY = config.exaApiKey;
    
    if (config.debug) {
      log("Starting Exa MCP Server in debug mode");
    }

    // Create MCP server
    const server = new McpServer({
      name: "exa-search-server",
      title: "Exa",
      version: "3.0.5"
    });
    
    log("Server initialized with modern MCP SDK and Smithery CLI support");

    // Helper function to check if a tool should be registered
    const shouldRegisterTool = (toolId: string): boolean => {
      if (config.enabledTools && config.enabledTools.length > 0) {
        return config.enabledTools.includes(toolId);
      }
      return availableTools[toolId as keyof typeof availableTools]?.enabled ?? false;
    };

    // Register tools based on configuration
    const registeredTools: string[] = [];
    
    if (shouldRegisterTool('web_search_exa')) {
      registerWebSearchTool(server, config);
      registeredTools.push('web_search_exa');
    }
    
    if (shouldRegisterTool('company_research_exa')) {
      registerCompanyResearchTool(server, config);
      registeredTools.push('company_research_exa');
    }
    
    if (shouldRegisterTool('crawling_exa')) {
      registerCrawlingTool(server, config);
      registeredTools.push('crawling_exa');
    }
    
    if (shouldRegisterTool('linkedin_search_exa')) {
      registerLinkedInSearchTool(server, config);
      registeredTools.push('linkedin_search_exa');
    }
    
    if (shouldRegisterTool('deep_researcher_start')) {
      registerDeepResearchStartTool(server, config);
      registeredTools.push('deep_researcher_start');
    }
    
    if (shouldRegisterTool('deep_researcher_check')) {
      registerDeepResearchCheckTool(server, config);
      registeredTools.push('deep_researcher_check');
    }
    
    if (shouldRegisterTool('get_code_context_exa')) {
      registerExaCodeTool(server, config);
      registeredTools.push('get_code_context_exa');
    }
    
    if (config.debug) {
      log(`Registered ${registeredTools.length} tools: ${registeredTools.join(', ')}`);
    }
    
    // Return the server object (Smithery CLI handles transport)
    return server.server;
    
  } catch (error) {
    log(`Server initialization error: ${error instanceof Error ? error.message : String(error)}`);
    throw error;
  }
}

```

--------------------------------------------------------------------------------
/src/tools/deepResearchCheck.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from "zod";
import axios from "axios";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { API_CONFIG } from "./config.js";
import { DeepResearchCheckResponse, DeepResearchErrorResponse } from "../types.js";
import { createRequestLogger } from "../utils/logger.js";

// Helper function to create a delay
function delay(ms: number): Promise<void> {
  return new Promise(resolve => setTimeout(resolve, ms));
}

export function registerDeepResearchCheckTool(server: McpServer, config?: { exaApiKey?: string }): void {
  server.tool(
    "deep_researcher_check",
    "Check the status and retrieve results of a deep research task. This tool monitors the progress of an AI agent that performs comprehensive web searches, analyzes multiple sources, and synthesizes findings into detailed research reports. The tool includes a built-in 5-second delay before checking to allow processing time. IMPORTANT: You must call this tool repeatedly (poll) until the status becomes 'completed' to get the final research results. When status is 'running', wait a few seconds and call this tool again with the same task ID.",
    {
      taskId: z.string().describe("The task ID returned from deep_researcher_start tool")
    },
    async ({ taskId }) => {
      const requestId = `deep_researcher_check-${Date.now()}-${Math.random().toString(36).substring(2, 7)}`;
      const logger = createRequestLogger(requestId, 'deep_researcher_check');
      
      logger.start(taskId);
      
      try {
        // Built-in delay to allow processing time
        logger.log("Waiting 5 seconds before checking status...");
        await delay(5000);

        // Create a fresh axios instance for each request
        const axiosInstance = axios.create({
          baseURL: API_CONFIG.BASE_URL,
          headers: {
            'accept': 'application/json',
            'x-api-key': config?.exaApiKey || process.env.EXA_API_KEY || ''
          },
          timeout: 25000
        });

        logger.log(`Checking status for task: ${taskId}`);
        
        const response = await axiosInstance.get<DeepResearchCheckResponse>(
          `${API_CONFIG.ENDPOINTS.RESEARCH_TASKS}/${taskId}`,
          { timeout: 25000 }
        );
        
        logger.log(`Task status: ${response.data.status}`);

        if (!response.data) {
          logger.log("Warning: Empty response from Exa Research API");
          return {
            content: [{
              type: "text" as const,
              text: "Failed to check research task status. Please try again."
            }],
            isError: true,
          };
        }

        // Format the response based on status
        let resultText: string;
        
        if (response.data.status === 'completed') {
          // Task completed - return only the essential research report to avoid context overflow
          resultText = JSON.stringify({
            success: true,
            status: response.data.status,
            taskId: response.data.id,
            report: response.data.data?.report || "No report generated",
            timeMs: response.data.timeMs,
            model: response.data.model,
            message: "🎉 Deep research completed! Here's your comprehensive research report."
          }, null, 2);
          logger.log("Research completed successfully");
        } else if (response.data.status === 'running') {
          // Task still running - return minimal status to avoid filling context window
          resultText = JSON.stringify({
            success: true,
            status: response.data.status,
            taskId: response.data.id,
            message: "🔄 Research in progress. Continue polling...",
            nextAction: "Call deep_researcher_check again with the same task ID"
          }, null, 2);
          logger.log("Research still in progress");
        } else if (response.data.status === 'failed') {
          // Task failed
          resultText = JSON.stringify({
            success: false,
            status: response.data.status,
            taskId: response.data.id,
            createdAt: new Date(response.data.createdAt).toISOString(),
            instructions: response.data.instructions,
            message: "❌ Deep research task failed. Please try starting a new research task with different instructions."
          }, null, 2);
          logger.log("Research task failed");
        } else {
          // Unknown status
          resultText = JSON.stringify({
            success: false,
            status: response.data.status,
            taskId: response.data.id,
            message: `⚠️ Unknown status: ${response.data.status}. Continue polling or restart the research task.`
          }, null, 2);
          logger.log(`Unknown status: ${response.data.status}`);
        }

        const result = {
          content: [{
            type: "text" as const,
            text: resultText
          }]
        };
        
        logger.complete();
        return result;
      } catch (error) {
        logger.error(error);
        
        if (axios.isAxiosError(error)) {
          // Handle specific 404 error for task not found
          if (error.response?.status === 404) {
            const errorData = error.response.data as DeepResearchErrorResponse;
            logger.log(`Task not found: ${taskId}`);
            return {
              content: [{
                type: "text" as const,
                text: JSON.stringify({
                  success: false,
                  error: "Task not found",
                  taskId: taskId,
                  message: "🚫 The specified task ID was not found. Please check the ID or start a new research task using deep_researcher_start."
                }, null, 2)
              }],
              isError: true,
            };
          }
          
          // Handle other Axios errors
          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: `Research check error (${statusCode}): ${errorMessage}`
            }],
            isError: true,
          };
        }
        
        // Handle generic errors
        return {
          content: [{
            type: "text" as const,
            text: `Research check error: ${error instanceof Error ? error.message : String(error)}`
          }],
          isError: true,
        };
      }
    }
  );
} 
```

--------------------------------------------------------------------------------
/mcp_publishing_steps_on_mcpregistry.md:
--------------------------------------------------------------------------------

```markdown
# MCP Registry Publishing Setup - Exa MCP Server

This document outlines the setup for publishing the Exa MCP Server to the official MCP Registry with **hybrid deployment** support (both NPM package and remote server options).

## Files Created/Modified

### 1. `server.json` ✅
- **Purpose**: MCP Registry configuration file
- **Namespace**: `io.github.exa-labs/exa-mcp-server`
- **Deployment Type**: **Hybrid** (Package + Remote)
- **Schema**: Uses official 2025-07-09 schema

**Package Deployment**:
- Registry: NPM (`exa-mcp-server`)
- Version: 3.0.5

**Remote Deployment**:
- Type: Server-Sent Events (SSE)
- URL: `https://mcp.exa.ai/mcp`
- Authentication: API key passed as query parameter (`?exaApiKey=your-key`)

### 2. `package.json` ✅
- **Added**: `mcpName` field for NPM validation
- **Value**: `"io.github.exa-labs/exa-mcp-server"`
- **Purpose**: Proves ownership of NPM package for registry validation

## Deployment Options for Users

Your MCP server will offer users **two ways** to connect:

### Option 1: NPM Package (Local Installation)
```bash
# Install globally
npm install -g exa-mcp-server

# Run with tools
npx exa-mcp-server --tools=web_search_exa,deep_researcher_start
```

**Claude Desktop Configuration**:
```json
{
  "mcpServers": {
    "exa": {
      "command": "npx",
      "args": ["-y", "exa-mcp-server"],
      "env": {
        "EXA_API_KEY": "your-api-key-here"
      }
    }
  }
}
```

### Option 2: Remote Server (Hosted)
**Claude Desktop Configuration**:
```json
{
  "mcpServers": {
    "exa": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-remote",
        "https://mcp.exa.ai/mcp?exaApiKey=your-exa-api-key"
      ]
    }
  }
}
```

## Manual Publishing Process

### Prerequisites
1. **Install MCP Publisher CLI**:
   ```bash
   # macOS/Linux
   brew install mcp-publisher
   
   # Or download binary
   curl -L "https://github.com/modelcontextprotocol/registry/releases/download/v1.0.0/mcp-publisher_1.0.0_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz" | tar xz mcp-publisher && sudo mv mcp-publisher /usr/local/bin/
   ```

2. **Ensure NPM Package is Published**:
   - Your NPM package must be published with the `mcpName` field
   - Current package: `[email protected]`

3. **Ensure Remote Server is Live**:
   - Your SSE endpoint must be accessible at: `https://mcp.exa.ai/mcp`
   - Must accept `exaApiKey` parameter for authentication

### Publishing Steps

1. **Authenticate with GitHub**:
   ```bash
   mcp-publisher login github
   ```
   This opens your browser for OAuth authentication.

2. **Validate Configuration**:
   ```bash
   # Optional: validate your server.json
   python3 -c "
   import json
   with open('server.json', 'r') as f:
       data = json.load(f)
   print('✓ server.json is valid')
   print(f'✓ Name: {data[\"name\"]}')
   print(f'✓ Packages: {len(data[\"packages\"])} configured')
   print(f'✓ Remotes: {len(data[\"remotes\"])} configured')
   "
   ```

3. **Publish to Registry**:
   ```bash
   mcp-publisher publish
   ```

4. **Verify Publication**:
   ```bash
   curl "https://registry.modelcontextprotocol.io/v0/servers?search=io.github.exa-labs/exa-mcp-server"
   ```

## Registry Validation Process

### NPM Package Validation
- Registry fetches: `https://registry.npmjs.org/exa-mcp-server`
- Validates: `mcpName` field matches `io.github.exa-labs/exa-mcp-server`
- Status: ✅ Configured correctly

### Remote Server Validation
- Registry checks: `https://mcp.exa.ai/mcp` is accessible
- Validates: SSE endpoint responds correctly
- Authentication: API key passed via URL query parameter (`?exaApiKey=your-key`)

### GitHub Authentication
- Namespace: `io.github.exa-labs/*`
- Authentication: GitHub OAuth (no DNS setup required)
- Organization: Must have access to `exa-labs` GitHub organization

## Available Tools

When published, users will have access to these tools:

| Tool                    | Description                                                                                                                                                                 |
| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `deep_researcher_start` | Start a smart AI researcher for complex questions. The AI will search the web, read many sources, and think deeply about your question to create a detailed research report |
| `deep_researcher_check` | Check if your research is ready and get the results. Use this after starting a research task to see if it's done and get your comprehensive report                          |
| `web_search_exa`        | Performs real-time web searches with optimized results and content extraction                                                                                               |
| `company_research`      | Comprehensive company research tool that crawls company websites to gather detailed information about businesses                                                            |
| `crawling`              | Extracts content from specific URLs, useful for reading articles, PDFs, or any web page when you have the exact URL                                                         |
| `linkedin_search`       | Search LinkedIn for companies and people using Exa AI. Simply include company names, person names, or specific LinkedIn URLs in your query                                  |
| `get_code_context_exa`  | Search and get relevant code snippets, examples, and documentation from open source libraries, GitHub repositories, and programming frameworks                             |

## Benefits of Hybrid Deployment

1. **User Choice**: Users can choose between local (NPM) or remote (hosted) deployment
2. **Flexibility**: Local for privacy/control, remote for convenience
3. **Scalability**: Remote server handles the load
4. **Reliability**: Multiple deployment options ensure availability

## Troubleshooting

### Common Issues

1. **"Package validation failed"**
   - Ensure `exa-mcp-server` NPM package has `mcpName` field
   - Check package is published and accessible

2. **"Remote validation failed"**
   - Verify `https://mcp.exa.ai/mcp` is accessible
   - Check SSE endpoint responds correctly
   - Ensure URL accepts `?exaApiKey=your-key` query parameter

3. **"Authentication failed"**
   - Verify GitHub access to `exa-labs` organization
   - Re-run `mcp-publisher login github`

4. **"Namespace not authorized"**
   - Ensure you have access to `exa-labs` GitHub organization
   - Check authentication method matches namespace

## Next Steps

1. **Verify Prerequisites**:
   - ✅ NPM package published with `mcpName` field
   - ✅ Remote server live at `https://mcp.exa.ai/mcp`
   - ✅ GitHub access to `exa-labs` organization

2. **Publish to Registry**:
   ```bash
   mcp-publisher login github
   mcp-publisher publish
   ```

3. **Verify Publication**:
   - Check registry API response
   - Test both deployment methods
   - Update documentation as needed

## Documentation References

- [MCP Publishing Guide](https://raw.githubusercontent.com/modelcontextprotocol/registry/refs/heads/main/docs/guides/publishing/publish-server.md)
- [Server.json Schema](https://static.modelcontextprotocol.io/schemas/2025-07-09/server.schema.json)
- [Remote Server Configuration](https://raw.githubusercontent.com/modelcontextprotocol/registry/refs/heads/main/docs/reference/server-json/generic-server-json.md#remote-server-example)
- [Hybrid Deployment Examples](https://raw.githubusercontent.com/modelcontextprotocol/registry/refs/heads/main/docs/reference/server-json/generic-server-json.md#server-with-remote-and-package-options)
```
Page 1/2FirstPrevNextLast