---
title: MCP tools
status: current
phase: 5
order: 2
---

# MCP tools

They all work on the token's site. Arguments and responses have the same shape as the [API](../api/index.md): **everything the API does, the MCP does**, and the last column says which route each tool maps to. A test fails if a route shows up without a tool.

Each tool needs a permission. The MCP **only shows the tools the token can use**; see [What the agent can do](#what-the-agent-can-do).

| Tool | What it does | Permission | In the API |
| --- | --- | --- | --- |
| `describe_space` | The site: locales and each content type with its fields, plus its [agent guidance](../guides/content-model.md#agent-guidance) if it has any | `read_published` | `GET /api/v1`, `GET /api/v1/spaces/:space_id`, `GET /api/v1/spaces/:space_id/content_types`, `GET /api/v1/spaces/:space_id/content_types/:id` |
| `update_site` | Sets the live and staging URLs, so entries come with their `url` and `preview_url` ([Staging](../guides/staging.md)); the content's language (`default_locale`, which moves the values, it doesn't translate) and `prefix_default_locale` ([Languages](../guides/languages.md)) | `manage_webhooks` | `PATCH /api/v1/spaces/:space_id` |
| `add_locale` | Adds a language for translations: `code`, `name`, `fallback_code` | `manage_model` | `POST /api/v1/spaces/:space_id/locales` |
| `update_locale` | Renames a language or changes its fallback | `manage_model` | `PATCH /api/v1/spaces/:space_id/locales/:code` |
| `remove_locale` | Removes a language; with values in it, only with `delete_values: true`, which deletes them | `manage_model` | `DELETE /api/v1/spaces/:space_id/locales/:code` |
| `search_docs` | Searches the Pluma docs and returns the matching pages | — | `GET /api/v1/spaces/:space_id/llms.txt` |
| `export_space` | The whole site in one JSON: types, entries (latest and published) and files | `write` | `GET /api/v1/spaces/:space_id/export` |
| `create_content_type` | Creates a type with its fields (each with `help`, `group`, `options`, `accept`), and optionally `kind`, `subtitle_field`, `image_field`, `position`, `entry_order`. With `dry_run: true` it saves nothing | `manage_model` | `POST /api/v1/spaces/:space_id/content_types` |
| `update_content_type` | Changes name, description, title field, guidance, previews, or how it shows in Content: `kind`, `subtitle_field`, `image_field`, `position`, `entry_order` ([How content is organized](../guides/content-model.md#how-content-is-organized)). The `api_id` doesn't change. `dry_run: true` saves nothing | `manage_model` | `PATCH /api/v1/spaces/:space_id/content_types/:id` |
| `add_field` | Adds a field to an existing type, with `help`, `group`, `options` (select types) and `accept` (file types). `dry_run: true` saves nothing | `manage_model` | `POST /api/v1/spaces/:space_id/content_types/:content_type_id/fields` |
| `update_field` | Edits a field: name, type (between text types, if every value fits), help, group, options, accept; `hidden: true` hides it without deleting data. `dry_run: true` saves nothing | `manage_model` | `PATCH /api/v1/spaces/:space_id/content_types/:content_type_id/fields/:id` |
| `set_order` | Orders the Content menu (`content_types`, `groups`; needs `manage_model`) or, with `content_type` + `entries`, that type's entries by hand (it then sorts by `sys.position`). `dry_run: true` saves nothing. See [Order](../api/management.md#order) | `write` | `POST /api/v1/spaces/:space_id/content_types/order`, `POST /api/v1/spaces/:space_id/content_types/:content_type_id/entries/order` |
| `set_quick_access` | Sets the shortcuts at the top of the dashboard's Content menu, in order: `items` like `[{"entry": "12"}, {"content_type": "faq", "label": "Questions"}]`, up to 8; `[]` removes them. Without `items` it answers with the current list. `dry_run: true` saves nothing. See [Quick access](../api/management.md#quick-access) | `manage_model` | `GET /api/v1/spaces/:space_id/quick_access`, `PUT /api/v1/spaces/:space_id/quick_access` |
| `list_entries` | Entries with filters: `content_type`, `fields` (equality), `order`, `limit`, `skip`, `rich_text`, `locale` | `read_published` | `GET /api/v1/spaces/:space_id/entries` |
| `get_entry` | One entry by `id`, in a `locale` if you want (`*` for all) | `read_published` | `GET /api/v1/spaces/:space_id/entries/:id` |
| `list_versions` | An entry's versions: who, when, which one is published | `read_drafts` | `GET /api/v1/spaces/:space_id/entries/:id/versions` |
| `create_entry` | Creates a draft entry: `content_type`, `fields`, and `locale` to write another language | `write` | `POST /api/v1/spaces/:space_id/entries` |
| `update_entry` | Changes only the fields you send. `version` keeps you from overwriting someone else's changes; `locale` writes another language | `write` | `PATCH /api/v1/spaces/:space_id/entries/:id` |
| `publish_entry` | Publishes the latest version (checks required fields, slugs, references, alt) | `publish` | `POST /api/v1/spaces/:space_id/entries/:id/publish` |
| `unpublish_entry` | Takes it off the site; the entry goes back to draft with its versions | `publish` | — |
| `archive_entry` | Archives: it leaves the site and stays saved | `publish` | `POST /api/v1/spaces/:space_id/entries/:id/archive` |
| `unarchive_entry` | Takes it out of the archive; it comes back as a draft | `publish` | `POST /api/v1/spaces/:space_id/entries/:id/unarchive` |
| `translate_entry` | Fills a language's missing localized fields from the default one with AI, as a draft version; `overwrite: true` redoes them all ([Translate with AI](../guides/languages.md#translate-with-ai)) | `write` | `POST /api/v1/spaces/:space_id/entries/:id/translate` |
| `batch` | Several operations in one call, all or nothing (up to 100). See [Batch](../api/management.md#batch) | `write` and `publish` depending on the operation | `POST /api/v1/spaces/:space_id/batch` |
| `list_assets` | The site's files, with URL, type, size and alt | `read_published` | `GET /api/v1/spaces/:space_id/assets` |
| `get_asset` | One file by `id` | `read_published` | `GET /api/v1/spaces/:space_id/assets/:id` |
| `upload_asset` | Uploads a file in base64 (`data`, `filename`) or from a `url`, with `title` and `alt` | `write` | `POST /api/v1/spaces/:space_id/assets` |
| `update_asset` | Changes a file's title, alt or description | `write` | `PATCH /api/v1/spaces/:space_id/assets/:id` |
| `list_api_keys` | The active keys, without the token | `manage_keys` | `GET /api/v1/spaces/:space_id/api_keys` |
| `create_api_key` | Creates a `delivery` or `preview` key and returns its token once | `manage_keys` | `POST /api/v1/spaces/:space_id/api_keys` |
| `get_activity` | The site's latest changes: who, what and when | `read_drafts` | — |
| `list_webhooks` | The deploy hooks, with their last delivery | `manage_webhooks` | `GET /api/v1/spaces/:space_id/webhooks` |
| `create_webhook` | Creates a deploy hook (`netlify`, `vercel` or `generic`) and returns its secret once | `manage_webhooks` | `POST /api/v1/spaces/:space_id/webhooks` |
| `test_webhook` | Sends a test hook and returns what the destination answered | `manage_webhooks` | `POST /api/v1/spaces/:space_id/webhooks/:id/test` |
| `delete_webhook` | Deletes a deploy hook | `manage_webhooks` | `DELETE /api/v1/spaces/:space_id/webhooks/:id` |
| `get_analytics` | The site's [visitors](../guides/analytics.md) for the last N days: from AI assistants, sources, pages, places, UTM campaigns, devices, and AI crawlers | `read_drafts` | `GET /api/v1/spaces/:space_id/analytics` |
| `get_answers` | What [ChatGPT answers](../guides/ai-answers.md) to the site's questions: mentioned, cited, position, competitors in the answers, wrong facts | `read_drafts` | `GET /api/v1/spaces/:space_id/answers` |
| `update_questions` | Replaces the questions Pluma asks ChatGPT every week (1 to 30) and, optionally, the country and city they're asked from | `manage_model` | `PATCH /api/v1/spaces/:space_id/questions` |
| `get_readiness` | The last [AI readiness check](../guides/ai-visibility.md) of the live site, with what to fix | `read_drafts` | `GET /api/v1/spaces/:space_id/readiness` |
| `check_readiness` | Reads the live site like an AI crawler and runs the checks again (once a minute at most) | `read_drafts` | `POST /api/v1/spaces/:space_id/readiness` |
| `get_search_queries` | What people [searched on Google and Bing](../guides/search-console.md): top queries and pages with clicks, impressions, CTR and position. Empty until an owner connects them | `read_drafts` | `GET /api/v1/spaces/:space_id/search_queries` |

## What the agent can do

The same as the person who connected it, based on their role on the site, and never more. The MCP **only shows it the tools it can use**; a tool it can't see gives `Tool not found`. In a [batch](../api/management.md#batch), an operation the role doesn't allow gives [`key_cannot`](../errors/key_cannot.md) and nothing is saved.

| Person's role | Agent's permissions | What it does |
| --- | --- | --- |
| Owner, Admin | all | Everything: content, publishing, model, keys, deploy hooks |
| Editor | `read_published`, `read_drafts`, `write`, `publish` | Creates, edits and publishes content and files. Doesn't touch the model, keys or deploys |
| Author | `read_published`, `read_drafts`, `write` | Creates and edits content and files, as drafts. Doesn't publish |
| Viewer | `read_published`, `read_drafts` | Read only |

- It's the role **today**: if it changes, the agent changes right away, without reconnecting.
- If an owner [limited the person to some content types or entries](../guides/team.md#limit-what-someone-can-edit), the agent gets the same limit: it only changes those, and elsewhere it only sees what's published.
- Keys (`delivery`, `preview`, `management`) have no person: their permissions are those of their kind (see [API](../api/index.md)).

## What clients confirm

Each tool tells the client what it does to the world, with the standard MCP annotations (`readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`, `title`). ChatGPT and Claude use them to decide when to ask you before running a tool: reading goes straight through, publishing or deleting asks first.

| Kind | What it means | Key annotation | Tools |
| --- | --- | --- | --- |
| read | Only reads. Clients run it without asking | `readOnlyHint: true` | `describe_space`, `search_docs`, `export_space`, `list_entries`, `get_entry`, `list_versions`, `list_assets`, `get_asset`, `list_api_keys`, `get_activity`, `list_webhooks`, `get_readiness`, `get_analytics`, `get_answers`, `get_search_queries` |
| change | Changes drafts, files or settings inside Pluma. Nothing reaches the live site | `readOnlyHint: false` | `create_content_type`, `update_content_type`, `add_field`, `set_order`, `set_quick_access`, `create_entry`, `update_entry`, `archive_entry`, `unarchive_entry`, `upload_asset`, `update_asset`, `create_api_key`, `create_webhook`, `update_site`, `update_questions`, `add_locale`, `update_locale`, `translate_entry` |
| public | Changes what the live site shows | `openWorldHint: true` | `publish_entry`, `unpublish_entry`, `batch` |
| destructive | Deletes something, or can lose data | `destructiveHint: true` | `update_field`, `delete_webhook`, `remove_locale` |
| external | Calls a URL outside Pluma | `openWorldHint: true` | `test_webhook`, `check_readiness` |

## Examples

**Create and publish**, in a conversation:

> You: "Create an event Science fair on November 5 at 2:00 PM, and publish it."
>
> The agent calls `describe_space` to see the fields of `event`, then `create_entry` with `{ "content_type": "event", "fields": { "title": "Science fair", "slug": "science-fair", "startsAt": "2026-11-05T14:00:00-06:00" } }`, and `publish_entry` with the `id` it got back.

**Read with filters:**

```json
{ "name": "list_entries", "arguments": { "content_type": "event", "order": "fields.startsAt", "limit": 5 } }
```

**Upload an image:**

```json
{ "name": "upload_asset", "arguments": { "filename": "playground.jpg", "data": "<base64>", "title": "Playground", "alt": "Recess in the playground" } }
```

Or with `"url": "https://…"` instead of `data` and `filename`. Same rules as the API: JPG, PNG, WebP, GIF, SVG or PDF up to 20 MB; MP4 or WebM up to 50 MB.

**Migrate lots of things at once:**

```json
{ "name": "batch", "arguments": { "operations": [
  { "op": "create_asset", "ref": "playground", "url": "https://…/playground.jpg", "alt": "Recess in the playground" },
  { "op": "create_entry", "ref": "post", "content_type": "post", "fields": { "title": "Hello", "cover": "$playground" } },
  { "op": "publish_entry", "id": "$post" }
] } }
```

If one operation fails, none are saved, and the error says which one in `details.operation`.

**Translate an entry** into a language the site has, then read it before publishing:

```json
{ "name": "add_locale", "arguments": { "code": "en" } }
{ "name": "translate_entry", "arguments": { "id": "12", "to": "en" } }
{ "name": "get_entry", "arguments": { "id": "12", "locale": "en" } }
```
