MCP Tools: Site Structure

Structure tools change the shape of your site rather than its words: which page types and search indexes exist, what the navigation tree looks like, which labels group your content, what lives at which URL, what your funnel stages are called, which phrases turn into links automatically, and the company’s own site-wide settings. Thirty-four tools cover that surface. Every write lands in a draft state and waits to be published.

All thirty-four tools at a glance
ToolReads or writesAlso in the assistant?
list_page_typesReadYes
get_page_typeReadNo
create_page_typeWrite — confirm-gatedNo
update_page_typeWriteNo
list_search_indexesReadNo
create_search_indexWriteNo
update_search_indexWriteNo
list_menusReadYes
get_menuReadYes
create_menuWriteYes
add_menu_itemWrite — one item or subtreeYes
set_menu_item_fieldsWrite — one itemYes
move_menu_itemWrite — one itemYes
reorder_menu_itemsWrite — one parent’s childrenYes
remove_menu_itemWrite — one itemYes
update_menu_fieldsWrite — the menu record onlyYes
list_labelsReadNo
get_labelReadNo
create_labelWriteNo
update_labelWriteNo
list_label_usersReadNo
list_pathsReadNo
resolve_pathReadNo
create_path_aliasWriteNo
delete_path_aliasDelete — confirm-gatedNo
list_broken_linksReadNo
list_journey_stagesReadNo
update_journey_stageWrite — name and description onlyNo
list_autolinksReadNo
create_autolinkWriteNo
update_autolinkWriteNo
reorder_autolinksWriteNo
update_company_settingsWriteNo
upload_company_imageWriteNo

Page types

Four tools. A page type is the template-plus-schema that decides what fields a page has and where its URLs live, so the two reads are things a model should call before it creates or edits a page, not after.

list_page_types

GET /api/ai/context/page_types · Returns { pageTypes: [{ pageTypeId, slug, name, type, editorFormattingOptions, formattingEnforced }] } · Also available in the Leed Assistant

Takes no parameters. type is one of documentation, api or posts, and is absent for a custom type. The tool’s own description instructs the model to call this before choosing a pageTypeId, and to ask you rather than guess when more than one page type plausibly matches — so “put this in the blog” resolves to the posts-kind entry and “add it to the docs” to the documentation-kind one.

get_page_type

GET /api/pagetypes/:pageTypeId · Returns { pageType } · MCP only

ParameterTypeRequiredDefaultNotes
pageTypeIdstringYes—From list_page_types

Returns the full page type, including its field schema, its required-field configuration and the same editorFormattingOptions and formattingEnforced that list_page_types reports. This is the call that tells a model that documentation pages must carry a summary before they can be published, so it is worth making before a bulk authoring run rather than discovering the requirement one rejected publish at a time. Read it before update_page_type too.

create_page_type

POST /api/pagetypes · Returns { pageType } · MCP only · Confirm-gated, local mode

Creates a posts or documentation page type; type is required, and API page types are created only by the OpenAPI import. Fields you leave out get the same per-kind defaults the Settings screen uses. A documentation page type gets a new left navigation menu of its own, unless documentationConfiguration.menus.left names an existing menu to bind instead — one that is not another type’s navigation or a company site menu, has no folder carrying a link and no page nested under a page. Creating a documentation page type also creates a search index for it. The first call returns a preview; nothing is created until the same call is repeated with confirm: true. There is no delete tool, so only a person in the CMS can remove a page type again.

update_page_type

PUT /api/pagetypes/:pageTypeId · Returns { pageType } · MCP only

Send only the fields to change. Nested objects — documentationConfiguration, requiredFields, editorFormattingOptions, ogCard and paging — are merged field by field, so naming one field leaves its siblings alone; send null to remove a setting. Arrays such as aliases and labels are replaced whole. The rules both write tools share:

  • The kind (type) is fixed at creation and cannot change.
  • slug must be unique across page types (409), and a slug-locked page type’s slug cannot change (403).
  • A setting that does not apply to the page type’s kind is rejected with a 400 rather than ignored.
  • documentationConfiguration.openAPI, tabGroups and highlighter are company-level only and are dropped if sent here — change them with update_company_settings.
  • locked and navMenuLocked can be set only by an Administrator or Content Publisher, and a custom theme or font needs Starter or above (402).

Read the company defaults a documentation page type inherits on Configuring a Page Type.

Search indexes

