Bluesky MCP server
A Bluesky MCP server that's live today.
Connect a Bluesky account with an app password, add one server to your agent, and post with links and hashtags that actually render as links.
The short answer · Updated Nov 1, 2026
A Bluesky MCP server exposes Bluesky posting as tools an AI agent can call over the Model Context Protocol. SocialsForAI's is hosted at https://socialsforai.com/api/mcp and live today; clients connect over Streamable HTTP with a bearer API key. It connects accounts with a Bluesky app password (never the main password), counts the 300-grapheme limit, builds link, mention and hashtag facets automatically, uploads up to 4 images, and verifies each post by reading it back.
Bluesky: live. Posting to Bluesky works on the API today.
What a Bluesky MCP server does
Bluesky's API is open, so posting from a script is easy. Posting correctly takes more: logging in with an app password, keeping the session alive, counting graphemes rather than characters, uploading images as blobs, and adding facets so links and mentions are clickable. Our server handles all of it and gives the agent eight tools.
When the agent calls create_post, we log in to the account's PDS (bsky.social by default, or a custom PDS URL the owner entered), run the text through the atproto SDK's facet detection, upload images, create the record, and then fetch it back with getPosts to set verified. More on the raw API is in our Bluesky API guide, and the protocol side is on the MCP server overview.
The tools that matter for Bluesky
Inputs for all eight tools are on the hub, under every tool and its inputs. This is what they return or do for a Bluesky account, one of the five networks you can run end to end today.
list_platforms: Bluesky's row hasavailable: true,maxChars: 300(graphemes),maxImages: 4,supportsVideo: falseanddailyPostCap: 300. Over REST,GET /v1/platformsalso showsconnect: "credentials", meaning a form rather than an OAuth redirect.create_connect_linkwithplatforms: ["bluesky"]: returns a link to a hosted page with three fields: handle or email, app password, and an optional PDS URL. It works on the hosted server now.list_accountswithplatform: "bluesky":handleis the account's handle from its session (for exampleyou.bsky.social) anddisplay_namecomes from the Bluesky profile when it can be read.create_post: text is checked against 300 graphemes and 3,000 UTF-8 bytes. Write links, @handles and #tags as plain text; facets are added at publish time. Images are fetched from your URLs, refused above 2,000,000 bytes, uploaded as blobs, and embedded with youralttext.get_post: a Bluesky target'surlishttps://bsky.app/profile/<handle>/post/<rkey>andexternal_post_idis the record'sat://URI.verified: truemeans we read the record back withgetPostsafter creating it.
A worked session: dry run, fix, publish
You ask the agent to announce a launch on Bluesky and LinkedIn. The shared draft is long enough for LinkedIn but 318 graphemes for Bluesky, so the dry run fails with text_too_long and points at platform_options.bluesky.text. The agent writes a short Bluesky version, keeps the long one for LinkedIn, and publishes after you approve.
The result is partially_published: Bluesky succeeded and LinkedIn failed with account_needs_reauth. Each account gets its own target, so one bad account never hides another's result. The agent's next step is in the fix field. Ids here are illustrative; the shapes are the server's real responses, trimmed with ....
// 1. Dry run the draft
create_post({ "accounts": ["acc_Bs2rTq8Lm5Wn1KdP"],
"text": "<318-grapheme thread opener>", "dry_run": true })
→ { "object": "preflight", "ok": false,
"issues": [{ "account_id": "acc_Bs2rTq8Lm5Wn1KdP", "platform": "bluesky", "severity": "error",
"code": "text_too_long", "field": "platform_options.bluesky.text",
"message": "Text is 318 characters; Bluesky allows 300.",
"fix": "Shorten it by 18+ characters, or set platform_options.bluesky.text to a shorter version." }],
"preview": [{ "account_id": "acc_Bs2rTq8Lm5Wn1KdP", "platform": "bluesky",
"text": "<318-grapheme thread opener>" }] }
// 2. Publish a shorter Bluesky version (after the human approves)
create_post({ "accounts": ["acc_Bs2rTq8Lm5Wn1KdP", "acc_Li4nKd8Qp2Zr7TmX"],
"text": "<long version for LinkedIn>",
"platform_options": { "bluesky": { "text": "Webhooks shipped. Docs: https://example.com/docs #devtools" } },
"idempotency_key": "webhooks-launch-1" })
→ { "id": "post_Zt6Mw1Qe8Rn4Yb2C", "object": "post", "status": "partially_published",
"targets": [
{ "account_id": "acc_Bs2rTq8Lm5Wn1KdP", "platform": "bluesky", "status": "published",
"url": "https://bsky.app/profile/you.bsky.social/post/<rkey>",
"external_post_id": "at://did:plc:<did>/app.bsky.feed.post/<rkey>",
"attempts": 1, "verified": true, "error": null, ... },
{ "account_id": "acc_Li4nKd8Qp2Zr7TmX", "platform": "linkedin", "status": "failed",
"attempts": 1, "error": { "code": "account_needs_reauth",
"message": "LinkedIn access token expired (they last 60 days).",
"fix": "Reconnect the LinkedIn account with a new connect link.", "retryable": false }, ... }
], ... }Bluesky rules the server handles
From Bluesky's lexicons and docs, and our Bluesky provider:
- Length: the
app.bsky.feed.postlexicon setsmaxGraphemes: 300(and 3,000 bytes). A grapheme is what a reader sees as one character, so an emoji with skin tone counts once. We count graphemes the same way. - Facets: Bluesky doesn't use markup. Links, mentions and hashtags are facets that point at UTF-8 byte ranges in the text, per Bluesky's rich text guide; without them a URL shows as plain text. We run the SDK's
detectFacetson every post, so the agent writes plain text and links work. - Images: the lexicon allows up to 4 images per post, each up to 2,000,000 bytes (formerly 1 MB). We fetch the URL you pass and reject larger files with
media_too_large. Passalttext; it goes into the image embed. - App passwords: atproto's own docs say password logins should use app passwords only, since they're designed for programmatic access. The connect page asks for an app password and never the main one. Revoke it in Bluesky's settings and our access ends.
- Self-hosted PDS: enter the PDS URL on the connect page. Leave it blank for bsky.social.
- Volume: our fair-use cap is 300 posts per Bluesky account per rolling 24 hours.
Before an agent posts to Bluesky for you
Bluesky charges no API fee and our 300-a-day cap is higher than on X or LinkedIn, so the guardrails here are mostly yours to set. These are the ones that matter on Bluesky.
- One app password per connection. Create a dedicated app password for this account, so revoking it in Bluesky's settings cuts off our access without touching your other apps. The next publish then fails with
account_needs_reauth. Disconnecting the account through our API deletes the stored credentials on our side too. - Read the mentions in the preview. Because facets are added automatically, an @handle in the agent's draft becomes a real mention linked to that account, and a URL becomes a live link. Check both in the dry-run text before you approve.
- Three hundred a day is generous. At our cap of 300 posts per account per rolling 24 hours, the duplicate check (
duplicate_contentfor identical text within 24 hours) and your approval prompt do more of the work than the cap does. - Approve the publish, not only the draft. Have the agent run
create_postwithdry_run: truefirst, then leave your client's tool approval on so the real call needs a click. - Key every publish. Send an
idempotency_key; a retried call with the same key and body returns the existing post, so a timeout never leaves two copies in your feed. - Practice failures in the sandbox. The sandbox platform's fake account fails on purpose when the text contains
[fail:retryable],[fail:permanent]or[fail:auth], so you can see how the agent reacts before it touches your real account.
Set up for Bluesky: add the server, then an app password
The server is the same for every network. Register it in your client; the Claude Code command is below, and the Cursor and Claude Desktop entries are in client setup.
Bluesky connects with a form instead of OAuth. Ask the agent to call create_connect_link with platforms: ["bluesky"] and open the URL yourself. Enter your handle (a leading @ is stripped) or account email, and an app password from Settings, Privacy and security, App passwords, at bsky.app. Leave the PDS URL blank unless your account lives on your own server. We log in, keep the session, and on later posts resume it; if the session can't be resumed we log in again with the app password. The app password is stored encrypted with AES-256-GCM and never returned by any tool.
export SOCIALS_API_KEY=sk_...
claude mcp add --transport http socials https://socialsforai.com/api/mcp \
--header "Authorization: Bearer $SOCIALS_API_KEY"Bluesky errors and their fixes
These are the Bluesky-specific codes. The ones every network shares, such as the 401 at connect time, preflight_failed and idempotency_key_reused, are covered once in troubleshooting by error code.
account_needs_reauthon Bluesky: the app password was revoked or the login failed. The fix says to create a new app password at bsky.app/settings/app-passwords and reconnect with a connect link.invalid_credentialson the connect page: the handle or app password is empty. Use the full handle (yourname.bsky.social) and paste the app password with its dashes.media_too_large: an image is over 2 MB. Compress or resize it and pass a new URL.text_too_longunder 300 graphemes: the text is over 3,000 UTF-8 bytes, which happens with many emoji or non-Latin scripts. Shorten it or setplatform_options.bluesky.text.platform_rejected: Bluesky answered 400 and its message is in the error. It isn't retried, so fix the post and send it with a new key.platform_rate_limitedorplatform_unavailable: Bluesky answered 429 or failed for another reason. Both are retried automatically; pollget_post.
Status: live
Bluesky posting is live on the hosted server today: text, links, mentions, hashtags, up to 4 images with alt text, scheduling, and read-back verification. Video isn't supported yet. X, LinkedIn (personal profiles), Mastodon and Telegram are live too; see the X MCP server and LinkedIn MCP server pages for how they behave.
Connect Bluesky to your agent in five steps
- 01
Make a key
Sign up or send POST /v1/workspaces. Free covers 3 accounts that post in a month and 100 posts, which is plenty to try Bluesky end to end.
- 02
Create a Bluesky app password
In Bluesky, open Settings, Privacy and security, App passwords, and create one for this connection. Keep the dashes when you copy it.
- 03
Add the server and ask for a link
Register the hosted URL in your client, then ask the agent for a Bluesky connect link.
- 04
Enter handle and app password
On the connect page, type your handle or email, paste the app password, and fill in a PDS URL only if you host your own. The agent never sees these fields.
- 05
Dry run and publish
Have the agent dry run under 300 graphemes, approve the post, and open the bsky.app URL from the result to see the links rendered.
Questions
What developers ask
Why use a hosted Bluesky MCP server instead of the SDK?
The SDK is fine for one script. A hosted server adds connect links, encrypted credentials, preflight checks, retries, receipts and the same tools for X, LinkedIn, Mastodon and Telegram.
Do I give the agent my Bluesky password?
No. The account owner enters an app password on our connect page, not in the chat. The agent only sees an account id.
Will links in my agent's Bluesky posts be clickable?
Yes. We detect links, mentions and hashtags and add facets automatically. Without facets Bluesky shows a URL as plain text.
Does the Bluesky MCP server work with self-hosted PDS accounts?
Yes. Enter the PDS URL on the connect page.
Can I try it without a real Bluesky account?
Yes. Connect the sandbox platform. Putting [fail:retryable], [fail:permanent] or [fail:auth] in the text simulates failures.
Can I log in with my email instead of my handle?
Yes. The connect page accepts a handle or the account's email in the first field, with the app password in the second.
How much does it cost?
Bluesky has no API fee. Our Free plan is enough to start; see pricing for paid plans, which are priced per account that posted in the month.
Get an API key and send your first post in five minutes.