MCP Tools: Publishing, Distribution and Users

The closest the Operator MCP gets to publishing is scheduling, and even that stops to ask. There is no tool that makes a page live now, no tool that pushes settings to your repository, and no tool that deletes anything. What is here is everything that surrounds a publish: the one tool that can put pages in the queue, the channels a publish fans out to, the calendar the whole workspace is scheduled on, a read-only view of everything you have changed but not published, your short links, and your team’s profiles.

This page ends with the definitive list of what MCP is never allowed to do, and where each of those things is actually done.

Scheduling

schedule_pages is the only tool on the MCP surface that can change when content goes live, and it is one of two tools anywhere on the surface that require an explicit confirmation.

schedule_pages

POST /api/page/schedule-bulk · Operator MCP only · confirm-gated (route mode) · requires page:publish

ParameterTypeRequiredDefaultNotes
pageIdsstring[]yes—1–100 page ids. Duplicates are removed before processing.
publishedAtstring (ISO-8601)yes—One shared slot for the whole batch. Must be in the future — a past timestamp is 400 publishedAt must be in the future.
confirmbooleannofalseOmit for a non-mutating preview. Pass true only after you have approved the previewed plan.

Returns a per-page result set: total, wouldSchedule/wouldFail on a preview or scheduled/failed on execution, and a results array with a row for every id.

Per page, never all-or-nothing

Every id in the batch is evaluated independently against four checks: that the page exists in your workspace, that you hold page:publish on it, that its current status can be scheduled, and that the page type’s required fields are filled. Pages that pass are scheduled even when others fail, and the result explains each failure in place.

ReasonWhat it meansFix
Page <id> not foundNo page with that id in this workspaceResolve the id with list_pages or resolve_path rather than guessing
Missing page:publish permissionYour role and overrides do not allow publishing this pageAsk for the privilege, or hand the batch to someone who has it
Page is PUBLISHED and cannot be scheduled — revise it firstThe page is already liveCreate a revision, then schedule the revision
Missing required fields: …The page type requires a feature image, form, summary or keywords that this page does not haveFill the named fields with update_page_draft, then re-run

Titles and current statuses appear in the result only after the permission check passes, so a failed permission check does not disclose what the page is.

Two-phase confirmation, route mode

The first call — no confirm, or confirm: false — is a dry run performed by the backing route itself. That matters: the preview is not a guess assembled in the MCP layer, it is the real validation run against real pages, permissions and page types, with nothing written.

{
  "dryRun": true,
  "publishedAt": "2026-09-15T14:00:00.000Z",
  "total": 3,
  "wouldSchedule": 2,
  "wouldFail": 1,
  "results": [
    { "pageId": "abc123", "title": "Release 4.10", "currentStatus": "DRAFT", "wouldSchedule": true },
    { "pageId": "def456", "title": "Changelog", "currentStatus": "IN_REVISION", "wouldSchedule": true },
    {
      "pageId": "ghi789",
      "title": "Pricing",
      "currentStatus": "PUBLISHED",
      "reason": "Page is PUBLISHED and cannot be scheduled — revise it first",
      "wouldSchedule": false
    }
  ],
  "instruction": "This was a preview — nothing was changed. Present this plan to the user and ask for approval; only call schedule_pages again with the same arguments plus confirm: true after the user explicitly approves."
}

Approving means the same call again with the same arguments plus the flag:

{ "pageIds": ["abc123", "def456"], "publishedAt": "2026-09-15T14:00:00.000Z", "confirm": true }
{
  "dryRun": false,
  "publishedAt": "2026-09-15T14:00:00.000Z",
  "total": 2,
  "scheduled": 2,
  "failed": 0,
  "results": [
    { "pageId": "abc123", "title": "Release 4.10", "currentStatus": "DRAFT", "scheduled": true },
    { "pageId": "def456", "title": "Changelog", "currentStatus": "IN_REVISION", "scheduled": true }
  ]
}

Scheduled pages go live on their slot through a per-minute cron, not through anything your client does — scheduling and automatic publishing describes what happens at the slot. Why confirmation exists at all, and how the other confirm-gated tool differs, is in how Leed’s AI is guarded.

Seeing what is unpublished

list_pending_settings_changes

GET /api/deployments/pending · Operator MCP only · read-only · requires deployment:read

No parameters. Returns { items: [...] }, one entry per dirty resource anywhere in the CMS:

