BrightSite
BrightSite, Inc. v1.1.0
BrightSite is a website hosting and content management platform. Connect your BrightSite account to build and edit pages, layouts, and reusable components; write, publish, and organize blog posts; manage forms and review submissions; upload and organize media; configure SEO, structured data, redirects, and analytics settings; review site traffic; and manage team members and roles. Content edits are saved to a staged draft first, so you can review changes and then publish them to the live site when they are ready. A staging site and full version history let you preview larger changes and roll back if needed.
Language: English · Automatically detected from descriptions.
Package details
Publisher declarations from the archived package. These are separate from our research and the live service's terms.
- Package author
- BrightSite, Inc.
Package observed Sep 30, 2026.
Files & skills
File archives
Skill instructions
blog-post-from-outline6.88 KB
---
name: blog-post-from-outline
description: Turn an outline (bullet points, headings, or a rough brief) into a full BrightSite blog post draft with proper structure, meta title, meta description, and excerpt. Always creates as `draft` — never publishes. Use when the user mentions "draft a blog post," "write a post from this outline," "turn this into a blog post," "blog draft," "post from brief," or has a structured outline they want fleshed out.
---
# Blog Post from Outline
Take a structured outline and produce a publishable-quality draft post in BrightSite. The agency reviews and edits before publishing.
This skill is intentionally NOT a "generate me a blog post from a topic" prompt. That produces generic, low-value content. This skill requires the user to have already done the thinking — outline, key points, target keyword — and turns that thinking into a clean draft.
## When to use this
- Agency has a content brief from a client and needs a fast draft.
- Subject matter expert recorded a voice memo / Loom and the agency has a transcript outline.
- Writer has a structured outline but doesn't want to write the connective prose.
Do NOT use this for:
- Generating posts from just a title or topic (output will be generic).
- Long-form pillar content (>2000 words) — the model produces better results with more iteration; do it in pieces.
## Inputs you need from the user
1. **Account ID**
2. **Outline** — bullet points, H2/H3 headings, key claims. The more structured, the better.
3. **Target keyword** — the primary phrase the post should rank for.
4. **Audience** — one sentence on who this is written for (e.g. "small business owners evaluating CRMs," "homeowners considering solar").
5. **Voice/tone reference** — link to or paste an existing post the client has written, OR describe the tone ("conversational, no fluff, US English").
6. **Word count target** — default 800-1200 words.
7. **CTA** — what action should the reader take at the end?
## Workflow
### Step 1: Confirm understanding
Before drafting, parrot back to the user:
- The H2 structure you'll use
- The target keyword and where you'll place it (title, H1, first 100 words, one H2, meta)
- The CTA
- The voice you're targeting
Wait for confirmation. Don't draft if any of those are unclear — go back and ask.
### Step 2: Draft the post
Write the post following these constraints:
- **Title**: under 60 chars, includes the target keyword naturally, action/benefit-oriented (not "How to do X" if there's a better angle).
- **First paragraph**: hook the reader in 2-3 sentences, mention the target keyword once, set up what the rest of the post delivers. No "In today's fast-paced world" openers.
- **H2 structure**: matches the outline. Each H2 should contain at least one tangible takeaway, example, or piece of evidence — not just transitional copy.
- **Voice**: match the reference. If no reference, default to: short sentences, active voice, no buzzwords (utilize, leverage, synergy), no AI tells ("dive in," "unleash," "in conclusion").
- **CTA**: one paragraph at the end. Direct ask, not a soft "let us know what you think."
- **Internal links**: if the user mentioned related posts or pages, work in 1-2 natural internal links.
- **Length**: hit the word count target ±15%.
### Step 3: Generate the metadata
- `meta_title` — under 60 chars. Often the same as the post title, but can be tighter for search.
- `meta_description` — 130-155 chars. Includes the target keyword. Reads like a benefit, not a summary.
- `excerpt` — 1-2 sentences, used on the blog listing page. Should make someone want to click. Different from meta_description (meta is for Google, excerpt is for humans browsing your blog).
- `slug` — kebab-case version of the title, but trim stop words. Under 60 chars.
### Step 4: Show the user the draft before creating
Output the full post in markdown to the user with a clear delineation:
```
--- DRAFT PREVIEW ---
Title: ...
Slug: ...
Meta title: ...
Meta description: ...
Excerpt: ...
[Full post body in markdown]
--- END PREVIEW ---
```
Ask the user to approve or request changes. Common revisions: tone is off, one H2 is weak, CTA needs work. Iterate before creating in BrightSite.
### Step 5: Create the post (draft only)
Once approved, convert the markdown body to clean HTML. Post bodies are plain HTML only — they do NOT support HEEx, Phoenix template helpers, or page components. If the outline requires dynamic/templated content, that belongs on a page (use a different skill), not a post.
Call `mcp__brightsite__create_post` with:
- `account_id`
- `title`
- `slug`
- `content` — HTML body
- `excerpt`
- `meta_title`
- `meta_description`
- `status: "draft"` — never "published" from this skill. Note: even `status: "published"` writes content into staged fields; going live requires a separate `mcp__brightsite__publish_post` call. Always create as draft so the user explicitly publishes after review.
- `feature_image_id` or `feature_image_url` — if the user provided one; otherwise skip
Return the post ID and a note like:
> Draft created: post ID `8fd2k3jq91pn`. Open it in the BrightSite editor to add a feature image, internal links, and publish when ready.
## Anti-patterns to avoid
- **Don't auto-publish.** Even if the user says "and publish it" — push back once. Tell them: "I always create as draft so you can review in the editor. Once you've previewed it, you can publish with one click."
- **Don't generate posts without an outline.** If the user gives just a topic, ask for an outline first. Don't produce a generic post.
- **Don't pad to hit word count.** If the natural length is 700 words and the target was 1000, return 700 words with a note. Padded posts hurt SEO and bounce rate.
- **Don't use AI tells.** Strip any of: "dive into," "unleash," "in today's [adjective] world," "in conclusion," "It's important to note that," em-dash-heavy sentences, "let's explore." If the user wants those, they can add them back.
- **Don't fabricate stats or quotes.** If the outline mentions "stat about adoption rates," ask the user for the source. Don't make it up.
## Example invocation
> Account `YOUR_ACCOUNT_ID`. Target keyword: "small business HVAC marketing." Audience: HVAC company owners with 5-20 employees. Voice: like the post at /blog/hvac-seo-basics on the same site. 1000 words. CTA: book a strategy call.
>
> Outline:
> - Most HVAC marketing fails because it's interchangeable
> - 3 things that actually work: Google Business Profile reviews, neighborhood-specific landing pages, seasonal email follow-up
> - For each: why it works, one example, how to start this week
> - Wrap with: pick one, do it for 90 days
## Tools used
- `mcp__brightsite__create_post` — create the draft
(Optional, if you want to enrich the draft)
- `mcp__brightsite__list_posts` — to find internal link opportunities
- `mcp__brightsite__list_pages` — same
- `mcp__brightsite__get_blog_settings` — to confirm blog URL prefix for internal links
bulk-seo-audit5.77 KB
--- name: bulk-seo-audit description: Audit every page and post on a BrightSite site for on-page SEO issues. Checks title length, meta description length and presence, H1 presence and uniqueness, canonical URLs, og:image, robots directives, and slug quality. Outputs a prioritized fix list grouped by severity. Use when the user mentions "SEO audit," "on-page SEO," "check my pages for SEO," "find SEO issues," "before launch SEO check," or wants a pre-handoff QA pass on a client site. --- # Bulk SEO Audit Walk every page and post on a BrightSite site, evaluate on-page SEO basics, and produce a triaged report. This is for catching common issues at scale — it does NOT replace a real SEO audit that includes backlinks, Core Web Vitals, or rank tracking. ## When to use this - Agency is about to hand off a client site and wants a final QA pass. - Client's traffic has dropped and you want to rule out on-page issues. - After a migration (e.g. after running the `migrate-wordpress-blog` skill). - Periodic check on a live site every 30-90 days. ## Inputs you need from the user 1. **Account ID** — the BrightSite account to audit. 2. **Scope** — pages only, posts only, or both? Default: both. 3. **Severity threshold** — show all issues, or only critical + warnings? Default: all. ## Workflow ### Step 1: Pull the inventory - `mcp__brightsite__list_pages` for all pages - `mcp__brightsite__list_posts` for all posts Note the counts and tell the user how many entities you're about to audit. If it's >200, ask for confirmation to proceed. ### Step 2: Fetch and evaluate each entity For each page or post, call `mcp__brightsite__get_page` or `mcp__brightsite__get_post`. Pages return both `heex` (live) and `heex_staged` (latest unpublished); posts return `content` and `content_staged`. **Audit both** — issues in `*_staged` will go live the next time the user publishes. Group findings by which version is affected so the user knows whether to publish a fix or just edit the live entity. Evaluate against these checks: **Critical** (must fix — actively hurts SEO) - `meta_title` empty or > 60 chars → truncated in SERPs - `meta_description` empty → Google generates one, often badly - `slug` contains spaces, capital letters, or special chars - Two or more entities share the same slug - HEEx/content body is empty or under 300 chars (check both live and staged) **Warning** (should fix) - `meta_title` < 30 chars (too short, missing keywords) - `meta_description` < 70 chars or > 155 chars - No H1 in the HEEx/content (check both live and staged) - More than one H1 in the HEEx/content (check both live and staged) - `canonical_url` set but points off-domain (suspicious — usually means duplicate content) - `og_image_id` missing → poor social sharing previews. Note: most BrightSite templates fall back to the post's `feature_image_id` (or the site identity logo) when `og_image_id` is null, so only flag this if neither fallback exists. For pages, flag any page where `og_image_id` is null AND the site has no logo. - Title and meta_title are identical (missed opportunity to differentiate for search vs. display) - `meta_robots` contains `noindex` — could be intentional (a thank-you page, an internal tool page). Surface each match and ask the user to confirm whether it's deliberate. **Info** (nice to fix) - Slug is very long (> 60 chars) - Slug contains stop words (`the`, `a`, `and`, `of`) - `meta_description` doesn't mention any word from the title - Post missing both `feature_image_url` and `feature_image_id` ### Step 3: Output the report Format as a markdown table grouped by severity, then by entity type. For each issue, include: - Severity marker (use `[!]` for critical, `[~]` for warning, `[i]` for info — no emojis unless user asked) - Entity type and title - The specific issue - Suggested fix - Page/post ID (so the user can jump to it in the editor) End with a summary block: ``` Audit summary ------------- Pages audited: 42 Posts audited: 118 Critical issues: 4 Warnings: 23 Info: 31 Top 3 fixes by impact: 1. 4 pages missing meta_description — generate descriptions for: [list] 2. 7 posts have meta_title > 60 chars — shorten: [list] 3. 2 pages share the slug "/services" — rename one ``` ### Step 4: Offer to fix After the report, ask the user: > Want me to auto-fix the critical issues? I can generate meta descriptions and trim long titles. I'll show you each change before saving. If they say yes, use `mcp__brightsite__update_page` / `mcp__brightsite__update_post` to apply fixes. Show each proposed change as a diff (`old → new`) and confirm before calling the update. Important: SEO metadata fields are not staged by the MCP; updating `meta_title`, `meta_description`, `meta_robots`, `canonical_url`, or `og_image_id` can affect live metadata immediately. Get explicit approval before each metadata update. ## Anti-patterns to avoid - **Don't auto-fix without explicit approval per change for critical issues.** Auto-generating a meta description that's wrong about the page's content is worse than no description. - **Don't flag stylistic preferences as issues.** "Title doesn't use power words" isn't an SEO issue, it's a copywriting opinion. Keep the audit objective. - **Don't fetch all entities in parallel without throttling** on accounts with hundreds of pages. Sequential with a small batch size (e.g. 5 concurrent) is safer. - **Don't trust `has_unpublished_changes: false` as proof of correctness.** Even published pages can have stale meta. ## Example invocation > Run a full SEO audit on account `YOUR_ACCOUNT_ID`. Both pages and posts. Show me everything. ## Tools used - `mcp__brightsite__list_pages` - `mcp__brightsite__list_posts` - `mcp__brightsite__get_page` - `mcp__brightsite__get_post` - `mcp__brightsite__update_page` / `mcp__brightsite__update_post` (only with explicit approval)
client-handoff-checklist7.53 KB
--- name: client-handoff-checklist description: Pre-launch QA pass for a BrightSite site before handing it to a client or going live. Verifies site identity, error pages, blog settings, forms, redirects, analytics, and meta defaults are all configured. Outputs a checklist with green/yellow/red status. Use when the user mentions "client handoff," "pre-launch checklist," "launch QA," "going live," "ready to ship," "is this site ready," or wants a final QA pass before publishing a site. --- # Client Handoff Checklist Every agency forgets something. This is the "before you push the launch button" QA pass for a BrightSite site. Run it the day before going live. > If the launch is a **redesign built on a staging site**, run this checklist against the > staging site (pass `site: "staging"` on the read tools below, or point at the staging > `preview_url`) *before* you `promote_staging`. See the `staging-redesign` skill. ## When to use this - About to point the domain at a BrightSite site. - About to email the client login credentials. - Just finished a site build and want to know what you missed. ## Inputs you need from the user 1. **Account ID** 2. **Public URL** — what URL will visitors hit? (Used to validate canonical URLs.) 3. **Anything skip-worthy?** — e.g. "no blog on this site, skip blog checks" or "no contact form, skip form checks." ## Workflow Run through every check below. For each, report `[OK]`, `[WARN]`, or `[FAIL]` and a one-line summary. Use plain text markers — no emojis unless the user explicitly asked for them. ### 1. Site identity (`mcp__brightsite__get_site_identity`) - `[FAIL]` if `title` is empty or still "Untitled Site" / default - `[FAIL]` if `logo_id` is null - `[WARN]` if `icon_id` (favicon) is null - `[FAIL]` if `business_type` is null (hurts schema/structured data) - `[WARN]` if `business_description` is missing - `[WARN]` if `email` or `phone` is missing (clients usually want at least one contact method) - For local businesses: `[WARN]` if address fields are missing or `geo_lat`/`geo_lng` is null - `[WARN]` if `social_profiles` is empty ### 2. Pages (`mcp__brightsite__list_pages` + `mcp__brightsite__get_page` on each) - `[FAIL]` if no page has `is_home_page: true` - `[FAIL]` if any page has `status` of `draft` and is linked from the homepage (sanity-check by scanning the home page HEEx for internal links) - `[WARN]` if any page has `has_unpublished_changes: true` — those staged changes won't be live - `[FAIL]` for any page missing `meta_title` or `meta_description` - `[WARN]` for pages where `canonical_url` is set to a different domain than the public URL Run a mini-version of `bulk-seo-audit` inline — count and report total SEO issues across all pages. ### 3. Error pages (`mcp__brightsite__list_error_pages`) The returned rows expose the HTTP status as `status_code` (integer), so filter on that field. - `[FAIL]` if no row with `status_code: 404` exists - `[WARN]` if no row with `status_code: 500` exists - `[WARN]` if any error page has `has_unpublished_changes: true` ### 4. Blog settings (`mcp__brightsite__get_blog_settings`) — skip if user said no blog - `[OK]` to skip if `enabled: false` and user confirmed no blog - `[FAIL]` if `enabled: true` but `blog_title` is empty - `[WARN]` if `blog_url` or `listing_url` are missing or do not start with `/`. These are path settings, not full domains. - `[WARN]` if `posts_per_page` is null or absurd (< 3 or > 50) - `[WARN]` if `enable_open_graph` is `false` (poor social sharing) ### 5. Posts (`mcp__brightsite__list_posts` + `mcp__brightsite__get_post` on each) — skip if no blog - `[WARN]` if zero published posts (clients usually want at least 1-3 launch posts) - `[WARN]` if any post has `has_unpublished_changes: true` - Inline mini-audit: fetch each post with `mcp__brightsite__get_post`, then count posts missing `meta_description` or both `feature_image_url` and `feature_image_id` ### 6. Forms (`mcp__brightsite__list_forms`) — skip if user said no forms - `[WARN]` if no forms exist but the homepage contains "contact" copy (probably an oversight). - `[OK]` if forms exist. - `[INFO]` Always print this reminder: the MCP cannot inspect form delivery/recipient configuration. Tell the user: **"I confirmed forms exist, but you must verify in the BrightSite dashboard that each form has a notification email or webhook configured. A form with no recipient silently drops submissions."** ### 7. Redirects (`mcp__brightsite__list_redirects`) - `[OK]` informational only — count how many redirects exist - If the site was a migration, `[WARN]` if zero redirects exist (probably forgot the redirect map) ### 8. Analytics (`mcp__brightsite__get_analytics` with `metric: "summary"`, `period: "7d"`) - `[OK]` if any data exists (means tracking is firing) - `[WARN]` if `total_page_views: 0` over the last 7 days — either it's pre-launch (fine) or tracking isn't wired up (bad) ### 9. Global code (`mcp__brightsite__get_global_code`) - `[WARN]` if `css_staged`, `js_staged`, or `tailwind_config_staged` differ from published versions (staged but unpublished global code) - `[OK]` otherwise ### 10. Production-readiness final pass - `[FAIL]` if any page or post still has placeholder text. Reuse the `heex`/`heex_staged` (pages) and `content`/`content_staged` (posts) strings already fetched in steps 2 and 5 — do NOT call any write tools. Scan BOTH live and staged versions; staged placeholder text will go live the next time the user publishes. Substrings to flag (case-insensitive): `"lorem ipsum"`, `"TODO"`, `"FIXME"`, `"test test"`, `"placeholder"`, `"xxx"` as a standalone token. Report the entity ID, which version (live vs staged), and the matched substring so the user can locate it in the editor. ## Output format End with a clear summary block: ``` Handoff readiness: NOT READY ----------------------------- [FAIL] 3 critical issues [WARN] 8 warnings [OK] 31 checks passed Critical issues to fix before launch: 1. Home page has no meta_description (page ID 8fd2k3jq91pn) 2. 404 error page does not exist 3. Site identity missing logo Warnings worth addressing: - 2 pages have unpublished staged changes - Blog has no posts published yet - ... Re-run this checklist after fixing the critical issues. ``` Or, on a clean site: ``` Handoff readiness: READY ------------------------- [OK] 41 checks passed [WARN] 2 warnings (non-blocking) Non-blocking warnings: - No social_profiles set in site identity - Blog has only 1 published post Ship it. ``` ## Anti-patterns to avoid - **Don't fix things automatically.** This is a checklist, not a remediation tool. The user wants to see what's wrong and decide. - **Don't grade on a curve.** A site missing a meta_description on the homepage is `[FAIL]`, even if the rest is great. Be honest. - **Don't skip checks the user didn't explicitly skip.** If the user didn't say "no blog," run the blog checks. ## Example invocation > Run the handoff checklist on account `YOUR_ACCOUNT_ID`, public URL is `https://acmehvac.com`. They have a contact form and a blog. ## Tools used - `mcp__brightsite__get_site_identity` - `mcp__brightsite__list_pages` + `mcp__brightsite__get_page` - `mcp__brightsite__list_error_pages` - `mcp__brightsite__get_blog_settings` - `mcp__brightsite__list_posts` + `mcp__brightsite__get_post` - `mcp__brightsite__list_forms` - `mcp__brightsite__list_redirects` - `mcp__brightsite__get_analytics` - `mcp__brightsite__get_global_code` This skill is **read-only**. It never calls a write tool. The placeholder-text check in step 10 runs against content already loaded into context — it does not call `search_replace` or any other mutating tool.
staging-redesign8.42 KB
---
name: staging-redesign
description: Build a BrightSite redesign or large change safely on a staging site, preview it, then promote it live in one atomic flip — instead of editing the live site directly. Use when the user wants a "staging site," "redesign safely," "build a new version without breaking the live site," "preview before it goes live," "rebuild the homepage," "flip the whole site at once," "a safe place to work," or is doing a full visual rebuild (new fonts/layout/multiple pages) they don't want visitors to see until it's ready. For per-entity staged edits on the live site (a single page's unpublished draft), the normal `*_staged` + `publish_page` flow is enough — use this skill only when you want a full isolated copy.
---
# Build a Redesign on a Staging Site, Then Promote
A **staging site** is a full, independent, editable copy of the live site's content
(pages, layouts, components, forms, blog, media, global CSS/JS, Tailwind config, site
identity, redirects) that lives under the same account. You build the redesign on it
invisibly, preview it at a gated subdomain, then **promote** it — one atomic flip that
makes the staging content live and archives the old live site (restorable). Account-level
data (custom domain, analytics, form submissions, team, billing, tracking IDs) is shared
and never touched by a promote.
Use this instead of editing the live site directly whenever the change is big enough that
you don't want visitors seeing a half-finished state, or you need to flip several entities
(global code + a layout swap + N pages) at the same moment.
## When to use this vs. the normal staged-fields flow
| Situation | Use |
|---|---|
| One page's copy tweak, a new draft page, a blog post | Normal flow: `update_page` writes `*_staged`, `publish_page` ships it. Live stays as-is until you publish. |
| Full redesign, new global fonts/layout, multi-page rebuild, "flip it all at once", "let me preview the whole thing first" | **This skill** — a staging site. |
The staged-fields flow only stages *content* fields (`heex_staged`, `css_staged`, …).
Structural fields — `layout_id`, `slug`, `status`, `is_home_page`, `meta_*`, and anything
`update_site_identity` sets — take effect on the **live** site immediately, with no staged
variant. A staging site stages *everything*, which is why it's the right tool for a rebuild.
## Key mechanic: the `site` param
Every content tool (`create_page`, `update_page`, `update_layout`, `update_global_code`,
`create_component`, `request_upload`, `update_site_identity`, `list_pages`, `get_page`, …)
takes an optional **`site`** param:
- **omitted** or `"live"` → the live site (default, backwards-compatible).
- `"staging"` → the account's staging site.
- an explicit **site ID** (from `list_staging_sites`) → that specific site, as long as it's
**active** (`live` or `staging`). Archived sites (rollback snapshots) are rejected —
they're immutable history; `restore_staging_site` first if you need to edit one.
Authorization is still by `account_id` — you must be a member of the org, and the target
site must belong to it. Passing a staging site ID as `account_id` does **not** work (it's a
site, not an account); always pass `account_id` = the org and select the site with `site`.
## Workflow
### 1. Create the staging site
```
mcp__brightsite__create_staging_site { account_id: "<ORG_ID>" }
→ { id, name, slug, type: "staging", preview_url, ... }
```
This clones the live site synchronously (content + media). It is **plan-gated** — it fails
with a "plan" / "limit" message if staging isn't on the account's plan or a staging site
already exists. If one already exists, either reuse it or `discard_staging_site` first.
Keep the returned `id` and `preview_url`. You can also fetch them any time with
`mcp__brightsite__list_staging_sites` or `mcp__brightsite__get_staging_status`.
### 2. Author into staging — pass `site: "staging"` on every write
Build the redesign exactly as you normally would, but add `site: "staging"` (or the site
ID) to each call. The live site is untouched.
```
mcp__brightsite__update_global_code { account_id, site: "staging", css_staged: "...", tailwind_config_staged: "..." }
mcp__brightsite__update_layout { account_id, site: "staging", id: "<layout_id>", heex_staged: "..." }
mcp__brightsite__create_page { account_id, site: "staging", title, slug, heex, params_schema, ... }
mcp__brightsite__request_upload { account_id, site: "staging", file_name, content_type } # media lands on staging
```
If you're building editable content, load the **visual-editor-authoring** skill and apply
it — `site: "staging"` composes with everything there.
> Reads honor `site` too. `list_pages`/`get_page` with `site: "staging"` show the staging
> copy; without it, they show live. Use this to diff-by-eye or verify your writes landed.
### 3. Preview before promoting
The staging site is served at its own gated subdomain — the `preview_url` returned by
`create_staging_site` / `list_staging_sites`. Open it (or screenshot it) and confirm the
redesign looks right. Tracking (GA/GTM/Pixel) is suppressed on staging previews, so you
won't pollute analytics.
### 4. Review the diff
```
mcp__brightsite__staging_diff { account_id: "<ORG_ID>" }
→ { has_changes, totals: {added, modified, removed}, by_type, drift }
```
`drift` = live items that were edited *after* the staging copy was made — a promote would
overwrite them with the staging version. If `drift` is non-empty and you didn't intend it,
reconcile before promoting (re-apply that live edit on staging, or accept the overwrite).
### 5. Promote (atomic flip)
```
mcp__brightsite__promote_staging { account_id: "<ORG_ID>" }
```
Runs in the **background**: it validates the staging content and takes a full backup of the
current live site first, then flips `live_site_id`. It returns immediately with
`status: "promoting"`. **Poll `get_staging_status`** until the staging site is gone and the
live site is the former staging site.
The previously-live site is archived and restorable:
```
mcp__brightsite__restore_staging_site { account_id } # undo the last promotion
mcp__brightsite__restore_staging_site { account_id, site_id: "<archived_id>" } # restore a specific archived site
```
### 6. (Optional) Discard instead of promoting
If the redesign is abandoned:
```
mcp__brightsite__discard_staging_site { account_id } # permanently deletes staging content + media, frees the slot
```
## Anti-patterns to avoid
- **Passing the staging site ID as `account_id`.** It's a site, not an account —
authorization is by org. Always pass `account_id` = the org and select the site with the
`site` param. (`account_id: "<staging_site_id>"` returns "Access denied".)
- **Forgetting `site: "staging"` on a write during a redesign.** The call silently targets
the LIVE site (that's the default). If a change you meant for staging shows up in
`staging_diff` as live `drift`, you wrote to the wrong site — reverse it and redo with
`site: "staging"`.
- **Using a staging site for a one-line copy fix.** Overkill. Use `update_page` +
`publish_page` on the live site instead.
- **Promoting without previewing.** Always open the `preview_url` (or screenshot it) and
run `staging_diff` first — a promote flips the public site.
- **Assuming promote is instant.** It's backgrounded (it backs up live first). Poll
`get_staging_status` to confirm; don't report "done" off the immediate `"promoting"`.
- **Expecting analytics/form submissions/tracking IDs to move.** They're account-level and
shared — a promote never touches them. Only site content moves.
## MCP tools used
- `mcp__brightsite__create_staging_site` — clone live → new staging site (plan-gated, sync).
- `mcp__brightsite__list_staging_sites` / `mcp__brightsite__get_staging_status` — find the
staging site ID + `preview_url`; check whether one exists.
- `mcp__brightsite__staging_diff` — added/modified/removed counts + `drift` vs. live.
- `mcp__brightsite__promote_staging` — flip staging → live (background; poll to confirm).
- `mcp__brightsite__restore_staging_site` — undo a promotion / restore an archived site.
- `mcp__brightsite__discard_staging_site` — delete the staging site and its content.
- Every content tool (`create_page`, `update_page`, `update_layout`, `update_global_code`,
`create_component`, `update_component`, `request_upload`, `update_site_identity`, …) with
the optional `site` param to target `"staging"` (or a site ID) instead of live.
visual-editor-audit10.3 KB
---
name: visual-editor-audit
description: Audit an existing BrightSite site for visual-editor editability — walk every page, component, and layout and flag what a non-technical user cannot edit (no data-bs-edit markers, components with empty props_schema, bare loops for repeating content, hardcoded internal links), then optionally fix it. Use when the user says "check what's editable," "the client can't edit X in the editor," "why can't I edit this," "audit the site for the visual editor," "make the existing site editable," "fix editability," or wants a pre-handoff QA pass on whether the site is editable. For authoring new content correctly the first time, use the visual-editor-authoring skill instead.
---
# Visual Editor Editability Audit
Walk every page, component, and layout on a BrightSite site and report what is **not
editable in the visual editor** — the things a non-technical client can't click and change
— then optionally fix them. This is the reactive counterpart to `visual-editor-authoring`
(which prevents the problem at create time).
The rules this audit enforces live in
**[editability-contract.md](editability-contract.md)** (in this skill's directory).
Read that file first; it is the source of truth for every check below.
## When to use this
- A client says they can't edit something in the visual editor.
- Pre-handoff QA: confirm the site is actually self-editable before giving it to the client.
- After a site was built quickly (or by another tool) and you suspect it's full of raw HTML.
- After running `visual-editor-authoring` on a large build, as a verification pass.
If you're creating new content, don't audit after — author it right the first time with the
`visual-editor-authoring` skill.
## Inputs you need from the user
1. **Account ID** — the BrightSite account to audit.
2. **Scope** — pages, components, layouts, or all. Default: all.
3. **Fix mode** — report only, or report then offer to fix? Default: report, then offer.
## The contract in one sentence
> The editor sees exactly two editable things: elements tagged `data-bs-edit="field"`, and
> `component()` instances whose component has a **non-empty `props_schema`**. Everything
> else is invisible.
Every check below is a way the authored content fails that rule.
## Workflow
### Step 1: Pull the inventory
- `mcp__brightsite__list_pages`
- `mcp__brightsite__list_components`
- `mcp__brightsite__list_layouts`
Report the counts before you start. If it's a large site (>150 entities), audit in batches
(≤5 concurrent) and tell the user you're throttling.
### Step 2: Fetch and evaluate each entity
For each entity, fetch with `get_page` / `get_component` / `get_layout`. Pages and layouts
return both live (`heex`) and staged (`heex_staged`) bodies — **audit both**, because a
staged body goes live on the next publish. Components return their `heex` and `props_schema`.
Evaluate against these checks:
**Critical** (the user genuinely cannot edit this content)
- **Page/layout has zero editable nodes** — no `data-bs-edit` anywhere AND no embedded
`component(...)`. The whole body is one opaque "Page Content" block.
- **Component has an empty `props_schema` (`{}`)** but its `heex` contains content/props.
The editor shows "no editable properties." (Caused by `create_component` without the
follow-up `update_component`.)
- **Repeating content rendered with a bare `:for`** (a `:for` with no surrounding
`data-bs-collection`/`data-bs-item` and not driven by a component `item_schema`). Items
can't be added, removed, reordered, or individually edited.
- **A collection prop's rendered loop has no `data-bs-collection`/`data-bs-item`/
`data-bs-edit` markers** — even when the component's `props_schema` has a populated
`item_schema`. Symptom: the panel shows empty placeholder fields for each item, and the
cards on the canvas don't hover-highlight or click-select. The `item_schema` makes the prop
appear; the rendered markers make it editable. Fix: add the three markers to the loop in the
component HEEx (`data-bs-collection="<prop>"`, `data-bs-item={idx}`, `data-bs-edit="<field>"`).
- **An editable prop passed as an inline literal** in a `component(...)` call — e.g.
`component("related", %{cards: [%{title: …, href: "/team"}]})`. The props panel reads the
component instance, not the literal, so those fields show as **empty defaults** and the
user can't edit the real per-page values. Fix: move the data into the page's `params_schema`
as a collection and bind it (`%{cards: @params.related_cards}`).
**Warning** (editable, but the editing experience is broken or fragile)
- **Internal links hardcoded** as `href="/slug"` instead of `href={page_url("id")}` — no
page picker, breaks on slug change. Flag missing `data-bs-edit-type="page"` on internal
`<a>` tags too.
- **`data-bs-edit-type="text"` on an element containing `<br>` or child tags** — the first
edit will flatten it. Should be `richtext`.
- **Tailwind classes inside a richtext field** (`class="…"` on a `<span>`/child within a
`data-bs-edit-type="richtext"` element) — dropped on save; should be inline `style="…"`.
- **`<a>` with an icon/child markup but no `data-bs-edit-target`** on the text child —
editing the label destroys the icon.
- **Component props with no `order` key** — fields list in arbitrary order in the panel.
- **`type:"page"` component prop whose default is a path** (`/contact`) not a page ID — the
picker shows "— Select a page —".
- **A reusable component takes a page-link/CTA prop but renders `href={page_url(@x)}`
directly** (no `resolve` helper) — breaks if the prop holds a literal path/anchor/`tel:`
instead of a page ID. Suggest the smart `resolve` helper (page ID → `page_url`, else raw).
- **`:if` guard or `||` fallback that relies on Elixir truthiness with possibly-empty
strings** (`:if={@label}`, `"" || @fallback`) — `""` is truthy, so an empty-label CTA still
renders and the fallback never fires. Should test `@label && @label != ""`.
**Info** (nice to improve)
- Long page with many `data-bs-edit` fields but no `data-bs-section` grouping — the element
tree is a flat wall; suggest wrapping logical sections in `data-bs-section="Label"`.
- A section's markup is duplicated across multiple pages as raw HTML — candidate to extract
into a reusable component.
- **Inline `data-bs-collection` without `data-bs-item-schema`** — it works, but only gets the
basic one-item-at-a-time editor. If the items have a clear shape, suggest adding
`data-bs-item-schema='{…}'` for the rich (collapsible / drag-reorder / typed) editor.
- **Ordered-list numbers stored as an editable field** — an item field like `num`/`number`
("01", "02") that's really just the position. Suggest rendering it with `bs_index(idx)` (or
`bs_index(idx, pad: 2)`) and dropping it from the data, so it auto-renumbers on reorder.
- **A CTA authored as two separate fields** (a `text` field + a `page` link side by side, or
a `type:"page"` link whose label is a separate prop) — suggest a single
`data-bs-edit-type="button"` (page) or a `type:"button"` component prop so label + link
edit as one grouped button.
### Step 3: Output the report
Markdown table grouped by severity, then by entity type. For each finding include:
- Severity marker — `[!]` critical, `[~]` warning, `[i]` info (no emoji unless asked).
- Entity type + title, and whether the issue is in the live or staged body.
- The specific defect and the contract rule it violates.
- The concrete fix (the marker/attribute to add, or the two-call sequence to run).
- The entity ID, so the user can jump to it.
End with a summary block:
```
Editability audit
-----------------
Pages audited: 14 (3 not editable)
Components audited: 9 (2 with empty props_schema)
Layouts audited: 2
Critical: 5
Warnings: 11
Info: 4
Top fixes by impact:
1. 3 pages have zero editable nodes — add data-bs-edit / componentize: [list]
2. 2 components have empty props_schema — run update_component: [list]
3. Gallery page uses a bare :for — convert to a data-bs-collection: [id]
```
### Step 4: Offer to fix
Ask before changing anything:
> Want me to fix the critical issues? I'll add `data-bs-edit` markers, set `props_schema`
> on the empty components, and convert bare loops to editable collections. I'll show each
> change as a diff and confirm before saving.
If yes, apply fixes with `update_page` / `update_component` / `update_layout`, following the
authoring contract. Show each change as a diff (`old → new`) and get approval per entity.
Key reminders while fixing:
- Setting a component's `props_schema` is an `update_component` call — `create_component`
can't do it.
- Content edits to pages/layouts go to the **staged** body; tell the user they must publish
(`publish_page` / `publish_layout`) to make the now-editable version live.
- When you add `data-bs-edit` markers, preserve the existing rendered output — markers
change editability, not appearance.
## Anti-patterns to avoid
- **Don't auto-fix without per-entity approval.** Rewriting a page's HEEx to add markers
can subtly change rendering if done carelessly. Show the diff.
- **Don't only audit the live body.** A clean live page with a non-editable staged body
ships the problem on the next publish. Check both.
- **Don't flag styled-but-static decorative elements as "not editable."** A background
flourish the client never needs to edit isn't a defect. Focus on content: headings,
body copy, images, links, CTAs, repeating items.
- **Don't report a component "fixed" after `create_component`-style edits alone** — verify
the `props_schema` is non-empty after the `update_component`.
- **Don't fetch hundreds of entities in parallel.** Throttle to ~5 concurrent.
## Example invocation
> Audit account `YOUR_ACCOUNT_ID` for visual-editor editability — pages, components, and
> layouts. Tell me what the client can't edit, then fix the critical stuff.
## Tools used
- `mcp__brightsite__list_pages` / `mcp__brightsite__list_components` / `mcp__brightsite__list_layouts`
- `mcp__brightsite__get_page` / `mcp__brightsite__get_component` / `mcp__brightsite__get_layout`
- `mcp__brightsite__update_page` / `mcp__brightsite__update_component` / `mcp__brightsite__update_layout` (only with explicit per-entity approval)
- `mcp__brightsite__publish_page` / `mcp__brightsite__publish_layout` (to make staged fixes live, with approval)
Referenced files: 1
visual-editor-authoring9.99 KB
---
name: visual-editor-authoring
description: Author BrightSite pages, components, and layouts whose content is editable in the visual editor — so a non-technical user can click and change text, images, links, and component props without a second pass. Use when creating or editing a page/component/layout via the BrightSite MCP (create_page, update_page, create_component, create_layout), when building a site or template, or when the user says "make this editable," "the client should be able to edit this," "build the site so they can change it themselves," or "use the visual editor / components properly." Load this BEFORE writing HEEx, not after.
---
# Author Visual-Editor-Editable Content
BrightSite content authored as plain HEEx renders fine on the public site but is often
**not editable in the visual editor** — the user can't click an element to change its text,
swap an image, or edit a component's props. They then have to come back and ask for it to
be "made editable." This skill makes you author it correctly the first time.
Read **[editability-contract.md](editability-contract.md)** (in this skill's directory) in
full before authoring. It is the source of truth for every rule below. This file is the
how-to; the contract is the reference.
## When to use this
- You are about to call `create_page`, `update_page`, `create_component`,
`update_component`, or `create_layout` and the content should be client-editable.
- You're building a whole site, a template, or a default/starter layout.
- The user wants non-technical end users (small-business owners) to self-edit content.
If you're only auditing or fixing an **existing** site for editability, use the
`visual-editor-audit` skill instead.
If you're doing a **full redesign or large rebuild** that shouldn't be visible on the live
site until it's ready, build it on a **staging site** — load the `staging-redesign` skill
and pass `site: "staging"` on the authoring calls below (it composes with every rule here).
## The rule that prevents 90% of second passes
> The editor sees exactly two editable things: elements tagged `data-bs-edit="field"`, and
> `component()` instances whose component has a **non-empty `props_schema`**. Raw HTML is
> invisible.
So before you write any HEEx, decide for each meaningful piece of content: is it a
`data-bs-edit` field, part of a component's `props_schema`, or a collection? If it's none
of those, the user can't edit it.
## Workflow
### Step 1: Read the contract
Read `editability-contract.md` in this directory. Internalize the decision rule and the
four silent traps.
### Step 2: Plan the editable surface before writing HEEx
For the page/component you're about to build, list the content the user will want to
change, and assign each a mechanism:
- **One-off scalar** (a heading, a single image, one button) → inline `data-bs-edit`.
- **A reused section** (hero, CTA band, footer block) → a `component()` with `props_schema`.
- **Repeating content** (pricing tiers, gallery, team, FAQ) → a collection (component
`item_schema`, or inline `data-bs-collection`/`data-bs-item` **+ `data-bs-item-schema`**
for the rich editor). Never a bare `:for`. Auto-number ordered items with `bs_index(idx)`.
Put the `data-bs-collection`/`data-bs-item`/`data-bs-edit` markers on the rendered DOM too.
- **Shared design, per-page data** (related-card grids, page-specific lists) → a reusable
component bound to a **page `@params` collection** (`%{cards: @params.related_cards}`),
with the data + `item_schema` in the page's `params_schema`. **Never** pass the editable
items as an inline literal in the `component(...)` call — the props panel can't read a
literal, so the fields show empty.
### Step 3: Author with the markers
Apply the contract. The high-frequency rules:
- Add `data-bs-edit="field_name"` to every editable element.
- Use `data-bs-edit-type="richtext"` for any text with `<br>` or inline formatting — and
inside richtext use inline `style="…"`, **never Tailwind classes** (they're dropped on
save).
- Internal links: `href={page_url("page_id")}` **and** `data-bs-edit-type="page"`; for a
CTA use `data-bs-edit-type="button"` (label + link edited as one grouped button).
- Inline collections with a known item shape: add `data-bs-item-schema='{…}'` to the
`data-bs-collection` wrapper for the rich (collapsible / drag-reorder / typed) editor.
- **Any collection — including one rendered inside a component — needs the markers on its
rendered DOM:** `data-bs-collection` on the container, `data-bs-item={idx}` per item,
`data-bs-edit` per field. A populated `item_schema` alone does NOT give canvas
hover/click-select; without the markers the panel shows empty fields and the cards don't
highlight. Never a bare `:for` with `{item.field}` and no markers.
- Ordered lists: render the number with `bs_index(idx)` (auto-renumbers) — don't store it.
- Link/CTA props in reusable components: resolve the href through the small `resolve` helper
(page ID → `page_url`, else raw) so the prop accepts a page ID *or* a literal
path/anchor/tel. (See the contract's "smart `resolve` href helper.")
- Elixir `""` is **truthy**: guard `:if`/`||` on non-empty (`x && x != ""`), or a CTA with an
empty label still renders / a fallback never fires.
- `<a>` with an icon/child markup: put `data-bs-edit-target` on the text child.
### Step 4: For components, do BOTH calls
`create_component` does **not** accept `props_schema`. A component without it has zero
editable fields. Always:
1. `create_component(...)` with the `heex`.
2. `update_component(..., props_schema: {…})` to define the editable props — each with a
`label`, `type`, `default`, and an integer `order`.
A `type:"page"` prop's `default` must be a page **ID** (not a path).
### Step 5: Verify before reporting done
Run the pre-ship checklist from the contract. Concretely, the page must have ≥1 editable
node, every reused section must be a component with a non-empty `props_schema`, every
repeating block must be a collection, and internal links must use `page_url` +
`data-bs-edit-type="page"`. If you can, re-fetch with `get_page` / `get_component` and
confirm the markers are present in the stored HEEx and the `props_schema` is non-empty.
## Anti-patterns to avoid
- **Shipping a page of clean `<div>`s with no `data-bs-edit` and no components.** It looks
done and edits nothing. This is the #1 cause of the second pass.
- **Creating a component and stopping** — leaving `props_schema` at `{}`. The editor shows
"no editable properties." Always follow `create_component` with `update_component`.
- **Tailwind classes inside a richtext field.** `class="text-teal-500 italic"` is silently
dropped on save. Use `style="color:#14b8a6; font-style:italic;"`.
- **`type:"text"` on text containing `<br>` or `<span>`.** The first edit flattens it to
plain text. Use `richtext`.
- **Bare `:for` loops for editable repeating content.** One opaque block — no add/remove/
reorder. Use a collection.
- **A component collection with `item_schema` but no markers on the rendered loop.** The
panel shows empty placeholder fields and the canvas cards don't hover/select. Add
`data-bs-collection`/`data-bs-item`/`data-bs-edit` to the rendered DOM — the `item_schema`
is necessary but not sufficient.
- **Passing editable items as an inline literal** in `component("x", %{cards: [...]})`. The
panel reads the instance, not the literal → empty fields. Bind to `@params.<collection>`.
- **Hardcoded `href="/slug"` for internal links.** No page picker, breaks on slug change.
Use `page_url(...)` + `data-bs-edit-type="page"`.
- **Trusting Elixir truthiness with empty strings.** `"" || @fallback` is `""`, and
`:if={@label}` is true when `@label == ""`. Guard on `x && x != ""`.
- **Props with no `order` key.** They list in random order; users notice. Set `order` on
every prop.
## The dangerous step
The component two-call sequence (Step 4). It is the easiest thing to get wrong because
`create_component` succeeds and looks complete — but a component with an empty
`props_schema` is locked. Never report a component done until `update_component` has set a
non-empty `props_schema`.
## Tools used
- `mcp__brightsite__create_page` / `mcp__brightsite__update_page` — author pages; put
`data-bs-edit` markers in `heex`, repeating content in `params_schema` collections.
- `mcp__brightsite__create_component` then `mcp__brightsite__update_component` — the
two-call sequence to create an editable component (`props_schema` only on update).
- `mcp__brightsite__create_layout` / `mcp__brightsite__update_layout` — same markers apply
to layout content.
- `mcp__brightsite__get_page` / `mcp__brightsite__get_component` — re-fetch to verify
markers and a non-empty `props_schema` before reporting done.
## Uploading images to the media library
To reference a real image in a page (`media(file_id)` / `media(file_id, aspect: "16:9")`
/ `media_url(file_id)`), the file must first exist in the org's media library. Use the
**two-step presigned flow** — do NOT use `mcp__brightsite__upload_file` (the local-path
convenience tool returns a bare `Internal error`):
1. `mcp__brightsite__request_upload` `{account_id, file_name, content_type}` → returns
`{file_id, upload_url}` (a presigned Cloudflare R2 URL, ~2h expiry).
2. HTTP **PUT** the raw bytes to `upload_url` with a matching `Content-Type` header:
`curl -X PUT -H "Content-Type: image/jpeg" --data-binary @file.jpg "$URL"` → expect **200**.
3. `mcp__brightsite__complete_upload` `{account_id, file_id, file_name, name,
content_type, width, height, size}` → creates the DB media record and returns
`{id, thumb_url, md_url, lg_url, orig_url}`. The returned `id` == the `file_id` you
passed; use it in `media(...)`.
Gotchas: use a `.jpg` extension, never `.jpeg` (the CDN signed-URL pipeline 404s on
`.jpeg` objects). Resize huge originals (3500px+) down to ~1400–2000px before the PUT so
uploads stay fast. For blog feature images, the same `id` is what you pass as
`feature_image_id` (preferred over `feature_image_url`, which is for external URLs).
Referenced files: 1
Technical details
- First seen
- Sep 30, 2026 · 22:02 UTC
- Last seen
- Oct 1, 2026 · 12:00 UTC
- Collection status
- Collected
plugin_asdk_app_69fe16bb7a048191af54574d5986aab2
Download listing JSON