Skip to main content

Source: This page and the Markdown under docs/case_studies/cloudflare-docs-site-mcp-workflow.md are kept in sync; edit both when the narrative changes.

Case study: Deploying docs.clawql.com with ClawQL MCP

An end-to-end narrative of shipping the ClawQL documentation site to Cloudflare Workers while using the same MCP server that powers day-to-day API work: search, execute, memory_recall, and memory_ingest. It records failures, fixes, mental models, and insights for future agents and humans.

Related: GitHub tracking issue (#87), repo website/ (Next.js + OpenNext + Wrangler), deploy script scripts/deploy/deploy-docs-to-cloudflare.sh.


1. Why this project is a stress test

The docs site is not static HTML in a bucket. It's Next.js 16 (App Router) compiled for Cloudflare Workers via OpenNext (@opennextjs/cloudflare). Request handling runs on Workers (V8 isolates), not a long-lived Node server. The runtime is not full Node: unenv polyfills gaps, and filesystem APIs used at request time can fail at runtime even when next build succeeded locally on macOS. Edge caching (Cache-Control, public/_headers) affects how fast users see fixes after deploy.

Two separate problems needed solving: operating Cloudflare's control plane with search / execute, and making the app Worker-safe. Both are required for a reliable docs.clawql.com.


2. Mental model: account, zone, Worker, hostname

ConceptWhat it isWhy it matters for this project
Cloudflare accountBilling + API namespaceaccount_id appears in REST paths; token must have Account read where needed
ZoneDNS authority for a domain (e.g. clawql.com)zone_id is required when attaching hostnames and debugging DNS
WorkerYour serverless JS bundle (clawql-docs)OpenNext emits .open-next/worker.js; Wrangler uploads it
Custom domaindocs.clawql.com → WorkerBound via workers.domains API (or dashboard); must point at the correct Worker name and environment
AssetsHashed static files (/_next/static/...)Served via ASSETS binding; long immutable caching is safe

When execute fails, the fix is often not "change Next.js" — it's token scope, wrong account_id, or a hostname already bound elsewhere. wrangler tail tells you whether the Worker is throwing (app bug) vs DNS (platform).


3. Goals

  1. Authenticate to Cloudflare and wire tokens for local Kubernetes MCP, Cursor (HTTP MCP), and CLI (wrangler).
  2. Deploy the docs app (website/) to a custom domain on a practical Cloudflare setup (Workers + OpenNext).
  3. Operate Cloudflare (Workers, domains, routes) via ClawQL where it beats ad-hoc curl — especially for discovering operation ids and building consistent request bodies.
  4. Persist setup, mistakes, and lessons in the Obsidian vault (and optional memory.db) so later sessions don't repeat the same failures.

4. Environment and stack

PieceRole
ClawQL MCPBundled Cloudflare provider (CLAWQL_PROVIDER=cloudflare or merged presets) for search / execute against Cloudflare's REST surface
CLAWQL_CLOUDFLARE_API_TOKENBearer for execute. Same variable name as in src/auth-headers.ts. Must be on the MCP process (stdio or HTTP), not only in an IDE-only .env
CLOUDFLARE_API_TOKENAlias accepted by the deploy script and many tools — normalize on one token to avoid "works in Wrangler, missing in MCP"
memory_recall / memory_ingestRequire CLAWQL_OBSIDIAN_VAULT_PATH (see docs/memory/memory-obsidian.md)
WebsiteNext.js App Router + OpenNext for Cloudflare (@opennextjs/cloudflare), Wrangler Worker clawql-docs, route docs.clawql.com
NEXT_PUBLIC_SITE_URLShould be https://docs.clawql.com at build time so canonical URLs, OG metadata, and sitemap bases match production

5. Wrangler deploy vs REST execute

PathWhen to use itCaveat
npm run deploy in website/ (opennextjs-cloudflare build + deploy)Iterating on the app — HTML, MDX, Worker bundleUses Wrangler auth; may succeed even when a narrow API token fails execute for domain APIs
ClawQL search + executeDiscovering Cloudflare operations, attaching custom domains, auditing what hostname maps to which WorkerToken needs explicit scopes for Workers + routes/domains + zone read as required
BothHealthy ops: deploy artifact with Wrangler; verify and repair routing with REST when automation or audit mattersTreat Wrangler success and REST success as independent until proven

The worst confusion in this case study was "I can deploy, but execute says 403" — almost always a scope problem or the wrong account.


6. Deploy script and custom domain

scripts/deploy/deploy-docs-to-cloudflare.sh encodes the happy path:

  1. Resolve account_id (or use CLAWQL_CLOUDFLARE_ACCOUNT_ID).
  2. Resolve zone_id for CLAWQL_DOCS_APEX_DOMAIN (default clawql.com).
  3. cd website && npm run deploy — OpenNext build + Wrangler upload.
  4. PUT /accounts/{account_id}/workers/domains to attach CLAWQL_DOCS_HOSTNAME (default docs.clawql.com) to clawql-docs.

Equivalent execute operations for interactive agents: zones list/get by name, workers.domains.update. The script comments map intent to API.

Prerequisites: jq, a token with Workers Scripts write + ability to manage Workers domains + zone read for the apex domain.


7. How the four tools worked together

search()

search was most useful for discovering operations and parameters without pasting multi-MB OpenAPI fragments into the chat. Concrete queries worked far better than vague ones: "Workers custom domain update PUT hostname service zone_id" found the right operation; "fix my site" returned noise. Once the right operationId and path shape were identified, execute took over.

execute()

execute handled Cloudflare REST calls once operation ids were known — custom domain attachment, Worker routing, listing domain bindings to confirm docs.clawql.com points at clawql-docs. The key constraint: execute is only as good as the token's permissions. A token that works for Wrangler OAuth may still fail API calls until Account / Workers / DNS scopes align with the task. IP allowlists on the token can also block CI or remote MCP egress silently.

memory_recall()

memory_recall was called at the start of non-trivial work and whenever past decisions or vault context were relevant. Queries with a concrete subject and a reasonable limit worked best. Raising maxDepth helped when graph relationships between notes mattered — for example, vault note links between Kubernetes and Cloudflare auth. If the vault path was unset, recall failed fast; the right move was to note it briefly and continue without blocking the main task.

memory_ingest()

memory_ingest was called after meaningful outcomes: decisions (custom domain vs Pages, token strategy, which Worker to use), debugging conclusions (Worker 1101, fs.readdir in Workers, Children.only in MDX), and user preferences. Using append: true with stable note titles built durable runbooks rather than fragmented one-off notes. Wikilinks between topics kept the Obsidian graph navigable. Secrets were never stored in ingests — only redacted configuration summaries.


8. End-to-end workflow (chronological)

  1. memory_recall — pull prior vault notes on Cloudflare + docs + MCP auth to avoid repeating dead ends.
  2. Configure CLAWQL_CLOUDFLARE_API_TOKEN on the MCP server process (k8s Secret or HTTP MCP env) — verify with a trivial execute if unsure.
  3. search Cloudflare operations; execute domain/Worker updates as needed.
  4. Deploy from website/ (opennextjs-cloudflare build + deploy), with NEXT_PUBLIC_SITE_URL=https://docs.clawql.com.
  5. Verify https://docs.clawql.com and wrangler tail clawql-docs for runtime errors.
  6. Fix app code if the Worker throws (see failures below).
  7. memory_ingest session summary (append: true) into a stable note title (e.g. "Cloudflare docs site — deploy runbook").

9. Failures and symptoms

SymptomLikely causeWhat helped
Missing / invalid auth on Cloudflare callsNo Authorization on the MCP processSet CLAWQL_CLOUDFLARE_API_TOKEN on the server running MCP, not only in local .env for the IDE
HTTP 403 / blockedToken IP allowlistAdd egress IP (or widen policy) for the environment where MCP runs
API errors despite Wrangler workingToken scope too narrow for custom domains / WorkersAccount / Workers / DNS-style permissions; align token with Wrangler vs REST needs
Browser 500 / Cloudflare 1101 ("Worker threw exception")Worker runtime exceptionwrangler tail <worker> — e.g. [unenv] fs.readdir is not implemented yet! when app code called filesystem APIs on Workers
Prerender crash on /conceptsReact.Children.only in CodePanelMDX + Shiki can yield multiple nodes under code; Children.only throws
Stale content at edge after deployCDN s-maxage / browser cacheSee docs/website/website-caching.md; purge if needed

10. Fixes and verification

10.1 Workers runtime: no fs.readdir on the request path

src/app/layout.tsx used fast-glob at request time to discover **/*.mdx and build the sidebar map. fast-glob uses fs.readdir, which unenv does not implement on Cloudflare Workers.

The fix was to remove runtime globbing and predefine section metadata for routes that need it via build-time codegen (website/scripts/generate-doc-layout-sections.mjssrc/generated/doc-layout-sections.generated.ts), then import from Layout.tsx through doc-layout-sections.ts.

The general pattern: anything that needs fs at runtime must move to build time, a static map, or an edge-safe data source.

10.2 Prerender: CodePanel and Children.only

Normalize children with Children.toArray, pick the code / Code element, and avoid Children.only when MDX can emit multiple nodes.

10.3 Verification

curl -I / HTTP 200 on https://docs.clawql.com/ after deploy. wrangler tail clean on normal page loads — no fs.readdir or Children.only stack traces.


11. Caching headers and purge

Production behavior is documented in docs/website/website-caching.md. next.config.mjs sets Cache-Control for HTML vs static chunks. public/_headers applies to assets served from the ASSETS binding.

After a deploy, if content still looks stale at the edge, use Cloudflare Purge Cache for docs.clawql.com, or lower s-maxage during heavy iteration.


12. Insights for future work

Workers is not Node. Any server code that runs on Workers must avoid unsupported fs on the hot path — move it to build-time generation or static maps.

Token parity matters. Wrangler success and REST execute success are independent. Validate scopes explicitly for the operations you automate rather than assuming the two auth paths are equivalent.

Vault cadence compounds. memory_ingest after decisions and failures, with append: true and stable titles, builds durable runbooks. Fragmented one-off notes don't survive across sessions in a useful form.

Case study hygiene. Link new case studies from README.md and docs/case_studies/README.md, then memory_ingest the canonical doc so recall finds "how we did X" in later sessions.


13. References

© Copyright 2026. All rights reserved. · ClawQL on GitHub