Quickstart
From zero to your AI agent using exray. About 5 minutes.
What exray is: an agent-native MCP web-scraping runtime. It isn't a tool you click — it's the tool backend your AI agent (Claude Code / Cursor / Windsurf) uses. Your agent scrapes pages through it, writes its own scraping logic, registers that logic as reusable tools, and can render results as a shareable web page. Zero ops on your side — it all runs on Cloudflare.
Mental model (get this first, the rest follows)
exray has three faces:
| Face | Who uses it | What it does |
|---|---|---|
MCP server (mcp.exray.dev/mcp) | your agent | The real work: scraping, defining/publishing assets, running tools. You speak plainly; the agent translates into tool calls. |
CLI (exray) | you (human) | What agents can't or shouldn't do: issue tokens, enable result pages, toggle bindings, script things in CI |
Dashboard (app.exray.dev) | you (human) | Read-only: usage, job records, source of registered assets, audit trail |
The key idea: you barely "operate" exray. You talk to Claude Code / Cursor, and it calls exray for you. Configuration and execution live on the agent side or in the CLI — the dashboard does not configure anything.
Step 1: Sign up, get an onboarding token
- Open the dashboard and sign in:
- Production:
https://app.exray.dev/sign-in - Staging (for trying things out):
https://app-staging.exray.dev/sign-in
- Production:
- You land on
/welcome, which issues you a one-time onboarding token (shaped likeexr_<id>_<secret>).- ⚠️ The plaintext is shown exactly once. Copy it now. If you lose it, issue a new one at
/console/tokens. - The welcome page already renders client config snippets with your token filled in.
- ⚠️ The plaintext is shown exactly once. Copy it now. If you lose it, issue a new one at
That token carries four scopes — tools.read, tools.execute, tools.define, tools.publish —
enough for an agent to scrape, define and publish its own tools end to end. It deliberately does
not include admin: creating/revoking tokens, enabling result pages and toggling bindings all
require admin, which you issue separately when you need it.
Step 2: Install an MCP client
Config snippets live in Connect an MCP client — that page is the single source for configuration, with complete setups for Claude Code, Cursor and Windsurf plus protocol details and troubleshooting.
Three things to know before you start:
- The endpoint is
<mcp-url>/mcp(production:https://mcp.exray.dev/mcp) - The token goes in the
Authorization: Bearerheader, never in the URL - Restart your client after installing — MCP config is read at startup
Step 3: Verify the connection
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
This domain is for use in documentation examples...That's a working connection. (Under the hood: initialize → tools/list → tools/call scrape,
all handled by the agent.)
If it doesn't work, see troubleshooting — a 401 response
carries a reason field that tells you whether the token was mistyped, expired, or never sent by
the client at all.
Step 4: Everyday use
Once installed, you just talk to your agent. exray exposes 15 tools and the agent picks:
| What you want | What you say | Tool it calls |
|---|---|---|
| Scrape a page | "grab the content of example.com" | scrape |
| Pull fields out of a page | "extract the title and price" | extract |
| Write a dedicated scraper | "define a fetcher for this shop's product pages" | define_fetcher |
| Write an extractor | "write an extractor that pulls product info from pages like this" | define_extractor |
| Build a result page | "make a web page listing what you scraped" | define_handler |
| Publish it for reuse | "publish that fetcher" | publish_definition |
| Use your own scraper | "use the fetcher I just made on these URLs" | fetcher_<your-name> |
| See what's registered | "list all my fetchers" | list_definitions |
| Crawl a site | "crawl example.com, 2 levels deep, 50 pages max" | crawl / define_crawl |
| Fetch crawl results | "give me the results of that crawl" | get_crawl_results |
The rest are asset lifecycle (deprecate_definition / disable_definition /
delete_definition) and evaluation (define_eval / run_eval).
What makes exray different: other scraping APIs hand you rough markdown and leave complex extraction to another LLM call — slow and expensive. exray lets your agent write TypeScript scraping and extraction code and register it as a tool. Write it once, then call it by name forever. Your extraction logic becomes an asset.
What actually happens inside
You say: "define a fetcher called shop-probe that opens the given URL and returns the title."
The agent calls define_fetcher with source like:
import type { FetcherModule } from "@exray/exray-api";
export default {
async fetch(ctx) {
await ctx.page.goto(String(ctx.params.url));
return await ctx.page.snapshot();
},
} satisfies FetcherModule;exray statically validates and compiles it. You say "publish", the agent calls
publish_definition, and from then on fetcher_shop-probe is a named tool you can call
repeatedly. Every call is metered, audited, and inspectable in the dashboard.
Step 5 (optional): Turn results into a web page
Once you've scraped something, the next question is usually "how do I show this to someone?"
Have your agent write a handler (entry point handle(request, ctx)). Publish it, enable a
subdomain, and you get a public address at <username>-<project>.exray.app:
exray site enable # requires an admin token
exray site statusA handler gets read-only access to your job data (list jobs, fetch a result), so it can be a real dashboard rather than JSON pasted into a chat window.
Full walkthrough: Result pages.
Step 6 (optional): Give your agent storage
By default agent code has no persistence at all — it forgets everything when it finishes. When you need it to remember things across calls, enable bindings per project:
| Binding | What the agent gets |
|---|---|
kv | Key-value store (ctx.kv) — good for caching and dedupe markers |
db | Structured document store (ctx.db) — queryable |
storage | Object storage (ctx.storage) — for larger artifacts |
exray bindings get # all three are off by default
exray bindings set --kv on # requires an admin tokenOff-by-default is deliberate. Before you enable one, agent code calling it gets an explicit
binding_not_enabled error rather than silently landing a writable store. Result pages (handlers)
get read-only access to all three — a public request should never be able to write your data.
Doing it without an agent
Everything above works from the command line. See CLI reference. Typical uses: registering and publishing assets from CI, scripted token issuance, bulk-enabling result pages.
exray define handler mysite --file ./site.ts
exray publish handler mysite
exray site enableWhat to look at in the dashboard
After signing in to /console (read-only):
- Overview (
/console) — Browser-seconds, job count, error rate, per-project usage this month - Projects (
/console/projects) — your namespaces (isolating assets and tokens per agent or workload) - Tokens (
/console/tokens) — issue and revoke tokens, inspect scopes - Code (
/console/projects/<slug>/code) — source and version history of registered assets - Jobs (
/console/jobs) — every tool call, with duration, metering and errors - Audit (
/console/audit) — a full operation trail
Common problems
- 401 on tool calls — check the
reasonfield in the response body; see the 401 table. - 403 forbidden — the token lacks a scope (execution needs
tools.execute, publishing needstools.publish, enabling result pages needsadmin).exray token listshows current scopes. - 402 — you've used up this month's quota.
exray budgetshows usage. - Empty tool list — nine times out of ten the URL ends in
/mcp/mcp, or the client wasn't restarted. - Full checklist: troubleshooting.
Related
- Connect an MCP client — client configs, protocol, troubleshooting
- CLI reference
- Result pages — turn scraped data into a shareable page
- Built-in tools — scrape / extract / crawl parameters
- Writing extractors