Three tools. A search index decides which page types a site search covers. A documentation or API page type usually has one already, because creating the page type creates it.

list_search_indexes

GET /api/searchindexes · Returns { searchIndexes, isDirty } · MCP only

Takes no parameters. Each index carries its id, name and pageTypeIds.

create_search_index

POST /api/searchindexes · Returns { searchIndex } · MCP only

ParameterTypeRequiredDefaultNotes
namestringYes—Unique among the company’s live indexes; a duplicate is a 400
pageTypeIdsstring[]No[]Each documentation or API type named that has no index yet is bound to this one

The id is generated by the server. Requires pagetype:write.

update_search_index

PUT /api/searchindexes/:searchIndexId · Returns { searchIndex } · MCP only

Takes searchIndexId plus name and/or pageTypeIds. pageTypeIds replaces the list rather than appending, so read it with list_search_indexes and send the full set. There is no delete tool for search indexes.

Nine tools: two reads, a create, and six writes that each change one thing. After a menu is created it changes only through those six — there is no whole-tree replace.

list_menus

GET /api/menu · Returns { menus } · Also available in the Leed Assistant

Takes no parameters. Each row carries menuId, name, isDirty and an isDocsMenu flag saying whether that menu is bound to a documentation page type as its left navigation — which matters, because the docs-menu rules below apply only to those.

get_menu

GET /api/menu/:menuId · Returns { menu, isDocsMenu } · Also available in the Leed Assistant

ParameterTypeRequiredDefaultNotes
menuIdstringYes—From list_menus

Returns the menu’s draft state — the tree as it currently stands, including unpublished edits, with every item’s menuItemId — and menu.revision, an opaque token. Every menu write takes it as baseRevision, and each write returns the new revision so calls chain without a re-read. A stale revision is rejected with 409 concurrent_update rather than overwriting a change somebody else made in the meantime.

create_menu

POST /api/menu · Returns { menu } · Also available in the Leed Assistant

ParameterTypeRequiredDefaultNotes
namestringYes—Must not be blank — see the warning about renaming
itemsarray of menu itemsNo[]The starting tree, in the item shape below
descriptionstringNo—Internal description only
expandedbooleanNo—Whether the menu renders expanded by default

Needs menu:write. The menu is a draft until it is bound to a page type or the company navigation and published.

Editing a menu one item at a time

Each of these posts a single op to POST /api/menu/:menuId/items/ops (or, for update_menu_fields, PATCH /api/menu/:menuId). All take menuId and baseRevision. The five item tools return { menuId, revision, isDocsMenu, applied } rather than the tree; update_menu_fields returns { menu, isDocsMenu }.

ToolArgumentsWhat it does
add_menu_itemparentItemId (omit or null for top level), index, itemAdds one item, which may carry a whole subtree in submenu. A minted id comes back in applied[0].menuItemId
set_menu_item_fieldsmenuItemId, fieldsChanges the named fields of one item; null clears an optional one
move_menu_itemmenuItemId, parentItemId (required; null is the top level), indexMoves an item or a whole folder, at any depth, in one call
reorder_menu_itemsparentItemId, orderReorders one parent’s children; order must list exactly its current child ids
remove_menu_itemmenuItemId, childrenRemoves one item. On an item with children, children must be promote or cascade
update_menu_fieldsname, description, expandedChanges the menu record’s own fields, never its items

A menu item is a node, and a node with children is a folder. The shape is the same either way:

FieldTypeMeaningDocs-menu constraint
menuItemIdstring, optionalStable id for the nodeMinted for you when omitted
namestring, requiredThe visible label in the navFor a folder, this is what gets slugified into the URL segment
titlestring, default ""The hover tooltipNever appears in a URL
hrefstring, default ""pageid:<pageId> for a page; "" for a folderBlanked automatically on folders — see below
targetstring, default ""Link target; "" for a normal in-site link—
submenuarray, optionalChild itemsA page item may not have children
icon + iconStylestring, optionalFont Awesome icon on the itemFolders carry no icon
expandedboolean, optionalFolder open by default—
disabledboolean, optionalRenders the item grayed out—
descriptionstring, optionalInternal note—

Unknown keys are refused.

