Paperboy CMS
OfficialThis MCP server exposes Paperboy CMS content-management tools, acting as a Paperboy user with role-based permissions and multisite/locale support.
Browse the page tree one level at a time, list all pages/blocks/globals/trash, and inspect content types and sites/locales.
Read a content item’s working draft or published data, including fields, slug, status, revision, and version history.
Create, update, or set individual fields on drafts, with validation, merge/revision safety, and language-mismatch guards.
Publish immediately or schedule publishing, set expiry, unpublish, discard drafts, and roll back to older versions.
Copy content between locales, duplicate items, move pages in the tree, trash/restore items and subtrees.
List and manage media assets: update alt text, delete assets, search stock images, create upload links, or upload from URL/base64/stock.
Run the CMS writing assistant for SEO text, summaries, rewrites, translations, drafts, variants, and schema suggestions.
The Astro starter is the frontend the CMS serves: content managed through the MCP tools is delivered to an Astro site via the typed delivery API and @paperboycms/client, with on-page visual editing bridged by @paperboycms/preview.
Alternative AI provider for the ai_assist tool — any OpenAI-compatible endpoint can be configured in Settings → AI to perform editorial tasks such as meta generation, summarization, translation, rewriting and schema.org field suggestions.
The MCP-first headless CMS. Point Claude, Cursor or any MCP client at Paperboy and it can build pages, translate them, fix SEO, migrate redirects and publish on a schedule — as a real user, with that user's permissions, through the same validation and audit trail as your editors. Then open the admin: a visual editor where you click any text on your live site and change it.
Agents do the volume. People decide what goes live. Self-hosted, MIT, one container and a Postgres.
Paperboy ONE — Open, No Extras. · DPX — Don't Pay eXtra. A proper headless CMS without the "experience platform" invoice.
The website you build on is a separate repo, the Astro starter.
Two front doors, one CMS
Your agent (MCP) | Your editors (admin) | |
Gets in with | OAuth sign-in with a Paperboy login (2FA included) or a site-scoped token | Password, or email + TOTP code once 2FA is on |
Sees | Only the tools its role can use — an Author is never offered publish, users or webhooks | Only the sections and actions its role allows |
Writes through | The same Zod validation, RBAC and audit log — parity is test-enforced | ← same |
Is recorded as |
|
|
Related MCP server: agent-cms
Connect your agent
In Claude or Cursor, add
https://<your-domain>/mcpas a connector.Sign in with your Paperboy login and pick which site it may work in.
Ask for something real:
"Write a post about our summer opening hours in Norwegian, put it under Blog, and schedule it for Monday 09:00."
"Here are 40 URLs from our old WordPress sitemap. Redirect each to the matching page and check every one lands in one hop."
"Create an FAQ page type and fill it from this document."
"Which pages are missing a meta description? Draft them, don't publish."
"Who changed the home page last week, and what did they change?"
"Export this month's contact-form submissions."
Running locally? An admin mints a token in Settings → Agent access and use stdio — see MCP setup.
Why "MCP-first" means more than an MCP server
Every rule below exists because a real agent run broke without it.
You decide how much to trust it. Every version records whether a person or an agent wrote it, and agent drafts are badged Agent edited. Switch on agent review (Settings → Agent access) and agent drafts become Needs review: the agent can still write, but nothing goes live until a person approves.
Nothing an agent does is permanent by accident. Full version history, a trash with restore, drafts by default, scheduled publishing instead of fake dates in a field.
Errors teach instead of fail. A bad write is refused with the field name, the expected JSON shape and a copyable example, so the agent fixes it in one step. Paperboy never "succeeds" with mangled data — that once sent an agent into a nine-attempt loop.
Guard rails for agent-typical mistakes. Norwegian text can't silently land on the English branch. A blog post created at the root gets told where it belongs. Updates merge instead of wiping fields.
Long text survives the trip.
set_fieldtakes one flat string, because some clients mangle long strings nested in JSON.Files from the agent's own machine.
create_upload_linkgives the agent a 15-minute upload URL; no base64 through the context window.Tested the way agents use it. CI drives the real MCP server through editorial tasks — a deterministic driver on every push, a real model every week — and tool descriptions are graded publicly (the Glama badge above). The full rules are in
CLAUDE.md.
Where to start****Where to start
You want to… | Go to |
Connect an AI agent | |
Try it in five minutes | |
Build a website on it | Astro starter, or any framework with |
Run it for real | |
Work on the CMS itself |
See it
Click any text on your page and edit it right there. The preview is your real frontend, not a mock-up, and it updates as you type.
Your site stays yours. This is the Astro starter's demo site, every page of it built through the Management API. One command runs it with the CMS: docker compose -f docker-compose.demo.yml up -d --build.
Highlights
Built for agents
MCP server — OAuth 2.1 sign-in or site-scoped tokens; acts as a user and lists only the tools that user's role can use. See why MCP-first.
Secure by default — Argon2id, server-side sessions, CSRF, TOTP 2FA, deny-by-default RBAC with object-level scopes, append-only audit log.
Typed client SDK —
@paperboycms/client, typed from the same Zod schemas the server serializes with. No codegen.
Content
Content types are data — pages, blocks and globals are defined in the admin, not in code. Content areas hold inline or shared blocks; a built-in template library covers the essentials.
Multisite — several sites in one instance, each with its own delivery keys, media, preview URL and start page. Types, languages and users are shared.
Multi-language with a fallback chain, hierarchical URLs from the page tree, version history, trash. Translation gaps show in the tree and on the dashboard.
Forms with submissions in your database — a form is content. Delivery ships it as a schema, submissions are validated server-side against the published form, and errors come back per field. Spam checks on by default, retention runs, erase-by-email is one audited action. One spread makes a form a WebMCP tool for in-browser agents.
A/B experiments — test a content area of a page against up to three other versions. The frontend picks a version server-side (no flicker), counts are stored per day with no visitor data, cookieless by default, and no winner is named before the planned sample size. The winner becomes the area's draft.
Delivery
One read path, no leaks — public keys see published content, preview keys see drafts, private fields never serialize. Lists filter, sort, paginate and search; every item carries
fieldTypesso frontends switch on schema.SEO and structured data computed by the CMS — meta, canonical, OpenGraph, Twitter and JSON-LD (Organization, breadcrumbs, FAQ Question/Answer,
dateModified) on every page. Preview is alwaysnoindex.Public files generated from content — robots.txt, sitemap.xml, llms.txt and security.txt.
Redirects that keep link equity — a rename or move records a 301 on publish. The frontend matches one manifest with
matchRedirect; a 404 log shows what visitors miss.Image transforms —
?w=800&format=webp&q=75on any media URL, generated once and cached.Webhooks — HMAC-signed
content.published,form.submittedanddelivery.changedfor every move, sort or settings change, so a cache can purge on webhooks alone.
Editing
Visual on-page editing — click an element in the preview to edit its field, drag blocks onto the page, cross-origin included. The bridge is
@paperboycms/preview.Admin — React 19, light/dark, ⌘K palette, keyboard-operable drag-drop, axe-clean. The dashboard deep-links gaps, missing alt text and unused blocks.
AI writing assistant (optional; Anthropic or any OpenAI-compatible endpoint) — improve, rewrite, translate, alt text from the image, schema.org suggestions. With no key it refuses instead of faking output.
How it compares
What ships in the box as of 2026-10-17. core = built in · plugin · paid · no. Every cell is sourced in docs/comparison-sources.md; corrections welcome.
Paperboy | Strapi | Payload | Directus | Sanity | |
License | MIT | MIT + paid tiers | MIT | source-available | closed backend |
Self-host | one image | Node app | your Next.js app | one image | no |
Forms, validated server-side | core | plugin | plugin, partial | no | no |
SEO meta + JSON-LD from the CMS | core | plugin, no JSON-LD | plugin, no JSON-LD | plugin, no JSON-LD | no |
Auto 301 on rename | core | plugin | plugin, manual | no | DIY |
MCP server acting as a user | core | core | plugin (beta) | core | core |
Multisite | core | no | plugin | pattern | datasets |
Click-to-edit on the live page | core | paid | soon | core | core |
GraphQL | no | plugin | core | core | core |
Hosted option | no | paid | paused | paid | yes |
Ecosystem | none yet | large | growing | growing | large |
Quickstart
1. Run the CMS (Docker):
./scripts/setup.sh # writes .env with unique secrets and your admin password — required
docker compose up -d # db → init (migrate + seed) → api, which also serves the admin
# Admin http://localhost:8090 API http://localhost:8091 (OpenAPI UI at /api/docs)setup.sh is safe to re-run and never overwrites .env. Paperboy refuses to boot on the
placeholder secrets in this repo and refuses to seed production with the published demo login.
2. Run the website. The Astro starter renders every built-in content type and is where your site lives:
git clone https://github.com/hybriden/paperboy-astro-starter my-site
cd my-site && npm install && npm run setup && npm run dev # http://localhost:4321The admin's side-by-side preview points at http://localhost:4321 by default. apps/web in this
repo is a Next.js contract reference, not a site to build on; it is opt-in
(docker compose --profile web up -d web, :8092).
🪟 Windows, no experience needed
Install Docker Desktop, restart if asked, open it and wait for Engine running.
Download Paperboy: GitHub → green Code button → Download ZIP → right-click → Extract All….
Open a terminal there: open the extracted folder in File Explorer, type
powershellin the address bar, press Enter.Create your login (required; it gives your copy its own private keys):
powershell -ExecutionPolicy Bypass -File .\scripts\setup.ps1It prints an email and password. Save them now, they cannot be shown again.
Start it:
docker compose up -d(the first run downloads for a few minutes).Open http://localhost:8090 and log in.
Everyday: docker compose stop / docker compose start / docker compose ps.
⚠️ Never run
docker compose down -vonce you have content:-vdeletes the database. If a page won't load, check that Docker Desktop says Engine running.
Production
The generated .env targets localhost. Before exposing a real host set COOKIE_SECURE=true, drop
ALLOW_INSECURE_COOKIES, and point CORS_ORIGIN at the admin's HTTPS origin. Redeploy one service
with docker compose up -d --no-deps --force-recreate <svc>; the seed refuses to wipe a database that
holds content unless FORCE_SEED=1. The deploy-safety rules are in CLAUDE.md.
Without a VM. One container, no proxy: it fits any "run a container" platform (App Service, Container Apps, Cloud Run, Fly, Render, Railway) plus a managed Postgres.
Setting | Value |
Image |
|
Port | the platform's |
|
|
Secrets |
|
| the admin's HTTPS origin, with |
| the load-balancer range, so rate limits see real IPs |
Storage | a persistent volume at |
Remote MCP |
|
Run one replica: rate limits and the publish/retention timers are in-process. Migrations run on
boot. Create the first admin by running the image once with pnpm --filter @paperboy/db seed and real
SEED_ADMIN_EMAIL, SEED_ADMIN_PASSWORD, PAPERBOY_PUBLIC_KEY, PAPERBOY_PREVIEW_KEY.
Without a Postgres server. For a demo or a small site, DATABASE_URL=pglite:///data/db runs
Postgres inside the container (PGlite, the same schema and migrations) with the
database in a folder. Seeded on first boot, never reseeded:
docker run -d -p 8090:8091 -v paperboy:/data \
-e DATABASE_URL=pglite:///data/db -e UPLOADS_DIR=/data/uploads \
-e SESSION_SECRET=$(openssl rand -hex 32) -e CSRF_SECRET=$(openssl rand -hex 32) \
-e COOKIE_SECURE=false -e ALLOW_INSECURE_COOKIES=true -e ALLOW_DEMO_CREDENTIALS=true \
ghcr.io/hybriden/paperboy-app # http://localhost:8090, admin@paperboy.test / Admin!Passw0rdThat is a localhost demo. Exposed, use HTTPS settings and real seed values as below. It is one
Postgres session, so writes queue: fine for a small site, not for heavy traffic. Expect ~0.5 GB of
memory. Back it up with ops/backup-pglite.sh, which stops the container for a few seconds, archives the volume and starts it again.
Accounts. Your admin login is what setup.sh printed. The demo logins (editor@, author@,
viewer@, passwords <Role>!Passw0rd) are development fixtures: a production seed creates only the
admin unless ALLOW_DEMO_CREDENTIALS=true.
Backups
Nothing backs itself up. ops/backup.sh is a working reference: nightly pg_dump
plus a tar of the uploads volume, 14-day rotation, alert on failure. It refuses to succeed with an
empty media archive and writes mode-600 files, because a backup is a full credential dump and,
once you have forms, visitor personal data. Cron lines and the restore procedure are in
ops/README.md. Known gap: backups land on the same disk as the data; add one
rclone copy line once you have a bucket.
MCP setup
Remote clients (Streamable HTTP). Start the MCP container; the api forwards /mcp to it, so
clients use the admin's own domain:
docker compose --profile mcp up -d --no-deps mcp # → https://<your-domain>/mcpOn a one-container host, set MCP_EMBEDDED=true on the api instead and it serves the MCP itself.
Local clients (stdio). The client starts the server, so this goes in its config, not a terminal.
An admin mints a token in Settings → Agent access and point -C at your Paperboy checkout (after pnpm install):
{
"mcpServers": {
"paperboy": {
"command": "pnpm",
"args": ["-s", "-C", "/path/to/paperboy", "--filter", "@paperboy/mcp", "start"],
"env": {
"MCP_TOKEN": "mcp_…",
"DATABASE_URL": "postgresql://paperboy:paperboy@localhost:5433/paperboy"
}
}
}
}Keep -s: without it pnpm prints its script banner to stdout, which corrupts the stdio protocol.
The MCP acts as a Paperboy user and inherits its permissions. Tokens are minted per site (or for
every site); PAPERBOY_SITE pins a server to one site, and every per-site tool also takes a per-call site.
Toolsets. A connection lists the content tools by default (writing, editing, publishing,
media). The rest is opt-in, so an agent writing a page isn't choosing among user and webhook tools:
Toolset | For |
| pages, blocks, globals, versions, trash, media, the writing assistant |
| what the public website receives (published output, search) |
| content types and type templates |
| redirect rules and the 404 log |
| A/B tests of a page's content area: set up, start, pause, read results, end |
| form submissions (export, erasure) |
| users, delivery keys, webhooks, audit log, start page |
Pick them with PAPERBOY_TOOLSETS=content,redirects on the server (in .env with Docker Compose), or per connection on the URL:
https://<your-domain>/mcp?toolsets=content,redirects (all lists everything). Your role still
decides what you may do; a tool outside the chosen toolsets answers with how to enable it.
OAuth for remote clients. Add https://<your-domain>/mcp as a connector in Claude or Cursor. The
client registers itself, sends you through Paperboy's own login (with 2FA), and a consent screen asks
which site the connection may work in. Revoke it in Settings → Connected apps. One domain is enough:
the API forwards /mcp and serves /.well-known/oauth-*. For other layouts set PUBLIC_URL (the
issuer, default CORS_ORIGIN) and MCP_PUBLIC_URL (default {PUBLIC_URL}/mcp) on both api and mcp.
Files from the agent's machine. create_upload_link mints a 15-minute link the agent curls files
to (POST /api/v1/uploads, 20 files of 5 MB per request). upload_asset takes a public URL or base64.
The npm packages
Frontends integrate against these two packages, not the CMS codebase. Both are versioned independently.
@paperboycms/client — dependency-free, typed end to end:
import { createClient, mediaUrl } from "@paperboycms/client";
const cms = createClient({ baseUrl: "https://cms.example.com", key: process.env.PAPERBOY_PUBLIC_KEY! });
const post = await cms.getBySlug<BlogPost>("hello-world", { locale: "en" });
const { items } = await cms.list<BlogPost>("BlogPost", { sort: "-data.publishDate", limit: 10 });
const src = post?.data.image && mediaUrl(post.data.image.url, { w: 640, format: "webp" }); // image fields arrive as {url, alt, …}Also getByPath, startPage, global, search, render helpers that follow fieldTypes,
matchRedirect, applyExperiments for A/B tests, formToolAttrs for WebMCP forms,
preview-token verification and an optional ETag cache. 404s return null, other
errors throw PaperboyError. Full README.
@paperboycms/preview — zero-dependency bridge that makes any frontend
editable inside the admin's preview. Call initPreviewBridge() in preview mode and mark the DOM with
data-pb-field, data-pb-area and data-pb-block-index (or let the client's pbAreaAttrs emit
them). Full README.
Developing
apps/api Fastify v5 — Management API (auth/RBAC) + Delivery API (read-only, key-scoped); serves the admin
apps/admin React 19 + Vite — the editor
apps/web Next.js 15 — reference frontend, pins the delivery contract (opt-in)
apps/mcp MCP server (stdio or Streamable HTTP)
packages/shared Zod schemas + types (single source of truth) + the AI provider
packages/db Drizzle schema, migrations, query layer (object-level authz), seed
packages/client @paperboycms/client — typed Delivery API SDK (npm)
packages/preview @paperboycms/preview — on-page-editing bridge (npm)pnpm install
docker compose up -d db
export DATABASE_URL=postgresql://paperboy:paperboy@localhost:5433/paperboy
export UPLOADS_DIR=$PWD/.uploads # the default, /app/uploads, is the container path
pnpm db:seed
pnpm dev # api :8091, admin :8090, web :8092 (mcp waits for MCP_TOKEN)
pnpm --filter @paperboy/api test # integration tests against a real Postgres
pnpm --filter @paperboy/admin test:e2e # Playwright e2e + axeCI also runs the MCP usability eval (evals/) on every push with a deterministic mock driver and
weekly with a real model. Rules of the road: CONTRIBUTING.md. Everything about
deploy safety, auth, the content model and the incidents behind each rule: CLAUDE.md.
Stack choices: STACK.md. Research behind the bigger features: docs/plans/.
Vulnerabilities: SECURITY.md.
License
MIT — Don't Pay eXtra. 📰
Available Tools
25 toolsassist_writingARead-only
Run the CMS's writing assistant on a piece of text: SEO titles and descriptions, summaries, polishing, rewrites, variants, translation, drafting from a brief, schema.org field suggestions. Returns {result, provider}: result is the text (for variants, a JSON array of 3 strings). It saves nothing — write the result with set_field or update_content. It uses the model set in Settings → AI; without one, only meta_title, meta_description and summarize work (by truncation) and the rest are refused with a hint. It cannot write image alt text, since it never sees the image: write that yourself and save it with update_asset_alt. Requires the content.update permission (Admin, Editor, Author — an Author only within their own sections).
| Name | Required | Description | Default |
|---|---|---|---|
| task | Yes | meta_title / meta_description = SEO text from the input; summarize; improve = polish grammar and flow; rewrite; variants = 3 alternatives as a JSON array; translate (needs targetLocale); write = 2-4 paragraphs about the topic in input; schema_fields = suggested fields for the schema.org @type named in input; alt_text is refused (see above). | |
| input | Yes | The text to work on (or, for write, the topic/brief). | |
| targetLocale | No | Locale code of the output language, e.g. 'nb'. Required for translate; for improve/rewrite/variants it overrides 'same language as the input'. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations declare readOnlyHint=true/openWorldHint=true, and the description is consistent ('It saves nothing'). Beyond that it adds substantial context the annotations cannot: the {result, provider} return shape, the truncation fallback when no model is configured, which tasks are refused and why, and the permission requirements. No contradiction with readOnlyHint since nothing is persisted.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Dense but front-loaded: capability list first, then the critical 'saves nothing' contract, then fallbacks, then the alt-text exception, then permissions. Every sentence carries actionable information with no filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a multi-task generation tool with no output schema and open-world behavior, the description covers return values, persistence contract, model dependency, refusals, alternative tools, and access control. Nothing an agent needs to invoke it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3, but the description adds meaning beyond the schema: it clarifies that 'variants' yields a JSON array of exactly 3 strings, that write expects a topic/brief, and that alt_text is refused. These operational details about parameter behavior are not fully derivable from the enum listing alone.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a concrete verb+resource ('Run the CMS's writing assistant on a piece of text') and then enumerates the actual task space (SEO text, summaries, polishing, rewrites, variants, translation, drafting, schema.org fields). This clearly separates it from sibling content tools like set_field or update_content, which persist rather than generate.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly routes the agent: 'It saves nothing — write the result with set_field or update_content' and 'write that yourself and save it with update_asset_alt'. It also states the precondition for the model dependency and the permission/role scope (Author limited to their own sections), plus which tasks are refused.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
copy_variantADestructive
Copy a document's working version (name, slug and EVERY field) from one locale to another, server-side and atomically. USE THIS to move content that landed in the wrong language branch — re-typing it field by field loses fields. It REPLACES the target locale's draft (its unpublished edits are lost; its published version stays live until you publish_content the copy). The text is copied as-is: to translate it afterwards, run assist_writing {task:'translate'} and save with set_field. Returns the target locale's new draft in the get_content shape. To copy into a NEW item instead, use duplicate_content. Requires the content.update permission (Admin, Editor, Author — an Author only within their own sections).
| Name | Required | Description | Default |
|---|---|---|---|
| site | No | Site slug or id to act in (multisite). Omit to use this server's default site. Call list_sites to see what exists — an empty tree or search usually means the wrong site, not missing content. | |
| toLocale | Yes | Locale to copy TO; must differ from fromLocale. | |
| documentId | Yes | The item's documentId (stable across locales and versions), e.g. from list_tree, list_content or get_content. | |
| fromLocale | Yes | Locale to copy FROM, e.g. 'en' (list_sites → locales). |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only flag destructiveHint=true and non-idempotent; the description goes well beyond, disclosing exactly what is destroyed (the target locale's draft and its unpublished edits), what survives (published version stays live until publish_content), that the copy is verbatim text, and the auth requirement (content.update with role-scoping details). This is rich behavioral context beyond structured fields.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Dense but every sentence earns its place: core action front-loaded, then destruction semantics, then translation follow-up, then return shape, then alternative tool, then permissions. No filler despite the length.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
No output schema exists, yet the description states the return value ('target locale's new draft in the get_content shape'). Combined with the disclosed destructive behavior, permissions, and sibling routing, an agent has everything needed to call it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so all four parameters are already documented (site, toLocale, documentId, fromLocale). The description adds usage context — text copied as-is, translate via assist_writing — but no per-parameter syntax or value detail beyond the schema. Baseline 3 is appropriate when the schema carries the parameter burden.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (copy), resource (a document's working version) and scope (name, slug, EVERY field; locale-to-locale; server-side and atomic). It explicitly distinguishes itself from duplicate_content (new item) and contrasts with re-typing field by field, so an agent can tell it apart from siblings without opening schemas.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives an explicit when: 'USE THIS to move content that landed in the wrong language branch'. Names alternatives with conditions — duplicate_content for a new item, assist_writing/set_field for translating the copy. When-to-use and when-to-use-something-else are both covered.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
create_contentA
Create a new content item (page, block or global) as an unpublished draft, filled in ONE call: pass its fields in data (shapes from list_content_types {name}). Without data you get an empty shell that stays blank until update_content/set_field fills it. A page's position decides its URL and the template that renders it, so find the right parentId with list_tree (blog posts go under their blog page); omit it only for top-level sections. Under a list page, omit type to inherit the type it lists — a mismatch is refused, since the item would never appear on that list. Returns the new item in the get_content shape (documentId, locale, revision, data…), plus a hint when it still needs filling or placing. Then publish_content. To copy an existing item instead, use duplicate_content. Requires the content.create permission (Admin, Editor, Author — an Author only within their own sections).
| Name | Required | Description | Default |
|---|---|---|---|
| data | No | Initial field values (field name → value), same shapes as update_content. Provide this to create a FILLED draft in one call. Call list_content_types {name} first for the field names/shapes. | |
| name | Yes | Display name of the item (also the source of a page's auto slug), e.g. 'Summer sale 2026'. | |
| site | No | Site slug or id to act in (multisite). Omit to use this server's default site. Call list_sites to see what exists — an empty tree or search usually means the wrong site, not missing content. | |
| slug | No | URL slug for a page. Omit to auto-derive from the name. | |
| type | No | Content type name. Under a list-page parent, OMIT to inherit the type that page lists; a mismatch is refused unless allowTypeMismatch. | |
| locale | No | The LANGUAGE BRANCH for this content — MATCH the language you are writing (Norwegian content → 'nb', English → 'en'). list_sites reports the site's branches (`locales`). Defaults to the site default locale, which is usually English. | |
| parentId | No | documentId of the parent page. Required in practice for pages — find it with `list_tree`. Omit only for top-level pages. | |
| allowTypeMismatch | No | Set true ONLY for a deliberate sub-page whose type differs from the parent list page's listedType | |
| allowLanguageMismatch | No | Set true ONLY to deliberately create text whose language differs from the locale branch (the guard otherwise refuses, so e.g. Norwegian text can't silently land on the 'en' branch) |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations cover only the safety hints (not read-only, not idempotent, not destructive), so the description carries the real behavioral burden and does so: the item is created as an unpublished draft, missing `data` yields an empty shell until update_content/set_field fills it, a type mismatch is refused, and the response includes a `hint` when filling/placing is still needed. It also discloses the permission model (content.create; Author limited to their own sections), which annotations do not.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the core action and the one-call contract, then ordered by decision relevance (data → placement → type → return → next step → alternative → permissions). It is long and dense for a single paragraph, but nearly every clause carries decision-relevant information rather than filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
No output schema exists, yet the description states the return shape (the get_content shape: documentId, locale, revision, data) plus the conditional `hint`. Combined with the placement, type-inheritance, locale-matching and permission guidance, an agent has everything needed to invoke this 9-parameter tool correctly and interpret the result.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the schema already documents each parameter and the 3 baseline would apply on its own. The description nonetheless adds cross-parameter meaning beyond the schema: parentId determines a page's URL and rendering template, `type` is inherited under a list page and refused on mismatch, and locale is framed as the language branch that must match the content language. That is genuine added semantics over the per-field schema text.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a precise verb and resource ('Create a new content item (page, block or global)'), including the resulting state (unpublished draft) and the one-call filling behavior. It explicitly names the alternative for copying (duplicate_content) and the follow-up (publish_content), so an agent can place it in the workflow without opening a schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives concrete when-to-use rules: pass `data` to get a filled draft, call list_content_types first for shapes, use list_tree to find parentId (blog posts under their blog page), omit parentId only for top-level sections, omit `type` under a list page, and use duplicate_content to copy an existing item. Exclusions and failure conditions (type mismatch refused) are stated rather than implied.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
create_upload_linkA
Get a 15-minute link for uploading LOCAL files (images, PDFs) into the media library — the way to upload files on your own machine, which this server can't read. POST them as multipart/form-data with the returned header, e.g. with curl (several -F file=@… per request is fine). The response lists each stored asset's documentId: set it on an image field with set_field, and give it alt text with update_asset_alt. The link uploads as you, into this site (pass site to upload into another), and expires after 15 minutes. Returns {uploadUrl, headers, expiresAt, example, limits}: PNG, JPEG, GIF, WEBP or PDF, max 5 MB per file, up to 20 files per request. Refused when the server has no PUBLIC_URL configured — then use upload_asset with dataBase64. For a file at a public URL, or a stock photo, use upload_asset instead. Requires the content.create permission (Admin, Editor, Author — an Author only within their own sections).
| Name | Required | Description | Default |
|---|---|---|---|
| site | No | Site slug or id to act in (multisite). Omit to use this server's default site. Call list_sites to see what exists — an empty tree or search usually means the wrong site, not missing content. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only assert a non-read-only, non-destructive, non-idempotent write; the description goes well beyond them by disclosing the 15-minute expiry, the multipart/form-data POST with a returned header, per-file and per-request limits (5 MB, 20 files), supported formats, the PUBLIC_URL refusal condition, and the required content.create permission with role scoping.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The purpose is front-loaded in the opening clause, and the remaining text is information-dense with no filler: each sentence contributes a usage rule, a constraint, or a follow-up step. Nothing here could be removed without losing an operational detail.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Despite having no output schema, the description enumerates the exact response shape ({uploadUrl, headers, expiresAt, example, limits}), the accepted media types, size/count limits, auth requirements, and the refusal/fallback path. For a single-parameter tool with safety already covered by annotations, this leaves nothing an agent needs unstated.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
With a single parameter at 100% schema description coverage, the baseline is 3. The description adds actionable meaning beyond the schema by clarifying the multisite behavior ('The link uploads as you, into this site (pass `site` to upload into another)'), which gives an agent the reason to set the parameter.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The first sentence states a specific verb and resource ('Get a 15-minute link for uploading LOCAL files ... into the media library') and immediately frames its niche versus the nearest sibling by contrasting local-machine files with the server's inability to read them. An agent can distinguish this from upload_asset without opening either schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It explicitly names when to use this tool (files on your own machine), when not to (public URL or stock photo -> use upload_asset instead), and the fallback condition (server has no PUBLIC_URL configured -> upload_asset with dataBase64). It even cross-references set_field and update_asset_alt for the follow-up steps.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
delete_assetADestructive
Permanently delete a media asset — by the asset's documentId from list_assets, not a page's — removing the database row and the stored file. Irreversible. Content that still points at it is not blocked: delivery resolves those fields to null, so the image silently disappears from those pages — clear or replace them first. To stop using an image on one page, just clear that field with set_field and keep the asset. Returns {ok:true}. Requires the content.delete permission (Admin, Editor).
| Name | Required | Description | Default |
|---|---|---|---|
| site | No | Site slug or id to act in (multisite). Omit to use this server's default site. Call list_sites to see what exists — an empty tree or search usually means the wrong site, not missing content. | |
| documentId | Yes | The asset's documentId from list_assets. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare destructiveHint=true and idempotentHint=false, but the description goes well beyond them: irreversible, row plus stored file both removed, dangling references are not blocked and silently resolve to null, and it names the required permission (content.delete, Admin/Editor). This is behavior an agent cannot infer from the schema or hints.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Every sentence earns its place: identity disambiguation, irreversibility, the dangling-reference consequence, the set_field alternative, the return shape, and the permission. Front-loaded with the destructive core before the caveats.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Covers everything an agent needs for a destructive, no-output-schema tool: what is destroyed, that it cannot be undone, the orphan-reference side effect, the safe alternative, the return value ({ok:true}), and the permission required. Nothing material is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3, but the description adds real meaning by specifying that documentId must be the asset's id from list_assets rather than a page's id — a distinction the schema description only partially conveys. The 'site' parameter's multisite behavior is left entirely to the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Permanently delete a media asset'), then immediately disambiguates which identifier to pass ('the asset's documentId from list_assets, not a page's'), routing the agent away from a common mix-up with sibling tools like get_content/list_assets.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly covers the alternative path: 'To stop using an image on one page, just clear that field with set_field and keep the asset' — a clear when-not-to-delete rule naming the sibling tool. It also prescribes the prerequisite action ('clear or replace them first') before deleting referenced assets.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
discard_draftADestructive
Throw away the unpublished draft changes of one item (documentId) in one locale (omitted: the item's own language), so the working version falls back to the published one. The live site is unchanged; the discarded draft cannot be recovered. Refused for content that was never published (there is nothing to fall back to) — use trash_content to remove that instead. To roll back to an older version rather than the live one, use restore_version. Returns {ok:true}. Requires the content.update permission (Admin, Editor, Author — an Author only within their own sections).
| Name | Required | Description | Default |
|---|---|---|---|
| site | No | Site slug or id to act in (multisite). Omit to use this server's default site. Call list_sites to see what exists — an empty tree or search usually means the wrong site, not missing content. | |
| locale | No | Locale code. Omit for the document's own locale (its site-default variant, else its only variant). | |
| documentId | Yes | The item's documentId (stable across locales and versions), e.g. from list_tree, list_content or get_content. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already flag destructiveness, but the description adds critical context beyond them: the live site is unchanged, the discarded draft cannot be recovered, the operation is refused for never-published content, the exact permission required (content.update) with role nuances, and the return shape ({ok:true}). This is rich behavioral disclosure that goes well beyond the structured hints.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single dense paragraph that front-loads the core action, then layers consequences, refusal conditions with alternatives, return value, and permissions. Every sentence carries essential information with no wasted words.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a destructive mutation tool with no output schema and full parameter coverage in the schema, the description covers all an agent needs: what it does, side effects, irreversibility, refusal condition, alternatives, required permissions, and return value. Nothing material is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the schema already documents all three parameters. The description repeats the locale omission behavior and clarifies documentId's role, but adds no new syntax, format, or edge-case details beyond what the schema provides. Baseline 3 is appropriate when the schema does the heavy lifting.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb ('Throw away') and resource ('unpublished draft changes of one item') with clear scope ('in one locale'). It explicitly differentiates from siblings by naming trash_content for never-published content and restore_version for rolling back to older versions, so an agent can select it without opening other schemas.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It provides explicit when-to-use context (discard draft to fall back to published), when-not (refused for never-published content — use trash_content instead), and an alternative for a related goal (restore_version for older versions). Nothing is left to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
duplicate_contentA
Copy a content item into a NEW unpublished document beside the original (same parent), named " (copy)", with every field of each locale's working version. A copied page gets a fresh slug, so it never collides with the original's URL. Children are not copied. locale only picks which language of the copy comes back. Returns the new item in the get_content shape; edit it, then publish_content. To copy one language into another locale of the SAME item use copy_variant; to start from nothing use create_content. Requires the content.create permission (Admin, Editor, Author — an Author only within their own sections).
| Name | Required | Description | Default |
|---|---|---|---|
| site | No | Site slug or id to act in (multisite). Omit to use this server's default site. Call list_sites to see what exists — an empty tree or search usually means the wrong site, not missing content. | |
| locale | No | Which locale of the copy to return in the result. Every locale is copied regardless. Omit for the document's own locale. | |
| documentId | Yes | The item's documentId (stable across locales and versions), e.g. from list_tree, list_content or get_content. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only cover the safety profile (readOnly=false, destructive=false, idempotent=false). The description adds substantial behavior beyond them: children are not copied, pages get a fresh slug that never collides with the original URL, locale affects only which language is returned, and the auth model (content.create; Admin/Editor/Author, with Authors limited to their own sections).
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Dense but front-loaded: the copy semantics and naming/slug behavior come first, then the locale caveat, then return shape, then alternatives and permissions. Every clause carries non-redundant information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
No output schema exists, yet the description tells the agent what comes back (the new item in the get_content shape) and what to do next (edit, then publish_content). Combined with the permission model and the children/slug caveats, nothing needed to invoke it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so all three parameters are already documented, including the locale behavior and the site/multisite caveat. The description restates locale semantics rather than adding syntax or format detail beyond the schema, so the baseline 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a precise verb (copy) and resource (content item), plus the exact outcome: a NEW unpublished document beside the original, same parent, named "<name> (copy)", with a fresh slug and no children copied. It also explicitly distinguishes itself from copy_variant and create_content, so an agent can route correctly without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Names both alternatives with the exact condition that selects them: copy_variant when copying one language into another locale of the SAME item, create_content when starting from nothing. It also gives the follow-on workflow (edit it, then publish_content) and the permission requirement.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_contentARead-only
Read one content item for editing: its working version (the draft if one exists, else the published version) in one locale, with name, slug, status, revision and the full data field map. Read this before update_content so you merge into current values, and pass its revision when replacing with merge:false. data maps each field name to its value in the shape list_content_types reports (valueFormat). An unknown documentId, or one from another site, is refused as not found (pass site for another site); omit locale for the item's own language. This is the editing view; for the public view (published only, private fields removed) use delivery_get, in the delivery toolset. Requires the content.read permission (Admin, Editor, Author, Viewer — an Author only within their own sections).
| Name | Required | Description | Default |
|---|---|---|---|
| site | No | Site slug or id to act in (multisite). Omit to use this server's default site. Call list_sites to see what exists — an empty tree or search usually means the wrong site, not missing content. | |
| locale | No | Locale code. Omit for the document's own locale (its site-default variant, else its only variant). | |
| documentId | Yes | The item's documentId (stable across locales and versions), e.g. from list_tree, list_content or get_content. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and openWorldHint=false, and the description adds genuinely useful behavioral context beyond them: the draft-else-published resolution rule, the not-found refusal for unknown or foreign-site documentIds, locale defaulting, and the required content.read permission with role scoping (Author limited to own sections). This is auth-need and edge-case disclosure the annotations do not provide.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the core purpose and return shape, then sequencing, then edge cases, then permissions. It is a dense single paragraph with essentially no filler, though its length pushes close to over-stuffing for a three-parameter read.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
There is no output schema, yet the description enumerates the return shape (name, slug, status, revision, full data field map) and ties it to list_content_types' valueFormat. Combined with the permission, site, and locale semantics, an agent has everything needed to call it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3, but the description adds meaning the schema doesn't: omit locale for the item's own language, pass site to reach another site, and how revision is meant to be reused in a later update. It stops short of detailing format/syntax, but the added semantics are real.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a precise verb+resource+scope: reads one content item's working version (draft if present, else published) in one locale, returning name, slug, status, revision and the data map. It explicitly distinguishes the 'editing view' from delivery_get's 'public view', so an agent can separate it from siblings without opening schemas.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives explicit sequencing ('Read this before update_content so you merge into current values') and the condition for passing revision ('when replacing with merge:false'). It also names the alternative path (delivery_get for published-only, private fields removed), so when-to-use and when-not are both covered.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_assetsARead-only
List this site's whole media library (images and PDFs), newest first, in one call (no paging, so a big library is a big response): each asset's documentId, filename, mime, size, public url, alt text, attribution for stock imports, and folder. Media is per site: pass site to see another site's library. An empty library gives []. Use it to reuse an existing image: an image field stores the asset's documentId, set with set_field. To add a new file use upload_asset (URL, base64 or stock photo) or create_upload_link (files on your machine). Requires the content.read permission (Admin, Editor, Author, Viewer — an Author only within their own sections).
| Name | Required | Description | Default |
|---|---|---|---|
| site | No | Site slug or id to act in (multisite). Omit to use this server's default site. Call list_sites to see what exists — an empty tree or search usually means the wrong site, not missing content. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations declare readOnlyHint/openWorldHint, but the description adds substantial context beyond them: no paging (so a big library is a big response), empty library returns [], the exact fields returned, per-site media isolation, and the permission requirement including the Author-within-own-sections caveat.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the core action and result shape, then routing and permission guidance. Dense and largely earned, though the 'no paging, so a big library is a big response' phrasing and listed return fields add some length.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
No output schema exists, so the description appropriately documents the return payload (documentId, filename, mime, size, url, alt text, attribution, folder) and the empty-case behavior. Permission, scope, and sibling routing are all covered for a single-parameter read tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% and already documents `site` in detail, so the schema carries most of the burden. The description reinforces the per-site semantics ('Media is per site: pass `site` to see another site's library'), adding light corroboration but no new syntax or format detail — slightly above the baseline 3 because it clarifies the operational consequence of the parameter.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource and scope: 'List this site's whole media library (images and PDFs), newest first, in one call'. It is clearly distinguishable from siblings like search_stock_images, upload_asset, list_content, and create_upload_link, and even enumerates the fields returned.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit routing: 'Use it to reuse an existing image: an image field stores the asset's documentId, set with set_field. To add a new file use upload_asset ... or create_upload_link'. It also names the alternative for another site's library via the `site` parameter and states the required permission tier.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_contentARead-only
List EVERY item of one kind in this site that you can see, as one flat array (documentId, name, type, per-locale status, plus parentId for pages and folderId for blocks) — no tree walking needed. kind 'page': pages, to find one by name or pick a parent/reference target (list_tree shows one level in display order). kind 'block': SHARED blocks — reusable block documents (banners, forms, CTAs…) that pages place by reference, so one edit updates every page using them; inline blocks live inside a page's data and are not listed. kind 'global': singletons (header, footer, site settings…), which are neither in the page tree nor blocks, so this is the only way to enumerate them. kind 'trash': items removed with trash_content (any kind), newest first, with deletedAt; nothing is purged automatically, and restore_content brings one back. Everything comes back in one call (no paging). Edit any of them with get_content/update_content on its documentId. Requires the content.read permission (Admin, Editor, Author, Viewer — an Author only within their own sections).
| Name | Required | Description | Default |
|---|---|---|---|
| kind | Yes | Which kind of content to list: page, block, global, or trash (what trash_content removed). | |
| site | No | Site slug or id to act in (multisite). Omit to use this server's default site. Call list_sites to see what exists — an empty tree or search usually means the wrong site, not missing content. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations cover the safety profile (readOnlyHint), but the description adds real behavioral context: the required content.read permission with per-role caveats (an Author only within their own sections), the no-paging guarantee ('Everything comes back in one call'), the fact that nothing is purged automatically, and the shared-vs-inline block distinction that affects blast radius of edits. This is well beyond what the annotations declare.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the core action and return shape before the per-kind detail, and every clause carries information. It is dense and slightly long, with some parenthetical enumeration that could be trimmed, but there is no filler or repetition.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
No output schema exists, so the description carries the return contract itself, naming the fields returned (documentId, name, type, per-locale status, parentId, folderId, deletedAt) plus ordering. Combined with the permission and paging disclosures, an agent has everything needed to call it correctly and interpret the result.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is already 100%, so baseline would be 3, but the description meaningfully enriches both parameters: it defines what each kind actually contains, notes that trash items carry deletedAt and are newest-first, and explains the 'site' parameter's failure mode ('an empty tree or search usually means the wrong site, not missing content').
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('List EVERY item of one kind in this site') and immediately scopes it as one flat array, distinguishing it from the tree-walking alternative by name. An agent can tell it apart from list_tree and get_content without opening a schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives explicit when-to-use guidance per enum value: 'page' to find one by name or pick a parent, 'block' for shared reusable blocks, 'global' because it is the only way to enumerate singletons, 'trash' for removed items. It names the alternative (list_tree shows one level in display order) and the follow-up tools (get_content/update_content, restore_content), leaving nothing to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_content_typesARead-only
Read the content model (instance-wide, shared by all sites). Pass name to get ONE type's definition — name, kind, displayName, schemaType and fields (name, type, required, options, allowed blocks of a content area…); an unknown name is refused with the list of real names. Call this before writing content of a type. Each field carries valueFormat + valueExample: the exact JSON shape create_content/update_content expect. These are LIVE types editors fill in; reusable type recipes (templates) live in the schema toolset. Omit name to get every full definition, which is large, so prefer name. Requires the content.read permission (Admin, Editor, Author, Viewer — an Author only within their own sections).
| Name | Required | Description | Default |
|---|---|---|---|
| name | No | Content type name, PascalCase, e.g. 'BlogPost' (as in a content item's `type`). Omit to list all. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Goes well past the readOnlyHint/openWorldHint annotations: it discloses the required content.read permission with a role breakdown (Author restricted to own sections), the error behavior for an unknown name (refused with the real names listed), and the cost/size characteristic of omitting name. That is exactly the extra context annotations do not carry.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Purpose is front-loaded in the first clause, followed by the parameter behavior, the downstream call to make, the sibling boundary, and the permission caveat. Dense but every sentence carries routing or behavioral value; only the parenthetical field enumeration is slightly overloaded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description compensates by sketching the returned structure (name, kind, displayName, schemaType, fields with type/required/options/allowed blocks) and the per-field valueFormat/valueExample. Combined with permissions and error behavior, an agent has enough to invoke and consume this correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3, but the description adds real meaning: name yields a single type's full definition, and the returned field metadata (valueFormat, valueExample) is framed as the exact JSON shape create_content/update_content expect. It stops short of documenting format constraints beyond the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Read the content model') and immediately scopes it as instance-wide and shared by all sites. It also distinguishes itself from the template/recipe toolset, so an agent can separate it from siblings like get_content or schema tools without inspecting schemas.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives an explicit when-to-use rule ('Call this before writing content of a type'), an explicit parameter-selection preference ('prefer name' over omitting it because the full dump is large), and names the alternative location for templates. Nothing is left to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_sitesARead-only
Report where this server acts and in which languages. Returns: sites (every site you can reach: id, slug, name), activeSiteId, tokenScopedTo (the one site your token is limited to, or null), activeSite (startPageId — the page served at /, or null — and previewBaseUrl, where the admin previews a page as {previewBaseUrl}/{locale}{urlPath}), and locales (enabled language branches in order: code, displayName, isDefault, fallback). It lists no content (use list_tree or list_content for that). Use it to pick the locale matching the language you write, so text lands in the right branch, and a slug to pass as site to other tools (pass site here too to see that site's setup). Content in another site is invisible to every tool, so an empty tree or search usually means the wrong site, not missing content. Requires the content.read permission (Admin, Editor, Author, Viewer — an Author only within their own sections).
| Name | Required | Description | Default |
|---|---|---|---|
| site | No | Site slug or id to act in (multisite). Omit to use this server's default site. Call list_sites to see what exists — an empty tree or search usually means the wrong site, not missing content. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover read-only and closed-world, but the description goes well beyond them: it documents the exact return shape (sites, activeSiteId, tokenScopedTo, activeSite, locales), the permission requirement with role nuances ('an Author only within their own sections'), and the key behavioral fact that content in another site is invisible to every tool. Rich disclosure for a read tool.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the core purpose, then a structured Returns inventory, then usage. Dense with parentheticals and long, but nearly every clause carries information (locale selection, permission model, multisite invisibility). Slightly over-packed rather than wasteful.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description fully enumerates the returned fields and their meanings (including the previewBaseUrl template), so an agent knows exactly what it gets. Permission, scoping, and locale-picking context round it out; nothing needed to call it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% and the single `site` param is already documented, so the baseline is 3. The description adds cross-tool meaning — a slug from here is what you pass as `site` elsewhere, and passing `site` here shows that site's setup — which is genuine value beyond the schema text.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('report where this server acts and in which languages') and explicitly carves out its boundary: 'It lists no content (use list_tree or list_content for that).' An agent can distinguish it from every content-listing sibling without opening a schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives concrete when-to-use guidance ('Use it to pick the `locale` matching the language you write', 'a slug to pass as `site` to other tools') and names alternatives for the content case. It also supplies a diagnostic heuristic: an empty tree or search usually means the wrong site, not missing content.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_treeARead-only
List ONE level of the page tree: the direct children of parentId, or the top-level pages when it is omitted, in the order the container sorts them, the whole level in one call. Returns an array of nodes, each with documentId, type, name, per-locale status and hasChildren — call list_tree again with a node's documentId to go deeper. Trashed pages are left out, and a page without children (or a parentId that matches nothing) gives [] rather than an error, so check hasChildren first. Use it to find the parentId for create_content/move_content. For every page at once use list_content {kind:'page'}; shared blocks and globals are not in the tree (list_content with kind block or global). Requires the content.read permission (Admin, Editor, Author, Viewer — an Author only within their own sections).
| Name | Required | Description | Default |
|---|---|---|---|
| site | No | Site slug or id to act in (multisite). Omit to use this server's default site. Call list_sites to see what exists — an empty tree or search usually means the wrong site, not missing content. | |
| parentId | No | documentId of the page whose children to list. Omit for the top level. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only declare readOnlyHint and openWorldHint=false, so the description must carry the rest, and it does: trashed pages excluded, ordering by container sort, [] instead of an error for leaf or unmatched parentId, and required content.read permission with the Author-scoped caveat. The 'check hasChildren first' warning is actionable behavioral advice, not restatement.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loads the level-by-level scope before return shape, exclusions, siblings and permissions. It is dense but long; a few clauses (the parenthetical permission role list) could be tightened without losing meaning.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description still enumerates the returned node fields (documentId, type, name, per-locale status, hasChildren) and the empty-array case, plus auth requirements. An agent has everything needed to call it and interpret the result.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3. The description goes beyond it by tying parentId to recursion semantics ('call list_tree again with a node's documentId') and by warning that an empty tree usually signals a wrong site rather than missing content — meaning the schema alone does not convey.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource with exact scope: 'List ONE level of the page tree: the direct children of parentId, or the top-level pages when it is omitted.' It immediately distinguishes itself from the sibling list_content by naming what it does not return (shared blocks, globals).
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives explicit when-to-use ('Use it to find the parentId for create_content/move_content') and explicit alternatives ('For every page at once use list_content {kind: page}'; blocks/globals elsewhere). The recursion guidance — call again with a node's documentId to go deeper — removes the main inference burden.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_versionsARead-only
List a content item's version history in one locale, newest first and complete (no paging); locale picks the language branch (omitted: the item's own). Each version's id, versionNumber, status, whether it is the live one, name, and who created it when. An unknown documentId is refused as not found. Use it to find what to roll back to — pass a version's id (not its versionNumber) to restore_version. For the current draft itself, use get_content. Requires the content.read permission (Admin, Editor, Author, Viewer — an Author only within their own sections).
| Name | Required | Description | Default |
|---|---|---|---|
| site | No | Site slug or id to act in (multisite). Omit to use this server's default site. Call list_sites to see what exists — an empty tree or search usually means the wrong site, not missing content. | |
| locale | No | Locale code. Omit for the document's own locale (its site-default variant, else its only variant). | |
| documentId | Yes | The item's documentId (stable across locales and versions), e.g. from list_tree, list_content or get_content. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover the safety profile (readOnlyHint, openWorldHint=false), yet the description adds real behavioral context: no paging/complete results, newest-first ordering, not-found refusal for unknown documentId, and the required content.read permission with role caveats (Author limited to own sections). It omits return size/rate considerations, so it is strong but not exhaustive.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the core action and scope, then alternatives, then permissions. Despite being dense, every clause carries actionable information with no repetition of the name or padding.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
No output schema exists, so the description compensates by enumerating the returned fields (id, versionNumber, status, live flag, name, creator/time). Combined with error behavior, permissions, and sibling routing, an agent has everything needed to call it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3; the description still adds value by clarifying that locale selects the language branch and defaults to the item's own variant, and by flagging the id-vs-versionNumber distinction used downstream in restore_version.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource (list a content item's version history) with scope qualifiers: one locale, newest first, complete/no paging. It is immediately distinguishable from siblings like list_content and get_content.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly routes to alternatives: use restore_version with the version's `id` (not versionNumber) to roll back, and get_content for the current draft itself. When-to-use versus when-to-use-something-else is spelled out.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
move_contentAIdempotent
Move a page in the page tree: parentId re-parents it (its URL path follows its new position, and the old URL gets a 301 automatically), beforeId/afterId place it among its siblings. Its whole subtree moves with it. Refused when the new parent is the page itself, is not a page, or lies outside your sections. Sibling order only sticks under a parent with a manual child sort; a computed sort (name, date…) re-orders it. Returns {ok:true}. To change only the URL's last segment, set a new slug with update_content instead; blocks and globals are not in the tree. Requires the content.update permission (Admin, Editor, Author — an Author only within their own sections).
| Name | Required | Description | Default |
|---|---|---|---|
| site | No | Site slug or id to act in (multisite). Omit to use this server's default site. Call list_sites to see what exists — an empty tree or search usually means the wrong site, not missing content. | |
| afterId | No | Place it directly after this sibling (documentId). | |
| beforeId | No | Place it directly before this sibling (documentId). | |
| parentId | No | documentId of the new parent page; null moves it to the top level. Omit to keep the current parent. | |
| documentId | Yes | The item's documentId (stable across locales and versions), e.g. from list_tree, list_content or get_content. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations declare it non-destructive and idempotent, but the description goes well beyond them: it discloses the automatic 301 redirect, that the whole subtree moves, the exact refusal conditions, the sibling-order caveat under computed sorts, and the required content.update permission with role-level nuance. This is rich behavioral context not derivable from the annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the core action and densely packed, with nearly every clause carrying distinct information (redirects, refusals, permissions). The parenthetical-heavy style makes it slightly harder to scan than an ideally structured description, but there is little waste.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a mutation tool with no output schema, the description covers the return value ('Returns {ok:true}'), refusal cases, permission requirements, and side effects like the 301 redirect and subtree move. Nothing an agent needs to call this correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3, but the description adds genuine meaning: parentId 're-parents' it and the URL path follows, beforeId/afterId place it among siblings, and sibling order only sticks under manual sort. That last constraint is not in the schema and materially affects how an agent sets these params.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Move a page in the page tree') and immediately distinguishes itself from siblings by scoping to tree repositioning rather than content editing. An agent can tell it apart from update_content, duplicate_content, and trash_content without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly routes the agent: 'To change only the URL's last segment, set a new slug with update_content instead' and 'blocks and globals are not in the tree.' It also names the permission and role conditions under which the tool is available, giving clear when-to-use and when-not-to-use guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
publish_contentA
Make a content item's working draft live in one locale (a LANGUAGE BRANCH: Norwegian text published into 'en' lands on the English site, so a clear mismatch is refused). A future publishAt SCHEDULES it instead: it stays invisible until then; call again with a new publishAt to reschedule, or without one to go live now. expireAt unpublishes it automatically later. Never fake a schedule with a date field — that publishes now. A changed slug or position gets a 301 from the old URL. Fires content.published. Refused when there is nothing new to publish, when required fields are empty (the error names them), or, if the site requires human review of agent drafts, until an editor approves it. Returns the item in the get_content shape with its new status. To take it down use unpublish_content; to drop unpublished edits, discard_draft.
| Name | Required | Description | Default |
|---|---|---|---|
| site | No | Site slug or id to act in (multisite). Omit to use this server's default site. Call list_sites to see what exists — an empty tree or search usually means the wrong site, not missing content. | |
| locale | No | Locale code. Omit for the document's own locale (its site-default variant, else its only variant). | |
| expireAt | No | When to unpublish automatically (ISO 8601). Must be after publishAt. | |
| publishAt | No | Future go-live time (ISO 8601, e.g. "2026-09-01T09:00:00Z"). Omit to publish immediately. | |
| documentId | Yes | The item's documentId (stable across locales and versions), e.g. from list_tree, list_content or get_content. | |
| allowLanguageMismatch | No | Set true ONLY when publishing content whose language deliberately differs from the locale branch |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only declare the write/non-idempotent/non-destructive profile; the description goes far beyond by disclosing scheduling semantics, automatic unpublication via expireAt, the 301 on slug/position change, the content.published event, language-mismatch refusal, and required-field/review refusals. It also warns against faking a schedule with a date field, which prevents a real misuse.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Dense but front-loaded: the core action and its scheduling semantics come first, with routing and refusal details trailing. Every sentence carries information, though the prose runs long for a single tool and could be tightened slightly.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description compensates by stating the return shape (the item in get_content shape with its new status) and covering the full behavioral envelope: scheduling, expiry, redirects, events, and refusals. Nothing an agent needs to call it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3, but the description adds genuine meaning: it explains the locale branch/language-mismatch interaction, that publishAt schedules rather than publishes now, and that rescheduling is done by re-calling with a new publishAt. It does not restate the schema's own wording verbatim.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Names a specific verb (publish/make live) and resource (a content item's working draft), scopes it to one locale, and distinguishes the sibling actions unpublish_content and discard_draft. An agent can tell this apart from update_content or create_content without opening a schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly covers when to use it (go live now vs. schedule via publishAt), how to reschedule (call again with a new publishAt), and which sibling to use instead for takedown or discarding edits. Failure/refusal conditions are enumerated, so the agent knows when the call will be rejected.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
restore_contentA
Bring a trashed item back to its old place in the tree, together with the descendants that were trashed with it. Restored content is NOT republished — call publish_content for each locale that should go live. Refused if its parent is still in the trash (restore that first) or a live sibling took its URL meanwhile. There is no locale to pass: every language comes back. Returns {restored: }. Find trashed ids with list_content {kind:'trash'}. To roll back an item that is not trashed, use restore_version instead. Requires the content.delete permission (Admin, Editor).
| Name | Required | Description | Default |
|---|---|---|---|
| site | No | Site slug or id to act in (multisite). Omit to use this server's default site. Call list_sites to see what exists — an empty tree or search usually means the wrong site, not missing content. | |
| documentId | Yes | The item's documentId (stable across locales and versions), e.g. from list_tree, list_content or get_content. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only mark it non-readonly/non-idempotent/non-destructive; the description adds substantial behavior beyond them — descendants come back too, restored content is NOT republished, refusal conditions (trashed parent, URL taken by a live sibling), no locale scoping, required content.delete permission, and the {restored: count} return shape.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the action and scope, then refusal conditions, return value, discovery hint, and alternative. Every sentence carries distinct information with no filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Despite no output schema, the return value is stated, and permissions, preconditions, non-republish behavior, and the alternative tool are all covered. Nothing an agent needs to call this correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3; both parameters are already fully documented in the schema. The description adds a meaningful clarification beyond the schema by explaining that no locale parameter exists and every language is restored, which prevents an agent from hunting for a locale argument.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Bring a trashed item back to its old place in the tree, together with the descendants'), and explicitly distinguishes itself from siblings publish_content and restore_version. An agent can tell it apart from restore_version without opening either schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives explicit when-to-use routing: use list_content {kind:'trash'} to find ids, call publish_content per locale to go live, and use restore_version instead for non-trashed rollback. It also names preconditions (parent must not still be trashed) that gate a successful call.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
restore_versionADestructive
Roll back an item to an older version: copy that version's name, slug and fields into the working draft of one locale. versionId is the version's id from list_versions of the SAME locale, not its versionNumber. It REPLACES the current draft of that locale, so unpublished edits there are lost; published versions stay in the history (a rollback can itself be rolled back), and nothing goes live until you publish_content. Returns the new draft in the get_content shape. To bring back a TRASHED item use restore_content; to simply drop unpublished edits use discard_draft. Requires the content.update permission (Admin, Editor, Author — an Author only within their own sections).
| Name | Required | Description | Default |
|---|---|---|---|
| site | No | Site slug or id to act in (multisite). Omit to use this server's default site. Call list_sites to see what exists — an empty tree or search usually means the wrong site, not missing content. | |
| locale | No | Locale code. Omit for the document's own locale (its site-default variant, else its only variant). | |
| versionId | Yes | The version's `id` from list_versions (not its versionNumber). | |
| documentId | Yes | The item's documentId (stable across locales and versions), e.g. from list_tree, list_content or get_content. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already flag destructiveHint=true and idempotentHint=false, but the description adds substantial context: it REPLACES the current draft so unpublished edits are lost, published versions remain in history, a rollback can itself be rolled back, and nothing goes live until publish_content. This is exactly the kind of behavioral disclosure that goes beyond structured annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long but front-loaded with the core action and effect, then layers on routing, side effects, and permissions. Every sentence carries information, though the density is high enough that a slightly tighter grouping of side effects would help.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Despite having no output schema, the description specifies the return shape ('Returns the new draft in the get_content shape'), covers side effects, alternatives, and permissions. An agent has everything needed to call this mutation correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3, but the description adds genuine meaning: versionId must be the `id` from list_versions of the SAME locale, not the versionNumber. That disambiguation is valuable beyond the schema's own phrasing and prevents a common mistake.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Roll back an item to an older version') and immediately clarifies the mechanism (copy name, slug and fields into the working draft of one locale). It differentiates itself from siblings by explicitly naming restore_content and discard_draft as different operations.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives explicit when-to-use and when-not-to-use guidance: use restore_content for trashed items, use discard_draft to drop unpublished edits. It also states the permission prerequisite (content.update) and the role constraints, including the Author scoping caveat.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_stock_imagesARead-only
Search the configured stock photo provider (Settings → Stock images; Unsplash) for photos to use on a page. Returns up to 24 candidates, best match first, each with id, description and attribution; nothing is stored yet. To USE one: upload_asset {stockImageId: } imports it into the media library, then set_field the returned asset documentId on an image field. Refused with a hint when no provider key is configured. To reuse a photo you already have, list_assets. Requires the content.read permission (Admin, Editor, Author, Viewer — an Author only within their own sections).
| Name | Required | Description | Default |
|---|---|---|---|
| site | No | Site slug or id to act in (multisite). Omit to use this server's default site. Call list_sites to see what exists — an empty tree or search usually means the wrong site, not missing content. | |
| query | Yes | What the photo should show, e.g. 'mountain lake sunrise' |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and openWorldHint=true, so safety is covered; the description adds substantial context beyond them — up to 24 results best-match-first, per-item fields (id, description, attribution), the fact that nothing persists, the graceful refusal with a hint when no key is configured, and required permission plus per-role scoping. This is rich behavioral disclosure.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loads what the tool does, then the follow-up workflow, then the fallback and permission caveats. Every sentence carries routing or constraint value (return shape, non-persistence, refusal, permission); none is filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description supplies the return contract (up to 24 candidates, best match first, id/description/attribution). Combined with the import workflow, alternative tool, refusal behavior, and permission requirements, an agent has everything needed to call it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% and both parameters (site, query) are documented in-schema, so the schema does the heavy lifting. The description adds no syntax or format detail for query or site beyond what is already there, so the baseline 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource ('Search the configured stock photo provider ... for photos') and immediately scopes it as candidate discovery, not storage ('nothing is stored yet'). It distinguishes itself from siblings like list_assets (reuse existing) and upload_asset (import), so an agent can route without opening another schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly gives the end-to-end path: search here, then upload_asset {stockImageId} to import, then set_field with the returned documentId. It also names the alternative for a different intent ('To reuse a photo you already have, list_assets') and the refusal condition (no provider key). Nothing is left to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
set_fieldAIdempotent
Write ONE field of an item's draft as a plain string; the other fields are kept. It is the safest way to send long text or Markdown, because a flat string survives client serialization that can mangle long strings nested in update_content's data. field is a text, markdown, select, datetime or image field from list_content_types {name} (an image takes an asset documentId), or name for the item's display name; value is the field's whole new value. For richtext, contentArea or reference fields, or several fields at once, use update_content. A value the field can't take is refused with the expected shape and an example; text in another language than the locale branch is refused unless allowLanguageMismatch. Like any agent edit, it cancels a pending scheduled publish. Returns the saved draft in the get_content shape; publish_content separately. Requires the content.update permission (Admin, Editor, Author — an Author only within their own sections).
| Name | Required | Description | Default |
|---|---|---|---|
| site | No | Site slug or id to act in (multisite). Omit to use this server's default site. Call list_sites to see what exists — an empty tree or search usually means the wrong site, not missing content. | |
| field | Yes | Field name from the content type (or 'name') | |
| value | Yes | The plain string value | |
| locale | No | Locale code. Omit for the document's own locale (its site-default variant, else its only variant). | |
| documentId | Yes | The item's documentId (stable across locales and versions), e.g. from list_tree, list_content or get_content. | |
| allowLanguageMismatch | No | Set true ONLY to deliberately write text whose language differs from the locale branch (the guard otherwise refuses, so e.g. Norwegian text can't silently land on the 'en' branch) |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations cover only idempotency/safety flags, and the description adds substantial behavior beyond them: it cancels a pending scheduled publish, refuses invalid values with expected shape and example, refuses language mismatches by default, and states the required content.update permission plus the Author-scoped caveat.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Dense but front-loaded: the core action and its safety rationale lead, then alternatives, then side effects and permissions. Six sentences is on the long side, though nearly every clause carries actionable information an agent needs.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description still closes the loop by saying it returns the saved draft in the get_content shape and that publishing is a separate call. Permissions, refusal behavior, and scheduling side effects are all covered for a mutation tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is already 100%, so the baseline is 3, but the description adds real meaning: which field kinds are legal (text/markdown/select/datetime/image from list_content_types) or 'name', that an image takes an asset documentId, that value is the field's whole new value, and the allowLanguageMismatch guard behavior.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a precise verb+resource+scope: 'Write ONE field of an item's draft as a plain string; the other fields are kept.' It immediately distinguishes itself from update_content by naming that sibling and the cases it doesn't cover (richtext, contentArea, reference, multiple fields).
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit routing: use this for long text/Markdown because a flat string survives client serialization, and use update_content for richtext/contentArea/reference fields or several fields at once. It also notes when allowLanguageMismatch is needed and that publish_content is separate.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
trash_contentA
Move a content item (by documentId) AND its whole page subtree to the trash: every locale is unpublished at once and disappears from delivery, so there is no locale to pass. Recoverable — restore_content brings it back (as drafts; publish again explicitly). An unknown documentId is refused as not found. Returns {trashed: }. To only take it off the live site, use unpublish_content instead. Requires the content.delete permission (Admin, Editor).
| Name | Required | Description | Default |
|---|---|---|---|
| site | No | Site slug or id to act in (multisite). Omit to use this server's default site. Call list_sites to see what exists — an empty tree or search usually means the wrong site, not missing content. | |
| documentId | Yes | The item's documentId (stable across locales and versions), e.g. from list_tree, list_content or get_content. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Adds substantial behavior beyond the annotations: the entire subtree is trashed, all locales are unpublished and removed from delivery, the operation is recoverable via restore_content (returned as drafts), unknown documentIds are refused, and it requires content.delete (Admin, Editor). This is rich operational context. The destructiveHint=false annotation is consistent with the explicitly stated recoverability, so no contradiction.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the core action and scope, then recovery, then the error case, return shape, alternative, and permission. Every sentence carries distinct, necessary information with no filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description supplies the return value ({trashed: n}), the permission requirement, the recoverability path, and the sibling alternative. Nothing an agent needs to call it safely and correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is already 100%, so baseline is 3. The description adds real meaning: it explains why no locale parameter exists ('every locale is unpublished at once ... so there is no locale to pass'), and clarifies the documentId not-found failure mode. That goes beyond what the schema documents.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb ('Move ... to the trash') plus resource ('a content item AND its whole page subtree') and scope ('every locale'). It explicitly differentiates from siblings, naming restore_content for recovery and unpublish_content for the softer alternative, so an agent can route correctly without opening other schemas.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives an explicit alternative: 'To only take it off the live site, use unpublish_content instead,' and names restore_content as the inverse. The recoverability caveat and 'publish again explicitly' note make the when-to-use decision unambiguous.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
unpublish_contentADestructive
Take a content item off the live site in one locale: delivery stops serving it there (its URL starts answering 404) and content areas that reference it drop it. Fires the content.unpublished webhook. The content and its version history are kept, so publish_content brings it back. Name the item by documentId and the branch by locale (omit it for the item's own language); other locales are unaffected. Returns the item in the get_content shape, now unpublished in that locale. To remove the item entirely use trash_content. Requires the content.publish permission (Admin, Editor).
| Name | Required | Description | Default |
|---|---|---|---|
| site | No | Site slug or id to act in (multisite). Omit to use this server's default site. Call list_sites to see what exists — an empty tree or search usually means the wrong site, not missing content. | |
| locale | No | Locale code. Omit for the document's own locale (its site-default variant, else its only variant). | |
| documentId | Yes | The item's documentId (stable across locales and versions), e.g. from list_tree, list_content or get_content. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With annotations covering the safety profile (destructiveHint true, readOnlyHint false), the description still adds rich context: the URL answering 404, content-area reference dropping, the content.unpublished webhook firing, retention of content and version history, and the required content.publish permission. This is exactly the behavior-beyond-annotations an agent needs.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loads the core action and effect, then layers webhook, reversibility, routing, return shape, and permissions. Dense but every sentence carries information; slightly blocky as a single paragraph rather than cleanly separated.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Complete for a mutation tool with no output schema: it describes the return shape (get_content shape, unpublished in that locale) and the permission requirement, while annotations already handle the safety hints. Nothing an agent needs to call it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3, but the description adds semantic value: it explains that the locale names the 'branch' and that omitting it targets the item's own language, and that other locales remain unaffected.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (unpublish) and resource (content item) with scope (one locale), and clarifies the observable effects (404, dropped references). It explicitly distinguishes itself from siblings publish_content and trash_content by naming both.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives explicit routing: 'To remove the item entirely use trash_content' for the destructive alternative, and notes publish_content as the reversing action. The locale-omission rule (omit for the item's own language) is stated as an operating condition, not left to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
update_asset_altAIdempotent
Set (overwrite) an image asset's alt text — the description screen readers announce and search engines index. Alt text belongs to the ASSET, not to a page: every page showing the image picks up the change, and set_field/update_content cannot set it. documentId is the asset's (from list_assets or an upload), not a page's; an unknown one is refused as not found. Describe what the image shows in one sentence; use "" only for a purely decorative image. Returns the updated asset with its new alt. assist_writing cannot write alt text (it needs the image itself), so look at the image and write it. Requires the content.update permission (Admin, Editor, Author — an Author only within their own sections).
| Name | Required | Description | Default |
|---|---|---|---|
| alt | Yes | The new alt text, e.g. 'Two hikers on a ridge above a fjord at sunset'. | |
| site | No | Site slug or id to act in (multisite). Omit to use this server's default site. Call list_sites to see what exists — an empty tree or search usually means the wrong site, not missing content. | |
| documentId | Yes | The asset's documentId from list_assets or an upload result. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Adds substantial context beyond annotations: alt text lives on the asset not the page, unknown documentId is refused as not found, the required permission and role scoping, and the return value. Annotations already cover idempotent/non-destructive, and the description reinforces rather than repeats.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Dense but front-loaded with the core action, and nearly every sentence carries distinct information (scope, id semantics, alt conventions, permissions). Slightly long, but little is redundant.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 3-param mutation tool with no output schema, it covers permissions, failure behavior, return value, and cross-page effects. An agent has everything needed to call it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3, but the description adds real meaning: documentId is the asset's (not a page's) from list_assets/upload, and alt should be one sentence with "" reserved for decorative images. This resolves the main ambiguity the schema alone leaves open.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ("Set (overwrite) an image asset's alt text") and immediately distinguishes it from siblings set_field/update_content. An agent knows exactly what this does and what it is not.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly names the alternatives that cannot do this (set_field/update_content) and the tool that cannot generate the text (assist_writing), with the condition (needs the image itself). Provides the when-to-use routing directly.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
update_contentAIdempotent
Save fields into a content item's working draft; nothing goes live until publish_content. Each value's shape follows its field type (list_content_types {name} gives valueFormat): text/markdown → string; richtext → TipTap doc {type:'doc',content:[…]}; contentArea → array of block instances; reference → {documentId}; image → asset documentId. data is MERGED over the draft by default, so omitted fields are kept; merge:false replaces the whole map (send every required field, and the get_content revision so a concurrent human edit is refused with 409 instead of overwritten). For one LONG text/markdown value use set_field: nested long strings can be mangled in transit. A value of the wrong shape is refused with the field, the expected shape and an example; text in another language than the branch is refused unless allowLanguageMismatch. An agent's edit marks the draft for review and cancels a pending scheduled publish of it. Returns the saved draft in the get_content shape, with its new revision. Requires the content.update permission (Admin, Editor, Author — an Author only within their own sections).
| Name | Required | Description | Default |
|---|---|---|---|
| data | Yes | Field name → value, shaped per the valueFormat list_content_types {name} reports. Pass {} to change only name/slug/displayInNav. | |
| name | No | New display name. Omit to keep it. | |
| site | No | Site slug or id to act in (multisite). Omit to use this server's default site. Call list_sites to see what exists — an empty tree or search usually means the wrong site, not missing content. | |
| slug | No | New URL slug for a page; publishing it adds a 301 from the old URL automatically. Omit to keep it. | |
| merge | No | Default true: shallow-merge data over the current draft. false = replace the whole field map | |
| locale | No | Locale code. Omit for the document's own locale (its site-default variant, else its only variant). | |
| revision | No | Optimistic concurrency: the `revision` from get_content. If a human (or another agent) has changed the draft since, the write is refused instead of overwriting their work. Recommended whenever merge:false | |
| documentId | Yes | The item's documentId (stable across locales and versions), e.g. from list_tree, list_content or get_content. | |
| displayInNav | No | Whether the page appears in the site navigation. Omit to keep it. | |
| allowLanguageMismatch | No | Set true ONLY to deliberately write text whose language differs from the locale branch (the guard otherwise refuses, so e.g. Norwegian text can't silently land on the 'en' branch) |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations are modest (idempotent, non-destructive), and the description adds substantial unrevealed behavior: merge-by-default semantics, the 409 refusal on stale revision, that an agent edit marks the draft for review and cancels a pending scheduled publish, the shape-mismatch refusal, the language guard, and the required permission set. This is well beyond what annotations supply.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single dense block, but front-loaded with the core purpose and merge rule before the edge cases. Nearly every clause carries actionable information; the long run-on construction around the field-shape list and the set_field aside slightly hurts scanability.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 10-parameter nested-object mutation with no output schema, the description covers the required permission, the return shape ('the saved draft in the get_content shape, with its new revision'), the concurrency contract, and the main refusal modes. Nothing needed to call it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3, but the description contributes real meaning not in the schema: concrete value shapes per field type (string, TipTap doc, block array, {documentId}), the merge:false consequence, and the revision/409 contract. It does not fully re-explain name, slug, or locale handling, which holds it below a 5.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Save fields into a content item's working draft') and immediately scopes it against publish_content ('nothing goes live until publish_content'). An agent can distinguish it from create_content and set_field without opening a schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives explicit routing conditions: use set_field for one long text/markdown value, use merge:false plus revision to replace the whole map, use allowLanguageMismatch only for deliberate cross-language writes. When-not guidance (draft vs live) is front-loaded.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
upload_assetA
Add a file (PNG, JPEG, GIF, WEBP or PDF, max 5 MB) to the media library from exactly one source: a public URL, base64 bytes, or a stock photo id from search_stock_images (stored with its attribution). Returns the new asset — set its documentId on an image field with set_field. Each call stores a NEW asset, even for a file already in the library (reuse one via list_assets). A URL must be publicly reachable: private-network addresses are refused at every redirect hop, and a download over 5 MB is cut off with an error. For files on YOUR machine prefer create_upload_link: base64 through a tool call costs roughly 1 token per 2-3 bytes. Requires the content.create permission (Admin, Editor, Author — an Author only within their own sections).
| Name | Required | Description | Default |
|---|---|---|---|
| alt | No | Alt text (for a stock photo, defaults to the provider's description) | |
| url | No | Public http(s) URL of the file itself (not a web page showing it) | |
| site | No | Site slug or id to act in (multisite). Omit to use this server's default site. Call list_sites to see what exists — an empty tree or search usually means the wrong site, not missing content. | |
| filename | No | Display name in the media library, e.g. 'hero.jpg' (url and base64 only; a stock photo is named by the provider) | |
| dataBase64 | No | The file's bytes, base64-encoded (a data: URL prefix is accepted) | |
| stockImageId | No | A photo id from search_stock_images; the image is downloaded from the stock provider. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations cover the safety profile (not read-only, open-world, non-idempotent, non-destructive), and the description adds substantial context beyond them: the 5 MB cap, per-redirect-hop refusal of private-network addresses, download cutoff behavior, the always-new-asset semantic, and the content.create permission with role scoping for Authors.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Purpose and source constraints are front-loaded, and each sentence carries operational weight (limits, permissions, cost, alternatives). It runs long as a single dense block, so structure could be improved, but there is little wasted text.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a multi-source upload tool with no output schema, the description covers the return value's use ('set its documentId on an image field with set_field'), the permission requirement, size and network constraints, and sibling alternatives. An agent has everything needed to invoke it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3, but the description adds real meaning the schema doesn't: the three sources are mutually exclusive ('from exactly one source'), the URL must be publicly reachable, and stock photos are stored with their attribution. It stops short of documenting the site/filename interplay more fully.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a precise verb+resource ('Add a file ... to the media library') and immediately scopes the three allowed sources, distinguishing it from siblings like create_upload_link, search_stock_images, and list_assets. An agent can identify the operation and its boundaries without opening the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly routes the agent: use create_upload_link for local files, list_assets to reuse an existing asset, set_field to attach the returned documentId, and search_stock_images as the source of stockImageId. It also names when-not (a new asset is always created). Nothing is left to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
51 tool updates
v0.1.7- Removed
ai_assist - Added
assist_writing - Changed
copy_variant2 fields changed- changed
Input schema / properties / fromLocale / descriptionPrevious value: -"Locale to copy FROM"New value: +"Locale to copy FROM, e.g. 'en' (list_sites → locales)." - changed
Input schema / properties / toLocale / descriptionPrevious value: -"Locale to copy TO"New value: +"Locale to copy TO; must differ from fromLocale."
- Changed
create_content1 field changed- changed
Input schema / properties / locale / descriptionPrevious value: -"The LANGUAGE BRANCH for this content — MATCH the language you are writing (Norwegian content → 'nb', English → 'en'). Call list_locales to see the site's branches. Defaults to the site default locale, which is usually English."New value: +"The LANGUAGE BRANCH for this content — MATCH the language you are writing (Norwegian content → 'nb', English → 'en'). list_sites reports the site's branches (`locales`). Defaults to the site default locale, which is usually English."
- Removed
create_content_type - Removed
create_delivery_key - Removed
create_redirect - Removed
create_type_template - Removed
create_user - Removed
create_webhook - Removed
delete_form_submission - Removed
delete_redirect - Removed
delete_type_template - Removed
delete_user - Removed
delete_webhook - Removed
delivery_get - Removed
delivery_list - Removed
delivery_search - Changed
duplicate_content1 field changed- changed
Input schema / properties / locale / descriptionPrevious value: -"Locale code. Omit for the document's own locale (its site-default variant, else its only variant)."New value: +"Which locale of the copy to return in the result. Every locale is copied regardless. Omit for the document's own locale."
- Removed
erase_form_submissions - Removed
export_form_submissions - Removed
export_type_templates - Removed
import_stock_image - Removed
import_type_templates - Removed
instantiate_type_template - Removed
list_audit - Changed
list_content2 fields changed- changed
Input schema / properties / kind / descriptionPrevious value: -"Which kind of content to list."New value: +"Which kind of content to list: page, block, global, or trash (what trash_content removed)." - changed
Input schema / properties / kind / enumPrevious value: -[ - "page", - "block", - "global" -]New value: +[ + "page", + "block", + "global", + "trash" +]
- Changed
list_content_types1 field changed- removed
Input schema / properties / siteRemoved value: -{ - "description": "Site slug or id to act in (multisite). Omit to use this server's default site. Call list_sites to see what exists — an empty tree or search usually means the wrong site, not missing content.", - "type": "string" -}
- Removed
list_delivery_keys - Removed
list_form_submissions - Removed
list_forms - Removed
list_locales - Removed
list_not_found - Removed
list_redirects - Removed
list_trash - Removed
list_type_templates - Removed
list_users - Removed
list_webhooks - Removed
publish - Added
publish_content - Removed
rename_delivery_key - Removed
revoke_delivery_key - Removed
set_start_page - Removed
test_redirect - Removed
unpublish - Added
unpublish_content - Removed
update_content_type - Removed
update_redirect - Removed
update_type_template - Removed
update_user - Changed
upload_asset3 fields changed- changed
Input schema / properties / alt / descriptionPrevious value: -"Alt text"New value: +"Alt text (for a stock photo, defaults to the provider's description)" - changed
Input schema / properties / filename / descriptionPrevious value: -"Display name in the media library, e.g. 'hero.jpg'"New value: +"Display name in the media library, e.g. 'hero.jpg' (url and base64 only; a stock photo is named by the provider)" - added
Input schema / properties / stockImageIdAdded value: +{ + "description": "A photo id from search_stock_images; the image is downloaded from the stock provider.", + "maxLength": 200, + "minLength": 1, + "type": "string" +}
37 tool updates
v0.1.4- Changed
copy_variant1 field changed- changed
Input schema / properties / documentId / descriptionPrevious value: -"The item's documentId (stable across locales and versions), e.g. from tree, list_pages or get_content."New value: +"The item's documentId (stable across locales and versions), e.g. from list_tree, list_content or get_content."
- Changed
create_content2 fields changed- changed
Input schema / properties / data / descriptionPrevious value: -"Initial field values (field name → value), same shapes as update_content. Provide this to create a FILLED draft in one call. Call get_content_type first for the field names/shapes."New value: +"Initial field values (field name → value), same shapes as update_content. Provide this to create a FILLED draft in one call. Call list_content_types {name} first for the field names/shapes." - changed
Input schema / properties / parentId / descriptionPrevious value: -"documentId of the parent page. Required in practice for pages — find it with `tree`. Omit only for top-level pages."New value: +"documentId of the parent page. Required in practice for pages — find it with `list_tree`. Omit only for top-level pages."
- Changed
create_content_type1 field changed- changed
Input schema / properties / definition / descriptionPrevious value: -"A ContentTypeDef: {name: 'PascalCase', kind: 'page'|'block'|'global', fields: [{name, type, …}], …}. Copy the shape of an existing one from get_content_type."New value: +"A ContentTypeDef: {name: 'PascalCase', kind: 'page'|'block'|'global', fields: [{name, type, …}], …}. Copy the shape of an existing one from list_content_types {name}."
- Added
delete_redirect - Removed
delete_redirects - Added
delivery_get - Removed
delivery_get_by_id - Removed
delivery_get_by_path - Removed
delivery_get_by_slug - Removed
delivery_global - Removed
delivery_start - Changed
discard_draft1 field changed- changed
Input schema / properties / documentId / descriptionPrevious value: -"The item's documentId (stable across locales and versions), e.g. from tree, list_pages or get_content."New value: +"The item's documentId (stable across locales and versions), e.g. from list_tree, list_content or get_content."
- Changed
duplicate_content1 field changed- changed
Input schema / properties / documentId / descriptionPrevious value: -"The item's documentId (stable across locales and versions), e.g. from tree, list_pages or get_content."New value: +"The item's documentId (stable across locales and versions), e.g. from list_tree, list_content or get_content."
- Changed
get_content1 field changed- changed
Input schema / properties / documentId / descriptionPrevious value: -"The item's documentId (stable across locales and versions), e.g. from tree, list_pages or get_content."New value: +"The item's documentId (stable across locales and versions), e.g. from list_tree, list_content or get_content."
- Removed
get_content_type - Removed
get_form_submission - Removed
get_site_config - Removed
get_type_template - Removed
list_blocks - Added
list_content - Changed
list_content_types1 field changed- added
Input schema / properties / nameAdded value: +{ + "description": "Content type name, PascalCase, e.g. 'BlogPost' (as in a content item's `type`). Omit to list all.", + "type": "string" +}
- Changed
list_form_submissions1 field changed- added
Input schema / properties / submissionIdAdded value: +{ + "description": "Return just this submission. Other filters are ignored.", + "type": "string" +}
- Removed
list_globals - Removed
list_pages - Added
list_tree - Changed
list_type_templates1 field changed- added
Input schema / properties / nameAdded value: +{ + "description": "Template name, e.g. 'FaqPage'. Omit to list all.", + "type": "string" +}
- Changed
list_versions1 field changed- changed
Input schema / properties / documentId / descriptionPrevious value: -"The item's documentId (stable across locales and versions), e.g. from tree, list_pages or get_content."New value: +"The item's documentId (stable across locales and versions), e.g. from list_tree, list_content or get_content."
- Changed
move_content1 field changed- changed
Input schema / properties / documentId / descriptionPrevious value: -"The item's documentId (stable across locales and versions), e.g. from tree, list_pages or get_content."New value: +"The item's documentId (stable across locales and versions), e.g. from list_tree, list_content or get_content."
- Changed
publish1 field changed- changed
Input schema / properties / documentId / descriptionPrevious value: -"The item's documentId (stable across locales and versions), e.g. from tree, list_pages or get_content."New value: +"The item's documentId (stable across locales and versions), e.g. from list_tree, list_content or get_content."
- Changed
restore_content1 field changed- changed
Input schema / properties / documentId / descriptionPrevious value: -"The item's documentId (stable across locales and versions), e.g. from tree, list_pages or get_content."New value: +"The item's documentId (stable across locales and versions), e.g. from list_tree, list_content or get_content."
- Changed
restore_version1 field changed- changed
Input schema / properties / documentId / descriptionPrevious value: -"The item's documentId (stable across locales and versions), e.g. from tree, list_pages or get_content."New value: +"The item's documentId (stable across locales and versions), e.g. from list_tree, list_content or get_content."
- Changed
set_field1 field changed- changed
Input schema / properties / documentId / descriptionPrevious value: -"The item's documentId (stable across locales and versions), e.g. from tree, list_pages or get_content."New value: +"The item's documentId (stable across locales and versions), e.g. from list_tree, list_content or get_content."
- Changed
trash_content1 field changed- changed
Input schema / properties / documentId / descriptionPrevious value: -"The item's documentId (stable across locales and versions), e.g. from tree, list_pages or get_content."New value: +"The item's documentId (stable across locales and versions), e.g. from list_tree, list_content or get_content."
- Removed
tree - Changed
unpublish1 field changed- changed
Input schema / properties / documentId / descriptionPrevious value: -"The item's documentId (stable across locales and versions), e.g. from tree, list_pages or get_content."New value: +"The item's documentId (stable across locales and versions), e.g. from list_tree, list_content or get_content."
- Changed
update_content2 fields changed- changed
Input schema / properties / data / descriptionPrevious value: -"Field name → value, shaped per get_content_type's valueFormat. Pass {} to change only name/slug/displayInNav."New value: +"Field name → value, shaped per the valueFormat list_content_types {name} reports. Pass {} to change only name/slug/displayInNav." - changed
Input schema / properties / documentId / descriptionPrevious value: -"The item's documentId (stable across locales and versions), e.g. from tree, list_pages or get_content."New value: +"The item's documentId (stable across locales and versions), e.g. from list_tree, list_content or get_content."
- Changed
update_content_type1 field changed- changed
Input schema / properties / definition / descriptionPrevious value: -"The full updated ContentTypeDef (same shape get_content_type returns; its valueFormat/valueExample annotations are ignored)."New value: +"The full updated ContentTypeDef (same shape list_content_types {name} returns; its valueFormat/valueExample annotations are ignored)."
50 tool updates
v0.1.3- Changed
ai_assist3 fields changed- added
Input schema / properties / input / descriptionAdded value: +"The text to work on (or, for write, the topic/brief)." - added
Input schema / properties / targetLocale / descriptionAdded value: +"Locale code of the output language, e.g. 'nb'. Required for translate; for improve/rewrite/variants it overrides 'same language as the input'." - added
Input schema / properties / task / descriptionAdded value: +"meta_title / meta_description = SEO text from the input; summarize; improve = polish grammar and flow; rewrite; variants = 3 alternatives as a JSON array; translate (needs targetLocale); write = 2-4 paragraphs about the topic in input; schema_fields = suggested fields for the schema.org @type named in input; alt_text is refused (see above)."
- Changed
copy_variant1 field changed- changed
Input schema / properties / documentId / descriptionPrevious value: -"Content documentId"New value: +"The item's documentId (stable across locales and versions), e.g. from tree, list_pages or get_content."
- Changed
create_content1 field changed- added
Input schema / properties / name / descriptionAdded value: +"Display name of the item (also the source of a page's auto slug), e.g. 'Summer sale 2026'."
- Changed
create_content_type1 field changed- added
Input schema / properties / definition / descriptionAdded value: +"A ContentTypeDef: {name: 'PascalCase', kind: 'page'|'block'|'global', fields: [{name, type, …}], …}. Copy the shape of an existing one from get_content_type."
- Changed
create_delivery_key2 fields changed- added
Input schema / properties / name / descriptionAdded value: +"Label for where it is used, e.g. 'Astro site production'." - added
Input schema / properties / type / descriptionAdded value: +"public = published only; preview = drafts too."
- Changed
create_redirect7 fields changed- added
Input schema / properties / keepQuery / descriptionAdded value: +"Carry the request's query string over to the target." - added
Input schema / properties / note / descriptionAdded value: +"Free-text note for editors, e.g. 'old WordPress URL'." - added
Input schema / properties / source / descriptionAdded value: +"Old path ('/old-page'), prefix ('/blog/*') or full URL on the old site." - added
Input schema / properties / status / descriptionAdded value: +"301 permanent (default), 302/307 temporary, 308 permanent, 410 gone (no target)." - added
Input schema / properties / target / descriptionAdded value: +"Destination path or https:// URL. Use this OR targetDocumentId." - added
Input schema / properties / targetDocumentId / descriptionAdded value: +"documentId of the destination page; the rule follows it if it moves. Use this OR target." - added
Input schema / properties / targetLocale / descriptionAdded value: +"Locale of targetDocumentId to send visitors to. Omit for the site default."
- Changed
create_user5 fields changed- added
Input schema / properties / email / descriptionAdded value: +"Sign-in email address; must be unique." - added
Input schema / properties / name / descriptionAdded value: +"Display name, e.g. 'Kari Nordmann'." - added
Input schema / properties / password / descriptionAdded value: +"Initial password, at least 10 characters." - added
Input schema / properties / roles / descriptionAdded value: +"Admin = everything; Editor = all content + publish + form submissions; Author = write content within its sections; Viewer = read-only." - added
Input schema / properties / sections / descriptionAdded value: +"documentIds of top-level pages this user is limited to (its subtree). Omit or [] for the whole site."
- Changed
create_webhook3 fields changed- added
Input schema / properties / events / descriptionAdded value: +"Events to send: content.published, content.unpublished, form.submitted. Omit or [] for all." - added
Input schema / properties / name / descriptionAdded value: +"Label, e.g. 'Netlify rebuild'." - added
Input schema / properties / url / descriptionAdded value: +"Public https:// endpoint to POST to."
- Changed
delete_asset1 field changed- changed
Input schema / properties / documentId / descriptionPrevious value: -"Content documentId"New value: +"The asset's documentId from list_assets."
- Changed
delete_form_submission1 field changed- added
Input schema / properties / submissionId / descriptionAdded value: +"The submission's id from list_form_submissions."
- Changed
delete_redirects1 field changed- added
Input schema / properties / ids / descriptionAdded value: +"Rule ids from list_redirects."
- Changed
delete_type_template1 field changed- added
Input schema / properties / name / descriptionAdded value: +"The stored template's name."
- Changed
delete_user1 field changed- added
Input schema / properties / id / descriptionAdded value: +"The user's id from list_users."
- Changed
delete_webhook1 field changed- added
Input schema / properties / id / descriptionAdded value: +"The webhook's id from list_webhooks."
- Changed
delivery_get_by_id3 fields changed- changed
Input schema / properties / documentId / descriptionPrevious value: -"Content documentId"New value: +"The item's documentId (stable across locales and versions), e.g. from tree, list_pages or get_content." - added
Input schema / properties / populate / descriptionAdded value: +"How many levels of references and shared blocks to resolve inline, 0-4 (default 0: references stay as ids)." - changed
Input schema / properties / preview / descriptionPrevious value: -"Use the preview perspective (drafts)"New value: +"true = read the latest drafts as the preview pane shows them (needs site-wide read access). Default false: published content only, exactly what visitors get."
- Changed
delivery_get_by_path3 fields changed- added
Input schema / properties / path / descriptionAdded value: +"The page's URL path without the locale prefix, e.g. '/blog/my-post'." - added
Input schema / properties / populate / descriptionAdded value: +"How many levels of references and shared blocks to resolve inline, 0-4 (default 0: references stay as ids)." - changed
Input schema / properties / preview / descriptionPrevious value: -"Use the preview perspective (drafts)"New value: +"true = read the latest drafts as the preview pane shows them (needs site-wide read access). Default false: published content only, exactly what visitors get."
- Changed
delivery_get_by_slug3 fields changed- added
Input schema / properties / populate / descriptionAdded value: +"How many levels of references and shared blocks to resolve inline, 0-4 (default 0: references stay as ids)." - changed
Input schema / properties / preview / descriptionPrevious value: -"Use the preview perspective (drafts)"New value: +"true = read the latest drafts as the preview pane shows them (needs site-wide read access). Default false: published content only, exactly what visitors get." - added
Input schema / properties / slug / descriptionAdded value: +"The slug, without slashes, e.g. 'about-us'."
- Changed
delivery_global2 fields changed- added
Input schema / properties / preview / descriptionAdded value: +"true = read the latest drafts as the preview pane shows them (needs site-wide read access). Default false: published content only, exactly what visitors get." - added
Input schema / properties / type / descriptionAdded value: +"The global's content type name, e.g. 'SiteHeader'."
- Changed
delivery_list5 fields changed- added
Input schema / properties / limit / descriptionAdded value: +"Page size, 1-500." - added
Input schema / properties / offset / descriptionAdded value: +"Items to skip, for paging." - added
Input schema / properties / populate / descriptionAdded value: +"How many levels of references and shared blocks to resolve inline, 0-4 (default 0: references stay as ids)." - changed
Input schema / properties / preview / descriptionPrevious value: -"Use the preview perspective (drafts)"New value: +"true = read the latest drafts as the preview pane shows them (needs site-wide read access). Default false: published content only, exactly what visitors get." - added
Input schema / properties / type / descriptionAdded value: +"Content type name, e.g. 'BlogPost'."
- Changed
delivery_search4 fields changed- added
Input schema / properties / limit / descriptionAdded value: +"Max results, 1-100." - added
Input schema / properties / preview / descriptionAdded value: +"true = read the latest drafts as the preview pane shows them (needs site-wide read access). Default false: published content only, exactly what visitors get." - added
Input schema / properties / query / descriptionAdded value: +"Words to search for, e.g. 'pricing plans'." - added
Input schema / properties / type / descriptionAdded value: +"Only return items of this content type."
- Changed
delivery_start2 fields changed- added
Input schema / properties / populate / descriptionAdded value: +"How many levels of references and shared blocks to resolve inline, 0-4 (default 0: references stay as ids)." - added
Input schema / properties / preview / descriptionAdded value: +"true = read the latest drafts as the preview pane shows them (needs site-wide read access). Default false: published content only, exactly what visitors get."
- Changed
discard_draft1 field changed- changed
Input schema / properties / documentId / descriptionPrevious value: -"Content documentId"New value: +"The item's documentId (stable across locales and versions), e.g. from tree, list_pages or get_content."
- Changed
duplicate_content1 field changed- changed
Input schema / properties / documentId / descriptionPrevious value: -"Content documentId"New value: +"The item's documentId (stable across locales and versions), e.g. from tree, list_pages or get_content."
- Changed
export_form_submissions1 field changed- added
Input schema / properties / formId / descriptionAdded value: +"Export only this form (formId from list_forms). Omit to export every form in the site."
- Changed
get_content1 field changed- changed
Input schema / properties / documentId / descriptionPrevious value: -"Content documentId"New value: +"The item's documentId (stable across locales and versions), e.g. from tree, list_pages or get_content."
- Changed
get_content_type1 field changed- added
Input schema / properties / name / descriptionAdded value: +"Content type name, PascalCase, e.g. 'BlogPost' (as in a content item's `type`)."
- Changed
get_form_submission1 field changed- added
Input schema / properties / submissionId / descriptionAdded value: +"The submission's id from list_form_submissions."
- Changed
get_type_template1 field changed- added
Input schema / properties / name / descriptionAdded value: +"Template name as listed by list_type_templates, e.g. 'FaqPage'."
- Changed
list_audit7 fields changed- added
Input schema / properties / action / descriptionAdded value: +"Action prefix, e.g. 'content.' (a whole category) or 'content.publish'." - added
Input schema / properties / actorUserId / descriptionAdded value: +"Only actions by this user id (from list_users)." - added
Input schema / properties / before / descriptionAdded value: +"Paging cursor: the id of the oldest row you already have." - added
Input schema / properties / documentId / descriptionAdded value: +"Only actions on this document." - added
Input schema / properties / from / descriptionAdded value: +"Earliest time, ISO 8601, e.g. '2026-10-01T00:00:00Z'." - added
Input schema / properties / limit / descriptionAdded value: +"Rows to return, 1-500 (default 100)." - added
Input schema / properties / to / descriptionAdded value: +"Latest time, ISO 8601."
- Changed
list_form_submissions3 fields changed- changed
Input schema / properties / formId / descriptionPrevious value: -"Limit to one form's submissions (documentId of the Form)."New value: +"Limit to one form's submissions (its formId from list_forms). Omit for all forms." - added
Input schema / properties / limit / descriptionAdded value: +"Page size, 1-200 (default 50)." - added
Input schema / properties / offset / descriptionAdded value: +"Rows to skip, for paging (default 0)."
- Changed
list_redirects1 field changed- added
Input schema / properties / targetDocumentId / descriptionAdded value: +"Only rules pointing at this page (documentId). Omit for all rules."
- Changed
list_versions1 field changed- changed
Input schema / properties / documentId / descriptionPrevious value: -"Content documentId"New value: +"The item's documentId (stable across locales and versions), e.g. from tree, list_pages or get_content."
- Changed
move_content4 fields changed- added
Input schema / properties / afterId / descriptionAdded value: +"Place it directly after this sibling (documentId)." - added
Input schema / properties / beforeId / descriptionAdded value: +"Place it directly before this sibling (documentId)." - changed
Input schema / properties / documentId / descriptionPrevious value: -"Content documentId"New value: +"The item's documentId (stable across locales and versions), e.g. from tree, list_pages or get_content." - added
Input schema / properties / parentId / descriptionAdded value: +"documentId of the new parent page; null moves it to the top level. Omit to keep the current parent."
- Changed
publish1 field changed- changed
Input schema / properties / documentId / descriptionPrevious value: -"Content documentId"New value: +"The item's documentId (stable across locales and versions), e.g. from tree, list_pages or get_content."
- Changed
rename_delivery_key2 fields changed- added
Input schema / properties / id / descriptionAdded value: +"The key's id from list_delivery_keys." - added
Input schema / properties / name / descriptionAdded value: +"The new label."
- Changed
restore_content1 field changed- changed
Input schema / properties / documentId / descriptionPrevious value: -"Content documentId"New value: +"The item's documentId (stable across locales and versions), e.g. from tree, list_pages or get_content."
- Changed
restore_version2 fields changed- changed
Input schema / properties / documentId / descriptionPrevious value: -"Content documentId"New value: +"The item's documentId (stable across locales and versions), e.g. from tree, list_pages or get_content." - added
Input schema / properties / versionId / descriptionAdded value: +"The version's `id` from list_versions (not its versionNumber)."
- Changed
revoke_delivery_key1 field changed- added
Input schema / properties / id / descriptionAdded value: +"The key's id from list_delivery_keys."
- Changed
set_field1 field changed- changed
Input schema / properties / documentId / descriptionPrevious value: -"Content documentId"New value: +"The item's documentId (stable across locales and versions), e.g. from tree, list_pages or get_content."
- Changed
set_start_page1 field changed- added
Input schema / properties / documentId / descriptionAdded value: +"documentId of a page in this site, or null to clear."
- Changed
test_redirect1 field changed- added
Input schema / properties / url / descriptionAdded value: +"A path ('/old-page?x=1') or full URL on this site."
- Changed
trash_content1 field changed- changed
Input schema / properties / documentId / descriptionPrevious value: -"Content documentId"New value: +"The item's documentId (stable across locales and versions), e.g. from tree, list_pages or get_content."
- Changed
tree1 field changed- added
Input schema / properties / parentId / descriptionAdded value: +"documentId of the page whose children to list. Omit for the top level."
- Changed
unpublish1 field changed- changed
Input schema / properties / documentId / descriptionPrevious value: -"Content documentId"New value: +"The item's documentId (stable across locales and versions), e.g. from tree, list_pages or get_content."
- Changed
update_asset_alt2 fields changed- added
Input schema / properties / alt / descriptionAdded value: +"The new alt text, e.g. 'Two hikers on a ridge above a fjord at sunset'." - changed
Input schema / properties / documentId / descriptionPrevious value: -"Content documentId"New value: +"The asset's documentId from list_assets or an upload result."
- Changed
update_content5 fields changed- added
Input schema / properties / data / descriptionAdded value: +"Field name → value, shaped per get_content_type's valueFormat. Pass {} to change only name/slug/displayInNav." - added
Input schema / properties / displayInNav / descriptionAdded value: +"Whether the page appears in the site navigation. Omit to keep it." - changed
Input schema / properties / documentId / descriptionPrevious value: -"Content documentId"New value: +"The item's documentId (stable across locales and versions), e.g. from tree, list_pages or get_content." - added
Input schema / properties / name / descriptionAdded value: +"New display name. Omit to keep it." - added
Input schema / properties / slug / descriptionAdded value: +"New URL slug for a page; publishing it adds a 301 from the old URL automatically. Omit to keep it."
- Changed
update_content_type2 fields changed- added
Input schema / properties / definition / descriptionAdded value: +"The full updated ContentTypeDef (same shape get_content_type returns; its valueFormat/valueExample annotations are ignored)." - added
Input schema / properties / name / descriptionAdded value: +"The existing type's name, e.g. 'BlogPost'."
- Changed
update_redirect8 fields changed- added
Input schema / properties / id / descriptionAdded value: +"The rule's id from list_redirects." - added
Input schema / properties / keepQuery / descriptionAdded value: +"Carry the request's query string over to the target." - added
Input schema / properties / note / descriptionAdded value: +"Free-text note for editors, e.g. 'old WordPress URL'." - added
Input schema / properties / source / descriptionAdded value: +"Old path ('/old-page'), prefix ('/blog/*') or full URL on the old site." - added
Input schema / properties / status / descriptionAdded value: +"301 permanent (default), 302/307 temporary, 308 permanent, 410 gone (no target)." - added
Input schema / properties / target / descriptionAdded value: +"Destination path or https:// URL. Use this OR targetDocumentId." - added
Input schema / properties / targetDocumentId / descriptionAdded value: +"documentId of the destination page; the rule follows it if it moves. Use this OR target." - added
Input schema / properties / targetLocale / descriptionAdded value: +"Locale of targetDocumentId to send visitors to. Omit for the site default."
- Changed
update_type_template2 fields changed- added
Input schema / properties / definition / descriptionAdded value: +"The full updated ContentTypeDef (same shape as create_content_type)." - added
Input schema / properties / name / descriptionAdded value: +"The stored template's name."
- Changed
update_user4 fields changed- added
Input schema / properties / id / descriptionAdded value: +"The user's id from list_users." - added
Input schema / properties / name / descriptionAdded value: +"New display name." - added
Input schema / properties / roles / descriptionAdded value: +"Admin = everything; Editor = all content + publish + form submissions; Author = write content within its sections; Viewer = read-only." - added
Input schema / properties / sections / descriptionAdded value: +"documentIds of top-level pages this user is limited to (its subtree). Omit or [] for the whole site."
74 tool updates
v0.1.0- First observed
ai_assist - First observed
copy_variant - First observed
create_content - First observed
create_content_type - First observed
create_delivery_key - First observed
create_redirect - First observed
create_type_template - First observed
create_upload_link - First observed
create_user - First observed
create_webhook - First observed
delete_asset - First observed
delete_form_submission - First observed
delete_redirects - First observed
delete_type_template - First observed
delete_user - First observed
delete_webhook - First observed
delivery_get_by_id - First observed
delivery_get_by_path - First observed
delivery_get_by_slug - First observed
delivery_global - First observed
delivery_list - First observed
delivery_search - First observed
delivery_start - First observed
discard_draft - First observed
duplicate_content - First observed
erase_form_submissions - First observed
export_form_submissions - First observed
export_type_templates - First observed
get_content - First observed
get_content_type - First observed
get_form_submission - First observed
get_site_config - First observed
get_type_template - First observed
import_stock_image - First observed
import_type_templates - First observed
instantiate_type_template - First observed
list_assets - First observed
list_audit - First observed
list_blocks - First observed
list_content_types - First observed
list_delivery_keys - First observed
list_form_submissions - First observed
list_forms - First observed
list_globals - First observed
list_locales - First observed
list_not_found - First observed
list_pages - First observed
list_redirects - First observed
list_sites - First observed
list_trash - First observed
list_type_templates - First observed
list_users - First observed
list_versions - First observed
list_webhooks - First observed
move_content - First observed
publish - First observed
rename_delivery_key - First observed
restore_content - First observed
restore_version - First observed
revoke_delivery_key - First observed
search_stock_images - First observed
set_field - First observed
set_start_page - First observed
test_redirect - First observed
trash_content - First observed
tree - First observed
unpublish - First observed
update_asset_alt - First observed
update_content - First observed
update_content_type - First observed
update_redirect - First observed
update_type_template - First observed
update_user - First observed
upload_asset
TDQS
Scored across 25 tools
Most tools target distinct resources and actions (content vs assets vs versions), but a few pairs overlap: list_content vs list_tree both list pages, update_content vs set_field both edit drafts, and upload_asset vs create_upload_link both add assets. Descriptions explicitly differentiate when to use each, so misselection is unlikely but possible.
All names use snake_case with a leading verb (list_, get_, create_, update_, etc.), and the noun patterns are predictable across content, asset, and version tools. Minor forms like assist_writing (verb + gerund) remain readable and do not break the overall convention.
25 tools is on the heavy side for a single server, but the domain is broad—content lifecycle, assets, versions, sites, and AI writing—and each tool covers a distinct operation without obvious redundancy. Slightly over the typical 3–15 range, but justified for a full CMS API.
Core content lifecycle is thoroughly covered: create/read/update/publish/unpublish/trash/restore, versioning, locale variants, and asset upload/delete/alt text. Minor gaps exist—no direct asset file replacement, no text search for content, and no schema or site write operations—but these are either out of scope or workable via list operations.
Maintenance
Related MCP Connectors
A blog CMS that AI agents write into over MCP: Markdown drafts, versions, media, human approval.
Agent control plane for multi-site content ops: research, drafts, review, CMS draft delivery.
Agent-first social media publishing, scheduling, analytics, and unified inbox API + 16-tool MCP server across 18 social platforms (X, LinkedIn, Instagram, YouTube, TikTok, Threads, Bluesky, Reddit, Telegram, Discord, Mastodon, Facebook, Pinterest).
Connect any AI agent to 11+ social platforms: schedule, publish & track posts via hosted MCP.
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceA self-hosted content engine with an MCP interface that enables AI agents to read, write, and manage content models through tools like describe_model, create_type, draft, and publish.Apache 2.0
- AlicenseNot gradedqualityBmaintenanceAgent-first headless CMS running as a Cloudflare Worker, providing MCP servers for schema/content management and editorial operations.17 npm5MIT
- AlicenseBqualityCmaintenanceOpen-source MCP server that turns any CMS backend into an AI-agent-ready content management system. It provides tools for content CRUD, idea management, and publication tracking, with safety features requiring human approval for publishing.52MIT
- AlicenseNot gradedqualityDmaintenanceAn MCP-native CMS that lets you create and edit content by chatting, with typed schemas and seamless publishing.4 npm3MIT