# cms-edit MCP guide

Use this guide when editing Contentful draft content through a hosted MCP connector (Claude Desktop Integrations + OAuth, or Grok Build).

{{SITE_INTRO}}

## Safety rules

cms-edit **cannot and will not**:

- Publish or unpublish entries
- Archive or delete published entries

All `save` operations create **drafts only**. A human must review and publish in Contentful.

**Assets:** cms-edit **can upload** draft assets via **staged upload** (hosted) or `--url` (public HTTPS last resort). **`--base64` is not supported on hosted MCP.** Always **search and reuse existing assets first** (`index sync`, then `asset search` or `list --type media`). Use `--if-exists-by-filename` when uploading to avoid duplicates.

## MCP tools

| Tool | Purpose |
|------|---------|
| `cms_edit` | Run any cms-edit command via an `args` array |
| `cms_edit_request_staged_upload` | Get a presigned upload URL for binary files (then `curl` + `asset upload --staged`) |
| `cms_edit_version` | Check the installed cms-edit version (no session required) |
| `cms_edit_help` | Read this guide by topic (`overview`, `workflow`, `commands`, `troubleshooting`, `hosted`) |

## Customer editor pack (read first)

When a site editor pack is deployed, these MCP resources provide **site-specific** context. Read them before creating or editing content:

| Resource | Purpose |
|----------|---------|
| `cms-edit://customer/index` | List of all customer resources |
| `cms-edit://customer/tasks-index` | Intent → task playbook map (filtered for this site) |
| `cms-edit://customer/capabilities` | Machine-readable list of what this site supports |
| `cms-edit://customer/checklist` | First-session onboarding steps |
| `cms-edit://customer/overview` | Site name, space, staging URL, handoff rules |
| `cms-edit://customer/routing` | Article/tag URL rules for this site |
| `cms-edit://customer/production-site` | Production URL and how to fetch page markdown (do not guess `.md` URLs) |
| `cms-edit://customer/components-index` | Component types and when to use each |
| `cms-edit://customer/collections-index` | Collection types |
| `cms-edit://customer/brand` | Brand voice and terminology (if configured) |
| `cms-edit://customer/articles` | Article/publication playbook (if configured) |
| `cms-edit://customer/defaults` | Featured image rules and article types (if configured) |
| `cms-edit://customer/site-facts` | Optional canonical business facts (if configured) |
| `cms-edit://customer/task-*` | Capability playbooks (e.g. `task-media-reuse-and-upload`) |

**Workflow:** read `tasks-index` → `capabilities` → `checklist` → `overview` → `routing` → `production-site` → pick a `task-*` playbook for your goal → `components-index` → then use `cms_edit` commands.

## MCP prompts

| Prompt | Purpose |
|--------|---------|
| `cms_edit_verify` | Check version and connectivity |
| `cms_edit_explore` | List site content and open a page |
| `cms_edit_edit_page` | Edit a page through diff → save |
| `cms_edit_edit_article` | Edit an article through diff → save |
| `cms_edit_index_sync` | Build/refresh local index (assets, media, templates, taxonomy) |
| `cms_edit_create_page` | Create a page with template selection |
| `cms_edit_create_article` | Create an article (requires article type; featured image rules) |
| `cms_edit_create_from_doc` | Create a page from a freeform document (infer → confirm → dry-run) |
| `cms_edit_create_article_from_doc` | Create an article from a freeform document |
| `cms_edit_taxonomy` | Import taxonomy entries (`create … --if-not-exists`) |
| `cms_edit_bulk_seo` | Update SEO meta descriptions (read `task-seo-descriptions`) |
| `cms_edit_bulk_find_replace` | Bulk find-replace in rich text across entries |
| `cms_edit_bulk_article_tags` | Update tags on one or more articles |
| `cms_edit_import_authors` | Create person/author entries from a list or sheet |
| `cms_edit_media` | Find existing assets or upload new ones (reuse-first) |

Read the full guide anytime via the `cms-edit://guide` resource or `cms_edit_help`.

## End-to-end QA prompt

For release QA after cms-edit or editor-pack upgrades, use a **copy-paste Claude Desktop test script** (phases: connector → customer resources → discovery → session → schema → media → dry-run writes → negatives). See [e2e-test-prompt.md](./e2e-test-prompt.md) to author one per customer site. Example: Brightline `docs/cms-edit/MCP-E2E-TEST.md`.

## Core workflow

Standard editing flow: **open → snapshot → read → set/rtf → diff → save**

Each step is a separate `cms_edit` call. Sessions persist in Redis between calls (hosted) or on disk (local). MCP auto-uses session `_mcp`.

### 1. Open a page

```
["open", "--page-slug", "/"]
["open", "--page-slug", "/pricing"]
["open", "--id", "<entry-id>"]
```

**Homepage (all SE Studio sites):** Contentful stores the marketing homepage slug as `index`. The live site URL is `/`. In cms-edit, `--page-slug /` and `--page-slug /index` are equivalent (both resolve to `index`). Read `cms-edit://customer/routing` for site-specific exceptions (e.g. Pedestal uses `home`).

