Skip to content

Tool reference ​

Generated from the server itself, so this is exactly what a client receives on tools/list.

Every tool except list_brands takes an optional brand argument. It is required once your account has more than one brand. See Brands.

The server reports itself as Postlyra Server, version 1.0.0, and exposes 27 tools.

list_brands ​

List the brands (workspaces) this token can act on. Every other tool accepts an optional "brand" argument taking either the brand's name or its brand_id, required when the account has more than one brand. Prefer the name the user used; pass a brand_id only when two brands share a name. Call this once at the start of a multi-brand session.

read-only, idempotent, needs ai:read

Takes no arguments.

get_mining_context ​

Fetch this team's idea-mining context: source refs already attached to existing ideas (drop matching commits before mining), the team's voice guide, content pillars, persona, and a summary of recently-known ideas/published hooks to avoid repeating. Call this first, before reading any git log.

read-only, idempotent, needs ai:read

ArgumentTypeDescription
brandstringoptionalOptional: the brand (workspace) to act on, either its name as the user says it ("Channel One") or a brand_id from list_brands. Required when the account has more than one brand.

push_ideas ​

Push up to 25 finished content ideas mined from the caller's own machine (e.g. a local git log). Each idea needs a headline and at least one source_ref: a stable identity string for the work it came from, e.g. "postlyra@abc1234" (repo-label@short-sha). Re-pushing the same headline with the same source_refs updates the existing idea instead of creating a duplicate. Ideas land with status "new" in Studio's review queue. This never creates a draft, approves anything, or publishes anything; only a human in Studio can do that.

idempotent, needs ai:write.standard

ArgumentTypeDescription
brandstringoptionalOptional: the brand (workspace) to act on, either its name as the user says it ("Channel One") or a brand_id from list_brands. Required when the account has more than one brand.
ideasarray of objectrequired1-25 ideas to push.

ideas[] fields

FieldTypeDescription
headlinestringrequiredA short working title naming the idea for Studio's list (4-8 words, max 255 characters). NOT the post text, which goes in "angle".
anglestringoptionalThe finished post text, written ready to publish: one insight, hook first, 300-600 characters. Platform, format, and length get adjusted in Studio afterwards.
pillarstringoptionalOptional: the content pillar this idea belongs to.
formatstringoptionalOptional: linkedin, reel, carousel, or video. Unrecognized or missing values default to linkedin.
recordingstringoptionalOptional: recording/shot instructions for a reel-format idea.
source_refsarray of stringrequiredRequired: at least one stable identity string for the work this idea was mined from, e.g. "repo-label@short-sha".

list_ideas ​

List this team's content ideas, newest-mined first, optionally filtered by status or format. Capped at 50 results; use get_idea for one idea's full detail including its source refs and linked drafts.

read-only, idempotent, needs ai:read

ArgumentTypeDescription
brandstringoptionalOptional: the brand (workspace) to act on, either its name as the user says it ("Channel One") or a brand_id from list_brands. Required when the account has more than one brand.
statusenum: new, shortlisted, dismissedoptionalOptional: filter to one triage status (new, shortlisted, dismissed). Promoted ideas are posts now (see get_idea).
formatenum: text, reel, carousel, videooptionalOptional: filter to one format (linkedin, reel, carousel, video).

get_idea ​

