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
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/oauth
The 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:
| 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
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_boards is 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_media
fetches 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 for create_post. A file
that exists only on your computer is better uploaded with
open_upload_widget where the client supports it, or in the composer, and
found afterwards with list_media.
- A post that goes to several platforms is saved as a group, one row per
platform.
update_post can rewrite a row of a group when you send back that
row's own accountIds; 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
platformsRemoved names them), and reschedule_post refuses it — schedule
a draft with update_post and action schedule, which checks each platform
and splits it into one row per platform.
delete_post removes iHatePosting's record. A post already live on a network
stays live, which is why it asks for force before touching one.
- Every call is scoped to the key's owner; the server holds no state of its own.
MIT © iHatePosting