> ## Documentation Index
> Fetch the complete documentation index at: https://docs.messora.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# MCP Server

> Connect LLM agents to Messora via the Model Context Protocol using the same API key and credits as the REST API.

# Messora MCP Server

Messora exposes a [Model Context Protocol](https://modelcontextprotocol.io)
(MCP) remote server over **Streamable HTTP**. It lets LLM agents (Claude, Codex,
Cursor and other MCP clients) call scrape, crawl, search and query jobs and
usage — reusing the same account, API key and credit pool as the REST API.

## Endpoint

| Resource | URL                              |
| -------- | -------------------------------- |
| MCP      | `https://mcp.messora.dev/mcp`    |
| Health   | `https://mcp.messora.dev/health` |

The `/health` endpoint is independent of the backend and returns
`{"status": "ok", "service": "messora-mcp"}` without requiring authentication.

## Authentication

The MCP uses **only** the `Authorization: Bearer` header:

```bash theme={null}
Authorization: Bearer $MESSORA_API_KEY
```

<Warning>
  The `X-API-Key` header used by the REST API is **not** accepted by the MCP.
  Always use `Authorization: Bearer`.
</Warning>

You need a Messora API key. If you don't have one yet, create it at
[www.messora.dev/auth/signup](https://www.messora.dev/auth/signup) → **API Keys**.

## Configuring clients

### Claude Desktop

Claude Desktop uses a JSON config at `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):

```json theme={null}
{
  "mcpServers": {
    "messora": {
      "type": "http",
      "url": "https://mcp.messora.dev/mcp",
      "headers": {
        "Authorization": "Bearer your-api-key-here"
      }
    }
  }
}
```

Restart Claude Desktop after saving. The Messora tools appear in the tool picker.

<Note>
  Claude Desktop does not support environment variable interpolation in config.
  Use a placeholder and replace it, or set the key directly — the file is local
  and not versioned.
</Note>

### Cursor

Cursor uses an `.mcp.json` file at the project root or `~/.cursor/mcp.json`:

```json theme={null}
{
  "mcpServers": {
    "messora": {
      "type": "http",
      "url": "https://mcp.messora.dev/mcp",
      "headers": {
        "Authorization": "Bearer your-api-key-here"
      }
    }
  }
}
```

Restart Cursor or run `Cursor → Settings → MCP → Refresh` to load the server.

### Codex CLI

Codex uses a `config.toml` file at `~/.codex/config.toml`:

```toml theme={null}
[mcp_servers.messora]
url = "https://mcp.messora.dev/mcp"
bearer_token_env_var = "MESSORA_API_KEY"
```

Export the API key before starting Codex:

```bash theme={null}
export MESSORA_API_KEY="your-api-key-here"
```

### MCP Inspector

```bash theme={null}
export MESSORA_API_KEY="your-api-key-here"
npx @modelcontextprotocol/inspector
```

In the Inspector, configure:

* **Transport type:** Streamable HTTP
* **URL:** `https://mcp.messora.dev/mcp`
* **Authentication:** Bearer token
* **Token env var:** `MESSORA_API_KEY`

<Note>
  Never hardcode your API key in versioned config files. For Codex, always use
  an environment variable (`$MESSORA_API_KEY`). For Claude Desktop and Cursor,
  the config is local (not versioned) — replace the placeholder with your key.
</Note>

## Available tools

The server exposes exactly five tools:

### `scrape_url`

Extracts content from a URL. Consumes credit only when the backend returns
`scrape_status = success`.

| Parameter           | Type           | Default        | Description                           |
| ------------------- | -------------- | -------------- | ------------------------------------- |
| `url`               | string (URI)   | **required**   | Target URL                            |
| `formats`           | `["markdown"]` | `["markdown"]` | Formats: `markdown`, `json`, `raw`    |
| `json_schema`       | object         | —              | Required if `formats` includes `json` |
| `render_js`         | boolean        | `false`        | Render JavaScript                     |
| `only_main_content` | boolean        | `false`        | Main content only                     |
| `timeout`           | int (1–180)    | `60`           | Timeout in seconds                    |
| `max_pages`         | int (1–50)     | `1`            | Max pages                             |
| `parse_pdf`         | boolean        | `true`         | Process PDFs                          |
| `tags`              | string\[]      | —              | Up to 10 tags (64 chars each)         |

### `start_crawl`

Starts an async crawl and returns `job_id`. Poll `get_job` until the state
becomes `SUCCESS`, `FAILURE` or `REVOKED`.

| Parameter           | Type              | Default      | Description       |
| ------------------- | ----------------- | ------------ | ----------------- |
| `url`               | string (URI)      | **required** | Seed URL          |
| `max_pages`         | int (1–50)        | **required** | Page cap          |
| `max_depth`         | int ≥ 0           | —            | Max BFS depth     |
| `only_main_content` | boolean           | `true`       | Main content only |
| `timeout_ms`        | int (1000–300000) | `60000`      | Per-page timeout  |
| `tags`              | string\[]         | `[]`         | Usage tags        |

### `start_search`

Starts an async premium search and returns `job_id`.

| Parameter      | Type                          | Default      | Description                                                                                                                                    |
| -------------- | ----------------------------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `query`        | string                        | **required** | Search query                                                                                                                                   |
| `num_results`  | int (10–100)                  | `10`         | Number of results                                                                                                                              |
| `country`      | string (ISO alpha-2)          | —            | E.g. `BR`, `US`                                                                                                                                |
| `freshness`    | `day\|week\|month\|year\|any` | —            | Recency filter                                                                                                                                 |
| `tags`         | string\[]                     | —            | Up to 10 tags                                                                                                                                  |
| `query_fanout` | boolean                       | `true`       | LLM-expand the query into up to 5 variants. `false` searches only the raw query — faster, with 1 fewer LLM call (relevance scoring still runs) |
| `use_cache`    | boolean                       | `false`      | Reuse a complete result from the last 10 minutes for the same query and filters                                                                |

### `get_job`

Queries the state of a crawl or search job.

| Parameter | Type   | Description                                      |
| --------- | ------ | ------------------------------------------------ |
| `job_id`  | string | UUID returned by `start_crawl` or `start_search` |

**States:** `PENDING` → `STARTED` → `SUCCESS` | `FAILURE` | `REVOKED`

<Note>
  We recommend polling every 2 seconds with backoff. The MCP adapter does not
  perform internal polling or automatic retries of charged operations.
</Note>

### `get_usage`

Queries plan, credits used and account balance. No product arguments.
Same payload as the REST [`GET /account/usage`](/en/api-reference/usage).

## Async flow: `start_*` → `get_job`

Crawl and search are async in the backend. The recommended flow is:

```
start_crawl(url, max_pages=3)
  → {"job_id": "d8cc042a-dcbb-4f8b-a159-91fd47d060f9", "status": "PENDING", "next_action": "Call get_job..."}

get_job(job_id="d8cc042a-dcbb-4f8b-a159-91fd47d060f9")
  → {"status": "STARTED", "meta": {"pages_done": 1, "max_pages": 3}}

get_job(job_id="d8cc042a-dcbb-4f8b-a159-91fd47d060f9")
  → {"status": "SUCCESS", "credits_used": 3, "results": [...]}
```

## Response size

Every tool response is capped at **100,000 characters**. If it exceeds that,
text fields (`markdown`, `rawHtml`) are truncated while preserving structure,
status, metering and valid JSON:

```json theme={null}
{
  "ok": true,
  "data": { "status": "SUCCESS", "credits_used": 3, "results": [...] },
  "output": { "truncated": true, "original_chars": 250000, "returned_chars": 99500 }
}
```

## Errors

| HTTP | MCP code                     | Retry | Meaning                                  |
| ---- | ---------------------------- | ----- | ---------------------------------------- |
| 401  | `missing_api_key`            | no    | Missing credential                       |
| 402  | `insufficient_credits`       | no    | Insufficient credits                     |
| 403  | `invalid_or_revoked_api_key` | no    | Invalid or revoked key                   |
| 404  | `not_found`                  | no    | Nonexistent job or wrong account         |
| 422  | `validation_error`           | no    | Invalid input                            |
| 429  | `rate_limit_exceeded`        | yes   | Rate limit hit (preserves `Retry-After`) |
| 5xx  | `backend_unavailable`        | yes   | Temporary backend failure                |
| —    | `backend_timeout`            | yes   | Network timeout                          |

<Note>
  The MCP is a thin adapter: auth, billing, rate limiting and jobs belong to the
  FastAPI backend. The same limits and costs as the REST API apply.
</Note>