Get one idea by id: its headline/description/pillar/format/status, the source refs it was mined from, and any drafts already promoted from it (id + status only; use Studio to see or edit a draft's content).

read-only, idempotent, needs ai:read

ArgumentTypeDescription
brandstringoptionalOptional: the brand (workspace) to act on, either its name as the user says it ("Channel One") or a brand_id from list_brands. Required when the account has more than one brand.
idea_idintegerrequiredThe idea id, e.g. from list_ideas or push_ideas' response.

list_content_profiles ​

List the brand's content profiles: the per-draft identities (name, handle, accent, pictures, pillars) a draft's slides can be styled and grounded with: a photo beside the handle, or a logo replacing the name in the masthead. create_post/update_post accept one of the returned ids as content_profile_id. Pillars are the profile's content pillar labels; a draft's optional "pillar" should be one of the pillar KEYS shown here.

read-only, idempotent, needs ai:read

ArgumentTypeDescription
brandstringoptionalOptional: the brand (workspace) to act on, either its name as the user says it ("Channel One") or a brand_id from list_brands. Required when the account has more than one brand.

List the brand's carousel templates (HOW slides look): each ready template plus the catalog default (id null, is_default true). create_post/update_post accept one of the returned ids as carousel_template_id; omitting it, or passing null, uses the default. settings is the resolved palette, font pairing and the page-counter/swipe-hint toggles (never shown on a single slide); size is the resolved canvas shape ("4:5" or "1:1"); default_content_profile_id is the identity a post lands on when create_post/update_post name this template but no content_profile_id, null for the catalog default.

read-only, idempotent, needs ai:read

ArgumentTypeDescription
brandstringoptionalOptional: the brand (workspace) to act on, either its name as the user says it ("Channel One") or a brand_id from list_brands. Required when the account has more than one brand.

create_post ​

Create a post in a brand's Planning queue, optionally scheduling it to one or more connected social accounts, e.g. "write a post for brand X for instagram and bluesky, scheduled Monday 15:00 UTC". Takes a kind (text, reel, carousel, video), the body text, and optionally a title, content_profile_id, pillar (text posts only), plan date/time, and publications: a list of {platform, scheduled_at} targets. Pictures come from exactly one of three places: the brand's library by id (media, see list_media), RENDERED from slides to design one inline (one slide is a single image, several a carousel, and the kind follows) through the chosen carousel_template_id (see list_carousel_templates; omitted means the brand's default template) in the chosen content_profile_id (see list_content_profiles; omitted means the brand's first identity), or an existing carousel reused by id (carousel_id, see list_carousels/create_carousel). A text post needs none of them: scheduled to a channel that needs a picture, its words publish as a picture whose identity is the name, handle and picture of the account the post is scheduled to, unless content_profile_id names one instead; every other channel still gets the text as text. By default the post lands AWAITING APPROVAL and the brand's users are notified; a scheduled publication only goes out if a human approves it before its time, otherwise it expires. A connection with the elevated access level may pass approve=true; that records the connection's own user as the approver (audited) and the brand can still reject in Studio until the scheduled time.

needs ai:write.standard

ArgumentTypeDescription
brandstringoptionalOptional: the brand (workspace) to act on, either its name as the user says it ("Channel One") or a brand_id from list_brands. Required when the account has more than one brand.
kindenum: text, reel, carousel, videorequiredThe post kind: text (a plain text post whose channels are picked at schedule time; a channel that needs a picture, such as Instagram or Pinterest, publishes the words as a picture), reel (a reel script), carousel (an image carousel, whose slide text goes in slides), or video. "linkedin" is accepted as a legacy alias for text.
bodystringrequiredThe post body: the post text (or script) itself.
threadarray of anyoptionalOptional: follow-up posts chained under the body as a thread, in order (the body is post 1). Published as replies on channels that chain (Threads and Bluesky), each bounded by that channel's own text limit (Bluesky 300, Threads 500); other channels get the body alone. Up to 25 items.
slidesarray of anyoptionalThe design's slides, in order, up to 10. A TEXT slide is {"title": "...", "body": "...", "background_id"?: <a library id>, "figure_id"?: <a library id>}: Postlyra RENDERS it to an image through the brand's carousel template (carousel_template_id, see list_carousel_templates) in the chosen identity (content_profile_id, see list_content_profiles), so keep it short (it has to fit a 1080x1350 canvas); the first text slide is the hook and the last the closing line (both use "title" alone), the ones between take a short "title" heading plus a "body". background_id is optional: a library image painted full-bleed behind the words under a dark scrim, the LinkedIn cover look; list_media / add_media give ids. figure_id is optional too: a library image shown INSIDE a middle slide above its words; the hook and the closing slide ignore it. A PICTURE slide is {"image_id": <a library id from list_media or add_media>}: that picture is shown as-is at that position (a screenshot as the hook, then rendered text slides after it), and never carries a background_id or figure_id. Mix them freely. One slide is a single designed image, two or more make a carousel (the post's kind follows). body stays the CAPTION posted with the images, and a post has either a design or attached media, never both.
carousel_idintegeroptionalOptional: attach an existing carousel from the library instead of designing one inline, an id from list_carousels or create_carousel. Mutually exclusive with slides and media: a post publishes its pictures, its video, or one carousel, never more than one.
mediaarray of anyoptionalOptional: the pictures this post publishes, in order, as [{"id": <a library id from list_media>, "alt": "what the picture shows"}] (up to 20; an id alone is accepted too). They replace whatever the post already had, and an empty list clears them. A post publishes either its own pictures or its design, never both. To attach a picture you have, add_media it first and pass the id it returns.
titlestringoptionalOptional: a short working title, max 255 characters.
content_profile_idintegeroptionalOptional: the content profile (identity) this post belongs to: an id from list_content_profiles. For a carousel it names WHO the slides speak as; for a text post that publishes as a card (see above) it overrides the card's default identity, the account the post is scheduled to.
carousel_template_idintegeroptionalOptional: the carousel template (HOW the slides look) this post's design renders through: a ready id from list_carousel_templates. Omit for the brand's default template.
pillarstringoptionalOptional: a content pillar KEY from the profile's pillars in list_content_profiles (e.g. "B"). Only meaningful for kind "text"; ignored otherwise.
plan_datestringoptionalOptional: the planning-calendar date, Y-m-d. A plan, not a publish time. Use publications to schedule actual publishing.
plan_timestringoptionalOptional: the planning-calendar time, 24h H:i (e.g. "09:10").
publicationsarray of anyoptionalOptional: publish targets, one object per connected account, each {"platform": "instagram"|"bluesky"|..., "scheduled_at": ISO-8601 instant (e.g. "2026-08-24T15:00:00Z"), "caption": optional per-network text override, "first_comment": optional text posted as the first comment right after it goes live (the hashtag block or a link); Instagram, Facebook, Threads and Bluesky take one; the call is refused for a channel that can't comment}. Each target uses the brand's connected account on that platform. The publication only fires if the post is approved before scheduled_at.
approvebooleanoptionalOptional, elevated access only: create the post already APPROVED, recorded in your name (approved_via "mcp"). Without elevated access this argument is rejected.

approve_post ​

Approve a post (elevated access only): moves it from draft/awaiting-approval to approved, recorded in YOUR name (approved_via "mcp"). Any pending scheduled publications become eligible to publish at their scheduled times. This never publishes anything immediately. Use get_idea/list tools or Studio to find the post id.

needs ai:write.full

ArgumentTypeDescription
brandstringoptionalOptional: the brand (workspace) to act on, either its name as the user says it ("Channel One") or a brand_id from list_brands. Required when the account has more than one brand.
post_idintegerrequiredThe post id, e.g. from create_post's response.

list_posts ​

List the brand's posts (drafts, awaiting approval, approved, rejected, and published), newest first, capped at 50 with a before_id cursor for the next page. Filters: status (draft, awaiting_approval, approved, rejected), published (true/false: whether the post has gone live), kind (text, reel, carousel, video), platform (posts with a publication targeting it), content_profile_id, pillar (a pillar key), and from/to (Y-m-d, on the planning date). Each item carries a 140-character body preview and its publications as {platform, scheduled_at, status}. Use get_post for the full body, thread, approval audit and permalinks, and get_post_performance for its metrics.

read-only, idempotent, needs ai:read

ArgumentTypeDescription
brandstringoptionalOptional: the brand (workspace) to act on, either its name as the user says it ("Channel One") or a brand_id from list_brands. Required when the account has more than one brand.
statusenum: draft, awaiting_approval, approved, rejectedoptionalOptional: filter to one approval status (draft, awaiting_approval, approved, rejected). A published post keeps status "approved": filter with published instead.
publishedbooleanoptionalOptional: true for posts that have gone live (published_at set), false for those that have not.
kindenum: text, reel, carousel, videooptionalOptional: filter to one format (text, reel, carousel, video).
platformstringoptionalOptional: only posts with a publication on this platform (instagram, threads, linkedin, bluesky, ...), in any publication status.
content_profile_idintegeroptionalOptional: only posts styled with this content profile (an id from list_content_profiles).
pillarstringoptionalOptional: only posts tagged with this pillar KEY (see list_content_profiles).
fromstringoptionalOptional: earliest planning date, Y-m-d (inclusive).
tostringoptionalOptional: latest planning date, Y-m-d (inclusive).
before_idintegeroptionalOptional: paging cursor. Pass the smallest id of the previous page to get the next 50 older posts.

get_post ​

Get one post by id: title, kind, approval status and derived display status, pillar, planning date/time, the full body and thread items in order, the approval audit (approved_by, approved_via, approved_at), the idea it was promoted from, and every publication with its account, scheduled/posted times, live permalink where the platform returned one, per-channel caption override, first comment and error. Ids come from list_posts, create_post or get_schedule. For its metrics call get_post_performance.

read-only, idempotent, needs ai:read

ArgumentTypeDescription
brandstringoptionalOptional: the brand (workspace) to act on, either its name as the user says it ("Channel One") or a brand_id from list_brands. Required when the account has more than one brand.
post_idintegerrequiredThe post id, e.g. from list_posts.

update_post ​

Edit a post that is not yet published: title, body, thread, its pictures (media from the library, a rendered design in slides, or an existing carousel by carousel_id), carousel_template_id, pillar, planning date/time, and its publication targets. publications is the desired list of pending targets, one {platform, scheduled_at, caption?, first_comment?} per connected account: a platform already booked is rescheduled in place, a new one is booked (same checks as create_post), and a pending booking left out of the list is skipped; published or in-flight publications are never touched. A caption or first_comment left out keeps the booking's current one; pass an empty string to clear it. Changing plan_date/plan_time without publications moves the pending bookings by the same amount, as Studio does. Passing slides on a carousel replaces its whole slide set and re-renders the images. Passing carousel_template_id alone (no slides) re-skins the existing slides in the new template (see list_carousel_templates) and re-renders it, keeping the words. carousel_id attaches an existing carousel from list_carousels instead (mutually exclusive with slides and media); pass carousel_id: null to detach it (the carousel itself stays in the library). Changing the title, body, thread or slides of an APPROVED post withdraws that approval and notifies the brand UNLESS this connection holds the elevated level (then, as in Studio, the post stays approved in the editor's name); bookings, plan date, pillar, carousel_template_id and carousel_id never affect approval. Refused for published posts and for ideas (use update_idea_status). Returns the post as get_post would.

needs ai:write.standard

ArgumentTypeDescription
brandstringoptionalOptional: the brand (workspace) to act on, either its name as the user says it ("Channel One") or a brand_id from list_brands. Required when the account has more than one brand.
post_idintegerrequiredThe post id, e.g. from list_posts.
titlestringoptionalOptional: a new working title, max 255 characters. Pass an empty string to clear it.
bodystringoptionalOptional: the new post body.
threadarray of anyoptionalOptional: the new follow-up posts, in order, replacing the whole thread (text posts only, up to 25 items; an empty list clears it). Each item is bounded by the chaining channels' limits (Bluesky 300, Threads 500).
slidesarray of anyoptionalOptional: the post's design, replacing the whole slide set (an empty list removes it). The design's slides, in order, up to 10. A TEXT slide is {"title": "...", "body": "...", "background_id"?: <a library id>, "figure_id"?: <a library id>}: Postlyra RENDERS it to an image through the brand's carousel template (carousel_template_id, see list_carousel_templates) in the chosen identity (content_profile_id, see list_content_profiles), so keep it short (it has to fit a 1080x1350 canvas); the first text slide is the hook and the last the closing line (both use "title" alone), the ones between take a short "title" heading plus a "body". background_id is optional: a library image painted full-bleed behind the words under a dark scrim, the LinkedIn cover look; list_media / add_media give ids. figure_id is optional too: a library image shown INSIDE a middle slide above its words; the hook and the closing slide ignore it. A PICTURE slide is {"image_id": <a library id from list_media or add_media>}: that picture is shown as-is at that position (a screenshot as the hook, then rendered text slides after it), and never carries a background_id or figure_id. Mix them freely. One slide is a single designed image, two or more make a carousel (the post's kind follows). body stays the CAPTION posted with the images, and a post has either a design or attached media, never both.
carousel_idintegeroptionalOptional: attach an existing carousel from the library instead of an inline design, an id from list_carousels. Mutually exclusive with slides and media. Pass null to detach the post's current carousel (it stays in the library).
carousel_template_idintegeroptionalOptional: the carousel template (HOW the slides look): a ready id from list_carousel_templates. Passed with slides, the new slides render through it; passed alone, it re-skins the post's existing slides in the new template, keeping the words.
mediaarray of anyoptionalOptional: the pictures this post publishes, in order, as [{"id": <a library id from list_media>, "alt": "what the picture shows"}] (up to 20; an id alone is accepted too). They replace whatever the post already had, and an empty list clears them. A post publishes either its own pictures or its design, never both. To attach a picture you have, add_media it first and pass the id it returns.
pillarstringoptionalOptional: a content pillar KEY from list_content_profiles. Text posts only.
plan_datestringoptionalOptional: the planning-calendar date, Y-m-d. With pending bookings and no publications, the bookings move by the same amount.
plan_timestringoptionalOptional: the planning-calendar time, 24h H:i.
publicationsarray of anyoptionalOptional: the desired pending publish targets, one object per connected account: {"platform": "instagram"|"bluesky"|..., "scheduled_at": ISO-8601 instant in the future, "caption": optional per-network text override, "first_comment": optional}. Pass an empty list to skip every pending booking. Omit to leave bookings alone.

delete_post ​

Delete a draft, awaiting-approval, approved-but-unpublished, rejected or expired post (elevated access only). Refused when the post has been published or has any published or currently publishing publication; pending bookings are removed with it. Irreversible. Ideas are not deleted here: dismiss them with update_idea_status.

needs ai:write.full

ArgumentTypeDescription
brandstringoptionalOptional: the brand (workspace) to act on, either its name as the user says it ("Channel One") or a brand_id from list_brands. Required when the account has more than one brand.
post_idintegerrequiredThe post id, e.g. from list_posts.

update_idea_status ​

Move an idea between the triage states new, shortlisted and dismissed. Only for rows list_ideas returns (status idea); a promoted idea is a post and is refused. Promoting an idea to a draft is done in Studio, not here.

idempotent, needs ai:write.standard

ArgumentTypeDescription
brandstringoptionalOptional: the brand (workspace) to act on, either its name as the user says it ("Channel One") or a brand_id from list_brands. Required when the account has more than one brand.
idea_idintegerrequiredThe idea id, e.g. from list_ideas.
statusenum: new, shortlisted, dismissedrequiredThe triage state to set: new, shortlisted or dismissed.

list_channels ​

List the brand's connected social accounts: platform, name, handle, connection status (active, needs_reauth = token expired or revoked, disconnected), token expiry, and capabilities: caption and first-comment limits, post kinds, whether the channel needs a picture (requires_media; a text post to one publishes its words as a picture), chains a thread, takes a first comment (per account, since it depends on the token's permissions), can auto-repost, its hashtag ceiling, and whether it reports post metrics and follower counts back (the platforms get_post_performance and get_account_performance can ever show). create_post and update_post target these by platform.

