Skip to main content
Glama
hermoso-ai

Hermoso

Official

Publish a post to X (Twitter)

post_to_x
Destructive

Publish a single post, thread, poll, or media to a connected X (Twitter) account, publicly and immediately. Requires X connected and explicit user approval before sending.

Instructions

Publish to the user’s connected X (Twitter) account — a single post, a post with an image or video render attached, a reply to an existing post, or a whole THREAD (pass thread as an array and each part is posted as a reply to the one before). This PUBLISHES immediately and PUBLICLY — ALWAYS show the user the exact text and get an explicit yes BEFORE calling. Can also run a POLL (2-4 options) instead of media, and restrict who may reply. LENGTH IS THE POSTING ACCOUNT’S, NOT A FLAT 280: 280 characters on an ordinary account, but UP TO 25,000 if that account has X Premium. Hermoso reads the account’s own subscription from X and sends the post rather than refusing something it may be entitled to; if X declines it on length, the reply says so and names the subscription X reported. Over 25,000 is refused for free — that is X’s own ceiling on every account. Text is NEVER truncated. AND A LONG POST IS CHEAPER THAN A THREAD: it is ONE billed X call where the same words split across five posts are five, so prefer one long post over threading when the account has Premium. Only the first ~280 characters show in the timeline; the rest sits behind “Show more”. Write altText whenever you attach a render. COSTS CREDITS: X charges per API request, so every post in a thread is billed, and a post containing a LINK costs roughly 13× one without — mention the cost before publishing a long thread. Each profile also has a rolling 24-hour ceiling on what it can spend at X, and a request that would cross it is refused WHOLE before anything publishes, so a batch of posts is bounded rather than open-ended. THIS TOOL POSTS ORGANICALLY — it does not create an ad campaign. X ads are built with create_x_ads_campaign -> create_x_ads_line_item -> create_x_ads_promoted_tweet, and a promoted post needs a post id, so publish here first and promote that post. Needs X connected (Settings > Connectors > X).

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
hookNoWHAT ANGLE this post is built on, recorded only at publish time. post_performance ranks hooks on it (a winner needs 5 posts sharing ONE hook), so pass a list_hooks id (e.g. "direct_callout", "before_after") or reuse your own wording EXACTLY across a campaign. Omit it and this post never votes on which hook works.
pollNorun a poll on the post. X does not allow a poll and media on the same post, and a poll cannot ride on a thread.
textNothe post text. 280 characters without X Premium, up to 25,000 with it — write the full thing, it is never truncated. Use this OR thread, not both.
brandNoWHICH PROFILE this post belongs to — id or exact name from list_brands (a shared workspace: its profile id). Beats the connection's pin for THIS CALL ONLY; a name that matches no profile, or two, is REFUSED and nothing is posted.
ideaIdNoshort id of the content-plan idea this post came from
recipeNothe post's FORMAT id, e.g. "slideshow" or "imessage_chat" — post_performance groups by it, so reuse one id per format
threadNoa thread: each string is one post, published in order, each replying to the previous. Max 25. Each part follows the same length rule as `text`, and on an X Premium account ONE long post is usually both better reading and cheaper than a thread.
accountNoWHICH connected account of this channel to post as — its @handle or id from list_connector_accounts. Needed only when the profile has more than one x account connected (several and none named is refused by name, never guessed); omit when there is one.
altTextNoaccessibility description of the attached media, max 1000 characters — write one whenever you attach a render. Costs a small extra amount: X bills one metadata write PER media. With SEVERAL media, pass an ARRAY aligned to their order — X attaches alt text per media id, and a single string describes only the FIRST one (X renders up to four media as a GRID, not a carousel, so one sentence would be wrong for the other three).
subjectNoWHAT THIS POST IS ABOUT — product, feature, offer or theme (e.g. "winter coat", "free trial"). post_performance's second grouping axis: reuse the exact wording, as with hook.
imageUrlNoalias of mediaUrl for an IMAGE — same as passing it as mediaUrl
mediaUrlNoa Hermoso render (image or video) to attach to the first post — pass its served URL, or an upload_file url for external media
videoUrlNoalias of mediaUrl for a VIDEO — same as passing it as mediaUrl
mediaUrlsNoUP TO FOUR Hermoso-hosted media attached to ONE post — X’s own schema caps media_ids at 4. X renders them as a GRID: every image visible at once, nothing to swipe to. That is NOT a carousel, and a numbered "1/6 · SWIPE" slide deck must still not be sent here — it would publish as a grid and the "swipe" instruction would make no sense. Order decides the layout. On a thread the media rides the FIRST post; give each later part its own post to attach more. Mutually exclusive with mediaUrl, and more than 4 is refused before anything is uploaded.
replyToIdNonumeric id of an existing X post to reply to. X ONLY ACCEPTS AN API REPLY TO A POST THAT MENTIONS THIS ACCOUNT OR THAT THIS ACCOUNT WROTE (X policy since Feb 2026, every tier below Enterprise): a cold reply under a stranger's post is refused by X with "You can only reply to or quote posts where you are mentioned or are the author" and nothing is posted. Reply to those from x.com itself; use this for replies in your own threads and to people who tagged you.
communityIdNopublish into an X COMMUNITY instead of the main timeline — the number in the community’s own URL (x.com/i/communities/<id>). The connected account must be a MEMBER of it; X answers a non-member and a non-existent id with the same refusal and does not separate them.
quotePostIdNonumeric id of a post to QUOTE — X renders the quoted post inside yours. This is NOT replyToId: a reply sits under the original in its thread, a quote stands alone on your own timeline with the original embedded, which is the one you want for commentary. X makes a quote mutually exclusive with media and with a poll (their own schema), and a quote is billed at the higher LINK rate because X appends the quoted post’s t.co URL whatever your text says. Same X rule as replyToId: a quote of a post that does not mention this account is refused by X.
platformCoverNoVIDEO COVER. X has no cover setting and shows the video’s FIRST frame, so by default Hermoso checks it and, only when that frame is blank (a template ad’s empty opening card), sends X a copy with the first frame replaced by the video’s best frame. Your Library file is never changed. true = send the file exactly as it is.
replySettingsNorestrict who can reply — omit for everyone, which is the right default for a brand post
paidPartnershipNolabel the post a PAID PARTNERSHIP on X — the same disclosure Hermoso already ships for TikTok. OPT-IN ONLY: set it when the post is sponsored, gifted or otherwise paid for, and never assume it on the user’s behalf.

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed2 schema fields changedv0.1.374
    • changedInput schema / properties / account / description
      Previous value: -"WHICH connected account of this channel to post as — its @handle or id from list_connector_accounts. Needed only when the brand has more than one x account connected (several and none named is refused by name, never guessed); omit when there is one."New value: +"WHICH connected account of this channel to post as — its @handle or id from list_connector_accounts. Needed only when the profile has more than one x account connected (several and none named is refused by name, never guessed); omit when there is one."
    • changedInput schema / properties / brand / description
      Previous value: -"WHICH BRAND this post belongs to — id or exact name from list_brands (a shared workspace: its profile id). Beats the connection's pin for THIS CALL ONLY; a name that matches no brand, or two, is REFUSED and nothing is posted."New value: +"WHICH PROFILE this post belongs to — id or exact name from list_brands (a shared workspace: its profile id). Beats the connection's pin for THIS CALL ONLY; a name that matches no profile, or two, is REFUSED and nothing is posted."
  2. Changed3 schema fields changedv0.1.320
    • changedInput schema / properties / brand / description
      Previous value: -"WHICH BRAND this post belongs to — the id or exact name from list_brands (a workspace shared with you: its profile id). Use it whenever the account has more than one brand and you are not certain which one this connection is pinned to: it beats the pin for THIS CALL ONLY and changes nothing about the connection. A name that matches no brand, or two brands, is REFUSED and nothing is posted — never resolved to the pin, which is the account you were guarding against."New value: +"WHICH BRAND this post belongs to — id or exact name from list_brands (a shared workspace: its profile id). Beats the connection's pin for THIS CALL ONLY; a name that matches no brand, or two, is REFUSED and nothing is posted."
    • changedInput schema / properties / hook / description
      Previous value: -"WHAT ANGLE THIS POST IS BUILT ON — the single most valuable field here, and the only moment it can ever be recorded. post_performance groups on it to answer \"which hooks work\", and it needs 5 posts sharing ONE hook before it will call anything a winner, so REUSE THE SAME WORDING across a campaign instead of rephrasing it every time. Best of all, pass a hook id from list_hooks (e.g. \"direct_callout\", \"mid_problem\", \"before_after\") — those fold onto a stable key however they are spelled, so a whole brand accumulates evidence on one row. Your own wording is fine too; it just only groups when you repeat it exactly. Omitting it means this post can never vote on which hook works."New value: +"WHAT ANGLE this post is built on, recorded only at publish time. post_performance ranks hooks on it (a winner needs 5 posts sharing ONE hook), so pass a list_hooks id (e.g. \"direct_callout\", \"before_after\") or reuse your own wording EXACTLY across a campaign. Omit it and this post never votes on which hook works."
    • changedInput schema / properties / subject / description
      Previous value: -"WHAT THIS POST IS ABOUT — the product, feature, offer or theme (e.g. \"winter coat\", \"free trial\", \"founder story\"). The second grouping axis in post_performance. Same rule as hook: reuse the exact wording so posts about one subject land in one group."New value: +"WHAT THIS POST IS ABOUT — product, feature, offer or theme (e.g. \"winter coat\", \"free trial\"). post_performance's second grouping axis: reuse the exact wording, as with hook."
  3. Changed2 schema fields changedv0.1.281
    • addedInput schema / properties / ideaId
      Added value: +{
      +  "description": "short id of the content-plan idea this post came from",
      +  "type": "string"
      +}
    • addedInput schema / properties / recipe
      Added value: +{
      +  "description": "the post's FORMAT id, e.g. \"slideshow\" or \"imessage_chat\" — post_performance groups by it, so reuse one id per format",
      +  "type": "string"
      +}
  4. Changed1 schema field changedv0.1.256
    • addedInput schema / properties / platformCover
      Added value: +{
      +  "description": "VIDEO COVER. X has no cover setting and shows the video’s FIRST frame, so by default Hermoso checks it and, only when that frame is blank (a template ad’s empty opening card), sends X a copy with the first frame replaced by the video’s best frame. Your Library file is never changed. true = send the file exactly as it is.",
      +  "type": "boolean"
      +}
  5. Changed1 schema field changedv0.1.251
    • addedInput schema / properties / brand
      Added value: +{
      +  "description": "WHICH BRAND this post belongs to — the id or exact name from list_brands (a workspace shared with you: its profile id). Use it whenever the account has more than one brand and you are not certain which one this connection is pinned to: it beats the pin for THIS CALL ONLY and changes nothing about the connection. A name that matches no brand, or two brands, is REFUSED and nothing is posted — never resolved to the pin, which is the account you were guarding against.",
      +  "type": "string"
      +}
  6. Changed4 schema fields changedv0.1.249
    • addedInput schema / properties / imageUrl
      Added value: +{
      +  "description": "alias of mediaUrl for an IMAGE — same as passing it as mediaUrl",
      +  "type": "string"
      +}
    • changedInput schema / properties / quotePostId / description
      Previous value: -"numeric id of a post to QUOTE — X renders the quoted post inside yours. This is NOT replyToId: a reply sits under the original in its thread, a quote stands alone on your own timeline with the original embedded, which is the one you want for commentary. X makes a quote mutually exclusive with media and with a poll (their own schema), and a quote is billed at the higher LINK rate because X appends the quoted post’s t.co URL whatever your text says."New value: +"numeric id of a post to QUOTE — X renders the quoted post inside yours. This is NOT replyToId: a reply sits under the original in its thread, a quote stands alone on your own timeline with the original embedded, which is the one you want for commentary. X makes a quote mutually exclusive with media and with a poll (their own schema), and a quote is billed at the higher LINK rate because X appends the quoted post’s t.co URL whatever your text says. Same X rule as replyToId: a quote of a post that does not mention this account is refused by X."
    • changedInput schema / properties / replyToId / description
      Previous value: -"numeric id of an existing X post to reply to"New value: +"numeric id of an existing X post to reply to. X ONLY ACCEPTS AN API REPLY TO A POST THAT MENTIONS THIS ACCOUNT OR THAT THIS ACCOUNT WROTE (X policy since Feb 2026, every tier below Enterprise): a cold reply under a stranger's post is refused by X with \"You can only reply to or quote posts where you are mentioned or are the author\" and nothing is posted. Reply to those from x.com itself; use this for replies in your own threads and to people who tagged you."
    • addedInput schema / properties / videoUrl
      Added value: +{
      +  "description": "alias of mediaUrl for a VIDEO — same as passing it as mediaUrl",
      +  "type": "string"
      +}
  7. Changed1 schema field changedv0.1.189
    • addedInput schema / properties / account
      Added value: +{
      +  "description": "WHICH connected account of this channel to post as — its @handle or id from list_connector_accounts. Needed only when the brand has more than one x account connected (several and none named is refused by name, never guessed); omit when there is one.",
      +  "type": "string"
      +}
  8. Addedv0.1.161

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations declare destructiveHint=true and openWorldHint=true, and the description adds substantial behavioral context beyond them: immediate public publication, credit costs, the ~13x link surcharge, a rolling 24-hour spend ceiling that refuses whole requests, per-media alt-text billing, length limits driven by the account's actual subscription, and how X's refusal is surfaced. This is exactly the extra context annotations cannot carry.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long, but nearly every sentence carries a distinct, actionable fact (confirmation requirement, cost model, length rule, thread-vs-post economics, ad-flow pointer). It is dense and information-rich rather than padded, though the heavy ALL-CAPS emphasis makes it harder to scan and could be trimmed slightly.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 20-parameter mutation tool with nested objects and no output schema, the description covers the operationally critical dimensions: consent, cost, length, threading strategy, reply/quote constraints, and account requirements. Parameter-specific semantics like hook/subject/recipe grouping are left to the (rich) schema, which is reasonable, so coverage is effectively complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description goes beyond the schema by explaining strategy that shapes parameter choice: thread vs single long post cost tradeoffs, quote vs reply semantics, poll exclusivity with media and threads, and the mediaUrls grid-not-carousel caveat. It adds real decision value rather than restating field descriptions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Publish to the user's connected X (Twitter) account') and immediately enumerates the modes it covers: single post, media post, reply, thread, poll. It also explicitly separates itself from the sibling ad flow (create_x_ads_campaign etc.) and from quote-vs-reply behavior, so an agent can route correctly without opening schemas.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives explicit when-to-use and when-not-to-use guidance: always get an explicit yes before publishing, prefer one long post over a thread on Premium accounts, use replyToId only for posts that mention the account, use x.com itself for cold replies, and use this tool before promoting a post. Alternatives and prerequisites (X connected via Settings > Connectors) are named.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Deploy Server

Other Tools