Connect an MCP client

exray is a standard Model Context Protocol server. Any MCP-capable client can connect.

This page is the single source for client configuration. Every other page links here instead of repeating a copy — once configuration is scattered, changing a domain or an auth scheme leaves a few copies quietly stuck on the old version.

You need:

  • An mcp-worker URL: production https://mcp.exray.dev, staging https://mcp-staging.exray.dev
  • A bearer token (exr_<id>_<secret>) — issued at /welcome after sign-up, or via exray token create / the dashboard at /console/tokens

Protocol

  • Transport: HTTP (POST + GET SSE), endpoint /mcp
  • Auth: Authorization: Bearer <token> request header
  • Protocol version: MCP 2025-06-18
  • Sessions: stateful; the server returns an mcp-session-id response header after initialize

⚠️ Tokens go in the header, never in the URL. exray does not support a ?token= form. URLs end up in browser history, proxy logs, shell history and config backups — a far larger exposure surface than a header.

Claude Code

One command:

claude mcp add --transport http exray https://mcp.exray.dev/mcp \
  --header "Authorization: Bearer exr_<id>_<secret>"

Or write ~/.claude/mcp.json (a project-level .mcp.json also works):

{
  "mcpServers": {
    "exray": {
      "type": "http",
      "url": "https://mcp.exray.dev/mcp",
      "headers": {
        "Authorization": "Bearer exr_<id>_<secret>"
      }
    }
  }
}

Cursor

~/.cursor/mcp.json (or project-level .cursor/mcp.json):

{
  "mcpServers": {
    "exray": {
      "type": "http",
      "url": "https://mcp.exray.dev/mcp",
      "headers": {
        "Authorization": "Bearer exr_<id>_<secret>"
      }
    }
  }
}

Windsurf

~/.codeium/windsurf/mcp_config.json. Note the field is serverUrl, not url:

{
  "mcpServers": {
    "exray": {
      "serverUrl": "https://mcp.exray.dev/mcp",
      "headers": {
        "Authorization": "Bearer exr_<id>_<secret>"
      }
    }
  }
}

All three need a client restart — MCP config is read at startup, so editing without restarting changes nothing.

Verify

Say this to your agent:

Use exray's scrape tool on https://example.com and give me markdown

You should see it call scrape and return # Example Domain ....

Troubleshooting

Look up by symptom. Each entry gives you a way to tell causes apart, not a guess.

401 Unauthorized

Five causes; the reason field in the response body distinguishes them:

reasonMeaningWhat to do
malformedWrong shape — never even hit the databaseTokens look like exr_<36-char UUID>_<64-char hex>. Usually a stray quote or newline got copied in, or the wrong string was pasted
unknownRight shape, but no such idIt was deleted, or it belongs to another environment (production and staging have separate databases)
bad_secretCorrect id, wrong secretTruncated on copy — check the secret is a full 64 hex characters
revokedRevokedIssue a new one. exray token list marks it revoked
expiredPast its expirySame. exray token list marks it expired

The most common cause is actually configuration: the client never sent an Authorization header at all (many clients silently drop malformed headers). Check the token itself with curl first:

curl -s -o /dev/null -w '%{http_code}\n' \
  -H "authorization: Bearer exr_<id>_<secret>" \
  https://mcp.exray.dev/api/projects

A 200 means the token is fine and the problem is in your client config. A 401 means the token itself is the problem.

403 forbidden

The token lacks the required scope. Running tools needs tools.execute, registering needs tools.define, publishing needs tools.publish, account management (issuing tokens, enabling result pages) needs admin. exray token list shows current scopes.

404 or wrong path

The usual culprit is /mcp becoming /mcp/mcp. Some clients append their own default path to the url you configure, turning https://mcp.exray.dev/mcp into .../mcp/mcp. It looks like a successful connection with an empty tool list, or a plain 404. Your configured URL should end in exactly one /mcp.

Check the domain too: the MCP endpoint is mcp.exray.dev, not the dashboard's app.exray.dev.

402 Payment Required

You've exhausted this month's quota (Browser-seconds or loader calls). exray budget shows current usage and limits; the dashboard at /console breaks it down per project. Quotas reset on calendar months.

Empty tool list

In order: (1) did you restart the client? (2) is the Authorization header actually being sent — use the curl check above; (3) is the URL doubled to /mcp/mcp?

Conflict: Only one SSE stream is allowed per session

The client is reconnecting its SSE stream before the old one closed. Restart the client.

429 / concurrent_exceeded

Too many in-flight calls for one account. Wait for the running ones to finish, or lower your parallelism. This is the abuse-prevention gate, not a fault.