read-only, idempotent, needs ai:read

ArgumentTypeDescription
brandstringoptionalOptional: the brand (workspace) to act on, either its name as the user says it ("Channel One") or a brand_id from list_brands. Required when the account has more than one brand.

get_schedule ​

The upcoming publications in a date range (default: today through the next 7 days, in the brand's schedule timezone), ordered by scheduled time: pending and in-flight bookings only, each with its platform and account, the post it carries (id, title, preview, approval status) and approved: a pending publication on an unapproved post EXPIRES at its time unless someone approves first. Optional platform filter; capped at 100 rows, narrow the range for more. Use get_post for a booking's full post and update_post to move it.

read-only, idempotent, needs ai:read

ArgumentTypeDescription
brandstringoptionalOptional: the brand (workspace) to act on, either its name as the user says it ("Channel One") or a brand_id from list_brands. Required when the account has more than one brand.
fromstringoptionalOptional: first day of the window, Y-m-d in the brand's schedule timezone. Defaults to today.
tostringoptionalOptional: last day of the window (inclusive), Y-m-d. Defaults to 7 days after from; at most a year.
platformstringoptionalOptional: only bookings on this platform (instagram, threads, bluesky, ...).

list_media ​

List the pictures in a brand's media library, newest first: uploads and AI images. Each item carries the id that media on create_post/update_post takes (attach as the post's own pictures) and that slides[].image_id takes (show it as a slide inside a design), plus its url, name, alt text, size and how many posts already use it. Paging: pass the smallest id of a page back as before_id. To put a picture you have INTO the library, call add_media with its bytes (base64) or a public URL. A carousel is edited whole in its post and is not listed here; videos are not listed either: a video is attached in Studio, not over MCP.