These rules are enforced by the route rather than by convention:

  • A folder can never be a link. On a docs-bound menu, the href of any item that has children is blanked. Sending a folder with an href is not an error — the href is simply discarded.
  • A page item may not have children. Sending one is a 400 with the message “A docs page item cannot have children — place pages inside folders instead”.
  • A documentation page may appear in exactly one docs left-nav menu, once. A duplicate inside the tree, or a page already carried by another docs menu, is a 409.
  • No change may leave a page of a docs or API set out of its menu. Removing a page’s entry, or cascading a folder that holds pages, is refused — move the page, use children: promote, or delete the page itself.
  • menuItemIds are unique at every depth. A tree carrying a duplicate is a 400 naming it, and a menu that already holds duplicates cannot be saved until they are removed.

There is no order field on a menu item. Order is array order, top to bottom, at every level.

Adding a folder holding one page is a single add_menu_item call, with the page inside the folder’s submenu. menuId and baseRevision travel as their own arguments alongside the item:

{
  "menuId": "docs-left-nav",
  "baseRevision": "<revision from get_menu>",
  "parentItemId": null,
  "index": 0,
  "item": {
    "name": "Getting Started",
    "expanded": true,
    "submenu": [
      {
        "name": "Install the CLI",
        "href": "pageid:9f1c0f5e-6b1a-4b2e-9a77-0c2d5a1f8e10",
        "icon": "fa-solid fa-download"
      }
    ]
  }
}

Publishing a menu

There is no per-menu publish tool. A menu edit appears in list_pending_changes as a menus item, and goes live through publish_pending_changes — confirm-gated, and documented on MCP Tools: Publishing, Distribution and Users — or from the CMS. If you want the same tree edited by hand instead, Building and Editing a Menu is the screen, and Left Navigation Menu covers the docs-specific rules from the editor’s side.

Labels

Five tools. A label does three jobs at once in Leed — a taxonomy, a series, and an RBAC boundary — so read Labels and Series before reshaping them programmatically.

list_labels

GET /api/labels · Returns { labels } · MCP only

Takes no parameters. Publish state is per label: each one carries its own needsPublish, along with locked.

get_label

GET /api/labels/:labelId · Returns { label } · MCP only

ParameterTypeRequiredDefaultNotes
labelIdstringYes—From list_labels

update_label

PUT /api/labels/:labelId · Returns { label } · MCP only

ParameterTypeRequiredDefaultNotes
labelIdstringYes—Which label to edit
namestringNounchangedDisplay name
descriptionstringNounchanged—
visiblebooleanNounchangedWhether the label is shown on the public site
isSeriesbooleanNounchangedTurns the label into a multi-part series
seriesmultipart or announcementNounchangedOnly meaningful when isSeries is true
slugstringNounchangedThe label’s URL segment

Only the fields you send change. A locked label is read-only and any update is a 403; a slug another label already uses is a 409; and series requires isSeries: true, in the call or already stored. Updating needs Content Publisher or above, or a Publish override on the label.

create_label

POST /api/labels · Returns { label } · MCP only

Takes the same fields as update_label, with name required and not blank. A slug must be unique (409), and series requires isSeries: true in the same call (400). Needs Content Publisher or above (label:write). There is no delete_label on the MCP surface.

list_label_users

GET /api/labels/:labelId/users · Returns { users: [{ userId, role }] } · MCP only

ParameterTypeRequiredDefaultNotes
labelIdstringYes—Which label to inspect

URL paths

Five tools that answer “what lives where”: three reads — list_paths, resolve_path and the broken-link report — plus alias create and delete.

list_paths

GET /api/paths · Returns { paths } · MCP only

ParameterTypeRequiredDefaultNotes
qstringNo—Free-text filter over the path text
tstringNoall typesComma-separated list of row types, e.g. alias or current,next

Each row carries id, pageId, path, type, title, createdAt and a cmsManaged flag. The type is the field that matters:

TypeMeaningCreated by
currentThe live URL of a published pagePublishing a page
nextA placeholder reserving a URL for a page that is not published yetCreating or moving an unpublished page, or a staged folder move
aliasA custom path that redirects to a pageAdding an alias in the CMS, or a page whose URL changed

A page that has moved typically owns several rows: one current at its new URL and one alias per old URL. URL Paths and Slugs is the full model.

resolve_path

GET /api/paths/lookup · Returns { pageId } (or { pageId: null }) · MCP only

ParameterTypeRequiredDefaultNotes
urlstringYes—A relative path or an absolute URL on your own domain

The reverse of list_paths, and the tool a model should reach for whenever it has a URL and needs a page. Anything that is not on your domain returns null rather than an error. Use it before writing a cross-link: a body should carry pageid:<id>, never a literal path, so the link survives the page moving.

