# Editing a bernard website over MCP — connect & instructions

> **This is the live connect reference** for editing a bernard site over MCP, served at `/agent`. Connect an assistant to `https://mcp.bernardmoves.com/mcp/site` (bearer auth) and it can read and edit your sites — every change lands on a draft you approve. Add the separate Shop, Courses or Email connectors when needed. (This replaces the retired feat-011 REST API.) See [docs/prd/feat-023-mcp-edit-server.md](prd/feat-023-mcp-edit-server.md).

bernard hosts your website. An AI assistant can edit it **through bernard's MCP server** — it calls tools that read and change your real site files, and every change lands on a **draft** you approve before anything goes live. (MCP = Model Context Protocol, the standard way assistants connect to tools.)

---

## Part A — For the site owner: connect your assistant

You don't need to be technical, and you never edit a config file. One connection covers **all your bernard sites**, and only yours.

1. **Get your setup prompt.** Log in to bernard → **Account → Your AI**. bernard hands you a ready-to-paste **setup prompt** — it carries a secure key scoped to **your own sites**, which you can replace any time (replacing it revokes the old key instantly).
2. **Connect your assistant** (one-time):
   - **Claude on the web (claude.ai)** — no key to paste: press **Connect to Claude** in Account → Your AI, sign in, press **Allow**. Disconnect any time from the same place.
   - **Claude Desktop / Cursor** — paste the setup prompt, or add the config by hand:
     ```json
     {
       "mcpServers": {
         "bernard": {
           "url": "https://mcp.bernardmoves.com/mcp/site",
           "headers": { "Authorization": "Bearer <your key>" }
         }
       }
     }
     ```
   - **Claude Code** — one command: `claude mcp add bernard-site --transport http https://mcp.bernardmoves.com/mcp/site --header "Authorization: Bearer <your key>"`.
   - **ChatGPT** — Settings → Connectors (developer mode) → add a custom MCP connector with that URL and the `Authorization: Bearer …` header. (Business/Enterprise plans may also offer a sign-in flow.)
   - **Other MCP clients** — any client that speaks Streamable HTTP MCP works: same URL, same header.
3. **Ask in plain English.** *"What's worth improving on my site?", "Fix the broken Instagram link", "freshen the copyright year", "make the buttons navy."* Your assistant makes the changes on a **draft**. If your assistant shows bernard's **prompts** (some clients list them as slash-commands or a task picker), each one is a ready-made job — health check, freshen, SEO audit, open a shop — you can run with one click; if it doesn't show them, just ask in your own words — the same jobs run either way.
4. **Review & publish.** It hands you a **preview link**. Look it over; if you're happy, press **Approve** in bernard and it goes live. **Nothing is live until you approve it.**

**Optional connectors.** Add only the areas you use: **Bernard Shop** at `/mcp/shop`, **Bernard Courses & Community** at `/mcp/courses`, and **Bernard Email** at `/mcp/email` on the same host. A bearer-key client can use the same account key for these URLs. A sign-in client asks you to authorise each connection. Bernard's in-app assistant chooses the relevant groups for each task itself. An external assistant can use another group only after that connector is enabled in its own settings; the Bernard Site connector can tell it which one to use, but cannot switch it on for you. The older `/mcp` URL retains the full catalogue for compatibility; remove that connection after adding the smaller ones if you want the context saving.

**What it costs:** bernard doesn't charge for editing over MCP — your assistant's own plan or API key pays for the AI's thinking; bernard just serves the tools.