### 2. Snapshot — see the content tree

```
["snapshot"]
```

Output shows `@c0`, `@c1`, … refs for each component. Use these refs in `read`, `set`, and `rtf`.

### 3. Read fields

```
["read", "@c1"]
["read", "@c1", "heading"]
```

### 4. Edit fields

```
["set", "@c1", "heading", "New heading text"]
["rtf", "@c1", "body", "--content", "# Title\n\nParagraph text."]
```

For rich text, prefer `--content` with inline markdown. Use `--base64` when special characters cause issues.

### 5. Review and save

```
["diff"]
["save"]
```

Always run `diff` before `save`. To abandon changes: `["discard"]`.

## When to use Contentful UI

| Task | Where |
|------|-------|
| Edit copy, add sections, create pages/articles | **Agent + cms-edit** (drafts) |
| Upload new images and files | **Agent + cms-edit** (`asset upload`) — search/reuse first |
| Publish approved drafts | **Contentful UI** |
| Edit copy directly in Contentful | **Avoid** — use cms-edit instead |

cms-edit saves **drafts only**. A colleague must **publish in Contentful** for changes to go live.

## Asset upload (hosted MCP)

Hosted connectors have **no server filesystem** and **reject `asset upload --base64`**. Use staged upload for all images and binary files.

| Source | When | Example |
|--------|------|---------|
| **Staged upload** (required for binary) | All images and files on hosted MCP | See **Staged upload** below |
| **`--url`** (last resort) | File already on public HTTPS fetchable by the host | `["asset", "upload", "--url", "https://…/photo.png", "--mime", "image/png", "--file-name", "photo.png"]` |

**After upload:** note the returned asset `id`, attach to the entry (`asset set @root media <id>` for person portraits), then `diff` → `save`. Upload alone does not update entries.

**PNG** when transparency matters.

Full workflow: `cms-edit://customer/task-media-reuse-and-upload`.

### Staged upload

Keeps binary **out of MCP tool args**. The agent uploads via `curl`, then passes only a short `uploadId`.

1. `cms_edit_request_staged_upload` with `fileName`, `mimeType`, optional `byteLength`, optional `title`
2. Run the returned `curlCommand` in a shell (POST multipart to `uploadUrl`)
3. `cms_edit` with the returned `consumeArgs` (includes `asset upload --staged <uploadId>`)
4. Link and save as usual

Hosted limit: **4 MB** per staged file (Vercel body limit). Rate limit: **1000 upload URLs per user per hour**. TTL: **60 minutes**, single-use.

## Featured images (articles)

Read `cms-edit://customer/defaults` when present. Otherwise:

1. **Custom** — set `featuredImage` or `featuredImageAssetFilename` on the article when the editor provides a specific image
2. **Article type** — if omitted, cms-edit inherits the linked **articleType** `featuredImage`
3. **Site default** — from `editor.defaults` in the site config (also in `defaults.json` in the editor pack)

Run `["index", "sync"]` before resolving assets by filename.

## Create content

### Create a page

```
["index", "sync"]
["list", "--type", "template"]
["create", "page", "--slug", "/about-us", "--title", "About Us", "--template-id", "<id>"]
["snapshot"]
```

Or use `create from-json` with `--dry-run --strict` for multi-section pages.

### Create an article

Every article requires an **articleType**:

```
["index", "sync"]
["list", "--type", "articleType"]
["create", "article", "--slug", "resources/blog/my-post", "--title", "My Post", "--article-type-slug", "resources/blog"]
```

Or `create from-json` with `"type": "article"` and `articleTypeSlug` / `articleTypeId`.

Use MCP prompt `cms_edit_create_article` for guided creation.

## Common sequences

### List all pages

```
["list", "--type", "page"]
```

### Refresh local index (assets, media, templates, taxonomy)

Run before asset filename search, media-by-filename list, or fast template/taxonomy lookup:

```
["index", "sync"]
["index", "status"]
["list", "--type", "template"]
```

Asset search and media-by-filename list **require** the index. Template/taxonomy list prefers the index when built.

### Explore the homepage

```
["open", "--page-slug", "/"]
["snapshot"]
["read", "@c1"]
```

### Edit copy on a page

```
["open", "--page-slug", "{{EXAMPLE_SLUG}}"]
["snapshot"]
["read", "@c1", "heading"]
["set", "@c1", "heading", "Updated heading"]
["diff"]
["save"]
```

## Field discovery

```
["schema", "component"]
["list", "--type", "page"]
["search", "pricing"]
```

## Grok Build setup

Grok connects to the same hosted MCP URL as Claude. Add to `~/.grok/config.toml`:

```toml
[mcp_servers.cms-edit-brightline]
url = "https://brightline.content.se.studio/api/mcp"
enabled = true
```

Replace the server name and host with your site's deployment. The URL **must** end with `/api/mcp` (not the site root).

**Connect:**

