Create Astro routes
Astro sites live in a different world from WordPress and Duda. There’s no
REST API, no “draft” concept — the live site is whatever’s checked into the
main branch (or whichever branch Cloudflare Pages is watching). The MCP
worker treats Astro pages as code that happens to be a route: you call
create_astro_route, the worker writes a .astro file to the linked GitHub
repo, Cloudflare Pages builds + deploys, and the page is live.
There’s no “publish later” — every commit is a deploy. You stay safe by working on a non-production branch, or by treating the production branch as the source of truth and reviewing changes via GitHub.
Prerequisites: see astro prerequisites for the full setup checklist (site registration, plugins, token scope).
| Tool | Purpose |
|---|---|
create_astro_route | Write a new .astro file at a route |
update_astro_route | Overwrite an existing .astro file |
list_astro_routes | Walk src/pages/**/*.astro from the branch |
delete_astro_route | Remove a file (super-admin only) |
check_astro_deploy | Poll Cloudflare Pages for build status |
Route validation
Section titled “Route validation”The worker is strict about what routes it will accept. Bad routes are
rejected with E_BAD_ROUTE before any
GitHub call is made.
| Input | Result |
|---|---|
/ | src/pages/index.astro |
/pricing | src/pages/pricing.astro |
/pricing/ | src/pages/pricing/index.astro (trailing slash → folder index) |
/blog/hello-world | src/pages/blog/hello-world.astro |
/../etc/passwd | E_BAD_ROUTE — .. rejected |
/[slug] | E_BAD_ROUTE — dynamic route brackets rejected (explicit only for v1) |
/blog%20post | E_BAD_ROUTE — %, ?, # rejected |
/UPPERCASE | OK (segments are case-insensitive in regex) |
Segments must be [a-z0-9][a-z0-9-]*[a-z0-9]? (case-insensitive). Empty
segments, leading/trailing dashes, and special chars are out.
End-to-end example
Section titled “End-to-end example”Create an Astro route on
marketing-siteat/pricingwith this HTML:<section><h1>Pricing</h1>...</section>. Title “Pricing — Acme”, description “Three plans for every team size”.
Claude calls:
{ "tool": "create_astro_route", "args": { "site_id": "marketing-site", "route": "/pricing", "html": "<section><h1>Pricing</h1>...</section>", "title": "Pricing — Acme", "description": "Three plans for every team size" }}The worker:
-
Validates the route →
src/pages/pricing.astro. -
Fetches the existing file (if any). If found → throws
E_ROUTE_EXISTS. For overwrite, callupdate_astro_routeinstead. -
Wraps the HTML in an Astro frontmatter shell with a marker comment:
---// Generated by SEO Navigator MCP — safe to edit, but a subsequent// update_astro_route call will overwrite the file.const title = "Pricing — Acme";const description = "Three plans for every team size";---<html lang="en"><head><meta charset="utf-8"><title>{title}</title><meta name="description" content={description}></head><body><section><h1>Pricing</h1>...</section></body></html>The marker comment is how
update_astro_routeanddelete_astro_routeidentify files generated by this server vs hand-written.astrofiles (which they refuse to touch withoutforce: true). -
Commits to the linked branch via the GitHub Git Data API. Commit message defaults to
[astro] create route /pricing— passcommit_messageto override. -
Returns a structured response:
{ "repo": "supportsn/marketing-site", "branch": "main", "route": "/pricing", "path": "src/pages/pricing.astro", "sha": "abc123…", "commit_sha": "def456…", "html_url": "https://github.com/supportsn/marketing-site/blob/…/src/pages/pricing.astro", "cf_pages_url": "https://marketing-site.pages.dev", "build_status": "pending", "note": "Cloudflare Pages auto-builds on push. Call check_astro_deploy with the commit_sha to verify success."}Layouts
Section titled “Layouts”If your Astro project already has a layout component, pass its import path:
{ "tool": "create_astro_route", "args": { "site_id": "marketing-site", "route": "/pricing", "html": "<section>…</section>", "title": "Pricing", "layout": "../layouts/Base.astro" }}The worker emits:
---import Layout from "../layouts/Base.astro";const title = "Pricing";---<Layout title={title}><section>…</section></Layout>The layout has to exist in the repo at the path you specify; the worker doesn’t verify, it just writes the import.
Full HTML documents
Section titled “Full HTML documents”The html arg accepts either a fragment or a full <!DOCTYPE html>…</html>
document. If it sees <body>…</body>, only the body’s inner HTML is
extracted (Astro renders its own <html> shell). The other elements
(<script>, <style>, etc.) inside body pass through unchanged.
Useful for “I generated a full HTML page with inline CSS — just deploy it”:
Deploy this full HTML page to
marketing-siteat/launch: (paste full HTML doc)
Verifying the deploy
Section titled “Verifying the deploy”Cloudflare typically takes 30–90 seconds. Claude can poll:
Check the deploy status of
marketing-sitefor commit def456.
Claude calls check_astro_deploy with commit_sha: "def456…". Response:
{ "status": "building", "stage": "deploy", "url": "https://marketing-site.pages.dev", "preview_url": "https://def456.marketing-site.pages.dev", "log_url": "https://dash.cloudflare.com/<account>/pages/view/marketing-site/def456", "deployment_id": "...", "commit_sha": "def456…", "branch": "main", "stages": [ { "name": "queued", "status": "success" }, { "name": "initialize", "status": "success" }, { "name": "clone_repo", "status": "success" }, { "name": "build", "status": "active" }, { "name": "deploy", "status": "idle" } ]}status settles to success | failure | canceled. On failure, the
log_url opens the Cloudflare Pages dashboard at the build log.
check_astro_deploy is hidden from tools/list when the worker doesn’t
have CLOUDFLARE_ACCOUNT_ID + CLOUDFLARE_API_TOKEN set. If Claude says
“I can’t see that tool”, check the secrets.
Hard rule and the documented exception
Section titled “Hard rule and the documented exception”This worker never deletes WordPress / Duda posts or pages. The one
documented exception is delete_astro_route:
- Astro
.astrofiles are code in a git repo, not “content” in a CMS. - Deleting a file is
git rm+ commit. Git history preserves it. - The tool is super-admin only at runtime — regular-user tokens get
E_SUPER_ADMIN_ONLY. - By default it refuses to delete files NOT generated by this server
(
E_NOT_MCP_MANAGED) unless you passforce: true.
Recovery: open the repo on GitHub, find the deletion commit, click Revert.
Or git revert <sha> locally and push.
Common errors
Section titled “Common errors”| Error code | When | Fix |
|---|---|---|
E_GITHUB_NOT_LINKED | Site has no github_repo | /admin/sites → edit → link the repo. |
E_GITHUB_NOT_INSTALLED | GitHub App not installed on the repo’s owner account | /admin/github → install on the org. |
E_BAD_ROUTE | Route contains .., [, ], ?, #, % or has bad segments | Rephrase the route — kebab-case alphanumeric segments only. |
E_ROUTE_EXISTS | File already at this path | Call update_astro_route instead. |
E_NOT_MCP_MANAGED | The existing file was hand-written | Pass force: true to overwrite (read GitHub first to be safe). |
E_SHA_MISMATCH | You passed an sha that no longer matches the file | Refetch via list_astro_routes, retry. |
E_NOT_SUPPORTED | Site platform is wordpress or duda | Astro tools only work on platform: astro. |
E_CF_PAGES_PROJECT_MISSING | check_astro_deploy called on a site without cf_pages_project | /admin/sites → edit → set the project name. |
What’s next
Section titled “What’s next”- Content + Design teams — combine Astro routes with the markdown-on-GitHub workflow.
- Architecture — how the worker uses the GitHub App + GitHub Git Data API under the hood.
- Tools reference — full schema for each Astro tool.