> ## Documentation Index
> Fetch the complete documentation index at: https://docs.avocadostudio.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# MCP server

> Drive Avocado Studio from any Model Context Protocol host — 49 tools over stdio or streamable HTTP.

This page is for the developer connecting an agent to a site through Model Context Protocol. The **Avocado Studio MCP server** (`@avocadostudio-ai/mcp-server`) exposes your site's pages, blocks, media, history and publishing as Model Context Protocol tools. Any MCP host can connect to it: Claude Code, Claude Desktop, Cursor, your own agent built on an MCP client library, or anything else that speaks the protocol.

It is not tied to one vendor. The server is built on the standard [`@modelcontextprotocol/sdk`](https://modelcontextprotocol.io/) and ships both transports the specification defines — **stdio** for a locally spawned subprocess, and **streamable HTTP** for a URL a remote host connects to.

<Note>
  The MCP server obeys the same boundary as every other Avocado surface. Its 49 tools all bottom out in typed content operations against the orchestrator. There is no tool for editing a file, adding a dependency, or changing a route — an agent driving this server cannot reach your codebase.
</Note>

## How it fits together

```mermaid theme={null}
flowchart LR
  host["Any MCP host<br/>(Claude Code, Claude Desktop,<br/>Cursor, your own client)"]
  mcp["Avocado MCP server<br/>stdio or streamable HTTP"]
  orch["Avocado orchestrator<br/>HTTP :4200"]
  db[("SQLite session state")]
  site["Your site<br/>blocks + content adapter"]
  host <--> |"JSON-RPC"| mcp
  mcp --> |"REST"| orch
  orch --> db
  orch <--> site
```

The MCP server is a thin wrapper with no state of its own. Every content mutation is sent to the orchestrator's `POST /ops`, so Zod validation, the undo stack, the version log and demo-mode gating all still apply. Everything else — history, publishing, media, screenshots — is a call to the matching orchestrator route.

Each install is bound to exactly one site. The `(session, siteId)` pair comes from environment variables at launch, so tools never take a session or site argument.

Every request carries `x-avocado-client: mcp`, so the version log records an MCP edit as one.

### Which orchestrator it can drive

The MCP server sends no access token on its calls. Only `avocado-publish-content` attaches a credential, as `Authorization: Bearer`. That decides where it works:

| Orchestrator | Reads and edits | Your own block types |
| - | - | - |
| Standalone server | Work — `/ops` and the read routes are open there | **Refused.** The standalone process never ran your `registerBlocks()`, and the MCP server sends no `componentsManifest`, so an op on a site block type fails with `schema_violation` naming the missing manifest. Built-in types work |
| Library mode, development (no credential configured) | Work | Work — `/ops` checks against the mount's own manifest |
| Library mode with `ACCESS_PASSWORD_HASH` or `ORCHESTRATOR_ACCESS_TOKEN` set | **Every call answers 401** | — |

The discovery tools read `GET /blocks/manifest`. On a library-mode mount that is your site's catalogue; on the standalone server it is Avocado's built-ins.

## Two transports

| Transport | Entry point | Bin | Use it when |
| - | - | - | - |
| stdio | `src/index.ts` | `avocado-mcp` | The host spawns the server as a local subprocess. The usual choice for local development. |
| Streamable HTTP | `src/http.ts` | `avocado-mcp-http` | The host connects to a URL. Needed for remote deployments and for hosts that only accept a connector URL. |

Both entry points register the identical tool set, so nothing about the catalogue below depends on which one you pick.

## Connect it

### The generic stdio launch spec

Most hosts that spawn a local MCP server want the same three things. The file they live in and the key they sit under differ per host, so check your host's own documentation for where to put this — the values themselves do not change.

| What the host needs | Value |
| - | - |
| Command | `npx` |
| Arguments | `-y`, `@avocadostudio-ai/mcp-server` |
| Environment | `AVOCADO_SITE_ID` (required), `ORCHESTRATOR_URL`, `AVOCADO_SESSION` |

If your host prefers an installed binary to `npx`, install the package and point it at the `avocado-mcp` bin instead:

```bash theme={null}
npm i -g @avocadostudio-ai/mcp-server
avocado-mcp    # speaks MCP over stdin/stdout
```

The server writes nothing but JSON-RPC to stdout. Diagnostics go to stderr, which is what the protocol requires.

<Tip>
  Test the launch spec outside any host before wiring it up. `AVOCADO_SITE_ID=your-site npx -y @avocadostudio-ai/mcp-server` should start and sit waiting for input. If `AVOCADO_SITE_ID` is missing it exits immediately with a message saying so — that is the most common reason a host reports "server failed to start".
</Tip>

<Tabs>
  <Tab title="JSON config file">
    Many hosts (Claude Desktop among them) take an `mcpServers` object in a JSON config file:

    ```json theme={null}
    {
      "mcpServers": {
        "avocado-studio": {
          "command": "npx",
          "args": ["-y", "@avocadostudio-ai/mcp-server"],
          "env": {
            "ORCHESTRATOR_URL": "http://localhost:4200",
            "AVOCADO_SESSION": "dev",
            "AVOCADO_SITE_ID": "your-site-id"
          }
        }
      }
    }
    ```

    On Claude Desktop that file is `~/Library/Application Support/Claude/claude_desktop_config.json` on macOS. Restart the app after editing it, then open **Settings → Connectors → avocado-studio** to set per-tool permissions. The discovery and `get` tools are safe to auto-allow; keep mutations on **Ask**.

    Other hosts use the same object under a different filename, or a different key name entirely. The `command` / `args` / `env` triple is what carries over.
  </Tab>

  <Tab title="Claude Code CLI">
    ```bash theme={null}
    claude mcp add avocado \
      --env ORCHESTRATOR_URL=http://localhost:4200 \
      --env AVOCADO_SESSION=dev \
      --env AVOCADO_SITE_ID=your-site-id \
      -- npx -y @avocadostudio-ai/mcp-server
    ```
  </Tab>

  <Tab title="Your own MCP client">
    Any MCP client library can spawn the stdio server directly. In the TypeScript SDK:

    ```ts theme={null}
    import { Client } from "@modelcontextprotocol/sdk/client/index.js"
    import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"

    const transport = new StdioClientTransport({
      command: "npx",
      args: ["-y", "@avocadostudio-ai/mcp-server"],
      env: {
        ORCHESTRATOR_URL: "http://localhost:4200",
        AVOCADO_SESSION: "dev",
        AVOCADO_SITE_ID: "your-site-id",
      },
    })

    const client = new Client({ name: "my-agent", version: "1.0.0" })
    await client.connect(transport)
    const { tools } = await client.listTools()
    ```
  </Tab>
</Tabs>

### Streamable HTTP

Start the HTTP server. A bearer token is mandatory — the process refuses to start without one.

```bash theme={null}
AVOCADO_SITE_ID=your-site-id \
AVOCADO_MCP_BEARER_TOKEN=$(openssl rand -hex 32) \
npx -y -p @avocadostudio-ai/mcp-server avocado-mcp-http
```

It prints `avocado-studio MCP server listening on http://localhost:4300/mcp (siteId: your-site-id)`.

Connection details, for any host:

| Detail | Value |
| - | - |
| Endpoint | `http://localhost:4300/mcp` (or your public HTTPS URL) |
| Method | `POST` only. `GET` and `DELETE` return 405. |
| Auth | `Authorization: Bearer <AVOCADO_MCP_BEARER_TOKEN>` |
| Accept | `application/json, text/event-stream` |
| Session mode | Stateless — each POST is a self-contained JSON-RPC call, no session id |
| Health probe | `GET /healthz`, no auth, for load balancers |

Verify it with a raw JSON-RPC call before pointing a host at it:

```bash theme={null}
curl -X POST http://localhost:4300/mcp \
  -H "Authorization: Bearer $TOKEN" \
  -H "content-type: application/json" \
  -H "accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
```

<Warning>
  The bearer token is a shared secret, not a login. Do not expose the HTTP transport on a public address without TLS and a token you rotate, and never reuse your orchestrator's `DRAFT_MODE_SECRET` as the MCP bearer token.
</Warning>

### Environment variables

| Variable | Required | Default | Notes |
| - | - | - | - |
| `AVOCADO_SITE_ID` | yes | — | Which site this install edits. The process exits at startup if it is missing. |
| `ORCHESTRATOR_URL` | no | `http://localhost:4200` | Where the orchestrator is. Trailing slashes are stripped. |
| `AVOCADO_SESSION` | no | `dev` | Session key that scopes draft state. |
| `AVOCADO_PUBLISH_TOKEN` | no | — | `avocado-publish-content` refuses to run without it, and sends it as `Authorization: Bearer`. See the warning below. |
| `AVOCADO_MCP_BEARER_TOKEN` | yes, in HTTP mode | — | Bearer token clients must present. Read only by the HTTP entry point. |
| `AVOCADO_MCP_PORT` | no | `4300` | Port for HTTP mode. |

<Warning>
  **`AVOCADO_PUBLISH_TOKEN` is not checked where its name suggests.** The tool's own description says to set it to the orchestrator's `DRAFT_MODE_SECRET`, but no orchestrator route compares it with that. The standalone server checks publishes against `PUBLISH_TOKEN` in an `x-publish-token` header, which the MCP server does not send: with `PUBLISH_TOKEN` unset, any non-empty value publishes; with it set, `avocado-publish-content` answers 401. On a credentialed library-mode mount, the bearer is read as an access token, so it must be the `ORCHESTRATOR_ACCESS_TOKEN` value.
</Warning>

## Tool catalogue

**49 tools in 12 groups.** Every one is scoped to the bound `(session, siteId)`.

<Note>
  Looking for `avocado-scope-url`? It is now
  [`avocado-scope <url>`](/reference/cli#avocado-scope). It was the only
  tool here needing no orchestrator, no session and no site — and the question it
  answers is asked *before* Avocado is installed, so putting it behind a
  configured MCP server was backwards.
</Note>

### Discovery

Call these first. They read the block manifest from the **orchestrator**, which is the process that actually runs your site's `registerBlocks()`, so they report your own custom blocks and not Avocado's built-ins.

| Tool | What it does |
| - | - |
| `avocado-list-block-types` | Lists every block type this site can render, with display name, category, description, and whether it is structural chrome. |
| `avocado-get-block-schema` | Returns the JSON schema plus field metadata for one block type — required keys, enum options, list item fields, image aspect ratios. |

If the manifest cannot be read, both tools fall back to the MCP server's own built-in registry and say so in a `warning` field. Treat a warned answer as a guess, not as your site's catalogue.

Both tools report `chrome`, `fixed` and `shared` for each type. Neither reports the picker keys a site may set — `role`, `insertable` and `group` — so an agent on this surface cannot tell a building block from a section, or see that a type is hidden from insertion. Read `GET /blocks/manifest` directly if that matters.

### Session

| Tool | What it does |
| - | - |
| `avocado-whoami` | Returns the bound session and a summary of its state: `siteId`, draft page count, version, last mutation. The `source` field says whose content is behind the session — `draft`/`adapter` is the site's own, `demo` is Avocado's bundled demo content, and `unknown-site` means no site by that id is known here. Call it first to confirm you are editing the draft you think you are. |
| `avocado-list-sessions` | Lists every session this orchestrator holds state for, most recently mutated first: `{ sessionKey, session, siteId, version, draftPageCount, lastMutatedAt }`. Use it to find another draft without shelling into SQLite. |

### Quality assurance

| Tool | What it does |
| - | - |
| `avocado-check-editing-surface` | Runs the panel-coverage check against the site's live manifest and its real pages: which list rows a person can tell apart, which polymorphic branches actually narrow, which props are in the content but described by nothing, and where block type names collide with Avocado's built-ins. No browser, no screenshot, no model call. |

This is the tool an agent should call **before reporting an integration as done**. A site can build cleanly, serve a valid manifest, pass type checking and still hand a marketer a property panel whose rows all read `Item 4`. Nothing else in the toolchain looks for that.

It errors rather than guessing when the manifest is unreadable — a QA check that silently measured the wrong site's blocks would report an all-clear on a panel nobody has looked at.

Its companion check, `editableCoverage`, measures the preview rather than the panel and runs site-side. See [coverage checks](/integration/coverage) for both, and for what the numbers mean.

### Pages

| Tool | What it does |
| - | - |
| `avocado-get-page` | Fetches a page's full draft document — blocks plus meta — by slug. |
| `avocado-list-pages` | Lists every page in the draft, home first. Returns `slugs` and a richer `pages` summary of `{ slug, path?, title, updatedAt, blockCount }`. Read `path` before navigating: on a site with locale prefixes or a `basePath`, the slug is not the URL. Check `source` too — `demo` or `unknown-site` means the list is not your site's. |
| `avocado-create-page` | Creates a page from a slug, title and initial blocks array. |
| `avocado-rename-page` | Changes the slug, the display title, or both. A slug change rewrites internal links automatically. Distinct from SEO `meta.title`. |
| `avocado-duplicate-page` | Clones a page, optionally under a new slug and title and at a chosen nav position. The response carries a `blockIdMap` so you can target the copied blocks without a follow-up read. |
| `avocado-remove-page` | Deletes a page. Undoable. |
| `avocado-update-page-meta` | Patches SEO metadata — `title`, `description`, `ogImage`. |

### Batch and validation

| Tool | What it does |
| - | - |
| `avocado-batch-apply` | Applies an array of ops in one atomic transaction. All commit or none do, and the preview version bumps once for the whole batch. Use it for multi-op work — translating every field on a page, bulk list edits — instead of chaining single-op tools. Ops use the wire-format `Operation` schema, whose field names differ from the single-op tool parameters; the tool's own description carries the per-op shape list. |
| `avocado-dry-run-ops` | Validates a hand-built ops array **without mutating draft state**. Returns per-op results — applied, skipped or failed, each with a reason — and a structured before→after diff of changed pages, blocks and fields. Unlike a batch, a failing op does not abort the others, so you get the full "what would happen" map in one call. |

<Tip>
  Probe a large batch with `avocado-dry-run-ops` first. It is the cheapest way to find which of thirty ops has the wrong shape, and it costs no tokens and no state.
</Tip>

### Blocks and list items

| Tool | What it does |
| - | - |
| `avocado-add-block` | Inserts a block into a page, optionally after a given block id. |
| `avocado-update-block-props` | Patches one or more props on a block. Keys not in the patch are untouched. |
| `avocado-remove-block` | Deletes a block. Undoable. |
| `avocado-move-block` | Moves one block within its page. |
| `avocado-duplicate-block` | Clones a block, optionally onto a different page and with an explicit new id. |
| `avocado-reorder-blocks` | Reorders a page's sections in one atomic call by stating the final block order. Prefer it over repeated moves whenever more than one block changes position — sequential moves work from stale positions. Omit pinned chrome blocks such as `SiteHeader` and `Footer`. |
| `avocado-add-list-item` | Appends or inserts an item into a block's list field. The new item gets a stable id automatically. |
| `avocado-update-list-item` | Patches fields on one list item, targeted by `itemId` (preferred) or zero-based index. |
| `avocado-remove-list-item` | Removes a list item by `itemId` or index. |
| `avocado-move-list-item` | Moves one list item within its field. |
| `avocado-reorder-list-items` | Reorders every item of a list field in one atomic call by stating the final order. Entries are stable item ids, or current indexes for id-less lists such as `Table` rows. Prefer it over repeated moves for any sort, reverse or rearrange. |

Target list items by `itemId` rather than index wherever you can. Ids survive sibling inserts and removals; indexes do not.

### Site and theme

| Tool | What it does |
| - | - |
| `avocado-register-site` | Registers or updates this install's site entry: display name, `previewUrl`, `draftPath`, port, purpose, and the draft secret. Required once before the editor can find the site. Set `draftPath` for a site that wired Avocado into its own app, or draft screenshots will photograph its 404 page. |
| `avocado-list-sites` | Lists every site registered under the current session. |
| `avocado-get-site-config` | Fetches the site config — name, logo, tone, purpose, nav labels and groups, theme overrides. |
| `avocado-update-site-config` | Patches name, logo, `navLabels` or `navGroups`. Undoable. |
| `avocado-update-theme` | Changes the site-wide visual theme — brand and accent colours, background and surface, heading and text colours, heading and body fonts, corner radius. A merge patch over semantic tokens, not raw CSS variables; pass an empty string to reset a token to its default, and use the `cssVars` escape hatch only when a semantic token cannot express what you want. Undoable. |

### Media

| Tool | What it does |
| - | - |
| `avocado-upload-image` | Uploads base64 image bytes and returns a URL usable as a block prop. |
| `avocado-generate-image` | Generates an image from a text prompt through the configured provider (Gemini or OpenAI). Returns a URL and alt text. |
| `avocado-search-unsplash` | Searches Unsplash and returns `{ imageUrl, thumbUrl, alt, author }` results. |
| `avocado-transcribe-audio` | Transcribes a base64 audio clip. The orchestrator tries OpenAI (`gpt-4o-mini-transcribe` by default) and falls back to Gemini (`gemini-2.5-flash` by default) when OpenAI fails or is over quota. |
| `avocado-interpret-image` | Vision analysis: an image in, a one-sentence description out. Useful for alt text and for turning a screenshot into intent. |

### Publishing

| Tool | What it does |
| - | - |
| `avocado-compute-publish-diff` | Diffs the current draft against the last published snapshot — what a publish would change. |
| `avocado-publish-content` | Publishes the draft. Pass `slugs` to publish only those pages and leave every other page exactly as it is live. Requires `AVOCADO_PUBLISH_TOKEN`. |
| `avocado-get-publish-status` | Fetches the publish tracker: target type, state, last deployment, URLs. `{ status: 'idle' }` is the normal answer before a first publish and after any orchestrator restart, not a sign that publishing is broken. |
| `avocado-list-snapshots` | Lists published snapshots available to restore from. Latest 30 by default. |
| `avocado-restore-snapshot` | Rewinds the draft to a published snapshot, identified by its commit sha. |

### History

| Tool | What it does |
| - | - |
| `avocado-undo-edit` | Undoes the last change on a page. History is per page. |
| `avocado-redo-edit` | Redoes the most recently undone change on a page. |
| `avocado-restore-version` | Jumps to a version number from the history log without consuming the undo or redo stacks. Current state is pushed to undo first. |
| `avocado-discard-changes` | Throws away selected changes from the history log. Each affected page rolls back to the state it held immediately before the earliest version selected on it. A page has one timeline, so discarding a change also discards the later changes to that page — the response lists them under `discarded[].alsoDiscarded`. Versions with nothing recorded before them come back under `skipped` rather than being guessed at. The discard itself is undoable. |

### Planner

| Tool | What it does |
| - | - |
| `avocado-chat-plan` | Sends a natural-language instruction to the planner and lets the orchestrator decide whether to apply it or hold it for approval. A draft screenshot of the touched page auto-attaches when ops apply. A `pendingPlanId` in the response means approval is needed next. |
| `avocado-preview-plan` | Runs the planner in plan-only mode: returns the would-apply ops and a `planPreview` diff without mutating state. For a hand-built ops array, use `avocado-dry-run-ops` instead. |
| `avocado-approve-pending-plan` | Applies the plan waiting for approval. Pass the `pendingPlanId` from the prior response — the orchestrator rejects stale approvals. |
| `avocado-discard-pending-plan` | Discards the pending plan and clears the orchestrator-side state. |

### Preview

| Tool | What it does |
| - | - |
| `avocado-screenshot-page` | Takes a full-page screenshot and returns it inline as a JPEG. Defaults to the draft, so in-progress edits are visible before publish; pass `published: true` for the live route. The site needs a `previewUrl` registered. This is the visual feedback channel for chat-only hosts that cannot render the live preview. |

## Tools can disappear, on purpose

Eight of the 49 are gated on what the site's content adapter says it can honour. On a CMS-routed site where pages are created in the CMS and not by Avocado, `avocado-create-page` would apply cleanly to the draft, preview correctly, and then fail the entire publish transaction. So the server asks, and hides what the site has refused.

| Capability | Tools gated on it |
| - | - |
| `createPages` | `avocado-create-page`, `avocado-duplicate-page` |
| `deletePages` | `avocado-remove-page` |
| `structuralEdits` | `avocado-add-block`, `avocado-remove-block`, `avocado-move-block`, `avocado-duplicate-block`, `avocado-reorder-blocks` |

Three things follow from how that is built, and they matter if you are debugging a tool list:

* **Every tool registers first, unconditionally.** Registration never waits on the network, or the server would fail to start whenever the site is down. Gated tools are disabled afterwards, once the site answers, and the SDK emits `tools/list_changed`.
* **Silence is permission.** An unreachable orchestrator, an older one, and an adapter that declared nothing all produce the same answer: unknown, and unknown permits.
* **A gated tool refuses at call time too**, with text explaining why, because some hosts cache the tool list and ignore `tools/list_changed`.

`avocado-whoami` reports the current capability answer.

## How agents should use it

<Steps>
  <Step title="Confirm the session">
    Call `avocado-whoami`. Check `source`: if it says `demo` or `unknown-site`, stop and fix the `AVOCADO_SITE_ID` before editing anything. Everything after this point applies to whatever session you are actually bound to.
  </Step>

  <Step title="Discover the blocks">
    Call `avocado-list-block-types` once, then `avocado-get-block-schema` for any type you are about to add or edit. On a site with custom blocks, the schema is the only place the real prop shape exists. If either answer carries a `warning`, the manifest could not be read and the list is not your site's.
  </Step>

  <Step title="Read before you mutate">
    Call `avocado-list-pages`, then `avocado-get-page` for block ids and item ids. Ids are stable and opaque; positions are not.
  </Step>

  <Step title="Prefer the structural op">
    `avocado-update-list-item` with an id and a patch is cheaper and safer than replacing a whole list through `avocado-update-block-props`. `avocado-reorder-list-items` and `avocado-reorder-blocks` beat chains of moves, which compute from stale positions.
  </Step>

  <Step title="Batch the bulk, dry-run the finicky">
    Use `avocado-batch-apply` for the reliable bulk of a multi-step edit. Probe anything you are unsure about with `avocado-dry-run-ops` first, or run it as a separate single-op call, so one rejection does not roll back good work.
  </Step>

  <Step title="Check the editing surface before you report success">
    Call `avocado-check-editing-surface`. Fix what comes back, or list what you are leaving and why. A clean build is not evidence that the panel is usable — see [coverage checks](/integration/coverage).
  </Step>
</Steps>

## Related

<CardGroup cols={2}>
  <Card title="Custom blocks" icon="cube" href="/integration/custom-blocks">
    Register your own components so the discovery tools can describe them.
  </Card>

  <Card title="Coverage checks" icon="clipboard-check" href="/integration/coverage">
    `editableCoverage` and `panelCoverage` — what `avocado-check-editing-surface` reports and how to read it.
  </Card>

  <Card title="Publishing" icon="rocket" href="/integration/publishing">
    What the publish tools diff, and how a partial publish is computed.
  </Card>

  <Card title="Field table" icon="table" href="/integration/field-table">
    One declaration that feeds the schema, the panel, the projection and the merge.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.