Skip to content

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).

ToolPurpose
create_astro_routeWrite a new .astro file at a route
update_astro_routeOverwrite an existing .astro file
list_astro_routesWalk src/pages/**/*.astro from the branch
delete_astro_routeRemove a file (super-admin only)
check_astro_deployPoll Cloudflare Pages for build status

The worker is strict about what routes it will accept. Bad routes are rejected with E_BAD_ROUTE before any GitHub call is made.

InputResult
/src/pages/index.astro
/pricingsrc/pages/pricing.astro
/pricing/src/pages/pricing/index.astro (trailing slash → folder index)
/blog/hello-worldsrc/pages/blog/hello-world.astro
/../etc/passwdE_BAD_ROUTE.. rejected
/[slug]E_BAD_ROUTE — dynamic route brackets rejected (explicit only for v1)
/blog%20postE_BAD_ROUTE%, ?, # rejected
/UPPERCASEOK (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.

Create an Astro route on marketing-site at /pricing with 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:

  1. Validates the route → src/pages/pricing.astro.

  2. Fetches the existing file (if any). If found → throws E_ROUTE_EXISTS. For overwrite, call update_astro_route instead.

  3. 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_route and delete_astro_route identify files generated by this server vs hand-written .astro files (which they refuse to touch without force: true).

  4. Commits to the linked branch via the GitHub Git Data API. Commit message defaults to [astro] create route /pricing — pass commit_message to override.

  5. 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."
}

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.

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-site at /launch: (paste full HTML doc)

Cloudflare typically takes 30–90 seconds. Claude can poll:

Check the deploy status of marketing-site for 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.

This worker never deletes WordPress / Duda posts or pages. The one documented exception is delete_astro_route:

  • Astro .astro files 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 pass force: true.

Recovery: open the repo on GitHub, find the deletion commit, click Revert. Or git revert <sha> locally and push.

Error codeWhenFix
E_GITHUB_NOT_LINKEDSite has no github_repo/admin/sites → edit → link the repo.
E_GITHUB_NOT_INSTALLEDGitHub App not installed on the repo’s owner account/admin/github → install on the org.
E_BAD_ROUTERoute contains .., [, ], ?, #, % or has bad segmentsRephrase the route — kebab-case alphanumeric segments only.
E_ROUTE_EXISTSFile already at this pathCall update_astro_route instead.
E_NOT_MCP_MANAGEDThe existing file was hand-writtenPass force: true to overwrite (read GitHub first to be safe).
E_SHA_MISMATCHYou passed an sha that no longer matches the fileRefetch via list_astro_routes, retry.
E_NOT_SUPPORTEDSite platform is wordpress or dudaAstro tools only work on platform: astro.
E_CF_PAGES_PROJECT_MISSINGcheck_astro_deploy called on a site without cf_pages_project/admin/sites → edit → set the project name.