How-to
Serve a WREN site on your own domain
Your site already has a public URL like /orgs/{slug}/tree/mysite/index.html. A few lines of Cloudflare Worker turn that into https://www.example.com/.
Before you start
- The tree is public: a
principal: "*"read rule ontree:mysite(see Publish a site). Check it by openinghttps://<wren-host>/orgs/{slug}/tree/mysite/index.htmlin a private window. - Your domain's DNS is on Cloudflare (any plan), and you have
wranglerinstalled.
The Worker
It maps paths onto the tree, adds index.html for directory URLs (WREN doesn't), and forwards only what the browser needs. No API key is involved, because the tree is public.
export default {
async fetch(request, env) {
const url = new URL(request.url);
if (url.pathname === "/mcp") { // AI agents: this domain's MCP endpoint
const headers = new Headers();
for (const h of ["content-type", "accept", "authorization", "mcp-protocol-version"]) {
const v = request.headers.get(h); if (v) headers.set(h, v);
}
return fetch(env.MCP, { method: request.method, headers, body: request.method === "POST" ? request.body : undefined });
}
if (request.method !== "GET" && request.method !== "HEAD") {
return new Response("Method not allowed", { status: 405 });
}
let path = url.pathname;
if (path.endsWith("/")) path += "index.html"; // directory → index.html
else if (!/\.[a-z0-9]+$/i.test(path)) path += "/index.html"; // /about → /about/index.html
const upstream = `${env.UPSTREAM}${path}${url.search}`;
const res = await fetch(upstream, {
headers: { Accept: request.headers.get("Accept") ?? "*/*" }, // WREN negotiates bytes vs JSON on Accept
});
const out = new Response(res.body, res);
out.headers.delete("set-cookie");
return out;
},
};name = "mysite"
main = "src/index.js"
compatibility_date = "2026-10-01"
routes = [{ pattern = "www.example.com", custom_domain = true }]
[vars]
UPSTREAM = "https://wren.aemwip.com/orgs/oak-spur-spur/tree/mysite"
MCP = "https://wren.aemwip.com/orgs/oak-spur-spur/mcp?tree=mysite"Rewrite /about to /about/index.html only if your site uses folder-style pages. If it has flat files like /about.html, drop that line.
Deploy it
npx wrangler deploy curl -sI https://www.example.com/ | head -5 # 200, content-type: text/html
From now on you only deploy to WREN (wren deploy, then wren promote). The Worker doesn't change.
AI agents on your domain
The /mcp route in the Worker gives your domain its own MCP endpoint, scoped to what the domain shows: https://www.example.com/mcp.
- Without a key (anyone's agent): read-only, published content only, and only the tree named in
?tree=. Other trees and collections of the org stay invisible. To also expose some collections, list them:…/mcp?tree=mysite&collections=results,standings. - With a key (your own agents): full tools, but only for keys of this org. A key from another org gets 403, so the domain can't be used as a door into a different org.
Without the Worker, the same endpoint is https://<wren-host>/orgs/{slug}/mcp?tree=mysite. See Connect AI agents with MCP.
Data on the same domain
Pages can call WREN's public data API directly, since public routes send Access-Control-Allow-Origin: *:
const API = "https://wren.aemwip.com/api/v1/orgs/oak-spur-spur";
const { items } = await (await fetch(`${API}/results?limit=100`)).json();Or with wren.js: <script src="https://wren.aemwip.com/wren.js" data-base="https://wren.aemwip.com/api/v1/orgs/oak-spur-spur"></script>. If you'd rather keep everything on your domain, add a second route in the Worker (e.g. /data/* → /api/v1/orgs/{slug}/*). Public data still needs no key.
Caching
- WREN sends
Cache-Control: public, max-age=60, stale-while-revalidate=86400on successful public reads, andno-storeon errors, so a 404 from before a deploy isn't kept. - Cloudflare's own settings can override that. If files stay stale after a promote, check the zone's Browser Cache TTL and Cache Rules, or add
?v=<hash>to asset URLs in your HTML. - WREN negotiates content on the
Acceptheader (file bytes vs JSON node) and sendsVary: Accept. PassAcceptthrough, as the Worker above does.
Don't
- Don't proxy private data through the Worker with an API key unless you add your own access control. Anyone who can reach the route can read what the key can read. If the data is meant for everyone, give it a public read rule instead.
- Don't put an API key in page code. Keys are long-lived and can write.
- Don't change the org slug without updating
UPSTREAM.