read-only, idempotent, needs ai:read

ArgumentTypeDescription
brandstringoptionalOptional: the brand (workspace) to act on, either its name as the user says it ("Channel One") or a brand_id from list_brands. Required when the account has more than one brand.
searchstringoptionalOptional: match the file name or the alt text.
before_idintegeroptionalOptional: paging cursor. Pass the smallest id of the previous page to get the next 50 older items.

add_media ​

Add a picture to the brand's media library and get back the id that media (attach as the post's own picture) and slides[].image_id (show it as a slide inside a design) take. Pass EITHER data, the file's bytes base64-encoded (a screenshot or chart you have, up to 8 MB before encoding), OR url, a public https address to fetch. JPEG, PNG, WebP and GIF; the type is sniffed from the bytes, not the name. Give alt (what the picture shows) so the post carries it. Videos cannot be added this way.

needs ai:write.standard

ArgumentTypeDescription
brandstringoptionalOptional: the brand (workspace) to act on, either its name as the user says it ("Channel One") or a brand_id from list_brands. Required when the account has more than one brand.
datastringoptionalThe picture's bytes, base64-encoded (plain base64, no "data:image/png;base64," prefix needed, though one is tolerated). Use this OR url.
urlstringoptionalA public https URL of the picture to fetch. Use this OR data.
namestringoptionalOptional: a file name, e.g. "appeal-screenshot.png". The extension is taken from the bytes, not from this.
altstringoptionalOptional: what the picture shows, for accessibility and for the post's alt text.

