Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions exa-mcp-server/.actor/actor.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"actorSpecification": 1,
"name": "exa-mcp-server",
"title": "Exa MCP Server",
"description": "HTTP MCP proxy to Exa's hosted MCP server (mcp.exa.ai). Connect via streamable HTTP.",
"version": "0.0",
"buildTag": "latest",
"usesStandbyMode": true,
"meta": {
"templateId": "ts-mcp-server"
},
"input": {
"title": "Actor input schema",
"description": "This is Actor input schema",
"type": "object",
"schemaVersion": 1,
"properties": {},
"required": []
},
"dockerfile": "../Dockerfile",
"webServerMcpPath": "/mcp"
}
67 changes: 67 additions & 0 deletions exa-mcp-server/.actor/pay_per_event.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
{
"actor-start": {
"eventTitle": "Price for Actor start",
"eventDescription": "Flat fee for starting an Actor run.",
"eventPriceUsd": 0.1
},
"tool-request": {
"eventTitle": "Generic tool request",
"eventDescription": "Fallback fee when tool cannot be identified.",
"eventPriceUsd": 0.03
},
"exa-get-code-context": {
"eventTitle": "Exa: get_code_context_exa",
"eventDescription": "Search and return relevant code/documentation context.",
"eventPriceUsd": 0.06
},
"exa-web-search": {
"eventTitle": "Exa: web_search_exa",
"eventDescription": "Perform real-time Exa web search.",
"eventPriceUsd": 0.04
},
"exa-company-research": {
"eventTitle": "Exa: company_research",
"eventDescription": "Crawl and summarize company information.",
"eventPriceUsd": 0.08
},
"exa-crawling": {
"eventTitle": "Exa: crawling",
"eventDescription": "Fetch and extract content from specific URLs.",
"eventPriceUsd": 0.02
},
"exa-linkedin-search": {
"eventTitle": "Exa: linkedin_search",
"eventDescription": "Search LinkedIn for companies and people via Exa.",
"eventPriceUsd": 0.09
},
"exa-deep-research-start": {
"eventTitle": "Exa: deep_researcher_start",
"eventDescription": "Start a deep research task.",
"eventPriceUsd": 0.12
},
"exa-deep-research-check": {
"eventTitle": "Exa: deep_researcher_check",
"eventDescription": "Check status and retrieve deep research results.",
"eventPriceUsd": 0.05
},
"prompt-request": {
"eventTitle": "Price for completing a prompt request",
"eventDescription": "Flat fee for completing a prompt request.",
"eventPriceUsd": 0.01
},
"completion-request": {
"eventTitle": "Price for completing a completion request",
"eventDescription": "Flat fee for completing a completion request.",
"eventPriceUsd": 0.001
},
"resource-request": {
"eventTitle": "Price for completing a resource request",
"eventDescription": "Flat fee for completing a resource request.",
"eventPriceUsd": 0.025
},
"list-request": {
"eventTitle": "Price for completing a list request",
"eventDescription": "Flat fee for completing a list request.",
"eventPriceUsd": 0.001
}
}
9 changes: 9 additions & 0 deletions exa-mcp-server/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
root = true

[*]
indent_style = space
indent_size = 4
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
end_of_line = lf
14 changes: 14 additions & 0 deletions exa-mcp-server/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# This file tells Git which files shouldn't be added to source control

.idea
.vscode
.zed
storage
apify_storage
crawlee_storage
node_modules
dist
tsconfig.tsbuildinfo

# Added by Apify CLI
.venv
1 change: 1 addition & 0 deletions exa-mcp-server/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.prettierignore
5 changes: 5 additions & 0 deletions exa-mcp-server/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"printWidth": 120,
"singleQuote": true,
"tabWidth": 4
}
56 changes: 56 additions & 0 deletions exa-mcp-server/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Specify the base Docker image. You can read more about
# the available images at https://docs.apify.com/sdk/js/docs/guides/docker-images
# You can also use any other image from Docker Hub.
FROM apify/actor-node:22 AS builder

# Check preinstalled packages
RUN npm ls crawlee apify puppeteer playwright

# Copy just package.json and package-lock.json
# to speed up the build using Docker layer cache.
COPY --chown=myuser:myuser package*.json ./

# Install all dependencies. Don't audit to speed up the installation.
RUN npm install --include=dev --audit=false

# Next, copy the source files using the user set
# in the base image.
COPY --chown=myuser:myuser . ./

# Install all dependencies and build the project.
# Don't audit to speed up the installation.
RUN npm run build

# Create final image
FROM apify/actor-node:22

# Check preinstalled packages
RUN npm ls crawlee apify puppeteer playwright

# Copy just package.json and package-lock.json
# to speed up the build using Docker layer cache.
COPY --chown=myuser:myuser package*.json ./

# Install NPM packages, skip optional and development dependencies to
# keep the image small. Avoid logging too much and print the dependency
# tree for debugging
RUN npm --quiet set progress=false \
&& npm install --omit=dev --omit=optional \
&& echo "Installed NPM packages:" \
&& (npm list --omit=dev --all || true) \
&& echo "Node.js version:" \
&& node --version \
&& echo "NPM version:" \
&& npm --version \
&& rm -r ~/.npm