create_path_alias

POST /api/paths · Returns { path } · MCP only

Creates an alias row — a custom path that redirects to a page. The server normalizes the path before storing it, so use the path in the response, which is what a browser will match. A path another row already holds is a 400 (Path already taken). current and next rows cannot be created here. Requires path:write.

delete_path_alias

DELETE /api/paths · MCP only · Confirm-gated, local mode

Deletes one row by the numeric id that list_paths reports. Only an alias row, or the next row of a deleted page, can be deleted; any other row is a 409. Deleting an alias breaks any live inbound URL pointing at it. Requires path:delete.

GET /api/brokenlinks · Returns { brokenLinks: { checkedAt, links } } · MCP only

The last stored report of broken external links, keyed by pageId, with each link’s final status, message, latency and redirect chain. It is not a fresh check and there is no way to request one, so read checkedAt before telling anyone a link is broken now. A workspace that has never been checked gets a 404. Requires company:read.

Journey stages

Journey stages are the funnel bands Engage scores contacts into. Two tools, and the write is deliberately narrow.

list_journey_stages

GET /api/journeys · Returns { journeys } · MCP only

Takes no parameters.

update_journey_stage

PUT /api/journeys/:journeyId · Returns { journeys } — the whole list, re-saved · MCP only

ParameterTypeRequiredDefaultNotes
journeyIdstringYes—Which stage to edit
namestringNounchangedDisplay name of the stage
descriptionstringNounchanged—

The patch is merged into the matching stage; an unknown journeyId is a 404. The set of stages is fixed — there is no tool to add one, remove one or reorder them, over MCP or in the assistant. Renaming a stage does not re-score anybody. Journey Stages covers what the stages mean and why the Engage journey is not editable.

An autolink turns a phrase into a link to a page everywhere that phrase appears, at build time. Four tools; the order they are applied in decides which of two overlapping phrases wins on a page.

GET /api/autolinks · Returns { autolinks, orderNeedsPublish } · MCP only

Optional filters: needsPublish, pageId and unlinked. Rows come back in application order, each carrying its phrase, target page, needsPublish and lastBuildPageCount / lastBuildAt — how many pages the phrase linked on the last public build. null means no statistics since the last edit; 0 means the phrase matched nothing or its target was not in that build.

POST /api/autolinks · Returns { autolink } · MCP only

ParameterTypeRequiredDefaultNotes
textstringYes—The phrase to match, whole words, case-insensitive. Need not be unique
pageIdstringNo—The page to link it to. Without one, the autolink is stored but never published
positionintegerNoappendedIndex in the application order, clamped rather than rejected

The id is generated server-side. Requires autolinks:write.

PUT /api/autolinks/:autolinkId · Returns { autolink } · MCP only

Changes text or pageId. An autolink is retarget-only: pageId can point at a different page but can never be cleared, and sending null is a 400. Naming a server-owned field such as lastBuildPageCount is a 403, and any change to the phrase or target clears that autolink’s build statistics.

POST /api/autolinks/reorder · Returns { order } · MCP only

Takes order, a list of autolink ids. It sets position only, never membership: an autolink missing from the list keeps its place at the end, and an unknown id is ignored.

Autolinking is a Starter-and-up feature at build time, and phrase ordering and the per-page opt-out both change what actually gets linked — Autolinks covers both.

Company settings

Two tools for the company-wide settings on Settings → General. Both need Content Publisher or above.

update_company_settings

PUT /api/company · Returns { company } · MCP only

A JSON merge patch: omitted fields keep their stored value, an explicit null clears a leaf, and nested objects merge field by field. Arrays, documentationConfiguration.tabGroups and documentationConfiguration.openAPI are replaced whole. documentationConfiguration here holds the company-wide documentation defaults every documentation and API page type inherits and may override — except openAPI, tabGroups and highlighter, which are company-wide only. A field the schema does not name is dropped rather than rejected, so check the response for what was stored. A change to a published setting sets needsPublish: true.

upload_company_image

POST /api/assets/favicon · Returns { target, path } · MCP only

Uploads one image, sent as base64, into one slot: favicon, logo, docLogoLight or docLogoDark. Only that slot changes. The file must decode to at most 100 KB (413 otherwise). A slot that points somewhere new marks the company as needing publication.

If the tool name you came here for is not on this page, the Operator MCP Tool Index lists every tool alphabetically with the page that documents it.

ESC