list_carousels ​

List the brand's carousels, newest first, capped at 50 with a before_id cursor for the next page. Each carries the id that create_post/update_post's carousel_id and get_carousel/update_carousel/delete_carousel take, its slide_count, its carousel_template_id and content_profile_id, and used_in_posts: how many posts currently carry it. Call get_carousel for the slide text.

read-only, idempotent, needs ai:read

ArgumentTypeDescription
brandstringoptionalOptional: the brand (workspace) to act on, either its name as the user says it ("Channel One") or a brand_id from list_brands. Required when the account has more than one brand.
before_idintegeroptionalOptional: paging cursor. Pass the smallest id of the previous page to get the next 50 older carousels.

One carousel in full: its carousel_template_id and content_profile_id, whether it ends on a closing line (closing_slide), its slides (title, body, rendered picture ids), and used_in_posts: the ids of the posts that currently carry it.

read-only, idempotent, needs ai:read

ArgumentTypeDescription
brandstringoptionalOptional: the brand (workspace) to act on, either its name as the user says it ("Channel One") or a brand_id from list_brands. Required when the account has more than one brand.
carousel_idintegerrequiredThe carousel id, e.g. from list_carousels.

Create a carousel in the brand's library: a design of slides that renders to images, reusable across posts (attach it with carousel_id on create_post/update_post, or list_carousels/get_carousel to find it again). Takes slides (required, same contract as create_post's), and optionally a name, carousel_template_id (see list_carousel_templates), content_profile_id (see list_content_profiles) and closing_slide. Renders as soon as it is saved; no approval is involved, a carousel is a library asset like a picture.