Lost or worried about a key? **Account → Your AI → "Want to change your connection key?" → Replace key** — the old key stops working that instant, and your drafts stay safe. If the connection won't work at all, see [Help: my assistant won't connect](https://bernardmoves.com/help/assistant-wont-connect).

---

## Part B — For the assistant (agent instructions)

### 1. What bernard is (relay this confidence)
Every bernard site was moved off a platform like Squarespace, WordPress, Wix or Webflow and is now hosted by bernard as plain static files — there is no old CMS, dashboard or page builder behind it any more, and bernard is where it is edited. *(Canonical — kept byte-identical with the REST doc + the in-app system prompt.)* You are the one who edits it here; never tell the owner to "go and change it in Wix/Squarespace" — that platform no longer hosts the site.

### 2. Connect
- **Transport:** Streamable HTTP MCP at `https://mcp.bernardmoves.com/mcp/site` for the core Site connector.
- **Smaller catalogues:** `/mcp/site`, `/mcp/shop`, `/mcp/courses`, `/mcp/email` on the same host. The original `/mcp` retains every tool for existing connections. Each smaller connector lists only its own tools, task prompts and relevant help pages. If a request crosses groups, use both connected servers; if the needed one is absent, tell the owner its exact name and URL rather than inventing a missing tool.
- **Auth:** `Authorization: Bearer <your key>` on **every** request. The token is **scoped to the owner's account** — it covers all of their sites (call `listMySites` to see them) and nothing else; a slug outside that set is refused (`site_forbidden`). Older per-site keys still work and cover just their one site. If the token is missing/expired/revoked you'll get a 401 — ask the owner for a fresh setup prompt from **Account → Your AI** (Replace key).

### 3. The surface (tools, prompts, resources)
- **Tools** (`tools/list`) — 90+ tools. **Call `getDocs` first**: it returns the guide to how bernard works (the draft model, CSS handles, the no-scripts rule, pure-CSS interactivity, limits) as a short table of contents, then one section per call; `getDocs('tool-map')` is the canonical map of every tool by job. The guide is also on the open web at [`/agent/docs`](https://bernardmoves.com/agent/docs).
- **Prompts** (`prompts/list`) — every ready-made job (health check, freshen, SEO audit, design polish, open a shop, start a course, …) is an MCP prompt; `prompts/get` returns the full engineered protocol for that task, tuned for these tools. Offer them to the owner as things bernard can do; `getDocs('what-bernard-can-do')` lists the same menu if your client doesn't surface prompts.
- **Resources** (`resources/list`) — the agent guide (`bernard://docs/{section}`) and the whole help library (`bernard://help/{slug}`: 120+ owner problems, each with a paste-ready ask).
- **No specific ask? Call `suggestImprovements(slug)`** — one read-only call runs bernard's deterministic checks and returns a ranked, evidence-backed list of what's worth doing on the owner's actual site, each item naming the prompt that does the job.

The everyday core, in one table (the tool-map section has the rest — audit packs, shop, courses, images, verify/render):

| Tool | Use |
|---|---|
| `getDocs` | **READ FIRST** — how bernard works; TOC then sections on demand |
| `suggestImprovements` | ranked "what's worth doing" for this site, with evidence |
| `searchSite` | **CHEAP grep** over HTML+CSS — use FIRST to locate anything. Scope with `paths`/`fileTypes` (a bare extension like `"html"` is read as `"*.html"`); a scope that matches NO files is reported, so never read that zero as "the text is not there" |
| `getSeoFacts` / `getDesignFacts` / `freshenScan` / `contactFacts` / `linkCheck` | **audit packs** — one call replaces a whole discovery loop; call the relevant pack FIRST for its task family. `getSeoFacts` reads 50 pages per call in path order: `sitePageCount` is the site's true total, `nextStartAfter` continues the sweep. `canonical: null` is NORMAL (bernard adds a self-referencing canonical when the page is served — see `canonicalNote`), and heading counts collapse the desktop/mobile viewport twins a moved site carries |
| `setSearchVisibility` | take the WHOLE site out of search engines, or put it back — the robots noindex tag across every editable page in one commit, plus a re-generation of the course/shop pages that refuse hand edits. A request engines honour, not a lock; publish before it means anything |
| `getLinks` / `findElements` / `readCssRule` | locate a link / element / CSS rule → a **stable handle** |
| `readSiteFileRange` / `readSiteFile` | the lines around a hit / a whole file (EXPENSIVE — locate first) |
| `editDraftFile` | exact-string replacement(s) on the draft (`expectedDraftHead` for safe concurrency) |
| `setElementText` / `setAttribute` / `setCssDeclaration` / `patchCss` | targeted element / CSS edits by handle (or site-wide by selector) |
| `updateCopyrightYear` | **codified transform** — set the year everywhere in one deterministic call |
| `screenshotPage` / `getRenderedLayout` / `interactElement` | see / measure / operate a real render of the draft to verify your work |
| `getDraftPreview` | what's staged: preview URL, pending commits, and the files they change |
| `publishDraft` | make the draft live — **only on the owner's explicit instruction** (see below) |
| `runReport` | the owner's OWN business numbers (payments / subscribers / traffic / activity / courses) over a window — **the only honest source** for "how are sales", "how many visitors", "who signed up"; account-wide by default, read-only |

### 4. The workflow contract
1. **No specific ask → `suggestImprovements` first**, and offer the top items in plain language.
2. **Find before you read** (audit packs / `searchSite` first; whole-file reads are a last resort).
3. **Edit** with the targeted tools — every write lands on the **draft** and needs a short, plain-English **reason** (it becomes the owner's version history; end it with which assistant you are, e.g. `(by Claude)`).
4. **Check your work** — `getDraftPreview` gives the preview URL; verify a changed page with `screenshotPage`/`getRenderedLayout`/`interactElement` (or fetch it back) to confirm.
5. **Hand over** — give the owner the preview URL: *"Have a look — if you're happy, say so (or press Approve in bernard) and it goes live."* The preview is a normal web page the owner can open in a browser.
6. **Publish only when told.** `publishDraft` performs the owner's Approve from here — call it ONLY when the owner has explicitly asked, in this conversation, to make it live; review `getDraftPreview` with them first. Never publish on your own initiative, and never claim a change is live unless a publish actually ran.

### 5. Rules of the road
- **No `<script>` / tracking / third-party embeds** (enforced server-side).
- **Follow the site's existing markup + classes**; make the smallest change that satisfies the request.
- **Don't invent content** (no made-up prices, dates, reviews). Ask if you need a fact.
- **You're responsible for SEO** — preserve/improve `<title>`, meta, headings, image `alt`.
- **Site-wide changes: be consistent, and verify at scale.** Make the same change the same way on every affected page; on a large site, fetch a **representative sample** of changed pages from the draft preview — **each distinct template** — and confirm it landed and nothing else moved. A blind find-and-replace silently corrupts real markup. Tell the owner roughly how many pages you changed.
- **The owner can also drag things into place by hand.** On a mouse or trackpad, an owner can pick up a product card on the shop page or a photo in an eligible grid and drop it into a new spot themselves — the same jobs `reorderProducts` and `moveGridItem` do for you. Mention this when they ask how to reorder something, rather than walking them through a call they could do in one drag.

**Invitation-only websites (whole-site access).** A site can be public or invitation-only, and a separate family of tools reads and changes that. Eight rules govern every one of them:

- **`user_login` is history, not authority.** The contact kind records that somebody was invited once. Only the person's current whole-site grant says whether they can get in now - read the grant, never answer from the kind.
- **Revoking a login preserves everything else.** It removes one whole-site grant. The contact, their audience session, their orders, their course enrolments and their memberships all survive. Never describe a revoke as a deletion.
- **An invitation is transactional, never marketing consent.** Adding somebody to a site's login list grants no permission to advertise to them, and an unsubscribe never removes their way into a site they were invited to.
- **Every write and every send needs exact-target confirmation.** Quote the exact site, the exact action and the exact addresses, get the owner's yes on that quote, then repeat the call with the quoted digest. One vague yes never covers a recipient the owner was not shown.
- **Never reveal or log a credential.** There is no reusable website password, and an invitation link is one-use. Do not read a link, a token or a password back in chat, into a file, or into a change reason - the tools never return one, so if you are about to type one you have invented it.
- **Never report edge completion before confirmation.** A pending result means the edge projection or the mail is still taking effect. Only a done result is Bernard's own edge check saying the door is in place. Say "still taking effect", not "it is private now".
- **Previously served and indexed copies cannot be recalled.** Closing the door governs requests made after it is confirmed. A page somebody already saved, a file already downloaded and a search snippet already indexed are all still out there, and search results take time to drop. Say this in the quote, before the owner approves, not afterwards.
- **Course, membership and whole-site authorities stay distinct.** They share one identity and one audience session, and that is all they share. A course enrolment does not open an invitation-only site, a whole-site invitation does not enrol anybody on a course or make them a paid member, and removing one never removes another.

### 6. Limits (the short version — `getDocs('limits')` has the full list)
- **Rate:** 60 requests/minute and 1,000 writes/day per key — generous for real editing; there to stop loops, not enthusiasm.
- **Sampling:** on large sites the audit packs and site-wide reads work over a capped page sample and say so (`capped: true`) — tell the owner when a result is sampled.
- **Handles go stale:** `cref`s (CSS) after any write; page/screenshot views after any edit. Re-read before reusing.
- **Renders cost real work:** the screenshot/layout/interact tools run a real browser render — verify once per change; don't re-shoot what hasn't changed.

### 7. Troubleshooting
- **401 / "unauthorized"** → the key is missing, revoked or replaced. Ask the owner for a fresh setup prompt (**Account → Your AI → Replace key**).
- **`site_forbidden`** → that slug isn't in this key's account. `listMySites` shows what is. This is an authorisation boundary, not a typo: don't retry spelling variants.
- **`slug_near_miss`** → the slug is a near-miss of one this key DOES cover, and the error names the candidate(s). Retry with the exact slug it gives you. Distinct from `bad_slug`, which means the string isn't slug-shaped at all (a file path, say) and is not retryable.
- **Connected but no tools listed** → the client added the server without the `Authorization` header, or against the wrong URL — start with exactly `https://mcp.bernardmoves.com/mcp/site` over Streamable HTTP.
- **Tool errors teach** — every refusal names what to call or do instead; relay it plainly to the owner rather than retrying blind.
- **"No approval received" (or any approval/permission wording with no bernard `error_code`)** → that is the assistant's OWN tool-approval step, not bernard: the call never reached the server. Don't retry it and don't report it as a bernard bug - ask the person at the keyboard to approve the tool (usually one tap, often with an "always allow").
- Owner-facing walkthrough: [help/assistant-wont-connect](https://bernardmoves.com/help/assistant-wont-connect).

### 8. Acceptable use
Do not help make a site that deceives (fake scarcity, impersonation, fabricated reviews/credentials, deceptive pricing). If a request would mislead visitors, decline and say why.

---

## Migration note (for bernard maintainers)
This replaced the feat-011 REST API (now removed). The canonical migrated-site sentence in §1 must stay byte-identical with the in-app system prompt (the `tests/ai-migrated-site-context.test.ts` guard). The rate/write limits in §6 mirror `content/help/_facts.json` (`requests_per_minute`, `writes_per_day`) — update both together.