# Copy built JS files from builder image
COPY --from=builder --chown=myuser:myuser /usr/src/app/dist ./dist

# Next, copy the remaining files and directories with the source code.
# Since we do this after NPM install, quick build will be really fast
# for most source file changes.
COPY --chown=myuser:myuser . ./

# Run the image.
CMD npm run start:prod --silent
98 changes: 98 additions & 0 deletions exa-mcp-server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
## Exa MCP Server

HTTP MCP proxy to Exa's hosted MCP server at https://mcp.exa.ai/mcp. This Actor exposes a streamable HTTP endpoint so MCP clients can connect using an Apify-hosted URL with Bearer auth.

## How to use

This server uses mcp-remote to connect to Exa and supports optional EXA_API_KEY (appended to the URL as exaApiKey).

## Connection URL

MCP clients can connect to this server at:

https://mcp-servers--exa-mcp-server.apify.actor/mcp

## Client Configuration

Use this configuration in your MCP client (replace your-apify-token with your token from Apify Console):

{
"mcpServers": {
"exa": {
"type": "http",
"url": "https://mcp-servers--exa-mcp-server.apify.actor/mcp",
"headers": {
"Authorization": "Bearer YOUR_APIFY_TOKEN"
}
}
}
}

Optionally set EXA_API_KEY as an Actor environment variable to use your Exa key.

### Pay per event

This Actor uses the [Pay Per Event (PPE)](https://docs.apify.com/platform/actors/publishing/monetize#pay-per-event-pricing-model) model. Exa tool calls map to events defined in [.actor/pay_per_event.json](.actor/pay_per_event.json) such as `exa-get-code-context`, `exa-web-search`, etc. Unknown tools fall back to `tool-request`.

To charge users, define events in JSON format and save them on the Apify platform. Here is an example schema with the `tool-request` event:

Event charging is performed in `src/billing.ts` based on the MCP method and tool name.

To set up the PPE model: in Actor Monetization settings, choose Pay per event and paste the content of [.actor/pay_per_event.json](.actor/pay_per_event.json).

## Credits and links

- [What is Anthropic's Model Context Protocol?](https://blog.apify.com/what-is-model-context-protocol/)
- [How to use MCP with Apify Actors](https://blog.apify.com/how-to-use-mcp/)
- All credits to the original authors of https://github.com/exa-labs/exa-mcp-server (or hosted Exa at https://mcp.exa.ai/mcp)
- Claim this MCP server – write to [email protected].
- [Apify MCP servers monorepo](https://github.com/apify/mcp-servers)
- [Apify MCP server](https://mcp.apify.com)
- [Apify MCP server documentation](https://docs.apify.com/platform/integrations/mcp)
- [Apify MCP client](https://apify.com/jiri.spilka/tester-mcp-client)
- [Model Context Protocol documentation](https://modelcontextprotocol.io)
- [TypeScript tutorials in Academy](https://docs.apify.com/academy/node-js)
- [Apify SDK documentation](https://docs.apify.com/sdk/js/)


## Getting started

For complete information [see this article](https://docs.apify.com/platform/actors/development#build-actor-locally). To run the Actor use the following command:

```bash
apify run
```

## Deploy to Apify

### Connect Git repository to Apify

If you've created a Git repository for the project, you can easily connect to Apify:

1. Go to [Actor creation page](https://console.apify.com/actors/new)
2. Click on **Link Git Repository** button

### Push project on your local machine to Apify

You can also deploy the project on your local machine to Apify without the need for the Git repository.

1. Log in to Apify. You will need to provide your [Apify API Token](https://console.apify.com/account/integrations) to complete this action.

```bash
apify login
```

2. Deploy your Actor. This command will deploy and build the Actor on the Apify Platform. You can find your newly created Actor under [Actors -> My Actors](https://console.apify.com/actors?tab=my).

```bash
apify push
```

## Documentation reference

To learn more about Apify and Actors, take a look at the following resources:

- [Apify SDK for JavaScript documentation](https://docs.apify.com/sdk/js)
- [Apify SDK for Python documentation](https://docs.apify.com/sdk/python)
- [Apify Platform documentation](https://docs.apify.com/platform)
- [Join our developer community on Discord](https://discord.com/invite/jyEM2PRvMU)
30 changes: 30 additions & 0 deletions exa-mcp-server/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import prettier from 'eslint-config-prettier';

import apify from '@apify/eslint-config/ts.js';
import globals from 'globals';
import tsEslint from 'typescript-eslint';

// eslint-disable-next-line import/no-default-export
export default [
{ ignores: ['**/dist', 'eslint.config.mjs'] },
...apify,
prettier,
{
languageOptions: {
parser: tsEslint.parser,
parserOptions: {
project: 'tsconfig.json',
},
globals: {
...globals.node,
...globals.jest,
},
},
plugins: {
'@typescript-eslint': tsEslint.plugin,
},
rules: {
'no-console': 0,
},
},
];
Loading
Loading