needs ai:write.standard

ArgumentTypeDescription
brandstringoptionalOptional: the brand (workspace) to act on, either its name as the user says it ("Channel One") or a brand_id from list_brands. Required when the account has more than one brand.
namestringoptionalOptional: a name for the carousel, max 255 characters.
slidesarray of anyrequiredThe design's slides, in order, up to 10. A TEXT slide is {"title": "...", "body": "...", "background_id"?: <a library id>, "figure_id"?: <a library id>}: Postlyra RENDERS it to an image through the brand's carousel template (carousel_template_id, see list_carousel_templates) in the chosen identity (content_profile_id, see list_content_profiles), so keep it short (it has to fit a 1080x1350 canvas); the first text slide is the hook and the last the closing line (both use "title" alone), the ones between take a short "title" heading plus a "body". background_id is optional: a library image painted full-bleed behind the words under a dark scrim, the LinkedIn cover look; list_media / add_media give ids. figure_id is optional too: a library image shown INSIDE a middle slide above its words; the hook and the closing slide ignore it. A PICTURE slide is {"image_id": <a library id from list_media or add_media>}: that picture is shown as-is at that position (a screenshot as the hook, then rendered text slides after it), and never carries a background_id or figure_id. Mix them freely. One slide is a single designed image, two or more make a carousel (the post's kind follows). body stays the CAPTION posted with the images, and a post has either a design or attached media, never both.
carousel_template_idintegeroptionalOptional: the carousel template (HOW the slides look): a ready id from list_carousel_templates. Omit for the brand's default template.
content_profile_idintegeroptionalOptional: the content profile (identity) the slides render as, an id from list_content_profiles. Omit for the brand's first identity (or the design's own default identity, when it has one).
closing_slidebooleanoptionalOptional, default true: whether the last text slide renders as the closing line. false renders it as an ordinary content slide instead.

