Skip to main content
Glama
tirth1263

ScaleKit Exa MCP Security

by tirth1263

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.

Related MCP server: m365-mcp-server

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

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.

{
  "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 }
}

Quickstart

Prerequisites

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

  1. Open ScaleKit Dashboard → MCP Servers → Add MCP Server.

  2. For local development, register http://localhost:8000/exa as the server URL.

  3. Add the exa:read scope.

  4. Save the MCP server and copy its resource ID and protected-resource metadata JSON.

  5. 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:

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: /exa returns 503 rather than becoming public when ScaleKit server credentials are missing.

  • Narrow authorization: the default policy requires the exa:read scope 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_KEY is 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 app account.

Read SECURITY.md before operating the service publicly.

CAUTION

ALLOW_INSECURE_DEV=true bypasses 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.

A
license - permissive license
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/tirth1263/ScaleKit-Exa-MCP-Security'

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