MCP Tools: Pages and Content

Eleven of the sixty-eight Operator MCP tools work on pages. Two create them, three read them, two write bodies, one changes metadata and three search published content. This page is the parameter reference: what each tool takes, what it gives back, and where the sharp edges are.

It is deliberately not the workflow. The order these tools must be called in, why a body can be filled only once, and what happens when an anchor goes stale are all on Authoring Pages Over MCP — read that first if you are writing content rather than looking up a field. Every tool name here is also in the alphabetical Operator MCP Tool Index.

All eleven are available to the Leed Assistant as well as to MCP clients, and all eleven dispatch to the same permission-checked API routes the CMS itself calls — so your role is the ceiling on every one of them.

Creating pages

create_page

POST /api/page · Returns { page: { pageId, title } }, also delivered as structuredContent · Available in the assistant

ParameterTypeRequiredDefaultNotes
pageTypeIdstringYes—From list_page_types. A locked page type refuses new pages
titlestringYes—2–150 characters. This is what the slug is derived from
slugstringNo—Accepted and ignored. See below

Creates the page in DRAFT status with an empty body and hands back its pageId. That id is minted server-side and cannot be supplied, which is what makes a multi-page import a two-pass job.

{ "pageTypeId": "u05nxr", "title": "Installing the CLI" }
{ "page": { "pageId": "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d", "title": "Installing the CLI" } }

A documentation or API page is appended to that page type’s bound left-nav menu at top level, with a flat path. It cannot be created inside a folder over MCP — folder placement is a menu save, described in Folders Set Your URLs.

create_page_draft

POST /api/page/from-markdown · Returns the created page · Available in the assistant

ParameterTypeRequiredDefaultNotes
titlestringYes—2–150 characters
pageTypeIdstringYes—From list_page_types
markdownstringYes—Leed Markdown, sent raw — not fenced, not escaped
labelsstring[]No—Label ids
plannedDatestringNo—ISO date, YYYY-MM-DD

The one-shot: creates the page and sets its body in a single call. Right for a standalone page, wrong for a set that cross-links, because the body it writes cannot reference a page that has not been reserved yet — and like fill_page_markdown, it is a fill, so you get one attempt at the body.

The markdown you send is Leed Markdown; Overview and Cheat Sheet has one example of every feature, and raw HTML other than <br> fails the whole call.

Reading pages

get_page

GET /api/page/:pageId · Returns the page record · Available in the assistant

ParameterTypeRequiredDefaultNotes
pageIdstringYes——

Metadata, status, labels, dates and SEO fields. The body is not included — that is get_page_markdown.

get_page_markdown

GET /api/page/:pageId/markdown · Returns { markdown }, also delivered as structuredContent · Available in the assistant

ParameterTypeRequiredDefaultNotes
pageIdstringYes——

Returns the body as Leed Markdown with every top-level block carrying its {data-id="…"} anchor. Those ids are the only addressable targets for apply_page_markdown_ops, and they must come from a read you just made — never from memory and never invented.

{
  "markdown": "## Requirements {data-id=\"8c07b3\"}\n\nNode 20 or later. {data-id=\"1de590\"}\n"
}

The markdown reflects the clean accepted-state body: a deletion that is still pending as somebody’s tracked suggestion is not shown.

list_pages

GET /api/page · Returns the pages visible to you · Available in the assistant

ParameterTypeRequiredDefaultNotes
labelIdstringNo—Narrow to one label
pageTypeIdstringNo—Narrow to one page type

Both filters are optional and independent. The list is already scoped by your role, so a Read Only connection sees a shorter list than an Administrator’s — that is the permission model, not a bug. Managing Pages covers the same list as the CMS presents it.

Writing bodies

fill_page_markdown

POST /api/page/:pageId/content/fill · Returns { page: { pageId } }, also delivered as structuredContent · Available in the assistant

ParameterTypeRequiredDefaultNotes
pageIdstringYes——
markdownstringYes—The complete body. Sent raw

New blocks need no data-id — ids are assigned on import. On a posts page type, markdown using a formatting feature the type disables is rejected with a 400 naming the offending features.

apply_page_markdown_ops

POST /api/page/:pageId/content/ops · Returns { page: { pageId } }, also delivered as structuredContent · Available in the assistant

ParameterTypeRequiredDefaultNotes
pageIdstringYes——
opsarrayYes—At least one op, applied in order. Each anchor may be targeted once

Each op is one of four shapes. insertAfter, insertBefore and replace take anchorId and non-empty markdown; delete takes anchorId alone.

{
  "pageId": "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
  "ops": [
    { "type": "replace", "anchorId": "1de590", "markdown": "Node 22 or later." },
    { "type": "insertAfter", "anchorId": "8c07b3", "markdown": "Bun 1.3 is also supported." }
  ]
}

Changes land as tracked suggestions attributed to the connected user, never as a direct write. If any anchor is missing, targeted twice, or already carries a pending suggestion, the entire op-set is rejected with a 409 and nothing is applied. The exact messages and the recovery for each are on Authoring Pages Over MCP; what your team then sees is on Suggesting Mode and Tracked Changes.

