ihateposting-mcp
v0.12.9
Published
MCP server for iHatePosting — draft, schedule, and inspect social posts from Claude, Cursor, or any MCP client.
Maintainers
Readme
ihateposting-mcp
MCP server for iHatePosting — draft, schedule, and inspect social posts from Claude, Cursor, ChatGPT, or any MCP client.
Write once, publish everywhere: iHatePosting cross-posts to Bluesky, X, LinkedIn, Facebook, Instagram, Threads, Mastodon, Telegram, Discord, Tumblr, Slack, Pinterest, TikTok and YouTube.
Sign in, or use an API key
Remote clients that support MCP sign-in (OAuth) need no key at all: point them
at https://ihateposting.com/mcp/oauth, sign in to iHatePosting in the browser
and press Allow (see Remote clients below).
This package, the local stdio server, takes an API key instead:
iHatePosting → Settings → Developers → create a key (pk_live_…).
Treat it like a password: it can post on your behalf.
Tools
| Tool | What it does |
|---|---|
| whoami | Which iHatePosting account this key belongs to — the login, not a social handle |
| get_platform_rules | Each network's real limits: characters, media, aspect ratios, video specs |
| validate_post | Check a draft against those rules before spending a post |
| create_post | Create a post — publish now, schedule, or save as a draft |
| list_posts | Your posts, newest first, with per-platform status and live URLs — 50 a page by default, up to 200, with filters |
| list_accounts | Your connected social accounts and their health |
| list_pinterest_boards | The Pinterest boards you can pin to, with the boardId a pin requires |
| get_analytics | How the posts did — per channel, with the top ones named |
| reschedule_post | Move a scheduled post to a different date and time |
| list_media | The images and videos already in the library, with the ids create_post wants |
| upload_media | Put a new image or video in the library from a URL (or small base64) and get an id back |
| open_upload_widget | In clients that show MCP app widgets, a file picker in the conversation for a file on your computer |
| get_post | One post in full: per-platform copy, options, media, per-send outcome |
| update_post | Rewrite a draft or scheduled post — replaces what you send |
| retry_post | Send a failed post again; a send that published is left alone |
| delete_post | Remove a post. This does NOT unpublish anything already live |
Apart from open_upload_widget, which is registered first, they are declared in
that order because it is the order a model reads them,
and it is the working sequence: know whose account this is, see what the
networks allow, check the draft, then spend the post — and only then manage
what is already there.
Use it
Claude Desktop / Claude Code / Cursor (local, stdio)
Requires Node.js 20 or later (since 0.10.0).
{
"mcpServers": {
"ihateposting": {
"command": "npx",
"args": ["-y", "ihateposting-mcp"],
"env": { "IHATEPOSTING_API_KEY": "pk_live_…" }
}
}
}Remote clients (hosted, HTTP)
Clients that can't run a local process use the hosted server instead.
Sign in — no key (preferred). If the client supports MCP sign-in (OAuth), add this address and nothing else:
https://ihateposting.com/mcp/oauthThe client opens your browser; sign in to iHatePosting and press Allow. It discovers the sign-in from the server's own metadata and registers itself, so there is no client id or secret to enter. Claude's connector directory, Cursor and Grok Build connect this way. Take a sign-in back under Settings → Developers → Connected apps.
With an API key, for clients that cannot sign in, use
https://ihateposting.com/api/mcp and pick whichever your client supports:
| Client can send | Use |
|---|---|
| A custom header | Authorization: Bearer pk_live_… (preferred) |
| Only a URL | https://ihateposting.com/api/mcp?key=pk_live_… |
⚠️ A key in a URL can end up in browser history, proxy logs and Referer headers. Prefer the header when your client allows it, and rotate the key (Settings → Developers) if a URL containing it gets shared.
Environment
| Variable | Default | Notes |
|---|---|---|
| IHATEPOSTING_API_KEY | — | Required (stdio). Your pk_live_… key. |
| IHATEPOSTING_API_URL | https://ihateposting.com | Override for self-hosted/staging. |
| IHATEPOSTING_CLIENT | — | Optional. Which tool is running this, e.g. claude-desktop; calls are recorded with that name. |
Notes
- Pinterest also requires a BOARD.
list_pinterest_boardsis the only way to learn one — a board id is an opaque number, so without it a pin cannot be created at all. - Instagram, Pinterest, TikTok and YouTube all require media.
upload_mediafetches a file from a URL (the reliable way, and the only one for video) or takes small files as base64, and hands back an id forcreate_post. A file that exists only on your computer is better uploaded withopen_upload_widgetwhere the client supports it, or in the composer, and found afterwards withlist_media. - A post that goes to several platforms is saved as a group, one row per
platform.
update_postcan rewrite a row of a group when you send back that row's ownaccountIds; adding or swapping an account is refused with a sentence saying so, because it would publish some platforms twice. To change which platforms a group posts to, delete it and create it again. - A draft is different: it stays ONE row for every platform it is aimed at
until it is scheduled. Deleting it removes every platform (the reply's
platformsRemovednames them), andreschedule_postrefuses it — schedule a draft withupdate_postand actionschedule, which checks each platform and splits it into one row per platform. delete_postremoves iHatePosting's record. A post already live on a network stays live, which is why it asks forforcebefore touching one.- Every call is scoped to the key's owner; the server holds no state of its own.
MIT © iHatePosting
