---
title: "Agents and MCP"
description: "Cloudhal is built for agents to work on sites, with people in charge. Agents connect over the Model Context Protocol (MCP), call semantic tools instead of editing files, and work inside a policy engine: each agent has a…"
url: https://cloudhal.dev/docs/agents
source: https://github.com/cloudhal-dev/cloudhal/blob/main/docs/agents/README.md
---
# Agents and MCP

Cloudhal is built for agents to work on sites, with people in charge. Agents connect over the [Model Context Protocol](https://modelcontextprotocol.io) (MCP), call semantic tools instead of editing files, and work inside a policy engine: each agent has a mode and scopes, risky actions wait for a person, every change is a snapshot, and every call is in the activity log.

## Two ways to connect

| | Local | Remote |
|---|---|---|
| Server | `cloudhal mcp`, started by your client over stdio | `https://<studio-host>/mcp` on a deployment (streamable HTTP) |
| Works on | The project folder it runs in (`.cloudhal/` store) | The deployment's sites |
| Who the agent is | You, the local developer | An agent key created in Studio |
| Authentication | None (a local process) | `Authorization: Bearer <key>` or `x-api-key: <key>` header |
| Approvals | Asked in your MCP client | Asked in your client, or wait in Studio's **Approvals** |
| Set up | [Connect an agent](https://cloudhal.dev/docs/agents/clients) | [Remote agents](#remote-agents-on-a-deployment) below |

Both expose the same tools and the same policy. Use local while building a site, remote for agents that work on a live site, including scheduled ones.

## Modes

A mode says how much the agent does on its own. Choose it with `--mode` locally, or per key in Studio.

| Mode | What the agent does on its own |
|---|---|
| `plan` | Reads and previews only |
| `manual` | Nothing: every change waits for you |
| `edits` | Content edits; publish, rollback and deletes wait (the default) |
| `auto` | Also small article-only publishes within the site's publishing window |
| `bypass` | Everything except what always needs a person. Local only; a remote key in `bypass` works as `edits` |

Some actions always wait for a person, in every mode: anything that makes a person's data public, approving reader comments, and structural changes such as merging terms or adding redirects. An agent can ask for a more autonomous mode for a while (`request_elevation`); you grant it in Studio, possibly for less time, and it ends by itself.

## Approvals

When a call needs a person, the server asks your client with MCP elicitation. The question carries a summary of the change, such as the diff of a publish. The call goes through only if you accept it. Clients without elicitation get a `confirmToken`; the agent repeats the identical call with it after you confirm in the conversation.

On a deployment, pending approvals also appear in Studio under **Approvals**, with the same summary.

## What agents can do

The tools work on Cloudhal's site graph: sites, pages and their sections, articles and other entries, taxonomies, menus, media, snapshots and releases.

- **Read:** `list_sites`, `get_site`, `list_pages`, `get_page`, `list_entries`, `get_entry`, `search_site`, the taxonomy and redirect listings, analytics (`get_analytics`, `top_content`, `content_stats`, `broken_links`).
- **Write:** `create_page`, `update_page`, `create_entry`, `update_entry`, `add_correction`, `edit_body` and `patch_draft` for article bodies, taxonomy changes through `propose_term`, media (`upload_asset`, `fetch_asset_url`), menus (`set_menu`), design and chrome (`set_design`, `set_chrome`), translations (`save_translation`). Updates carry the node's current `rev`, so an agent never overwrites a change it has not seen.
- **Check:** `preview_page` returns a private preview link; `render_page` renders a page in Chrome and reports layout, contrast, alt text and heading problems.
- **Publish:** `create_snapshot`, `diff_snapshots`, `publish`, `rollback`, scheduled releases and embargoes.
- **Mode:** `get_mode`, `request_elevation`.

About 100 tools in all; a client lists them with their JSON Schemas. Every mutation creates exactly one snapshot. Errors have stable codes and say what to do next.

## Skills

The server publishes skills as MCP resources: `skill://<name>/SKILL.md` and its references. A skill is a short playbook for a task, such as writing an article or triaging comments. When a client follows a skill it sends `_meta["cloudhal.skill"]`, and the policy engine limits that call to the skill's declared scopes. Add your own with `cloudhal mcp --skills <dir>`.

## Remote agents on a deployment

1. In Studio, open **Agents** and create a key. Choose its mode and scopes; they can never exceed your own role. The secret is shown once.
2. Point your client at `https://<studio-host>/mcp` with the header `Authorization: Bearer <key>` (or `x-api-key: <key>`). See [Connect an agent](https://cloudhal.dev/docs/agents/clients#remote-server) for each client's format.
3. The agent's calls run as that key: Studio's **Activity** shows them, **Approvals** collects what waits, and you can change the key's mode or revoke it at any time.

Keep keys narrow. A key that reads reader comments should not also publish: comment text is written by strangers and reaches the agent. The [comments guide](https://cloudhal.dev/docs/guides/comments) and the [schedules guide](https://cloudhal.dev/docs/guides/schedules) (agent tasks with their own keys and budgets) go further.

## Security

- The local server trusts the person who started it; never expose `cloudhal studio --no-auth` beyond localhost.
- Remote keys are scoped, rate limited, revocable and audited. The `/mcp` endpoint refuses cross-origin browser requests.
- Treat content an agent reads (comments, fetched pages, imported files) as untrusted. Policy limits what any instruction in it can make the agent do.

More: the [threat model](https://cloudhal.dev/docs/security/threat-model), [ADR-0011](https://cloudhal.dev/docs/adr/0011-agent-layer) (the agent layer) and [ADR-0015](https://cloudhal.dev/docs/adr/0015-agent-modes) (modes).


---
title: "Connect an agent"
description: "Every MCP client connects to Cloudhal in one of two ways (which one?):"
url: https://cloudhal.dev/docs/agents/clients
source: https://github.com/cloudhal-dev/cloudhal/blob/main/docs/agents/clients.md
---
# Connect an agent

Every MCP client connects to Cloudhal in one of two ways ([which one?](https://cloudhal.dev/docs/agents#two-ways-to-connect)):

- **Local server:** the client starts `cloudhal mcp` in your project folder. Nothing to authenticate; the agent works on the project's local store.
- **Remote server:** the client calls `https://<studio-host>/mcp` on a deployment, sending an agent key created in Studio under **Agents** as `Authorization: Bearer <key>` or in the `x-api-key` header. Both work; use whichever your client supports. Bearer needs a deployment on Cloudhal 0.3.4 or later; on older ones use `x-api-key`, or upgrade and run `cloudhal deploy`. Studio shows each client's setup with the key filled in right after you create it.

`cloudhal mcp` serves the project folder it starts in. Configure local servers **per project** (the project-scoped files below), or start the client from the project folder.

## Run it with your package manager

The CLI is a dependency of your project, so the client runs the project's copy. Use the line for your package manager wherever the examples below say `npx cloudhal mcp`:

| Package manager | Command | `command` / `args` in JSON configs |
|---|---|---|
| npm | `npx cloudhal mcp --mode edits` | `"npx"`, `["cloudhal", "mcp", "--mode", "edits"]` |
| pnpm | `pnpm exec cloudhal mcp --mode edits` | `"pnpm"`, `["exec", "cloudhal", "mcp", "--mode", "edits"]` |
| Yarn | `yarn cloudhal mcp --mode edits` | `"yarn"`, `["cloudhal", "mcp", "--mode", "edits"]` |
| Bun | `bunx cloudhal mcp --mode edits` | `"bunx"`, `["cloudhal", "mcp", "--mode", "edits"]` |

Do not use `npx -y @cloudhal/cli mcp` for a local server: it downloads a separate copy of the CLI that may not match your project's version.

`--mode` is one of `plan`, `manual`, `edits` (default), `auto` or `bypass` ([modes](https://cloudhal.dev/docs/agents#modes)). `cloudhal mcp --preview-url http://localhost:4321` makes preview links point at your running `cloudhal dev`.

For the remote examples, put the key in an environment variable instead of the file:

```sh
export CLOUDHAL_API_KEY="<the key from Studio>"
```

## Claude Code

Local, from the project folder. `--scope project` writes `.mcp.json`, which you can commit so everyone on the project gets the server:

```sh
claude mcp add --scope project cloudhal -- npx cloudhal mcp --mode edits
```

Remote:

```sh
claude mcp add --transport http cloudhal https://studio.example.com/mcp \
  --header "Authorization: Bearer $CLOUDHAL_API_KEY"
```

Or in `.mcp.json`, with the key read from the environment when Claude Code starts:

```json
{
  "mcpServers": {
    "cloudhal": {
      "type": "http",
      "url": "https://studio.example.com/mcp",
      "headers": { "Authorization": "Bearer ${CLOUDHAL_API_KEY}" }
    }
  }
}
```

Check the connection with `/mcp` inside Claude Code. Approvals appear as questions in the conversation.

## Codex

Local, from the project folder:

```sh
codex mcp add cloudhal -- npx cloudhal mcp --mode edits
```

That writes `~/.codex/config.toml`, which applies to every folder. To keep the server with the project, put it in the project's `.codex/config.toml` instead (Codex reads it in trusted projects):

```toml
[mcp_servers.cloudhal]
command = "npx"
args = ["cloudhal", "mcp", "--mode", "edits"]
```

Remote, with the key taken from the environment and sent as `Authorization: Bearer`:

```sh
codex mcp add cloudhal --url https://studio.example.com/mcp --bearer-token-env-var CLOUDHAL_API_KEY
```

Or in `config.toml`:

```toml
[mcp_servers.cloudhal]
url = "https://studio.example.com/mcp"
bearer_token_env_var = "CLOUDHAL_API_KEY"
```

## Cursor

Local, in the project's `.cursor/mcp.json`:

```json
{
  "mcpServers": {
    "cloudhal": {
      "command": "npx",
      "args": ["cloudhal", "mcp", "--mode", "edits"]
    }
  }
}
```

Remote:

```json
{
  "mcpServers": {
    "cloudhal": {
      "url": "https://studio.example.com/mcp",
      "headers": { "Authorization": "Bearer ${env:CLOUDHAL_API_KEY}" }
    }
  }
}
```

## VS Code (GitHub Copilot)

Local, in the project's `.vscode/mcp.json`:

```json
{
  "servers": {
    "cloudhal": {
      "type": "stdio",
      "command": "npx",
      "args": ["cloudhal", "mcp", "--mode", "edits"]
    }
  }
}
```

Remote. VS Code asks for the key once and stores it securely:

```json
{
  "inputs": [
    { "type": "promptString", "id": "cloudhal-key", "description": "Cloudhal agent key", "password": true }
  ],
  "servers": {
    "cloudhal": {
      "type": "http",
      "url": "https://studio.example.com/mcp",
      "headers": { "Authorization": "Bearer ${input:cloudhal-key}" }
    }
  }
}
```

## Claude Desktop and claude.ai

Use the remote server: **Settings › Connectors › Add custom connector**, the URL `https://studio.example.com/mcp`, and a request header `Authorization` with the value `Bearer <your key>`. Claude connects from Anthropic's cloud, so this needs a deployment; `localhost` is not reachable. For a local project, use Claude Code.

## Windsurf

Remote, in `mcp_config.json` (global, so prefer the remote server):

```json
{
  "mcpServers": {
    "cloudhal": {
      "serverUrl": "https://studio.example.com/mcp",
      "headers": { "Authorization": "Bearer ${env:CLOUDHAL_API_KEY}" }
    }
  }
}
```

## Zed

In the project's `.zed/settings.json`:

```json
{
  "context_servers": {
    "cloudhal": {
      "command": "npx",
      "args": ["cloudhal", "mcp", "--mode", "edits"]
    }
  }
}
```

Remote: `{ "url": "https://studio.example.com/mcp", "headers": { "Authorization": "Bearer …" } }` in place of `command` and `args`.

## Gemini CLI

Local, in the project's `.gemini/settings.json`:

```json
{
  "mcpServers": {
    "cloudhal": { "command": "npx", "args": ["cloudhal", "mcp", "--mode", "edits"] }
  }
}
```

Remote (Gemini uses `httpUrl` for streamable HTTP):

```sh
gemini mcp add --transport http -H "Authorization: Bearer $CLOUDHAL_API_KEY" cloudhal https://studio.example.com/mcp
```

## Any other client

- **stdio:** command `npx`, arguments `cloudhal mcp --mode edits`, working directory: your project.
- **Streamable HTTP:** URL `https://<studio-host>/mcp`, header `Authorization: Bearer <key>` (or `x-api-key: <key>`).

A client that cannot send headers can reach the remote server through [`mcp-remote`](https://www.npmjs.com/package/mcp-remote) as a local stdio command: `npx -y mcp-remote https://studio.example.com/mcp --header "Authorization:Bearer ${CLOUDHAL_API_KEY}"`.

## Troubleshooting

- **The client lists no tools.** Run the command yourself in the project folder: `npx cloudhal mcp` should start and keep running (it waits for a client on stdin). An error there (Node older than 24, not a Cloudhal project) is what the client sees.
- **The agent edits a different site.** The local server serves the folder it started in. Use a project-scoped config, or start the client from the project.
- **`401 unauthorized` remotely.** The key is missing, revoked, or sent in another header. Cloudhal reads `Authorization: Bearer <key>` and `x-api-key`; on `/mcp` a bearer must be an agent key, not the deployment token.
- **`403 not_a_member`.** The key's owner no longer belongs to the deployment; create a new key.
- **A call says it is waiting for approval.** Accept it in your client, or in Studio under **Approvals** on a deployment.


---
title: "Docs for agents"
description: "These docs are published for agents as well as for people. Point your agent at them when it works on a Cloudhal project."
url: https://cloudhal.dev/docs/agents/docs-for-agents
source: https://github.com/cloudhal-dev/cloudhal/blob/main/docs/agents/docs-for-agents.md
---
# Docs for agents

These docs are published for agents as well as for people. Point your agent at them when it works on a Cloudhal project.

## Markdown pages

Every page is also available as Markdown. Add `.md` to its URL:

```txt
https://cloudhal.dev/docs/start/quickstart      → HTML
https://cloudhal.dev/docs/start/quickstart.md   → Markdown
```

`<page>/index.md` works too, and so does asking for the page itself with `Accept: text/markdown` (the answer carries `Vary: Accept` and an `x-markdown-tokens` estimate). Each Markdown file starts with frontmatter: `title`, `description`, `url` and `source` (the file in the repository). Links in it are absolute, so it reads correctly anywhere.

## llms.txt

[`/llms.txt`](https://cloudhal.dev/llms.txt) lists every page with a one-line description, grouped by section ([llmstxt.org](https://llmstxt.org)). [`/llms-full.txt`](https://cloudhal.dev/llms-full.txt) holds the whole documentation in one file, for tools that index or load everything at once.

Each section has its own pair, smaller to load:

| Section | Index | Full text |
|---|---|---|
| Start | [`/docs/start/llms.txt`](https://cloudhal.dev/docs/start/llms.txt) | [`/docs/start/llms-full.txt`](https://cloudhal.dev/docs/start/llms-full.txt) |
| Agents and MCP | [`/docs/agents/llms.txt`](https://cloudhal.dev/docs/agents/llms.txt) | [`/docs/agents/llms-full.txt`](https://cloudhal.dev/docs/agents/llms-full.txt) |
| Reference | [`/docs/reference/llms.txt`](https://cloudhal.dev/docs/reference/llms.txt) | [`/docs/reference/llms-full.txt`](https://cloudhal.dev/docs/reference/llms-full.txt) |
| Guides | [`/docs/guides/llms.txt`](https://cloudhal.dev/docs/guides/llms.txt) | [`/docs/guides/llms-full.txt`](https://cloudhal.dev/docs/guides/llms-full.txt) |
| Contributing and internals | [`/docs/contributing/llms.txt`](https://cloudhal.dev/docs/contributing/llms.txt) | [`/docs/contributing/llms-full.txt`](https://cloudhal.dev/docs/contributing/llms-full.txt) |
| Architecture decisions | [`/docs/adr/llms.txt`](https://cloudhal.dev/docs/adr/llms.txt) | [`/docs/adr/llms-full.txt`](https://cloudhal.dev/docs/adr/llms-full.txt) |
| Proposals | [`/docs/rfcs/llms.txt`](https://cloudhal.dev/docs/rfcs/llms.txt) | [`/docs/rfcs/llms-full.txt`](https://cloudhal.dev/docs/rfcs/llms-full.txt) |

## Tell your agent about Cloudhal

Add a few lines to your project's `AGENTS.md` (or `CLAUDE.md`):

```md
This is a Cloudhal site. Content (pages, articles, menus, settings) lives in Cloudhal's store,
not in files: change it through the `cloudhal` MCP server's tools, never by editing `.cloudhal/`.
The theme is the Astro code in `src/`.
Docs: https://cloudhal.dev/llms.txt (every page also as Markdown: add .md to its URL).
```

## The JSON Schemas

Cloudhal's contracts are JSON Schemas at permanent URLs: [`/schemas/v1/index.json`](https://cloudhal.dev/schemas/v1/index.json) lists them. Every node an MCP tool returns follows one of them.

## Searching the docs: the docs MCP

`https://cloudhal.dev/mcp` is a public MCP server for the docs: read-only, no sign-in, no key. It has two tools:

- `search_docs` with `query` (and optionally `section`: `start`, `agents`, `reference`, `guides`, `contributing`, `adr` or `rfcs`) returns up to 8 pages with title, URL and a snippet;
- `read_doc` with `url` (a `https://cloudhal.dev/…` URL or a path such as `/docs/agents/clients`) returns that page's Markdown.

It is **separate from Cloudhal's own MCP server** ([`cloudhal mcp`](https://cloudhal.dev/docs/agents/clients), or `/mcp` on a deployment), which works on your sites and has no docs tools. Connect the docs MCP to the agent that writes your theme or integrates Cloudhal; an agent that only edits content does not need it. The answers are public docs text, and the only thing sent to cloudhal.dev is the query. Requests are rate limited per IP address.

Claude Code:

```sh
claude mcp add --transport http cloudhal-docs https://cloudhal.dev/mcp
```

Codex:

```sh
codex mcp add cloudhal-docs --url https://cloudhal.dev/mcp
```

or in `~/.codex/config.toml`:

```toml
[mcp_servers.cloudhal-docs]
url = "https://cloudhal.dev/mcp"
```

Cursor, in `.cursor/mcp.json`:

```json
{
  "mcpServers": {
    "cloudhal-docs": { "url": "https://cloudhal.dev/mcp" }
  }
}
```

VS Code (GitHub Copilot), in `.vscode/mcp.json`:

```json
{
  "servers": {
    "cloudhal-docs": { "type": "http", "url": "https://cloudhal.dev/mcp" }
  }
}
```

Claude Desktop and claude.ai: **Settings › Connectors › Add custom connector** with the URL `https://cloudhal.dev/mcp` and no authentication. Other clients take the same URL as a Streamable HTTP server without headers.
