https://mcp.contenthero.ai and by the @contenthero/mcp npm package. A write tool changes your account; a read tool does not.
list_spaces
read
List the account’s SPACES. A space is the planner’s top-level container: Space > Stage > Card > Post. Each space has its own stages, so two spaces can both hold a stage called ‘Published’. Call this FIRST to discover which board to work in, then pass a space id to list_stages, list_cards or create_card. Archived spaces are excluded unless archived is set, which lists only them.
boolean
Only archived spaces. Archived spaces are excluded by default, matching the grid in the app.
string
The previous page’s cursor.
boolean
Only favorited spaces.
integer
How many to return.
`asc` \| `desc`
The sort direction.
string
Search space names.
`updatedAt` \| `name` \| `cardCount`
The field to sort by.
get_space
read
Return one space with its live card count. Use list_spaces to discover ids. The count excludes archived cards, and is the same number list_spaces reports for that space.
string
required
The space id.
create_space
write
Create a space: a new planner board with its own stages. duplicateFrom copies another space’s STAGES, never its cards, so the new board arrives with the columns and none of the work. Requires the planner:write scope.
string
required
The space name.
string
A cover image URL for the space tile.
string
Copy this space’s stages into the new one. Cards are never copied.
update_space
write
Rename a space or change its cover. This is a PATCH: a field you omit is left alone, so renaming does not disturb the cover. Pass coverUrl as an empty string to REMOVE the cover. To favorite or archive a space, use the favorite and archive tools with assetType ‘space’ instead. Requires the planner:write scope.
string
required
The space id to update.
string
A new cover image URL. Pass an empty string to remove the cover entirely.
string
A new name.
delete_space
write
Delete a space. The server REFUSES a space that still holds cards and names the count, because the delete cascades to every card in it along with their covers, captions, posts and schedules. Archive the space instead if you want it out of the way. Requires the planner:write scope.
string
required
The space id to delete.
list_stages
read
List one SPACE’s stages, in order. ⚠️ STAGES ARE PER-SPACE: without spaceId this is the account’s DEFAULT space, and two spaces can each hold a stage named ‘Published’ with different ids, so a stage name resolved against the wrong space is a different column. Stages are user-customizable (renamed, reordered, added, removed), so call this to discover the real stages before placing a card; pass a stage’s id (most stable), slug, or name to create_card / update_card.
string
The previous page’s cursor.
integer
How many to return.
string
Which space’s stages, from list_spaces. Omit only when you mean the account’s default space.
create_stage
write
Create a stage (a column on one board). ⚠️ STAGES ARE PER-SPACE: without spaceId this creates on the account’s DEFAULT board, which is rarely what you want once more than one space exists, so call list_spaces first. The slug is DERIVED from the name and is not settable; a board cannot hold two columns whose names produce the same slug and the server refuses the second rather than renaming it for you. Place the column with afterId, beforeId or position (top is the first column), or name none to put it at the end. Requires the planner:write scope.
string
required
The column name, for example “In Review”. Must contain a letter or number.
string
Place it immediately after this item of the same list.
string
Place it immediately before this item of the same list.
string
A hex color such as “#3B82F6”.
`top` \| `bottom`
Place it at an end of the list instead of beside an item. Name neighbors or a position, not both.
string
Which board, from list_spaces. Omit only when you mean the account’s default space.
update_stage
write
Rename, recolor or move one stage. This is a PATCH: a field you omit is left alone. ⚠️ spaceId is REQUIRED, because a stage id alone does not tell the server which board you mean and guessing the wrong one silently changes nothing. Renaming re-derives the slug, so renaming a column away from ‘Published’ also stops publishing auto-moving cards into it; a rename that collides with another column on the same board is refused. Moving names NEIGHBORS or an end, never a number: pass afterId, beforeId, or position (top is the first column). Requires the planner:write scope.
string
required
The board this stage is on, from list_stages or list_spaces.
string
required
The stage id to update, from list_stages.
string
Place it immediately after this item of the same list.
string
Place it immediately before this item of the same list.
string
A new hex color such as “#3B82F6”.
string
A new name. The slug follows it automatically.
`top` \| `bottom`
Place it at an end of the list instead of beside an item. Name neighbors or a position, not both.
delete_stage
write
Delete a stage and move its cards to another column. The server REFUSES a column that still holds cards when you name no targetStageId, and tells you how many there are: cards are never destroyed by deleting a column, and the delete and the reassignment happen in one transaction. Returns the board that is left. Requires the planner:write scope.
string
required
The board this stage is on.
string
required
The stage id to delete, from list_stages.
string
Where this column’s cards should go. Required unless the column is empty.
list_cards
read
List one SPACE’s cards (newest-updated first). ⚠️ SCOPED, NOT COMPLETE: without spaceId this lists the account’s DEFAULT space only, and cards on any other board are absent with nothing in the response saying so (search misses them too). Call list_spaces FIRST and pass spaceId unless you specifically mean the default board. Filter by status, platform, stage (id/slug/name), favorite, tag, or a title search. Call get_card for one card’s full detail (posts + assets).
boolean
Only ARCHIVED cards. Archived cards are excluded by default, matching the board.
string
The previous page’s cursor.
boolean
Only favorited cards.
integer
How many to return.
`asc` \| `desc`
The sort direction.
`youtube` \| `instagram` \| `tiktok` \| `facebook` \| `linkedin` \| `x` \| `threads` \| `general`
Filter by the post platform.
string
Case-insensitive title search, scoped to the chosen space.
`updatedAt` \| `createdAt` \| `scheduledAt` \| `title` \| `position`
The field to sort by.
string
Which space’s board to list, from list_spaces, or ‘all’ for every space. Omit ONLY when you mean the account’s default space; omitting it does not search every board.
string
Filter by a stage id, slug, or name. Resolved within the chosen space. Needed for sort position, which reads that stage’s own order.
string
Only cards carrying this tag.
get_card
read
Get one CARD in full: its fields (title, description, script, notes, status, stage, schedule), plus its posts (one per platform, which is how it publishes) and its attached assets in carousel order. An [inspiration] asset is a tracked post attached as a reference; its line already carries the headline metrics (creator, outlier score, views, likes, duration) plus transcript and breakdown availability, and a content id to pass to get_content for the transcript and the full Break It Down analysis. To attach one, add { contentId } to the assets list of update_card; a linked editor or canvas project is added the same way with { projectId }.
string
required
The card id from list_cards.
create_card
write
Create a card, the container in the content pipeline. A card holds the work (title, notes, cover) and the posts that publish it. Attach posts and media by passing posts and assets to update_card, then publish with publish_post. ⚠️ WITHOUT spaceId THIS LANDS ON THE ACCOUNT’S DEFAULT BOARD, which is rarely what you want once more than one space exists, so call list_spaces first. stage accepts a stage id/slug/name and DECIDES the space when it is an id; a spaceId that disagrees with it is rejected rather than guessed. Requires a key with the planner:write scope.
`youtube` \| `instagram` \| `tiktok` \| `facebook` \| `linkedin` \| `x` \| `threads` \| `general`
required
Primary platform for the post.
string
required
Post title (required).
string
Place it immediately after this item of the same list.
string
Place it immediately before this item of the same list.
string
A media token (short id, output id or first 8 characters, optionally with “-N”) for the cover, resolved to its URL. Use this or coverUrl.
string
Public URL for the post cover (the card thumbnail).
`top` \| `bottom`
Place it at an end of the list instead of beside an item. Name neighbors or a position, not both.
string
Which board to create the card on, from list_spaces. Omit only when you mean the account’s default space.
string
Stage id, slug, or name. Defaults to the first stage.
string[]
Tag names to set on the post (must already exist; see list_tags / create_tag). Replaces the set.
update_card
write
Update a card: its fields (title, notes, platform, cover, stage), its POSTS (one per platform, which is how it publishes), its ASSETS (its files, links, linked projects and inspiration posts, in order), and its SCHEDULE. posts and assets are DECLARATIVE: pass the WHOLE set, because anything you leave out is removed. Posts key on platform. Assets key on id, and THE ARRAY ORDER IS THE carousel ORDER, so reordering is just sending the same ids in a different order; keep an existing asset by id, and add a new one by EXACTLY ONE reference: outputId (media: a generation, an upload or an import), assetUrl (a link), contentId (an inspiration post, shown on the card’s Inspiration tab) or projectId (an editor or canvas project); get_card reads each back under the same field name. Listing the same thing twice is refused. To attach one thing, read the card, append the entry to its assets, and send the whole list back. scheduledAt sets the time on the card AND every post (pass null to clear); give a post its own scheduledAt to override it for that platform. To publish NOW, use publish_post. Pass spaceId to MOVE the card to another space; without a stage it lands in the target space’s stage whose slug matches its current one, or that space’s first stage. Pass cardIds to update several cards at once, which crossed with spaceId is how a selection moves in one call; fields that describe ONE card (title, notes, cover) still need exactly one. WRITING notes REQUIRES expectedRevision: read the card first and pass the revision it reported, or the call is refused. Requires the planner:write scope.
string
required
The card id.
string
Place it immediately after this item of the same list.
object[]
The post’s assets IN ORDER, each { id } to keep an existing one or ONE of { outputId } | { assetUrl, assetType? } | { contentId } | { projectId } to add. REPLACES the list; [] clears it.
string
Place it immediately before this item of the same list.
string[]
Update several cards at once. Fields that describe ONE card (title, notes, cover) still require exactly one.
string
A media token (short id, output id or first 8 characters, optionally with “-N”) for the cover, resolved to its URL.
string
Public URL for the post cover.
integer
REQUIRED when writing notes: the revision get_card reported for this card. A card’s notes have four independent writers, each reading the whole document and writing it back, so without this the last writer silently erases the others and still gets a success. If the card changed since you read it, the call fails with a conflict carrying the current notes and revision: merge onto those and retry with the revision they came with. Never guess it or add one to it, because it only advances when notes actually change.
string
Working notes on the card. Plain text or markdown; tables render here. No emojis.
object[]
Change the notes in place without resending them. Each edit is { append: text }, which adds to the end, or { find, replace }, where find must match exactly one place in the current notes. Edits apply in order, all or none, to the notes as they are when the call arrives, so no expectedRevision is needed. Prefer this to notes for any change smaller than a rewrite.
`youtube` \| `instagram` \| `tiktok` \| `facebook` \| `linkedin` \| `x` \| `threads` \| `general`
The card’s primary platform. This is a label on the card; what actually publishes is its posts.
`top` \| `bottom`
Place it at an end of the list instead of beside an item. Name neighbors or a position, not both.
object[]
The card’s posts, each { platform, format?, connectedAccountId?, platformSpecificData?, scheduledAt?, status? }. REPLACES the set, keyed by platform; [] detaches all.
string,null
ISO time to publish. Sets the card AND every post. null clears the schedule.
string
Move the card to a different space (id or slug). Without a stage, it lands in that space’s stage whose slug matches its current one, or that space’s first stage.
string
Move the card to this stage (id, slug, or name).
string[]
Tag names to set on the post (must already exist; replaces the set). Omit to leave tags unchanged.
string
Rename the card. Keep it short: a long title wraps and makes the column unreadable.
list_tags
read
List the account’s tags (the organizational tag library). Set a post’s tags with the tags field on create_card / update_card. A tag is just a lowercase name.
string
The previous page’s cursor.
integer
How many to return.
create_tag
write
Create a tag in the account’s tag library (the name is lowercased). Tags organize posts; apply them with the tags field on create_card / update_card. Requires the planner:write scope.
string
required
The tag name (lowercased on save).
update_tag
write
Rename a tag (preserves its assignments on all posts). To detach a tag from one post, set that post’s tags without it via update_card. Requires the planner:write scope.
string
required
The new tag name (lowercased on save).
string
required
The tag id (from list_tags).
delete_tag
write
Delete a tag from the account’s library. This DESTROYS the tag and removes it from every post it was on. To just detach a tag from one post, set that post’s tags without it via update_card instead. Requires the planner:write scope.
string
required
The tag id (from list_tags).
publish_post
write
Publish a card’s posts NOW: every post on the card, or only the named platform’s post when platform is given. Each post must have a connected account. Requires a key with the publish:write scope; holding that scope is the account owner’s consent to autonomous publishing. Returns one result per post.
string
required
The card id to publish.
`youtube` \| `instagram` \| `tiktok` \| `facebook` \| `linkedin` \| `x` \| `threads` \| `general`
Publish only this platform. Omit to publish all posts.
list_connected_accounts
read
List the social accounts the owner has connected (the publish targets), default first. Use an account’s id as connectedAccountId on a post in update_card, then publish_post. Read-only: connecting an account is done in the ContentHero app.
string
The previous page’s cursor.
integer
How many to return.
get_connected_account
read
Get one connected account’s detail: platform, status, and capabilities. Use it to confirm a target can publish before attaching it to a post.
string
required
The connected account id from list_connected_accounts.