Edit a carousel: rename it, replace its whole slide set (slides, same contract as create_carousel's; an empty list clears it), or re-skin it in a new carousel_template_id / content_profile_id while keeping the words (passed alone, with no slides). Re-renders whenever the result is stale. Never refused by a post's status: a post already carrying this carousel keeps its current pictures until the next render.

needs ai:write.standard

ArgumentTypeDescription
brandstringoptionalOptional: the brand (workspace) to act on, either its name as the user says it ("Channel One") or a brand_id from list_brands. Required when the account has more than one brand.
carousel_idintegerrequiredThe carousel id, e.g. from list_carousels.
namestringoptionalOptional: a new name. Pass an empty string to clear it.
slidesarray of anyoptionalOptional: replaces the whole slide set (an empty list clears it). The design's slides, in order, up to 10. A TEXT slide is {"title": "...", "body": "...", "background_id"?: <a library id>, "figure_id"?: <a library id>}: Postlyra RENDERS it to an image through the brand's carousel template (carousel_template_id, see list_carousel_templates) in the chosen identity (content_profile_id, see list_content_profiles), so keep it short (it has to fit a 1080x1350 canvas); the first text slide is the hook and the last the closing line (both use "title" alone), the ones between take a short "title" heading plus a "body". background_id is optional: a library image painted full-bleed behind the words under a dark scrim, the LinkedIn cover look; list_media / add_media give ids. figure_id is optional too: a library image shown INSIDE a middle slide above its words; the hook and the closing slide ignore it. A PICTURE slide is {"image_id": <a library id from list_media or add_media>}: that picture is shown as-is at that position (a screenshot as the hook, then rendered text slides after it), and never carries a background_id or figure_id. Mix them freely. One slide is a single designed image, two or more make a carousel (the post's kind follows). body stays the CAPTION posted with the images, and a post has either a design or attached media, never both.
carousel_template_idintegeroptionalOptional: the carousel template (HOW the slides look): a ready id from list_carousel_templates. Passed with slides, the new slides render through it; passed alone, it re-skins the existing slides in the new template, keeping the words.
content_profile_idintegeroptionalOptional: the content profile (identity) the slides render as, an id from list_content_profiles. Passed alone (no slides), it re-renders the existing slides under the new identity, keeping the words.
closing_slidebooleanoptionalOptional: whether the last text slide renders as the closing line. false renders it as an ordinary content slide instead.

Delete a carousel from the library (elevated access only). Refused when any unpublished post, or a pending/publishing publication, still carries it, the posts are named in the refusal; detach it there first (carousel_id: null on update_post). Otherwise deletes its rendered pictures, its slides and the row. Irreversible.

needs ai:write.full

ArgumentTypeDescription
brandstringoptionalOptional: the brand (workspace) to act on, either its name as the user says it ("Channel One") or a brand_id from list_brands. Required when the account has more than one brand.
carousel_idintegerrequiredThe carousel id, e.g. from list_carousels.

get_post_performance ​

Metrics for one published post, per channel it went out on: impressions (views on video platforms), reactions, comments, saves, shares, clicks and their engagement sum, with when they were last synced. A channel that cannot report metrics (LinkedIn, or a platform that reports nothing for that post type) says so in reason instead of showing zeros; metrics is null when the platform reports but no sync has run yet. Numbers are the platform's cumulative totals, refreshed nightly for about 30 days after publish. Post ids come from list_posts or get_top_posts.

read-only, idempotent, needs ai:read

ArgumentTypeDescription
brandstringoptionalOptional: the brand (workspace) to act on, either its name as the user says it ("Channel One") or a brand_id from list_brands. Required when the account has more than one brand.
post_idintegerrequiredThe post id, e.g. from list_posts (published: true) or get_top_posts.

get_account_performance ​

Aggregate performance per connected platform over a date range (default: the last 30 days, at most a year): posts published, posts with metrics, summed impressions/reactions/comments/saves/shares/clicks and engagement, plus each account's follower series (one point per day: followers, profile views) with the change over the range. Metrics are each post's cumulative totals keyed on its publish date, not daily deltas. Platforms that cannot report metrics or followers are listed with reports_post_metrics / reports_follower_count false rather than omitted. Optional platform filter. For the best posts in the range call get_top_posts.

read-only, idempotent, needs ai:read

ArgumentTypeDescription
brandstringoptionalOptional: the brand (workspace) to act on, either its name as the user says it ("Channel One") or a brand_id from list_brands. Required when the account has more than one brand.
platformstringoptionalOptional: one platform (instagram, threads, bluesky, ...). Every connected platform when omitted.
fromstringoptionalOptional: first day, Y-m-d. Defaults to 30 days ago.
tostringoptionalOptional: last day (inclusive), Y-m-d. Defaults to today.

get_top_posts ​

The best-performing published posts over a date range (default: the last 30 days), ranked by engagement (reactions + comments + saves + shares + clicks) or by impressions, across all platforms or one. Each row carries the metrics, the platform, the post's first line, its permalink, and post_id when the row is linked to a Postlyra post (null for posts published outside Postlyra or before matching existed): pass it to get_post or get_post_performance. Limit up to 25, default 10.

read-only, idempotent, needs ai:read

ArgumentTypeDescription
brandstringoptionalOptional: the brand (workspace) to act on, either its name as the user says it ("Channel One") or a brand_id from list_brands. Required when the account has more than one brand.
platformstringoptionalOptional: rank within one platform (instagram, threads, bluesky, ...). All platforms when omitted.
metricenum: engagement, impressionsoptionalOptional: what to rank by. Default engagement.
fromstringoptionalOptional: earliest publish date, Y-m-d. Defaults to 30 days ago.
tostringoptionalOptional: latest publish date (inclusive), Y-m-d. Defaults to today.
limitintegeroptionalOptional: how many rows, 1 to 25. Default 10.

get_best_time_to_post ​

When this brand's own published posts have earned the most engagement, by weekday and hour in the brand's schedule timezone, computed from the last 180 days (up to 365) of Postlyra-published posts and their synced metrics. Honest by construction: it names no window until at least 30 published posts have metrics, and a window needs at least 3 posts behind it; below that it reports how many it has. Optional platform filter. Times are averages of real past posts, not predictions.

read-only, idempotent, needs ai:read

ArgumentTypeDescription
brandstringoptionalOptional: the brand (workspace) to act on, either its name as the user says it ("Channel One") or a brand_id from list_brands. Required when the account has more than one brand.
platformstringoptionalOptional: one platform (instagram, threads, bluesky, ...). All platforms that report metrics when omitted.
daysintegeroptionalOptional: how far back to look, 30 to 365 days. Default 180.

Server instructions ​

The server ships this instruction block to clients on connect. It is worth reading, because it is what shapes how an agent uses the tools.

Tools for managing content ideas across the brands (workspaces) the
caller's bearer token can access. One account-level token serves every
brand the user belongs to; each call is pinned to exactly one brand:
pass a brand_id from list_brands in the tool's optional "brand"
argument. An account with a single brand needs no argument; an account
with several gets an error naming list_brands if the argument is
missing. There is no way to reach a brand the token's user doesn't
belong to. In a multi-brand session, call list_brands once up front and
pass "brand" explicitly on every subsequent call.

Idea mining runs client-side, not on this server (docs/plans/2026-08-11
idea-import-and-cli.md): this server never reads anyone's git history.
The intended flow for an AI agent running on the operator's own machine
is: (1) call get_mining_context to fetch the team's voice guide,
content pillars, persona, and already-known source refs;
(2) read the user's LOCAL git log yourself (you run on their machine,
they don't) and drop any commit whose "<repo-label>@<short-sha>" ref
already appears in the refs the context call returned; (3) draft ideas
grounded in that voice/pillars/persona, each carrying source_refs built
the same way (e.g. "postlyra@abc1234"); (4) call push_ideas with the
result. Re-pushing the same headline + source_refs updates the existing
idea instead of duplicating it, so it is safe to re-run mining.

list_ideas and get_idea are read-only lookups over what has already been
pushed or created in Studio. All dates are Y-m-d.

Posting (2026-08-16, posts-and-publications Phase 5): create_post
writes a post into a brand's Planning queue and can schedule it to
connected accounts: "write a post for brand X for instagram and
bluesky, scheduled Monday 15:00 UTC" is list_content_profiles followed
by create_post with a `publications` list. plan_date/plan_time are
only a calendar note; `publications` carries the real publish times.
A carousel's `slides` render through the brand's chosen TEMPLATE
(carousel_template_id, see list_carousel_templates: HOW it looks) in
the chosen IDENTITY (content_profile_id, see list_content_profiles:
WHO it speaks as); either can be left out to use the brand's default.

Carousels are their own library asset (2026-09-05): list_carousels,
get_carousel, create_carousel and update_carousel build and edit them
directly, independent of any post, and delete_carousel (elevated)
removes one nothing still needs. create_post/update_post's `slides`
designs one INLINE for that post alone; `carousel_id` instead attaches
an EXISTING carousel from list_carousels, so one design can be reused
across posts. `slides`, `carousel_id` and `media` are mutually
exclusive on both tools. Building or editing a carousel is never gated
by approval: it is a library asset, exactly like a picture.

HARD RULE 1: approval is an explicit, AUDITED human act. By default a
created post lands AWAITING APPROVAL, the brand's users are notified,
and a scheduled publication only goes out if a human approves before
its time (it expires otherwise). A connection whose user consented to
the ELEVATED access level may pass approve=true on create_post or call
approve_post: that records the connection's own user as the approver
(approved_via "mcp"), and the brand can still reject in Studio until
each scheduled time. Nothing on this server publishes immediately.
Never tell a user something was published; at most it is scheduled
behind the approval gate.

Reading and managing (2026-08-28, mcp-read-parity): list_posts (filters
plus a before_id cursor) and get_post (full body, thread, approval
audit, per-publication permalinks) are the post readers; update_post
edits an unpublished post and reconciles its pending bookings under
Studio's rules (an edit never changes approval); delete_post (elevated)
removes a post that never went live. list_channels says what is
connected and what each channel can do, including whether it reports
metrics back. get_schedule answers "what's going out this week" in one
call and says per booking whether it will fire or expire unapproved.
update_idea_status triages ideas (new, shortlisted, dismissed).

Analytics: get_post_performance (one post, per channel),
get_account_performance (per-platform totals and follower series over a
range), get_top_posts (ranked published posts) and get_best_time_to_post
(the brand's own best hours, only once enough posts have metrics).
Metrics exist only for platforms whose connector reports them (Instagram,
Threads, Bluesky, TikTok, YouTube today); a channel that reports nothing
is named as such, never shown as zero. Every number is the platform's
cumulative total per post, refreshed nightly.

Postlyra, by MAVA Design