{
  "items": [
    { "type": "labels", "id": "label-release-notes", "label": "Release Notes", "targetBranch": "main" },
    { "type": "menus", "id": "menu-leeddocs", "label": "Docs Navigation", "targetBranch": "main" },
    { "type": "company_settings", "id": "company", "label": "Company Settings", "targetBranch": "main" }
  ]
}

type is the deployment reason — labels, page_types, autolinks, menus, forms, users, dynamic_ctas, company_settings and the rest — and targetBranch says whether publishing it would touch the live site or the preview.

This is the only publishing-adjacent visibility the Operator MCP has, and it is deliberately read-only: it shows exactly what a publish from the CMS would commit, and stops there. The matching action, publish_pending_settings_changes, is not on this surface. The same list is what the Your-edits card shows in the CMS — publishing changes is the human version of this call.

Distribution channels

Three tools manage the per-page channel rows that a publish fans out to. They sit with the publishing tools rather than with campaign planning because that is what they attach to: a page, and what happens when it goes live.

list_distributions

GET /api/distributions · Operator MCP and the assistant · read-only · requires contact:read

ParameterTypeRequiredDefaultNotes
pageIdstringyes—The page whose channel rows you want

Returns { distributions: [...] } — one row per armed channel with its enabled flag, config and on-page behaviors. Reads are never plan-gated: a workspace can always see its own rows, including on a tier that could not create them.

upsert_distribution

POST /api/distributions · Operator MCP and the assistant · requires contact:write

ParameterTypeRequiredDefaultNotes
pageIdstringyes—The page this channel is armed for
channelemailxlinkedinbluesky
enabledbooleannounchangedWhether the channel is armed for the next publish
configobjectnounchangedChannel-specific settings, validated per channel at the API boundary
behaviorsobjectnounchangedautoShareOnPublish, showShareButtons, includeFeatureImage

update_distribution

PATCH /api/distributions/:id · Operator MCP and the assistant · requires contact:write

ParameterTypeRequiredDefaultNotes
idintegeryes—The distribution row id, from list_distributions
enabledbooleannounchanged
configobjectnounchanged
behaviorsobjectnounchanged

An unknown id is a 404 before any plan check, so a wrong id never masquerades as an upgrade prompt.

What the channels are, and what each one costs

ChannelMinimum plan to arm or configureWhat actually sends
emailStarter (releaseNotesEmail)Nothing yet — the setting is stored
xGrowth (leadWorkflows + autoSocialPosting)Nothing yet
linkedinGrowthNothing yet
blueskyGrowthNothing yet
discordGrowthNothing yet

Three deliberate asymmetries are worth knowing before you script against these tools. Reads are never gated. A pure disable — { "enabled": false } with no other field — is never gated either, so a workspace that downgrades can always switch its leftover channels off. And deleting a row is not available over MCP at all; delete_distribution is assistant-only.

Permissions are the other surprise: these tools are guarded by contact:read and contact:write, the same privileges as the Engage surface, rather than by a page privilege. That is because the email channel reaches your contact list.

The same rows are edited by hand on the page editor’s Workflows tab, described in page workflows, and which plan carries which feature key is in feature availability by plan.

The plan calendar

get_plan_calendar

GET /api/plan/calendar · Operator MCP and the assistant · read-only · requires contact:read

ParameterTypeRequiredDefaultNotes
fromintegeryes—Window start, milliseconds since the epoch — not an ISO string
tointegeryes—Window end, milliseconds since the epoch

Returns { from, to, items }, merging three feeds that are otherwise queried separately: scheduled and planned page revisions, deployments, and campaign deliverables. It is the only tool that shows all three on one timeline, which makes it the right first call for “what is going out next week”.

It is not plan-gated even though the campaign tools around it are — the schedule view is core. The screen it backs is the Plan calendar, and the deliverables it merges in have their own tools in MCP tools: campaigns and personas.

Two read tools. Creating a branded short link is not on this surface, because a shortcode is permanent once created.

list_shortcodes

GET /api/shortcodes · Operator MCP only · read-only · requires shortcode:read · Growth and up

No parameters. Returns { shortcodes: [...] } — the raw metadata for every UTM-tagged short link in the workspace.

get_shortcode

GET /api/shortcodes/:code · Operator MCP only · read-only · requires shortcode:read · Growth and up

ParameterTypeRequiredDefaultNotes
codestringyes—The short code itself, not the full URL

Returns { shortcode } resolved to its destination URL and its short link. An unknown code is a 404.

Users

Three tools, all scoped to profile data. None of them can change who a person is or what they may do.

list_users

GET /api/user · Operator MCP only · read-only · requires user:read

No parameters. Returns { users: [...], isDirty: boolean } — every user in the workspace, plus whether the user set has unpublished changes.

get_user

GET /api/user/:userId · Operator MCP only · read-only · requires user:read

ParameterTypeRequiredDefaultNotes
userIdstringyes—The CMS user id

Returns { user, rbacOverrides } — the profile plus the resource-level overrides granted in this workspace.

update_user

PUT /api/user/:userId · Operator MCP only · requires user:write for anyone but yourself

ParameterTypeRequiredDefaultNotes
userIdstringyes—Which user to update
firstName, lastNamestringnounchangedCannot be set to an empty string
jobTitle, department, biography, location, countrystringnounchangedProfile fields as shown on the team screen
phone, language, timezonestringnounchanged
image, imageAssetIdstringnounchangedAvatar URL, or the id of an asset already in the library
slugstringnounchangedThe author page’s URL segment
visiblebooleannounchangedWhether the person appears as an author on the public site
externalAccountsobjectnounchangedLinked social profiles

Updating your own profile needs no special privilege. Updating anyone else’s requires user:write — Content Publisher or above.

What update_user cannot do

role and email are stripped out of the tool’s schema entirely, so a client cannot send them even by accident, and the route rejects an email change with 400 Unable to change email regardless of who asks. There is no tool on any surface that creates a user, deletes a user, or sends an invitation.

That is a deliberate line, not an oversight: changing what someone may do, or letting someone new in, is a decision a person makes on the team screen. Managing team members is where roles and the Billing and Developer overrides are granted, and inviting people covers bringing someone new into the workspace.

What MCP is never allowed to do

The tools below are not restricted-but-available. They are absent from the tool surface — never listed to a client, and refused by name with unknown tool if a client tries anyway. Every one of them is either available to the in-CMS assistant, where it parks for your approval, or is a CMS action only.

ToolClassWhere this is actually done
publish_pagePublishThe page editor’s publish and schedule controls — publishing and scheduling a page. Over MCP, use schedule_pages for a future slot.
publish_formPublishThe form builder — building a form
publish_menuPublishMenus go live through the deployments flow, not a per-menu publish button — publishing changes
publish_pending_settings_changesPublishThe Your-edits card in the CMS — publishing changes
delete_pageDeleteThe page list — managing pages
delete_formDeleteThe form builder; blocked with a 409 while any page references the form or it holds submissions — building a form
delete_distributionDeleteThe page editor’s Workflows tab — page workflows
remove_autolinkDeleteSettings → Autolinks — autolinks
delete_personaDeleteThe Engage personas screen — personas
delete_campaignDeleteThe Engage campaign screen — campaigns and deliverables
delete_campaign_deliverableDeleteThe same campaign screen — campaigns and deliverables
approve_campaign_deliverableApproveThe Plan calendar’s approval overlay — Plan calendar
create_shortcodeCreate, permanentA short code cannot be changed or removed once minted — short links and attribution
get_analyticsAssistant chat affordanceRenders a chart inside the chat; MCP clients get rows from query_analytics instead — MCP tools: analytics and introspection
get_current_resourceAssistant chat affordanceReads whatever you are looking at in the CMS. An MCP client has no such context — Leed Assistant
suggest_next_stepsAssistant chat affordanceRenders the clickable chips under a chat reply — Leed Assistant

Everything in the first thirteen rows can be run by the Leed Assistant, which parks each one as a pending action and waits for you to press Approve. That is the same list from the other direction: the assistant has an approval step, MCP clients do not have a uniform one, so the destructive tools stay off the MCP surface entirely rather than relying on a client to ask.

Four whole domains with no tools at all

Beyond the tool-by-tool exclusions, four areas of the product have no MCP representation whatsoever — not a read tool, not a write tool.

DomainWhere it is done
Billing and subscription stateManaging your subscription. Nothing here reads your plan except get_company, which returns the tier name and nothing else.
User creation and invitationInviting people
Role and email changesManaging team members
The site repositoryYour site repository and the Leed CLI

Why these boundaries hold even when a client is persistent — and what a refusal looks like at each layer — is how Leed’s AI is guarded. The complete alphabetical index of every tool, with the page that documents it, is the Operator MCP tool index.

ESC