@chirpie/mcp
by Firefloco
README.md
# @chirpie/mcp
**Post, schedule, and track social posts on X, Bluesky, LinkedIn, Instagram and more from AI agents.** Chirpie is one API for X/Twitter, Bluesky, LinkedIn, Threads, Mastodon, Instagram, Facebook and Telegram, covering posting, threads, scheduling, drafts, deletion and analytics. This MCP server puts all of it in front of Claude, Cursor, ChatGPT or any other MCP-capable agent, so "post this to X and LinkedIn, and schedule the follow-up for 9am" is a single sentence rather than a pile of platform SDKs, OAuth dances and rate-limit handling.
## Hosted server (recommended)
You don't need to install anything. Point your client at:
```
https://chirpie.ai/mcp
```
Sign in when prompted and you're connected. No API key to copy, nothing to keep up to date.
**Claude Code**
```bash
claude mcp add --transport http chirpie https://chirpie.ai/mcp
```
**Claude**: Settings → Connectors → Add custom connector → `https://chirpie.ai/mcp`
**Cursor**: add to `.cursor/mcp.json`:
```json
{
"mcpServers": {
"chirpie": {
"url": "https://chirpie.ai/mcp"
}
}
}
```
**ChatGPT**: Settings → Connectors → Create → MCP server → `https://chirpie.ai/mcp`
Prefer a key over OAuth (CI, scripts, clients without an OAuth flow)? Send it as a header:
```json
{
"mcpServers": {
"chirpie": {
"type": "http",
"url": "https://chirpie.ai/mcp",
"headers": { "Authorization": "Bearer chirpie_sk_your_key_here" }
}
}
}
```
## Local server (this package)
Run the same tool set locally over stdio.
```bash
npm install -g chirpie
chirpie login
```
**Claude Code**
```bash
claude mcp add chirpie -- npx @chirpie/mcp
```
**Claude Desktop**: `~/Library/Application Support/Claude/claude_desktop_config.json`:
```json
{
"mcpServers": {
"chirpie": {
"command": "npx",
"args": ["@chirpie/mcp"]
}
}
}
```
**Cursor**: add to your MCP settings:
```json
{
"mcpServers": {
"chirpie": {
"command": "npx",
"args": ["@chirpie/mcp"]
}
}
}
```
The local server resolves credentials in this order:
1. `CHIRPIE_API_KEY` environment variable
2. `~/.chirpie/config.json` (written by `chirpie login`)
The CLI and this server share that config, so one `chirpie login` covers both.
## Tools
| Tool | What it does |
|------|--------------|
| `chirpie_upload_media` | Upload an image or video and get the id a post can attach. `idempotency_key` makes a retry safe |
| `chirpie_post` | Post to any connected account, now or scheduled, or to several at once. `configuration` publishes it as a story or a reel instead of a feed post; `timezone` reads a `schedule_at` with no offset in an IANA zone; `idempotency_key` makes a retry safe; `draft` saves it instead |
| `chirpie_thread` | Post a 2-25 part thread, to one account or to several at once. Takes `timezone` and `idempotency_key` too; `draft` saves it instead |
| `chirpie_list_posts` | List posts, filtered by status, account, or the group of a multi-account publish |
| `chirpie_get_post` | Fetch one post |
| `chirpie_update_post` | Edit a post that has not published yet, or finish a draft and schedule or publish it. `configuration` changes where it publishes |
| `chirpie_retry_first_comment` | Post a first comment that failed, again. `idempotency_key` makes a retry safe |
| `chirpie_delete_post` | Take a post down from the platform. Chirpie keeps it, marked deleted |
| `chirpie_hide_post` | Hide a post from your Chirpie listings. Nothing reaches the platform |
| `chirpie_unhide_post` | Put a hidden post back in your listings |
| `chirpie_list_accounts` | List connected social accounts, active and inactive |
| `chirpie_activate_account` | Activate an account so it can publish |
| `chirpie_deactivate_account` | Deactivate an account (stays connected, frees a plan slot) |
| `chirpie_disconnect_account` | Disconnect an account (ends the connection, cancels its scheduled posts, frees a plan slot) |
| `chirpie_analytics` | Engagement metrics for a published post. `refresh` asks the platform now instead of reading the stored snapshot, once per post every 5 minutes |
| `chirpie_create_key` | Create an API key, optionally narrowed with `scopes` so it can do less than yours |
| `chirpie_list_keys` | List API keys |
| `chirpie_revoke_key` | Revoke an API key |
| `chirpie_connect_x` | Connect X/Twitter (returns an authorization link) |
| `chirpie_connect_linkedin` | Connect a LinkedIn profile |
| `chirpie_connect_linkedin_pages` | Connect the LinkedIn Pages you administer (coming soon) |
| `chirpie_connect_threads` | Connect Threads (coming soon) |
| `chirpie_connect_instagram` | Connect Instagram, `via` `instagram` (the default) or `facebook` (coming soon) |
| `chirpie_connect_facebook` | Connect a Facebook Page (coming soon) |
| `chirpie_connect_bluesky` | Connect Bluesky with an app password |
| `chirpie_connect_mastodon` | Connect Mastodon on any instance |
| `chirpie_connect_telegram` | Connect a Telegram bot |
| `chirpie_set_x_keys` | Register your own X developer app for connecting X accounts |
| `chirpie_get_x_keys_status` | Check whether your own X developer app is configured |
| `chirpie_remove_x_keys` | Remove your own X developer app |
The hosted and local servers expose exactly the same tools. On the hosted server,
`chirpie_create_key`, `chirpie_list_keys`, `chirpie_revoke_key` and
`chirpie_remove_x_keys` require API-key auth. Sign in with OAuth and they are not
offered, since an OAuth connection must not leave a long-lived key behind or tear
down credentials your other connections depend on.
## Several accounts in one call
`chirpie_post` and `chirpie_thread` take `account_ids` in place of `account_id`,
up to 25 of them. The answer is then a `group_id` plus one result per account,
in the order they were named, and `account_configurations` gives a single
account its own text, media or thread. The accounts that worked stay published
when another one's platform refuses, so an agent should read `success` on each
result. Pass the `group_id` to `chirpie_list_posts` to read the whole group
back.
## First comment
`chirpie_post` and `chirpie_thread` take `first_comment`, a comment published
under the post the moment it goes out. On a thread it is one comment for the
whole thread, published under the last part. X, Threads, Instagram and Facebook
only: anywhere else the call is refused with `400 first_comment_unsupported`
rather than the comment dropped. It counts as one post against the monthly
quota.
On a multi-account call the shared `first_comment` reaches every account unless
its `account_configurations` entry says otherwise: an entry naming a
`first_comment` replaces it for that account, and `"first_comment": ""`
publishes that account with none, which is how one call sends a first comment
to the accounts that take one while an account whose platform has none still
publishes the post.
Every post carries `first_comment` back, either `null` or
`{ text, status, comment_id, error }`, with `status` one of `pending`, `posted`
and `failed`. A failed first comment never fails its post, so a published post
can be carrying one that did not go out: `chirpie_retry_first_comment` sends it
again, and `chirpie_update_post` changes the text or, with an empty string,
removes it.
## Stories, reels and other publishing options
`chirpie_post`, `chirpie_thread` and `chirpie_update_post` take `configuration`,
the per-platform publishing options, keyed by platform. Instagram takes a
`feed` post, a `story` or a `reel`, and a Facebook Page takes a `feed` post or a
`story`. Leave it out and everything publishes to the feed. Instagram and
Facebook are coming soon.
```json
{
"account_id": "...",
"text": "Three minutes on how we schedule posts.",
"media_ids": ["..."],
"configuration": {
"instagram": {
"placement": "reel",
"video_cover_timestamp_ms": 1500,
"collaborators": ["a_co_author"],
"share_to_feed": true
}
}
}
```
Each placement carries its own options:
- `instagram` `feed`: up to 10 images, `collaborators` (at most 3 usernames)
and `user_tags`, each of which needs both `x` and `y`.
- `instagram` `story`: exactly one image or video, no caption (send empty
text), no first comment and no collaborators. `user_tags` may carry
coordinates or leave them out.
- `instagram` `reel`: exactly one video and no images, plus `collaborators`,
`user_tags` (the username on its own, since Instagram reads coordinates only
on images and stories), `cover` or `video_cover_timestamp_ms` but never both,
`share_to_feed` and `trial_reel`.
- `facebook` `feed`: a `link`, shown as a preview.
- `facebook` `story`: exactly one image or video, no text, no first comment
and no link.
Nothing is dropped quietly: a block keyed on a platform that takes no options,
or a field the chosen placement does not carry, is refused with
`400 configuration_unsupported` naming the platform and the field. A story and a
reel are each a single post, so `chirpie_thread` refuses either placement. On a
multi-account call one block serves every account of that platform, and an
`account_configurations` entry naming its own `configuration` replaces it for
that account. On `chirpie_update_post` an absent `configuration` keeps the
options the post already has, and `"configuration": {}` puts it back to a plain
feed post.
An Instagram account connects one of two ways, and `chirpie_connect_instagram`
takes `via` to pick: `instagram` (the default) signs in with Instagram, and
`facebook` signs in with Facebook and connects the Instagram accounts linked to
the Pages the user shares, several at once. Both publish identically, whatever
the placement. The one difference is deleting a published post, which works on
an account connected via Facebook and is refused with
`501 delete_unsupported` on one connected through Instagram. Connecting an
account that is already connected through the other route moves it rather than
adding a second one: it keeps its id and everything it has published, and
deleting is the only thing that changes hands. Where the two routes report
different Instagram accounts you get a second account instead, so list the
accounts again afterwards.
## Drafts
`draft: true` on `chirpie_post` or `chirpie_thread` saves the content and sends
nothing: no platform call, no quota, and a draft never publishes on its own. A
draft is held to far less than a post, so the text may be empty and a draft
thread may be a single part, and the answer carries a `warnings` list saying,
per account, what would go wrong if it were sent as it stands.
Promote it with `chirpie_update_post`: `schedule_at` queues it, `publish: true`
sends it now, and the two are never valid together. Promotion runs every rule a
create runs and takes the quota, so anything refused leaves the draft exactly as
it was.
## Try it
Once connected, ask your agent:
- "Post to X and LinkedIn: we just shipped v2, but keep the X one shorter."
- "Draft a 5-post thread about why we moved off cron, and schedule it for 9am tomorrow."
- "How did my last Bluesky post do?"
- "Save that as a draft, I will pick the wording tomorrow."
- "Connect my X account."
## Links
- Docs: https://chirpie.ai/docs/mcp
- API reference: https://chirpie.ai/docs
- Dashboard: https://chirpie.ai/dashboard
MIT © Fireflo LLC
---
> This repository is an automatically maintained source mirror of `packages/mcp` in the Chirpie monorepo. Pull requests opened here cannot be merged.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues