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, staginghttps://mcp-staging.exray.dev - A bearer token (
exr_<id>_<secret>) — issued at/welcomeafter sign-up, or viaexray 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-idresponse header afterinitialize
⚠️ 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:
reason | Meaning | What to do |
|---|---|---|
malformed | Wrong shape — never even hit the database | Tokens look like exr_<36-char UUID>_<64-char hex>. Usually a stray quote or newline got copied in, or the wrong string was pasted |
unknown | Right shape, but no such id | It was deleted, or it belongs to another environment (production and staging have separate databases) |
bad_secret | Correct id, wrong secret | Truncated on copy — check the secret is a full 64 hex characters |
revoked | Revoked | Issue a new one. exray token list marks it revoked |
expired | Past its expiry | Same. 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/projectsA 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.
Related
- Quickstart — sign-up through first scrape
- CLI reference — drive exray from the command line
- Built-in tools
- Writing extractors