A Bluesky MCP serverthat never sees your password
Search for a Bluesky MCP server and every result tells you to paste an App Password into claude_desktop_config.json. That credential is not scoped to posting, and a config file is the worst place on your machine to keep one. This server connects over atproto OAuth instead, and mirrors your X posting to Bluesky from any MCP client.
Hosted at https://mcp.opentweet.io/mcp. 43 tools.
7-day free trial. Cancel anytime.
The config every other guide gives you
This is the shape of the instructions on essentially every Bluesky MCP server today.
{
"mcpServers": {
"bluesky": {
"command": "npx",
"args": ["-y", "some-bluesky-mcp-server"],
"env": {
"BLUESKY_IDENTIFIER": "you.bsky.social",
"BLUESKY_PASSWORD": "xxxx-xxxx-xxxx-xxxx"
}
}
}
}Two things are wrong with it, and they compound. The first is what the credential can do: a Bluesky App Password is not a posting scope, it is close to full account access. The second is where it now lives. That file sits unencrypted in your home directory, readable by any process running as you, and it is one of the most commonly committed files in a dotfiles repo.
None of this is the fault of the people who wrote those servers. Bluesky issues no API keys, so an App Password was the only option available for a long time. atproto OAuth is the current model, and it is the reason this page exists.
If you already pasted one somewhere
Open Bluesky settings and delete that App Password. Deleting it revokes it everywhere, which is also the problem with the model: anything else you gave the same string to stops working at the same moment.App Password against an OAuth grant
The same two rows people argue about, plus the one that only matters inside an MCP config.
| App Password | atproto OAuth | |
|---|---|---|
| What you hand over | A credential string you paste into a file | Nothing. You approve on your own server |
| What it is allowed to do | Close to everything your account can do | Create posts and upload images |
| Where it ends up | Plaintext in claude_desktop_config.json on disk | A server-side grant. Your config holds an OpenTweet key instead |
| Turning one tool off | Delete the App Password, breaking everything sharing it | Revoke that grant. Nothing else is affected |
| Standing in atproto | The older model OAuth is replacing | The current auth model |
| Self-hosted account | Works only if the tool did not hardcode a host | Resolved from your handle, works anywhere |
Both can be revoked, so revocation is not the gap. The gap is scope, blast radius, and whether a secret is sitting in a file your agent can read.
Four steps
Get your OpenTweet API key
Sign in, connect your X account, and copy the key from the dashboard. This is the only secret that goes near your MCP config, and it is an OpenTweet key, not a network credential. Revoking it removes the agent, not your accounts.
Connect Bluesky over atproto OAuth
Type your handle, yourname.bsky.social or your own domain. OpenTweet resolves which server holds the account and sends you there to sign in and approve. That page belongs to your server. Your password is typed where it already lives and never reaches a config file.
Add the server to your client
One line for Claude Code, or the JSON block for Claude Desktop and Cursor. The hosted endpoint is Streamable HTTP, so there is no local process to keep running and no npm install to maintain.
Tell the agent where a post goes
Pass platforms on the tool call to pin a post, or leave it out and let the account cross-post setting decide. Omitting it is not the same as pinning to X, which is the one thing that catches agent authors out. Call opentweet_list_platforms to see what an unpinned post would do.
The config
One line for Claude Code, or the JSON block for Claude Desktop and Cursor.
claude mcp add --transport http opentweet \
https://mcp.opentweet.io/mcp \
--header "Authorization: Bearer ot_your_key"{
"mcpServers": {
"opentweet": {
"type": "streamable-http",
"url": "https://mcp.opentweet.io/mcp",
"headers": { "Authorization": "Bearer ot_your_key" }
}
}
}Notice what is not in there
No Bluesky handle, no Bluesky password, no App Password. The only secret is an OpenTweet key, and the Bluesky OAuth grant lives server-side where your agent cannot read it. Rotate the key and your Bluesky account is untouched.What the agent can actually do
Every posting tool takes a platforms parameter, so cross-posting is not a separate tool to learn. It is an argument on the ones you already use.
// Mirror one post to both networks
opentweet_create_tweet({
text: "Shipped the new scheduler. Notes in the changelog.",
platforms: ["x", "bluesky"],
publish_now: true
})
// Queue a thread for later, both networks
opentweet_create_thread({
tweets: ["Three things I got wrong about queues.", "One.", "Two.", "Three."],
platforms: ["x", "bluesky"],
scheduled_date: "2026-09-08T14:00:00Z"
})
// Check where an unpinned post would actually land
opentweet_list_platforms({})Omitting platforms is not the same as pinning to X
A call that leaves the parameter out follows your account cross-post setting at publish time, which may send it to both networks. If an agent is writing posts unattended, pin them explicitly.opentweet_list_platforms reports where an unpinned post would go and which of those targets will actually land.What this is, and what it is not
Worth being blunt, because it decides whether this is the right server for you.
Use this if
- X is where you post and Bluesky is the mirror
- You want the agent to schedule, not just publish this second
- Threads should post as real reply chains on both networks
- You want your X analytics and best-times data in the same session
- You would rather not keep a credential in a config file
Use something else if
- You want to read the Bluesky firehose or search atproto
- You need follows, likes, or moderation actions on Bluesky
- You want Bluesky engagement numbers, which do not exist to read
- You are posting video, which does not cross-post to Bluesky
- Bluesky is your only network and X is not in the picture
The limits that decide what lands are the same ones the composer enforces: 300 characters on Bluesky, 4 images at 2MB with alt text, and a per-network result on every post. The full list is here.
Frequently asked questions
Do I need a Bluesky API key for an MCP server?
No, because Bluesky does not issue API keys. There is no developer program and no application form. What the existing Bluesky MCP servers ask for instead is an App Password, which is a different thing: a credential with close to full account access that you paste into a config file. OpenTweet connects over atproto OAuth and asks for neither.
What does a Bluesky App Password actually let a tool do?
Close to everything your account can do. It is not scoped to posting. Anything holding it can read and write your repository, and the only way to withdraw it is to delete that App Password in Bluesky settings, which cuts off everything else sharing it. An atproto OAuth grant is scoped and revoked on its own.
Why is an App Password worse inside an MCP config?
Because of where the file lives. claude_desktop_config.json sits in plaintext in your home directory, it is readable by any process running as you, and dotfile repos routinely swallow it. An MCP server config is the single worst place to store a near-root credential, and it is exactly where every guide tells you to put one.
Is this a Bluesky-only MCP server?
No, and that is deliberate. It is an X automation server that publishes to Bluesky as well. You write and schedule from one place, pass platforms to say where a post goes, and get a per-network result back. If what you want is to read the Bluesky firehose or manage follows on Bluesky, a dedicated atproto server is the better fit.
Which MCP clients does it work with?
Anything that speaks MCP. Claude Desktop, Claude Code, and Cursor over the hosted Streamable HTTP endpoint at https://mcp.opentweet.io/mcp with an Authorization header, or over the npm package if you prefer a local process.
What happens if my post is too long for Bluesky?
It is skipped there with the reason recorded and still published on X. Bluesky stops at 300 characters, X allows 280 or 25,000 on Premium, so a long post is legal on one network and impossible on the other. Nothing silently truncates.
Can the agent read my Bluesky analytics?
No. Bluesky analytics do not exist. Nothing reads engagement back from Bluesky, so the analytics tools cover X only and the Bluesky side is publishing and scheduling.
Does it work with a self-hosted Bluesky account?
Yes. OAuth resolves your personal data server from your handle, so an account on a server you run connects the same way as one on bsky.social. No host is hardcoded anywhere in the flow.
Related
The full tool reference, the Claude Code walkthrough, and the OAuth argument in detail.
The MCP server
All 43 tools, the hosted endpoint, and installers for every client.
Tool reference
Every tool, its parameters, and what it returns. Including the platforms parameter.
Post to Bluesky from Claude Code
The same setup as a worked session, from empty terminal to a live post on both networks.
Bluesky OAuth, not App Passwords
What an App Password grants, why third-party tools still ask for one, and what to do instead.
XMCP compared
X ships its own MCP server. What it does, what it costs per call, and what it cannot do.
Pricing
From $11.99 a month. The MCP server is on every plan.
Keep the credential out of the config file
Hosted MCP server, 43 tools, atproto OAuth on the Bluesky side. From $11.99 a month with the MCP server on every plan.