1. In Grok, run `/mcps` and select your cms-edit server
2. Press **`i`** to initiate OAuth (not just enable the toggle)
3. Sign in with Contentful when the browser opens
4. Verify with `grok mcp doctor <server-name>` or call `cms_edit_version`

OAuth is two steps: Grok obtains an MCP token, then you sign in with Contentful to link your account. If the browser flashes and closes immediately, reconnect after upgrading cms-edit (loopback redirect handling).

## Hosted connector rules

{{HOSTED_RULES}}

## Session tips

- After `open`, subsequent MCP commands share session `_mcp` automatically (stored in Redis on hosted).
- Use `--session <name>` only when juggling multiple pages — pass it **before** the subcommand on every call: `["--session", "my-work", "open", "--page-slug", "/"]`.
- Snapshot JSON includes `sessionName` and `sessionKey` for debugging.
- If you see "No active session", check `/api/health` (Redis must be ok), then run `open` again. Set `LOG_CMS_SESSION=1` on Vercel and search runtime logs for `[cms-edit:session]` (`redis save ok` / `redis load hit` / `requireSession miss`).

### peek vs open vs read

| Goal | MCP args |
|------|----------|
| Edit workflow (session) | `["open", "--page-slug", "/"]` then `["read", "@c0"]` |
| Preview tree + fields, no session | `["peek", "--page-slug", "/pricing"]` or `["peek", "--id", "<entry-id>"]` (JSON includes `rootFields`) |
| Read fields by entry ID, no session | `["read", "<entry-id>", "--id"]` or `["read", "<entry-id>", "--id", "heading"]` |
| Debug session persistence | `["diagnose"]` — reports store, userId, active session key |

- **`peek` is one-shot** — prints tree and **root field values** (`rootFields` in JSON). No session is created; do not chain `read @ref` afterward.
- **Use flat lookup flags** for `open` and `peek` — same as CLI (`--page-slug`, `--id`, etc.). See `help lookup`.
- **`read @root`** only works after `open` (uses the active session). The hint in peek output is for terminal users who already ran `open`.
- **Do not invent flags** — there is no `--read`, `--then`, or chained peek+read syntax. One command per `cms_edit` call.

## Troubleshooting

| Problem | What to do |
|---------|------------|
| **401 / connection failed** | Reconnect: Claude → Settings → Integrations, or Grok `/mcps` → press `i`. Complete Contentful sign-in. On Vercel, search runtime logs for `[cms-edit:oauth] auth_failure` (always logged). Set `LOG_CMS_OAUTH=1` for connect success traces. |
| **No cms_edit tool** | Confirm the integration is connected under Settings → Integrations |
| **No active session** | Do not pass `--space` on hosted. Check `/api/health` — `redis` must be ok. MCP uses `_mcp` session automatically. Manual session: `["--session", "name", "open", "--page-slug", "/"]`. Fallback: `["read", "<entry-id>", "--id"]` |
| **Wrong space** | Your Contentful account must have access to this deployment's space |
| **Check version** | Call `cms_edit_version` or `curl /api/health` and read `cmsEdit` |

## Articles

**Recent articles** (by publication `date`, not last-updated):

```json
["list", "--type", "article", "--sort", "date", "-n", "10"]
```

Alternative: `["search", "<keyword>", "--type", "article"]` (also sorts by date).

**Draft articles** — `peek` and `open` load drafts via CMA (not published-only). To discover drafts:

```json
["list", "--type", "article", "--sort", "date"]
["search", "<title>", "--type", "article"]
["list", "--type", "article", "--slug", "<slug>"]
```

Then open: `["peek", "--article-slug", "<slug>"]` or `["open", "--id", "<entry-id>"]`. Use `--article-slug`, not `--page-slug`.

**Published-only** is opt-in: `list --published` and `resolve --published` use the Delivery API index and omit never-published drafts.

After upgrading cms-edit, run `["index", "sync"]` once so the local index includes article `date` values (schema v4).

**Index freshness on hosted MCP:** The catalog index is shared via Redis (gzipped SQLite blob). `index status` on a cold instance may show **not built** until `list`, `open`, or another index command runs — that is normal. Committed writes (`save`, `create page`, `asset upload`, …) mark the index dirty; the next discovery command refreshes it. In-session `set` edits do not trigger a sync.

## Command reference (cms_edit args)

**Discovery:** `list --type <type>`, `search <query>`, `schema <contentType>`, `index sync`, `index status`

**Reading:** `open --page-slug <slug>`, `open --id <id>`, `peek --page-slug <slug>`, `peek --id <id>`, `snapshot`, `read <ref>`, `read <ref> <field>`, `read <id> --id [field]`

**Diagnostics:** `diagnose` — session store, userId, active session (after open/read issues)

**Editing:** `set <ref> <field> <value>`, `rtf <ref> <field> --content "<md>"`, `diff`, `save`, `discard`

**Creating:** `create page --slug … --title …`, `create article --slug … --title … --article-type-slug …`, `create from-json --file …`, `add <type> --content-type … --parent @c0 --target content`

**Other:** `health`, `version`, `guide [topic]`
