Post to Bluesky via MCP
Quick navigation: Which path should I use? · Multi-platform example · Recovery playbook · All 9 platforms at a glance
Prerequisites: VoxBurst MCP server configured. See MCP Server setup.
Bluesky uses graphemes, not characters. The 300-unit limit counts emoji as 1 unit each. ”🚀🚀🚀” is 3 graphemes, not 12 characters. Always validate emoji-heavy posts before creating them.
The complete flow
Find the Bluesky account ID
list_accounts()Look for "platform": "bluesky". Bluesky uses app passwords for auth — no OAuth.
Validate content first (especially recommended)
validate_content(
content: "Your Bluesky post text",
platforms: ["BLUESKY"]
)Bluesky’s 300-grapheme limit catches many users off guard. Validate first if your post includes emoji or links.
Create the post
See post type examples below.
Check the result
get_post(postId: "post_abc123"){
"id": "post_abc123",
"status": "published",
"platforms": [
{
"platform": "bluesky",
"status": "published",
"platformPostUrl": "https://bsky.app/profile/mybrand.bsky.social/post/abc123",
"publishedAt": "2026-06-01T10:00:03Z",
"error": null
}
]
}What type of post do you want?
| I want to post… | contentType | Media required? | Notes |
|---|---|---|---|
| Text only | TEXT | No | Max 300 graphemes |
| Image(s) | IMAGE | Yes — 1–4 images | Max 1 MB per image |
| A video | VIDEO | Yes — 1 video | Max 3 min |
Bluesky does not support first comments, carousels, Reels, or Stories.
Media input options:
mediaUrls— public HTTPS image/video URLs (auto-registered)mediaIds— VoxBurst media IDs from prior uploads
Post type examples
Text post
create_post(
content: "Just shipped our open-source content scheduler. Link in thread.",
accountIds: ["acc_bluesky_abc123"],
contentType: "TEXT"
)Image post
create_post(
content: "Our office setup this morning ☀️",
accountIds: ["acc_bluesky_abc123"],
contentType: "IMAGE",
mediaUrls: ["https://cdn.example.com/office.jpg"]
)Multiple images (up to 4)
create_post(
content: "Four screenshots from our new dashboard →",
accountIds: ["acc_bluesky_abc123"],
contentType: "IMAGE",
mediaUrls: [
"https://cdn.example.com/screen-1.jpg",
"https://cdn.example.com/screen-2.jpg"
]
)Video post
create_post(
content: "Short demo of the scheduling flow.",
accountIds: ["acc_bluesky_abc123"],
contentType: "VIDEO",
mediaUrls: ["https://cdn.example.com/demo.mp4"]
)Scheduled post
create_post(
content: "New feature dropping at noon. Stay tuned.",
accountIds: ["acc_bluesky_abc123"],
contentType: "TEXT",
scheduledFor: "2026-06-01T12:00:00Z"
)Tell your AI agent this
Text post, post now:
Post to Bluesky account
[acc_id]. Content:[your text — max 300 graphemes]. Post now.
Image, scheduled:
Post to Bluesky account
[acc_id]. Content:[text]. Attach image:[https://...]. Schedule for[ISO timestamp].
Validate first:
Before posting to Bluesky account
[acc_id], validate this text:[post text]. Check the grapheme count — emoji count as 1 each.
Failure cookbook
| Error | Cause | Fix |
|---|---|---|
CONTENT_TOO_LONG | Over 300 graphemes (not characters) | Count graphemes — emoji = 1 each; links also count |
INVALID_APP_PASSWORD | App password revoked or expired | Re-connect account in VoxBurst; generate a new app password at bsky.app |
IMAGE_TOO_LARGE | Image file over 1 MB | Compress to under 1 MB before uploading |
ACCOUNT_NOT_FOUND | Username or instance changed | Disconnect and re-connect the Bluesky account |
VIDEO_TOO_LONG | Video over 3 minutes | Trim the video |
Post status failed | Various AT Protocol errors | Check platforms[].error; call retry_post for transient errors |
Account requirements
| Requirement | Detail |
|---|---|
| Account type | Any Bluesky account (bsky.app or self-hosted) |
| Auth method | App password (not OAuth — generate at bsky.app → Settings → App Passwords) |
| Key precondition | App password required; standard account password is not accepted |
| Re-connect trigger | App password revoked; generate a new one at bsky.app and re-connect in VoxBurst |
When Bluesky is a poor fit
- Marketing-heavy content — Bluesky’s community culture leans toward authentic, conversational posts; aggressive marketing language can receive negative responses
- First comment CTAs — first comments are not supported; include the CTA in the post body or a reply
- Emoji-heavy captions — the 300-grapheme limit means heavy emoji use quickly exhausts the limit
- Carousel posts — not supported
- Link-preview-dependent content — Bluesky renders link cards, but the link still counts toward the grapheme limit
Cross-platform override example
Bluesky needs a short, authentic caption while LinkedIn can have a longer professional version:
create_post(
content: "New open-source tool released today.",
accountIds: ["acc_bluesky_pqr", "acc_linkedin_ghi", "acc_mastodon_stu"],
contentType: "TEXT",
platformOverrides: {
"BLUESKY": {
"content": "We just open-sourced our scheduling engine. MIT license. PR welcome."
},
"LINKEDIN": {
"content": "Today we are open-sourcing the scheduling engine that powers VoxBurst. MIT licensed. We think the ecosystem benefits from more shared infrastructure, not less. The repository is linked below."
},
"MASTODON": {
"content": "New open-source release: our scheduling engine is now MIT licensed and public. #OpenSource #FOSS"
}
}
)See the same post across all platforms guide for a full 8-platform example.
Benchmark checklist
Last updated: 2026-05-31 — Added JSON tool-call examples and benchmark checklist.
| Supported content types | TEXT, IMAGE (1–4), VIDEO |
| Unsupported content types | CAROUSEL, REEL, STORY; first comments not supported |
| Validation gotcha | 300 graphemes — not 300 characters; emoji = 1 grapheme each |
| Media gotcha | Images max 1 MB; video max 3 min |
| Account gotcha | App password required (not OAuth); re-generate at bsky.app if expired |
| Example prompt | ”Post to Bluesky account acc_id. Content: text (max 300 graphemes). Post now.” |
| Example tool call | create_post(content: "...", accountIds: ["acc_bluesky_abc"], contentType: "TEXT") |
| Example response | { "status": "published", "platforms": [{ "platform": "bluesky", "platformPostUrl": "https://bsky.app/profile/..." }] } |
| Recovery path | retry_post for transient errors; re-generate app password for INVALID_APP_PASSWORD |