MCP server

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.

claude_desktop_config.json
{
  "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 Passwordatproto 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

  1. 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.

  2. 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.

  3. 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.

  4. 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 code
claude mcp add --transport http opentweet \
  https://mcp.opentweet.io/mcp \
  --header "Authorization: Bearer ot_your_key"
claude_desktop_config.json
{
  "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.

tool calls
// 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.

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.