# iHatePosting (npm · ihateposting-mcp)

Draft, check and schedule posts to your connected social accounts from Claude, ChatGPT or Cursor.

- Trust score: 68/100 (medium)
- Registry status: active
- Liveness: live
- Owner verified: no
- Last scored: 2026-10-04

## Components

- remote · `ihateposting.com`: 78/100, [markdown](https://verifymcp.io/servers/com-ihateposting-mcp/api-mcp.md), [page](https://verifymcp.io/servers/com-ihateposting-mcp/api-mcp)
- remote · `ihateposting.com`: 38/100, [markdown](https://verifymcp.io/servers/com-ihateposting-mcp/mcp-oauth.md), [page](https://verifymcp.io/servers/com-ihateposting-mcp/mcp-oauth)
- npm · `ihateposting-mcp`: 68/100 (this document), [markdown](https://verifymcp.io/servers/com-ihateposting-mcp/ihateposting-mcp.md), [page](https://verifymcp.io/servers/com-ihateposting-mcp/ihateposting-mcp)

## Channel facts

- Registry: `npm`
- Package: `ihateposting-mcp`
- Version: `0.10.0`
- Transport: `stdio`

## Trust breakdown

How this component scores in each security and reliability category. Every signal is checked automatically from public evidence about the published package, including repeated runs of it in an isolated sandbox, and we only credit what we can confirm. Scores are 0–100 per category. Scoring method: https://verifymcp.io/docs/scoring (what has changed: https://verifymcp.io/docs/scoring/changelog)

Scored 2026-10-04.

- **Supply Chain Security**: 100/100
  - No malware found by supply-chain analysis.
  - No known CVEs affecting this package version or its production dependencies.
  - No install/post-install scripts declared.
  - 0 of 3 dependencies flagged as unhealthy.
- **Provenance & Transparency**: 19/100
  - Repository check failed: no source repository is declared.
  - Provenance check failed: no build-provenance attestation is published.
  - Clear OSI-approved license (MIT).
  - Actively maintained (last published 0 days ago).
  - Security-disclosure policy not yet verified: we couldn't inspect the source repository.
- **Schema Quality & AI Usability**: 79/100
  - 100% of prompts and resources have a non-trivial description (not blank, and not just the item's name).
  - AI-judged instruction clarity (excellent).
  - Context-footprint check failed: tool/resource definitions use about 3504 tokens (~194/item across 18 items; 17 tools + 1 resources), over budget; trim descriptions and params.
  - Usage-examples check failed: none of the tools include examples.
- **Stability & Change Management**: 17/100
  - Stability observed for 5 of 30 days with no destabilising changes; credit accrues until the full window elapses.
- **Tool Coverage**: 99/100
  - 100% of tools have a non-trivial description (not blank, and not just the tool's name).
  - 98% of tool parameters carry a description.
- **Tool Safety**: 100/100
  - No prompt-injection markers were found in the server instructions, tool names or descriptions we captured.
  - All 2 tool(s) whose name or description implies an irreversible operation declare an MCP destructiveHint annotation.
  - An AI judge read all 18 captured unit(s) of tool text and found none that tries to manipulate the model reading it.
- **Capabilities**: 100/100
  - Implements a current MCP spec version (2026-07-28).
  - Supports UI / widget rendering.

## Install

### How do I install the iHatePosting MCP server?

iHatePosting runs locally as an npm package, launched with npx -y ihateposting-mcp. Ready-made configuration for Claude, Cursor, VS Code, Codex and 5 more is on this page, copied from each client's own documentation.

### Claude

```bash
claude mcp add com-ihateposting-mcp -- npx -y ihateposting-mcp
```

### Cursor

```json
{
  "mcpServers": {
    "com-ihateposting-mcp": {
      "command": "npx",
      "args": [
        "-y",
        "ihateposting-mcp"
      ]
    }
  }
}
```

### VS Code

```json
{
  "servers": {
    "com-ihateposting-mcp": {
      "command": "npx",
      "args": [
        "-y",
        "ihateposting-mcp"
      ]
    }
  }
}
```

### Codex

```bash
codex mcp add com-ihateposting-mcp -- npx -y ihateposting-mcp
```

### opencode

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "com-ihateposting-mcp": {
      "type": "local",
      "command": [
        "npx",
        "-y",
        "ihateposting-mcp"
      ],
      "enabled": true
    }
  }
}
```

### OpenClaw

```bash
openclaw mcp add com-ihateposting-mcp --command npx --arg -y --arg ihateposting-mcp
```

### Hermes

```yaml
mcp_servers:
  com-ihateposting-mcp:
    command: "npx"
    args: ["-y", "ihateposting-mcp"]
```

### Netclaw

```json
{
  "McpServers": {
    "com-ihateposting-mcp": {
      "Transport": "stdio",
      "Command": "npx",
      "Arguments": [
        "-y",
        "ihateposting-mcp"
      ]
    }
  }
}
```

### Vellum

```bash
assistant mcp add com-ihateposting-mcp -t stdio -c npx -a -y ihateposting-mcp
```

### Other

```json
{
  "mcpServers": {
    "com-ihateposting-mcp": {
      "command": "npx",
      "args": [
        "-y",
        "ihateposting-mcp"
      ]
    }
  }
}
```

## Changelog

Every change recorded for this component, newest first. Days that predate change tracking, or that we cannot explain, say so: "we were watching and nothing happened" and "we were not watching" are different claims.

### 2026-10-03 (score 68, +16)

- [security improvement] Malware scan: unverified → pass

### 2026-10-02 (score 52, −13)

- [security regression] Malware scan: pass → unverified
- [functional improvement] Dependency health: 0.84 → 1.00
- [functional improvement] Stability: unverified → 0.10
- [functional] MCP protocol: Implements a current MCP spec version (2026-07-28).
- [functional] Package version: 0.9.2 → 0.10.0

### 2026-09-30 (score 65, +15)

- [security improvement] Malware scan: unverified → pass

### 2026-09-29 (score 50)

First indexed and scored.

## MCP tools (17)

### `open_upload_widget` (~187 tokens)

Show an upload box

Show the person their iHatePosting media box: they can upload an image or video from their own device, browse what is already in their library with every file previewable, see how much storage their plan leaves them, pick a file to use, or delete one. THIS IS THE ANSWER WHENEVER SOMEONE NAMES A FILE ON THEIR OWN COMPUTER — a path like C:\clips\launch.mp4 or ~/Movies/clip.mov is not something any tool here can read, and explaining that is not an answer: open this instead, in the same reply, and they pick the file from a picker right in the conversation. Also use it for anything they pasted or attached here. Do NOT try to send those bytes yourself — they would travel as your own output and arrive cut short. If the file already has a public address, use upload_media with `url` instead.

### `get_upload_ticket` (~32 tokens)

Get a one-time upload ticket

Internal: issues a one-time ticket the upload box uses to send a file. The upload box calls this itself.

### `whoami` (~78 tokens)

Show the signed-in iHatePosting account

Which iHatePosting login this connection acts for (email and account name), whether it connected by signing in or with an API key. Use this for any question about who the user is or which account is connected. This is NOT a social media handle — the handles from list_accounts belong to the connected profiles, which may carry other people's names.

### `get_platform_rules` (~81 tokens)

Get each platform's posting rules

What each platform will and will not accept: character limit, whether media is required, how many images, video formats and length, and which per-platform options are MANDATORY (e.g. Pinterest needs a board, YouTube needs a title). Call this BEFORE writing a post — it is the difference between composing something that publishes and something the platform rejects.

### `validate_post` (~373 tokens)

Check a post before sending

Check a post against every platform you plan to send it to, WITHOUT creating anything. Runs the same checks create_post runs, so the answer is what will really happen: platform rules ('X counts this as 1200/280 characters', 'YouTube needs a title', 'TikTok slideshows take JPEG or WebP photos'), whether each account is connected and signed in, AND the account's own limits — trial expiry, whether the plan includes X, and the monthly posting allowances. Pass the same `action`/`scheduledDate`/`scheduledTime` you will pass to create_post: a cap counts the month the post is DUE, so checking without them answers a different question. Anything not tied to one platform comes back under the key "account". Free to call as often as you like; use it before create_post rather than discovering a problem after the post is spent.

Input parameters:

- `action` (string): What you will ask create_post for. Defaults to 'now' — the strictest check. 'draft' skips the limits, because a draft costs nothing.
- `mediaIds` (array): Media library ids from list_media or upload_media, in attach order
- `platforms` (array): Shorthand for targets. PLATFORM NAMES ONLY — an account id belongs in create_post, not here. Use this OR `targets`.
- `scheduledDate` (string): YYYY-MM-DD, with action 'schedule'. The monthly limits count the month the post is DUE, so this changes the answer.
- `scheduledTime` (string): e.g. '9:00 AM' (with action 'schedule'), in the account owner's timezone
- `targets` (array): Platforms with their per-platform options. Use this OR `platforms`.
- `text` (string, required): The post text

### `create_post` (~406 tokens)

Create a post

Create a social post on iHatePosting. action 'draft' saves it (the default, and the safe choice); 'schedule' needs scheduledDate (YYYY-MM-DD) + scheduledTime (e.g. '9:00 AM', in the account owner's timezone); 'now' publishes immediately to a real audience. Platforms with requirements — Pinterest a board, YouTube a title, TikTok and Instagram media — are set through `options` and `mediaIds`; call get_platform_rules for what each one needs, and validate_post to check before you commit. The reply lists every send it created, each naming its account (handle, displayName, nickname) AND carrying its own `postId` — tell the user what it says rather than what you asked for; they are not always the same. A multi-platform post is stored as one post per send, so that `postId` is what cancels or reschedules a single platform later without touching the others.

Input parameters:

- `action` (string)
- `mediaIds` (array): Media library ids from list_media or upload_media, in attach order
- `options` (object): Per-platform options, e.g. { pinterest: { boardId: '...' }, youtube: { ytTitle: '...' } }
- `overrides` (object): Different text for one platform, e.g. { x: 'a shorter version' }
- `platforms` (array, required): Where it goes. Either a platform name — bluesky, x, linkedin, facebook, threads, mastodon, telegram, discord, tumblr, slack, instagram, pinterest, tiktok, youtube — which targets EVERY account you ha…
- `scheduledDate` (string): YYYY-MM-DD (schedule only)
- `scheduledTime` (string): e.g. '9:00 AM' (schedule only)
- `text` (string, required): The post text

### `list_posts` (~318 tokens)

List recent posts

List posts with per-platform status (draft/scheduled/published/failed) and live URLs. Rows sharing a `batchId` are ONE submission the person made to several accounts — report them as one post going to N places, not as N posts. ALWAYS CHECK `total` IN THE REPLY: it is the number of posts matching your filter, and `returned` is how many you got. If they differ you are holding one page, not the answer — page on with `cursor` until `hasMore` is false before you count anything or tell anyone a number. Use `status` and `from`/`to` to ask the real question instead of counting a page: for "how many are scheduled" pass status 'scheduled' and read `total`; for a month or a date range pass from/to, which match the SCHEDULED time.

Input parameters:

- `cursor` (string): From `nextCursor` in the previous reply. Keep going while `hasMore` is true.
- `from` (string): Earliest SCHEDULED time, 'YYYY-MM-DD' or an ISO timestamp. Inclusive, and a bare date means the whole day.
- `limit` (integer): Posts per page, default 50, max 200.
- `status` (string): Only posts in this state. For a count, set this and read `total` — do not count the page.
- `to` (string): Latest SCHEDULED time, same formats. Inclusive of the whole day when given as a date.

### `list_accounts` (~66 tokens)

List connected social accounts

List the connected social accounts (platform, handle, health) available for posting. The response also names the iHatePosting account this connection acts for — if the accounts listed are not the ones you expect, check that `account.email` is the right person before assuming anything is missing.

### `list_pinterest_boards` (~149 tokens)

List Pinterest boards

The Pinterest boards you can pin to, with the `id` that create_post's `options.pinterest.boardId` requires. Pinterest REFUSES a pin without a board, and a board id is an opaque number you cannot guess — call this before creating any Pinterest post. If several Pinterest accounts are connected, pass `accountId` from list_accounts, because boards belong to one account and pinning to another account's board fails. An empty list with no error means the account is connected and simply has no boards yet — make one in Pinterest first.

Input parameters:

- `accountId` (string): Which Pinterest account's boards, from list_accounts. Omit to use the most recently connected one.

### `get_analytics` (~159 tokens)

Get post analytics

How the posts actually performed: impressions, engagement and follower numbers per connected channel, plus the top posts. Use this to answer 'how did that do', 'which platform is working' or 'what should I post more of' — it reports what iHatePosting collected from each network, not an estimate. Some platforms report nothing (they are named in `unmeasuredPlatforms`), so a zero there means 'not measurable', not 'no reach'. If a plan ever gates a range, the answer says so in a sentence with `locked: true` rather than returning silently empty numbers.

Input parameters:

- `range` (string): How far back to look. '12m' is a year; 'all' is everything we have recorded.

### `reschedule_post` (~170 tokens)

Reschedule a post

Move a scheduled post to a different date and time. ONE SEND AT A TIME: a post going to several accounts is stored as one post per send, so this moves just that platform and leaves the others where they are. Takes the post id from list_posts, plus scheduledDate (YYYY-MM-DD) and scheduledTime (e.g. '9:00 AM') read in the ACCOUNT OWNER'S timezone, not yours or the user's device. A post that has already published cannot be moved — its time is a record of what happened — and one that is publishing right now is refused until it finishes.

Input parameters:

- `postId` (string, required): Post id from list_posts
- `scheduledDate` (string, required): YYYY-MM-DD
- `scheduledTime` (string, required): e.g. '9:00 AM'

### `list_media` (~101 tokens)

List the media library

The images and videos already in the user's iHatePosting library, newest first, with the `id` that create_post's `mediaIds` wants. Call this whenever a platform needs a file — Instagram, Pinterest, TikTok and YouTube all do — to see whether what you need is already uploaded. Returns ids and dimensions only; there is no downloadable link, by design.

Input parameters:

- `limit` (integer): How many of the newest assets to return

### `upload_media` (~476 tokens)

Upload media to the library

Put an image or video into the library and get back an id for create_post. PREFER `url` — ALWAYS, including for a picture you just generated: we fetch it ourselves at full length, and it is the only thing that works for video or for anything more than a few kilobytes. We take whatever the link serves, so a CDN that will not name the type (application/octet-stream, as presigned S3, Drive and Dropbox links do) is fine. `base64` is a last resort for small files that exist at no address: it travels as your own output, a conversation truncates it, and the cut-off file is refused. Either way the API checks type, size and quota, then registers the file.

Input parameters:

- `altText` (string): Alt text. Worth writing — several networks publish it.
- `base64` (string): The file's bytes, base64. A data: prefix is stripped. LAST RESORT, and small files only. Everything here becomes YOUR OWN OUTPUT, and a long string of it gets cut short on the way — the file then arr…
- `filename` (string): e.g. 'launch-photo.jpg' — the library label. Optional with `url`, where the link's own name is used.
- `mime` (string): e.g. 'image/jpeg', 'image/png', 'video/mp4'. Required with `base64`; with `url` the server's own content type decides.
- `url` (string): Public https link to the file ITSELF, not to a page showing it. We download it. Use this whenever the file has an address. A path on the person's own computer is NOT a link — nothing here can read th…

### `get_post` (~86 tokens)

Get one post's details

Everything about ONE post: the shared text, each platform's own copy and options, the attached media, and per-send status with the live URL or the error. list_posts gives you the id; this explains what actually happened to it. Read it before update_post so you change what is there rather than overwriting it.

Input parameters:

- `postId` (string, required): Post id from list_posts

### `update_post` (~365 tokens)

Replace a draft or scheduled post

Rewrite a draft or scheduled post. THIS REPLACES THE WHOLE POST, so read it with get_post first and send back the complete set — anything you leave out is removed, including platforms and per-platform options. Note the shape differs from create_post: this one wants `accountIds` (the ids from list_accounts, NOT platform names) and it requires `action`. Two limits: a post that has already published cannot be rewritten at all, and a post that goes to several platforms and was saved as a group keeps the platforms it has — send back that row's own accountIds and everything else is editable, but 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.

Input parameters:

- `accountIds` (array, required): The FULL set of destination account ids, from list_accounts. Account IDS, not platform names. Anything omitted is removed from the post.
- `action` (string, required): 'draft' keeps it unscheduled, 'schedule' needs the date and time below, 'now' publishes to a real audience.
- `baseContent` (string, required): The shared text. REQUIRED — it replaces what is there.
- `mediaIds` (array): Ids from list_media or upload_media, in attach order
- `options` (object): Per-platform options, e.g. { pinterest: { boardId: '...' } }
- `overrides` (object): Different text for one platform
- `postId` (string, required): Post id from list_posts
- `scheduledDate` (string): YYYY-MM-DD (schedule only)
- `scheduledTime` (string): e.g. '9:00 AM', in the account owner's timezone (schedule only)

### `retry_post` (~190 tokens)

Retry a failed post

Send a failed post again. ONE SEND AT A TIME, usually: a post to several accounts is stored as one post per send, so a post id normally names a single platform and retrying it touches only that one. (An older row, or a draft, can still carry several — without targetId every failed platform on that row is retried; with one, only that send.) Only a send that FAILED is retried — one that published is left alone, so this cannot double-post. If the cause was the account rather than the post (a revoked or paused connection) the answer says so, and retrying will not help until the user reconnects.

Input parameters:

- `postId` (string, required): Post id from list_posts
- `targetId` (string): Retry ONE platform's send, if you already hold that send's id. Omit it to retry every failed send on the post.

### `delete_post` (~242 tokens)

Delete a post from iHatePosting

Delete a post from iHatePosting. ONE SEND AT A TIME: a post going to several accounts is stored as one post PER SEND, so deleting one cancels only that platform and leaves the rest scheduled — pass the `postId` that create_post returned on that particular send, or the id of the row list_posts shows for it. There is no need to cancel a whole multi-platform post to drop one platform from it. THIS DOES NOT UNPUBLISH ANYTHING: a post that has already gone out stays live on the network, and deleting it here only removes iHatePosting's record of it. For that reason a published post is refused unless you pass force: true, and you should only do that after telling the user in words that the platform post will remain. A post that is publishing at this moment is never deletable, forced or not — the worker is mid-flight on it. Deleting a scheduled post stops it going out.

Input parameters:

- `force` (boolean): Required to delete a post that already published. The platform post stays live — say so before using this.
- `postId` (string, required): Post id from list_posts

## Diagnostics

Captured diagnostic sections: Provenance, Dependencies. The full working is on the page: https://verifymcp.io/servers/com-ihateposting-mcp/ihateposting-mcp#diagnostics

## Score history

- 2026-10-04: 68
- 2026-10-03: 68
- 2026-10-02: 52
- 2026-10-01: 65
- 2026-09-30: 65
- 2026-09-29: 50

## Common questions

### What is the iHatePosting MCP server?

iHatePosting is an MCP server listed in the public MCP registry as com.ihateposting/mcp. Draft, check and schedule posts to your connected social accounts from Claude, ChatGPT or Cursor. This page covers its npm package (ihateposting-mcp).

### Is the iHatePosting MCP server safe to use?

iHatePosting scores 68 out of 100 on VerifyMCP. We found no known CVEs affecting it as of 4 October 2026. It declares no install or post-install scripts. That is a record of what we were able to check automatically, not an endorsement. The category breakdown on this page shows every signal behind the number, including the ones we could not confirm.

### What tools does the iHatePosting MCP server expose?

iHatePosting exposes 17 tools: open_upload_widget, get_upload_ticket, whoami, get_platform_rules, validate_post, and 12 more. Their descriptions and schemas cost roughly 3,479 tokens of context every time the server is loaded.

### Is the iHatePosting MCP server still maintained?

iHatePosting is still listed as active in the MCP registry. We last reached this channel on 4 October 2026. Those dates come from our own scans of the registry and the channel itself, not from anything the publisher announced.

### What licence is the iHatePosting MCP server under?

iHatePosting declares the MIT licence, which is OSI-approved. That covers the source only, and says nothing about the cost of any service it calls.

## Links

- npm package: https://www.npmjs.com/package/ihateposting-mcp
- Socket report: https://socket.dev/npm/package/ihateposting-mcp
- Website: https://ihateposting.com/
- Changelog RSS feed: https://verifymcp.io/servers/com-ihateposting-mcp/ihateposting-mcp.xml
- Changelog JSON feed: https://verifymcp.io/servers/com-ihateposting-mcp/ihateposting-mcp.json
- HTML version of this page: https://verifymcp.io/servers/com-ihateposting-mcp/ihateposting-mcp
