---
title: "The standard MCP workflow"
description: "The standard ComStack MCP workflow for a fresh chat: resolve the project and page, pick the editor, draft, get approval, then publish."
url: "https://comstack.ai/docs/mcp/guide/"
---
## What this is

The standard workflow for managing a ComStack site over MCP, written as the *first-run loop* — the path a fresh chat follows with zero prior context. Given only a page URL, an assistant should resolve the project and page, open the right editor, draft a change, get the user's approval, and publish — without getting lost when the document id changes underneath it.

This page is the human-readable companion to two machine resources every client already loads: the server instructions (sent once per session) and the `get-guide` tool (full schema + Lighthouse rules). The canonical machine-readable agent guide is also the `transformento://guide` resource.

## How it works

The loop has seven moves:

```mermaid
flowchart LR
  A[Page URL] --> B[get-project-state]
  B --> C[Resolve page by slug plus language]
  C --> D{page_type}
  D -->|knowledge| E[create-page / update-page]
  D -->|custom| F[upload-custom-page]
  E --> G[Draft plus approval]
  F --> G
  G --> H[publish dry-run]
  H --> I[publish-confirm]
  I --> J[publish-status to live URL]
```

1. **Project.** Call `get-project-state` first. It returns `project_name` (echo it back on destructive tools), `site_url`, `available_doc_templates`, the live and draft pages, a compact `nav` tree, and a `workflow_hint` that restates this loop inline.
2. **Page.** Address a page by its **slug + language** — the durable handle. `get-page-content` and `update-page` accept either the Firestore doc id (`path`) or a `(slug, language)` pair; `language` defaults to the project's default locale. A reference that matches nothing returns a clean `Page not found`, never a raw database error.
3. **Editor.** Each page carries a `page_type`. A `knowledge` page is markdown — edit it with `create-page` / `update-page`. A `custom` page is vibe-coded `body_source` — edit it with `upload-custom-page`. Using the wrong tool is refused; see [Page types](/docs/mcp/page-types.md).
4. **Draft.** Editing a live page never changes it in place — it writes a draft and leaves the live URL up. Repeated edits to the same page collapse into one draft, and a no-op edit creates no draft at all.
5. **Approval.** `publish` is a dry run: it returns a diff manifest and a single-use `confirmation_token` (5-minute TTL) and changes nothing. Show the manifest to the user and get explicit approval — every time.
6. **Publish.** `publish-confirm` consumes the token, re-checks that nothing drifted since the dry run, and starts the deploy asynchronously, returning `{ publish_id, status: "running" }`.
7. **Verify.** Poll `publish-status` until `succeeded`. Its `published_urls` carry the resulting live path — the new doc id — so you read the post-publish identity back instead of guessing it.

## When to use it

- A fresh MCP session where the only input is a page URL or a vague request
- Onboarding an assistant or a teammate to the publish flow end to end
- Debugging "which step am I on?" when a tool response is unexpected

## Tools in the loop

| Step | Tool | Notes |
|---|---|---|
| Project snapshot | `get-project-state` | First call. Carries `workflow_hint`, `nav`, and `page_type` on every entry. |
| Read a page | `get-page-content` | By `path` **or** `(slug, language)`. Returns `page_type` + the resolved `path`. |
| Edit knowledge | `create-page` / `update-page` | Markdown. Editing live creates a draft. |
| Edit custom | `upload-custom-page` | Opaque `body_source` + translation markers. |
| Drop a draft | `discard-draft` | Removes a pending draft; refuses live, hidden, or archived paths. |
| Dry run | `publish` | Manifest + `confirmation_token`. No writes. |
| Deploy | `publish-confirm` | Async. Returns `publish_id`. |
| Poll | `publish-status` | `succeeded` → `published_urls`. |

## Example

> *"Change the headline on https://example.com/pricing/ to 'Simple, honest pricing'."*

```text
get-project-state(project_id)
  -> project_name, site_url, the pricing page (slug 'pricing', page_type 'knowledge')

update-page(project_id, slug: 'pricing', language: 'en',
            content: '## Simple, honest pricing ...')
  -> { action: 'draft_created_from_live', draft_id, draft_url }

(present the draft_url and the change, get approval, then publish:)
publish(project_id, project_name)
  -> manifest { drafts: [ { slug: 'pricing', change_type: 'replace', will_publish_to } ] },
     confirmation_token   (single-use, 5-min TTL)

publish-confirm(project_id, confirmation_token)
  -> { publish_id, status: 'running' }

publish-status(project_id, publish_id)
  -> { status: 'succeeded', published_urls: [ { slug: 'pricing', url } ] }
```

## Common errors

- **Editing with the wrong tool.** A markdown edit on a `custom` page (or `upload-custom-page` on a knowledge page) is refused with a redirect — check `page_type` first. See [Page types](/docs/mcp/page-types.md).
- **Caching a doc id.** The `path` rotates across an edit-publish cycle; address pages by `(slug, language)` and read the new `path` back from `publish-status`. See [Publishing](/docs/mcp/publishing.md).
- **Publishing without approval.** `publish` only returns a token; nothing deploys until `publish-confirm`. Always show the manifest first.
- **Expired token.** The `confirmation_token` lasts 5 minutes and is single-use — call `publish` again for a fresh one.

Full recovery steps for every structured error are in the [error reference](/docs/mcp/errors.md).

## Related

- [Set up the server](/docs/mcp/setup.md) — connect a client and try the first prompts
- [Page types](/docs/mcp/page-types.md) — knowledge vs custom, and the editor each uses
- [Custom pages](/docs/custom-pages.md) — author vibe-coded pages end to end
- [Publishing](/docs/mcp/publishing.md) — draft lifecycle, doc-id rotation, the dry-run to confirm flow
- [Errors](/docs/mcp/errors.md) — every structured error and how to recover
- [Tool catalogue](/docs/mcp/tools.md) and [Workflow examples](/docs/mcp/examples.md)