Changing metadata

update_page_draft

PUT /api/page/:pageId · Returns the updated page · Available in the assistant

pageId is the only required field; every other field is optional and only the ones you send are written. These are the same fields the editor’s right rail exposes, described for humans on Page Settings.

Three of them behave in ways worth knowing before you send a bulk update:

  • slug — changing it recomputes the page’s URL and stages a next path row for the next publish. The value is de-duplicated on the way in, so the slug you get back may carry a -2 suffix. Changing title alone does not re-slug an existing page; only create_page derives a slug from a title.
  • pageTypeId — moving a page to another page type also recomputes its path, and moving it to a non-documentation type clears its folder ancestry.
  • path — present in the tool’s schema and silently dropped by the route. The docs menu is the only writer of a docs page’s folder ancestry; a path persisted here would desync the published file location from the recorded URL. A call that sets it appears to succeed and does nothing.
Every field update_page_draft accepts
FieldTypeNotes
pageIdstringRequired. Which page to update
titlestring2–150 characters. Does not change the slug of an existing page
slugstringLower-case alphanumerics and single hyphens, max 150. De-duplicated; recomputes the URL
pageTypeIdstringMoves the page to another page type and recomputes its path
statusenumDRAFT, IN_REVISION, APPROVED, SCHEDULED, PUBLISHED, REPLACED
summarystringRequired at publish on documentation page types
keywordsstring[]Rendered as meta keywords
labelsstring[]Label ids. Also drives label-scoped permission overrides
authorsstring[]User ids shown as bylines
contributorsstring[]User ids shown alongside authors
publishedAtdateDrives collection sort order, sitemap <lastmod>, feed dates and JSON-LD datePublished
plannedDatedateEditorial calendar date; controls display order and scheduling
dateFormatstringDisplay format for plannedDate, e.g. MMMM yyyy
hasBeenPublishedbooleanWhether this page has ever been live
formIdstringForm to render on the page
journeyIdstringLinks the page to a journey for attribution
featureImageobject{ assetId, src, width, height, alt, svg } — assetId, src, width, alt and svg are all required together
featureVideoobject{ assetId, videoId, videoOptions }
ogCardobject{ title (≤70), description (≤240), image }
sitemapPrioritynumber0.0–1.0
wordCountnumberNormally computed on save; settable but rarely useful
extraFrontmatterobjectUnmodeled frontmatter passed through verbatim on publish
disableCtabooleanSuppress the page’s call to action
disableAutolinkbooleanExclude the page from autolink rewriting
eleventyExcludeFromCollectionsbooleanKeep the page out of site collections and listings
privatebooleanRestrict who can see the page
previewOnlybooleanExcludes the page from production builds and from vector indexing. Defaults false
pathstringDropped by the route. Folder placement is a docs-menu save

Two notes on that table. previewOnly is documented because it is in the schema, not because it is a recommended workflow — there is no CMS control for it, and a page carrying it is absent from your production site and from the search index that search_docs queries. This documentation set sets it on zero pages.

And modifiedAt is not settable. It is stamped server-side on every metadata write, so an import cannot backdate it. publishedAt is settable and is load-bearing for sort order, sitemaps, feeds and structured data — an AI creating pages should set it deliberately rather than leave it for somebody to notice later.

Searching your own content

search_docs, search_pages and search_api are one search engine behind three hard-scoped views. The page-type kind is baked into each tool’s route rather than passed as an argument, so a model cannot cross from documentation into blog posts by changing a parameter.

ToolSearches
search_docsPublished documentation pages
search_pagesPublished posts — blog and article pages
search_apiPublished API reference pages

All three take the same arguments.

GET /api/page/content-search · Returns ranked pages with snippets · Available in the assistant

ParameterTypeRequiredDefaultNotes
querystringYes—2–200 characters
modeenumNohybridfreetext, vector or hybrid
limitintegerNo101–50
pageTypeIdsstring[]No—Narrows within the tool’s kind; can never widen it

pageTypeIds is the versioning axis: it is intersected server-side with the page types the tool’s kind already resolves to, so passing a blog page type to search_api narrows the search to nothing rather than escaping into blog content.

ModeHow it matchesWhen to use it
freetextFull-text search over published revisionsExact terms, product names, error strings, anything you would quote
vectorEmbeds the query and searches the workspace’s vector index by meaningConceptual questions where the page never uses your words
hybridRuns both and merges them with reciprocal rank fusion, deduplicated by pageThe default, and the right answer almost always

Only published pages are searchable. A draft an AI just created will not appear until somebody publishes it, which surprises people mid-import; use list_pages to see drafts.

This is the same engine that powers search on your published site, described from the reader’s side on Live Documentation Search.

Which fields a page type requires before any of these pages can be published is set per type; Configuring a Page Type covers that, and the full boundary of what a connected client may do is on What the Operator MCP Can and Cannot Do.

ESC