ScaleKit Exa MCP Security
A production-ready MCP server that provides AI agents secure access to Exa web search with OAuth-based authentication, scoping, and auditing via ScaleKit.
README
<p align="center"> <img src="docs/banner.svg" alt="ScaleKit Exa MCP Security — secure web intelligence for AI agents" width="100%" /> </p>
<p align="center"> <a href="https://github.com/tirth1263/ScaleKit-Exa-MCP-Security/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/tirth1263/ScaleKit-Exa-MCP-Security/actions/workflows/ci.yml/badge.svg" /></a> <a href="https://tirth1263.github.io/ScaleKit-Exa-MCP-Security/"><img alt="Live website" src="https://img.shields.io/badge/website-live-21e39b?logo=githubpages&logoColor=07130f" /></a> <a href="https://modelcontextprotocol.io/"><img alt="MCP" src="https://img.shields.io/badge/MCP-Streamable_HTTP-08714e" /></a> <a href="https://www.python.org/"><img alt="Python 3.11+" src="https://img.shields.io/badge/Python-3.11%2B-3776ab?logo=python&logoColor=white" /></a> <a href="LICENSE"><img alt="MIT License" src="https://img.shields.io/badge/license-MIT-f4c95d" /></a> </p>
<h1 align="center">ScaleKit Exa MCP Security</h1>
<p align="center"> A production-ready <strong>Model Context Protocol server</strong> that gives AI applications access to Exa web search while ScaleKit keeps every tool call authenticated, scoped, and auditable. </p>
<p align="center"> <a href="https://tirth1263.github.io/ScaleKit-Exa-MCP-Security/"><strong>Explore the live website →</strong></a> · <a href="#quickstart">Quickstart</a> · <a href="#tool-reference">Tool reference</a> · <a href="#deployment">Deploy</a> </p>
Why this project exists
AI agents are most useful when they can reach current information, but passing a raw search API key into every desktop client creates unnecessary risk. This server keeps the Exa credential on trusted infrastructure and places an OAuth 2.1 boundary in front of the MCP transport.
The result is a clean separation of responsibilities:
- Exa finds, extracts, and ranks web content.
- ScaleKit handles OAuth discovery and validates bearer tokens.
- FastAPI hosts public service endpoints and the remote MCP transport.
- MCP gives Claude, Windsurf, Cursor, custom agents, and other compatible clients the same typed tool interface.
Highlights
| Area | What is included |
|---|---|
| Search | Semantic, fast, instant, deep, domain-filtered, date-filtered, and category-focused Exa queries |
| Content | Full text, highlights, summaries, live crawling, and cache-age controls for up to 100 results |
| Discovery | RFC 9728 protected-resource metadata at root, /mcp, and endpoint-specific well-known URLs |
| Authorization | ScaleKit signature, issuer, audience, expiry, and exa:read scope validation |
| Transport | Stateless MCP Streamable HTTP with JSON responses at /exa |
| Production | Pinned dependencies, lockfile, non-root Docker image, health check, Render blueprint, CI, and tests |
| Website | Responsive documentation/landing page served by FastAPI and published independently through GitHub Pages |
Architecture
sequenceDiagram
autonumber
participant Client as MCP Client
participant API as FastAPI / MCP
participant SK as ScaleKit
participant Exa as Exa API
Client->>API: POST /exa without token
API-->>Client: 401 + WWW-Authenticate metadata URL
Client->>SK: OAuth 2.1 authorization + PKCE
SK-->>Client: Scoped access token
Client->>API: tools/call + Bearer token
API->>API: Validate signature, issuer, audience, expiry, scope
API->>Exa: Server-side x-api-key request
Exa-->>API: Ranked results / content
API-->>Client: Structured MCP tool result
Only /exa is protected. The landing page, health check, service information, and OAuth metadata remain public so browsers, deployment health checks, and MCP clients can discover the service.
Endpoints
| Endpoint | Access | Purpose |
|---|---|---|
/ |
Public | Interactive project website and documentation |
/service |
Public | Machine-readable service and tool information |
/health |
Public | Deployment health and safe configuration flags |
/api/docs |
Public | FastAPI OpenAPI explorer for service endpoints |
/.well-known/oauth-protected-resource/exa |
Public | RFC 9728 metadata for the /exa resource |
/.well-known/oauth-protected-resource/mcp |
Public | Compatibility alias requested by common MCP clients |
/exa |
Bearer token | Stateless MCP Streamable HTTP transport |
Tool reference
exa_search
Search the live web and optionally enrich each result with extracted content.
| Parameter | Type | Default | Description |
|---|---|---|---|
query |
string | required | Search query, 1–2,000 characters |
search_type |
string | auto |
auto, fast, instant, deep-lite, deep, deep-reasoning, plus Exa legacy modes neural and keyword |
category |
string | null |
Focus hint such as research paper, news, company, or people |
num_results |
integer | 10 |
Number of results from 1 to 100 |
include_domains |
string[] | null |
Return results only from these domains |
exclude_domains |
string[] | null |
Exclude results from these domains |
start_published_date |
ISO 8601 | null |
Only results published after this date |
end_published_date |
ISO 8601 | null |
Only results published before this date |
include_content |
boolean | true |
Include extracted page text |
include_highlights |
boolean | true |
Include relevant passages |
include_summary |
boolean | true |
Include an AI-generated summary |
user_location |
string | null |
Two-letter country code such as US |
moderation |
boolean | false |
Ask Exa to filter unsafe content |
exa_get_contents
Retrieve complete content for Exa result IDs or URLs.
| Parameter | Type | Default | Description |
|---|---|---|---|
ids |
string[] | required | One to 100 Exa result IDs or URLs |
include_text |
boolean | true |
Include extracted page text |
include_highlights |
boolean | true |
Include relevant passages |
include_summary |
boolean | true |
Include page summaries |
livecrawl |
boolean | false |
Translate to Exa's livecrawl: always behavior |
max_age_hours |
integer | null |
Cache freshness from -1 to 720; mutually exclusive with livecrawl |
exa_find_similar
Find pages that are semantically related to an absolute HTTP or HTTPS URL. It supports result count, domain filters, publication dates, content, highlights, summaries, and moderation.
<details> <summary><strong>Example tool response</strong></summary>
{
"success": true,
"query": "recent MCP authorization research",
"search_type": "auto",
"resolved_search_type": "neural",
"request_id": "req_01J...",
"num_results": 1,
"results": [
{
"title": "Example result",
"url": "https://example.com/article",
"id": "https://example.com/article",
"publishedDate": "2026-06-01T12:00:00Z",
"author": "Example Author",
"text": "Extracted page content...",
"highlights": ["A relevant passage..."],
"summary": "A concise result summary..."
}
],
"cost_dollars": { "total": 0.007 }
}
</details>
Quickstart
Prerequisites
- Python 3.11–3.14
- uv for reproducible dependency management
- An Exa API key
- A ScaleKit account with an MCP protected resource
1. Clone and install
git clone https://github.com/tirth1263/ScaleKit-Exa-MCP-Security.git
cd ScaleKit-Exa-MCP-Security
uv sync --all-extras
2. Register the MCP resource in ScaleKit
- Open ScaleKit Dashboard → MCP Servers → Add MCP Server.
- For local development, register
http://localhost:8000/exaas the server URL. - Add the
exa:readscope. - Save the MCP server and copy its resource ID and protected-resource metadata JSON.
- Copy the environment URL, client ID, and client secret from Settings → API credentials.
The token audience must exactly match SCALEKIT_AUDIENCE_NAME. When you deploy, update both values to the public HTTPS /exa URL.
3. Configure environment variables
cp .env.example .env
Populate the following values in .env:
EXA_API_KEY=your_exa_api_key
SCALEKIT_ENVIRONMENT_URL=https://your-environment.scalekit.com
SCALEKIT_CLIENT_ID=your_client_id
SCALEKIT_CLIENT_SECRET=your_client_secret
SCALEKIT_RESOURCE_ID=res_your_resource_id
SCALEKIT_AUDIENCE_NAME=http://localhost:8000/exa
SCALEKIT_REQUIRED_SCOPE=exa:read
PUBLIC_BASE_URL=http://localhost:8000
ALLOWED_HOSTS=localhost,127.0.0.1
You can also set METADATA_JSON_RESPONSE to the exact JSON copied from ScaleKit. That value takes precedence over metadata generated from the individual settings.
4. Run
uv run main.py
Open http://localhost:8000 for the website or check the service:
curl http://localhost:8000/health
curl -i http://localhost:8000/exa
The second request should return 401 Unauthorized with a WWW-Authenticate header when OAuth is configured.
Connect an MCP client
Remote clients can connect directly to https://your-host.example/exa. Clients that need a local stdio bridge can use mcp-remote:
{
"mcpServers": {
"secure-exa": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://your-host.example/exa"
]
}
}
}
For protocol-level testing, start the MCP Inspector:
npx -y @modelcontextprotocol/inspector@latest
Then connect its Streamable HTTP transport to http://localhost:8000/exa and complete the ScaleKit authorization flow.
Development and verification
# Lint the full repository
uv run ruff check .
# Run unit and ASGI integration tests
uv run pytest
# Build the production container
docker build -t scalekit-exa-mcp-security .
# Run through Docker Compose
docker compose up --build
The test suite verifies Exa request shaping, sanitized upstream errors, public-route behavior, OAuth challenges, scope options, fail-closed configuration, security headers, and input validation.
Deployment
Docker
The included multi-stage Dockerfile runs as a non-root user, uses the pinned uv.lock, exposes a container health check, and honors the platform-provided PORT.
docker build -t scalekit-exa-mcp-security .
docker run --rm -p 8000:8000 --env-file .env scalekit-exa-mcp-security
Render blueprint
After forking the repository, use the included render.yaml:
<p> <a href="https://render.com/deploy?repo=https://github.com/tirth1263/ScaleKit-Exa-MCP-Security"> <img src="https://render.com/images/deploy-to-render-button.svg" alt="Deploy to Render" /> </a> </p>
Set every secret marked sync: false, then update:
PUBLIC_BASE_URL=https://your-service.onrender.com
SCALEKIT_AUDIENCE_NAME=https://your-service.onrender.com/exa
ALLOWED_HOSTS=your-service.onrender.com
Finally, update the MCP server URL in ScaleKit to the same public /exa address. OAuth will fail if the registered audience and deployed audience differ.
Public website
The same web/ experience served at the FastAPI root is published through the included GitHub Pages workflow:
https://tirth1263.github.io/ScaleKit-Exa-MCP-Security/
GitHub Pages hosts the public project website. A production MCP runtime must be deployed with the private Exa and ScaleKit environment variables; secrets are never added to the static site.
Security design
- Fail closed:
/exareturns503rather than becoming public when ScaleKit server credentials are missing. - Narrow authorization: the default policy requires the
exa:readscope and the exact configured audience. - No browser token storage: the website is documentation only and never asks for or stores access tokens.
- No client-side Exa credential:
EXA_API_KEYis read exclusively by the backend. - Sanitized errors: network details and secrets are not returned through tool results.
- Constrained browser surface: CORS is disabled unless an allowlist is supplied; credentials are never enabled with wildcard origins.
- Defensive headers: CSP, frame denial, MIME sniffing protection, referrer policy, and browser permission restrictions are added centrally.
- Non-root container: the final Docker image runs under an unprivileged
appaccount.
Read SECURITY.md before operating the service publicly.
[!CAUTION]
ALLOW_INSECURE_DEV=truebypasses authentication. It exists only for isolated local tool development and must never be enabled on a network-accessible service.
Project structure
.
├── src/scalekit_exa_mcp/
│ ├── auth.py # ScaleKit bearer-token ASGI middleware
│ ├── config.py # Environment configuration and RFC 9728 metadata
│ ├── exa_client.py # Async Exa REST client
│ ├── main.py # FastAPI app, public routes, and MCP mount
│ └── server.py # exa_search, exa_get_contents, exa_find_similar
├── tests/ # Unit and ASGI integration tests
├── web/ # Responsive website deployed to GitHub Pages
├── .github/workflows/ # CI and Pages deployment
├── Dockerfile # Multi-stage, non-root production image
├── render.yaml # One-click Render blueprint
├── pyproject.toml # Pinned application and test dependencies
└── main.py # `uv run main.py` entry point
Troubleshooting
| Symptom | Check |
|---|---|
401 invalid_request |
Add Authorization: Bearer <access_token> or let an OAuth-capable MCP client complete discovery |
401 invalid_token |
Verify token issuer, exact /exa audience, expiry, and exa:read scope |
503 server_not_configured |
Set all ScaleKit credentials and audience; check deployment secret names |
Metadata returns 503 |
Set METADATA_JSON_RESPONSE, or set environment URL, resource ID, audience, and public base URL |
| Exa tool reports missing key | Add EXA_API_KEY to the backend secret manager—not the website or client config |
| OAuth redirects to an old host | Update the MCP URL in ScaleKit and clear the client's cached MCP authorization state |
Acknowledgements
This project is an original, production-focused implementation inspired by Arindam Majumder's ScaleKit + Exa MCP tutorial project and video walkthrough. It follows the current official guidance from ScaleKit MCP authentication, the Model Context Protocol, and the Exa API reference.
License
Released under the MIT License. Built by Tirth Rank.
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。