---
name: postwire
description: Connect this agent to PostWire and publish the user's first social post with it. PostWire writes a native version of one idea for each social network and publishes them through the accounts the user connected - TikTok, Instagram, Facebook Pages, YouTube, LinkedIn (personal profiles), Bluesky, Mastodon, Telegram and Discord. Use when the user says "set up PostWire", "set up https://postwire.io/SKILL.md", mentions postwire.io, or wants to write, publish or schedule social media posts from this agent and no PostWire tools are connected yet.
metadata:
  version: "1"
  updated: "2026-10-04"
  canonical: https://postwire.io/SKILL.md
---

# PostWire

PostWire is one MCP server, `https://postwire.io/api/mcp` (Streamable HTTP), that turns one idea into a different,
native post per social network and publishes them through the user's connected accounts. It has no credits: the
user's plan sets the limits (Free: 1 business, 20 posts a month, 5 AI drafts a day, 2 networks per post, 3
scheduled posts at a time, no card, no time limit).

Networks live today: TikTok, Instagram (business or creator accounts), Facebook Pages, YouTube, LinkedIn (personal
profiles, not company Pages), Bluesky, Mastodon, Telegram (through the user's bot) and Discord (through a webhook).
Not available: X, Reddit, Threads, Pinterest. Never promise those.

This setup is done when **the user's first post is live and you have shown them its link**. Not when the server is
added. Follow the steps in order.

## 1. Add the PostWire MCP server, at user scope

If your client already lists a `postwire` server for `https://postwire.io/api/mcp` and its tools (`my_account`,
`generate_posts`, `post_to_social`…) are available, skip to step 3. Never add a second entry: duplicate servers
collide on tool names. Prefer sign-in in the browser (OAuth). Use an API key only where a step says so or where no
browser can open; the user creates one in the dashboard, https://postwire.io/dashboard.html, under **API & MCP**,
and it goes in an environment variable (`POSTWIRE_API_KEY`), never in a file you commit, never printed back.

**Claude Code**

```bash
claude mcp add --transport http --scope user postwire https://postwire.io/api/mcp
# then, inside Claude Code: run /mcp, pick postwire, Authenticate (or: claude mcp login postwire)
```

Without a browser: `claude mcp add --transport http --scope user postwire https://postwire.io/api/mcp --header 'Authorization: Bearer ${POSTWIRE_API_KEY}'`
(single quotes, so the entry keeps the variable and not the key). Check with `claude mcp list`.

**Claude app (claude.ai, Claude Desktop, mobile)**: you cannot change its settings. Tell the user: PostWire is in
Claude's connector directory, https://claude.ai/directory/bc01e7da-eba7-4754-9e88-6cacae80f000 - press
Connect. Or by hand: **Customize → Connectors → Add custom connector**, URL `https://postwire.io/api/mcp`, then
sign in. On Team and Enterprise plans an Owner adds it under **Organization settings → Connectors**. Then start a
new chat with the connector enabled.

**OpenAI Codex**

```bash
codex mcp add postwire --url https://postwire.io/api/mcp
codex mcp login postwire   # only if adding it did not start the sign-in by itself
```

Without a browser: `codex mcp add postwire --url https://postwire.io/api/mcp --bearer-token-env-var POSTWIRE_API_KEY`.
Same thing in `~/.codex/config.toml`: `[mcp_servers.postwire]` with `url = "https://postwire.io/api/mcp"`. Check with `codex mcp list`.

**Cursor** (`~/.cursor/mcp.json`, every project)

```json
{
  "mcpServers": {
    "postwire": { "url": "https://postwire.io/api/mcp" }
  }
}
```

Cursor shows a Connect/sign-in button for it. If that sign-in fails, use the key instead:
`"postwire": { "url": "https://postwire.io/api/mcp", "headers": { "Authorization": "Bearer ${env:POSTWIRE_API_KEY}" } }`.
Check in Cursor Settings → Tools & MCP, or Output → MCP Logs.

**GitHub Copilot in VS Code** (command palette → **MCP: Open User Configuration**; the root key is `servers`, not `mcpServers`)

```json
{
  "servers": {
    "postwire": { "type": "http", "url": "https://postwire.io/api/mcp" }
  }
}
```

VS Code asks to sign in when the server starts. Check with **MCP: List Servers**, then use the tools from Copilot
Chat in agent mode.

**Windsurf** (`~/.codeium/windsurf/mcp_config.json`; Windsurf is now Devin Desktop, whose docs put it in
`~/.config/devin/mcp_config.json`, `%APPDATA%\devin\mcp_config.json` on Windows)

```json
{
  "mcpServers": {
    "postwire": {
      "serverUrl": "https://postwire.io/api/mcp",
      "headers": { "Authorization": "Bearer ${env:POSTWIRE_API_KEY}" }
    }
  }
}
```

Its sign-in redirect is not documented, so this one uses the key. With the Devin CLI:
`devin mcp add -s user postwire https://postwire.io/api/mcp`, then `devin mcp login postwire`.

**Gemini CLI**

```bash
gemini mcp add -s user --transport http postwire https://postwire.io/api/mcp
# then, inside Gemini CLI: /mcp auth postwire
```

`-s user` matters: the default scope is the project. This writes `~/.gemini/settings.json` (`"httpUrl"` is the
Streamable HTTP field; `"url"` there means SSE). Gemini CLI blanks environment variables whose name contains KEY or
TOKEN, so do not pass the key as `$POSTWIRE_API_KEY` in a header: sign in with `/mcp auth postwire`. Check with
`gemini mcp list`.

**CoreSpeed, or any MCP gateway**: add `https://postwire.io/api/mcp` as a remote MCP server with OAuth (each
member signs in to their own PostWire) or a static bearer key. Steps: https://postwire.io/guides/postwire-in-corespeed-composio-mcp-gateway/

**Any other client**: find where it keeps user-level (global) MCP servers and add the same URL there, as a
Streamable HTTP server. PostWire supports OAuth 2.1 with dynamic client registration and PKCE, and accepts
redirect URIs on `https://` or on `http://localhost` / `http://127.0.0.1` (any port).

## 2. Sign in

Finish the browser sign-in your client opened. On PostWire's page the user types their email and a 6-digit code:
that also creates a free account if they have none, with no card and no password. Many clients load a new server
only in a new session: if the tools do not appear, tell the user to start a new session and continue there.

## 3. Keep this skill for next time

Download it rather than writing it from memory (a fetch tool that summarizes pages does not return it verbatim):

```bash
mkdir -p ~/.agents/skills/postwire
curl -fsSL https://postwire.io/SKILL.md -o ~/.agents/skills/postwire/SKILL.md
```

`~/.agents/skills/` is read by Codex, Cursor, VS Code Copilot, Gemini CLI and Devin. Claude Code reads
`~/.claude/skills/postwire/SKILL.md`; Cursor also `~/.cursor/skills/`; Gemini CLI also `~/.gemini/skills/`. If a
copy is already there, replace it when this one has a higher `metadata.version`. In the Claude app the user
uploads it under **Customize → Skills** as a ZIP whose root is a `postwire` folder holding this file.

## 4. Check the account

Call `my_account`. It returns the plan, usage against each limit, the brands and the connected social accounts
(platform, handle, brand). A 401, or the tools missing, means the sign-in did not finish: run the client's
authenticate step again (or check the key), do not retry the call in a loop.

Tell the user in one or two lines who they are signed in as, their plan, and what is connected.

## 5. Connect the first network

If nothing is connected, `my_account` already includes a one-hour `connect_url` and `message_for_user`; otherwise
call `create_connect_link` (with `platform` to offer only that network). Give the user the link and the sentence.
They sign in to each network on the network's own page; you never see a password or a platform token, and
neither does any other agent. When they say they are done, call `my_account` again to confirm.

For the fastest first post suggest a network that takes text alone: LinkedIn, Bluesky, Mastodon, Facebook Pages,
Telegram or Discord. TikTok and YouTube need a video and Instagram a photo or video (step 6).

## 6. Write the first post, show it, get approval

1. Ask what the post is about (one idea, a sentence or two) and which connected networks it goes to. On the Free
   plan one post goes out to 2 networks; the versions for the others are written but held.
2. Call `generate_posts` with the idea and the platforms (optional `brand_voice`). It writes one draft per network
   inside that network's rules and publishes nothing.
3. Show every draft **exactly as it will be published**, network by network: the full text, YouTube's title and
   tags, and the length against the limit (Bluesky 300, Mastodon 500 on most servers, Facebook 2,000, Discord
   2,000, TikTok 2,200, Instagram 2,200, LinkedIn 3,000, Telegram 4,000, YouTube 5,000).
4. Ask: "Publish these to <networks> now?" Edit if they ask, then show the edited text again.
5. TikTok: ask who can see it (public, friends or private), every time; TikTok requires the account owner to
   choose. Without `privacy` a TikTok post goes out private.

Media: a file attached to the chat does not reach PostWire. Call `create_upload_link`, ask the user to open it on
the device that has the file, then `get_uploaded_file` with its `upload_id` until it is `done`, and pass its
`media_url` as `video_url` or `photo_url` (several items: `media`). With words and no video, `make_video_from_text`
makes an 8-12 second vertical MP4 for YouTube or TikTok; show it before posting.

## 7. Publish and return the links

Only after an explicit yes: call `post_to_social` with `platforms` and the approved drafts as `per_platform`,
unchanged (plus `video_url` / `photo_url` / `privacy` when needed). Use one `idempotency_key` per approved post,
so a retry of the same call can never post twice.

Then report each network from `results`: the live `url` and the account it went to. TikTok and Instagram videos can
still be `processing`: call `get_post_status` with the platform and the returned `id` until it gives the link.
A network with `held: true` and code `networks_per_post` was written and not sent (Free plan: 2 per post); say so
in one sentence and pass on the upgrade link the result carries, once. The setup is done when the user has the
link to their live post.

## Rules

- Never publish, schedule (`schedule_post`, `plan_week`) or cancel without the user's explicit approval of the
  exact text, the networks and, for scheduling, the time with its timezone. A published post cannot be withdrawn
  from PostWire.
- Never rewrite a draft after it was approved. Never post to a network that is not connected: offer the link.
- Respect each network's rules: the length limits above, a video for TikTok and YouTube, a photo or video for
  Instagram, Bluesky up to 4 images, Instagram carousels up to 10.
- Plan limits are not errors to work around. When one is reached the result names the plan that lifts it, its
  monthly price and a checkout link: pass that on once, as given, and continue with what the plan allows. Nothing
  is charged from the chat; the link opens a Stripe page, and paid plans have a 14-day refund.
- Before scheduling, ask for the timezone if you do not know it: `run_at` needs an offset.
- Do not retry blindly. Read the error's `code`, `what` and `fix`.

## Errors and what to do

| What you see | What to do |
| --- | --- |
| 401, or no PostWire tools | Sign-in not finished or key wrong: authenticate again, or start a new session. |
| `not_connected` | `create_connect_link` for that platform; or publish only to the connected ones. |
| `ambiguous_brand` | The network is connected in several brands: ask which, send `brand_id` (`list_brands`). |
| `media_required` / `media_unreachable` | Add a video or photo, or a public direct link; use `create_upload_link`. |
| `networks_per_post` (held) | Free plan sends 2 networks per post: the rest was written, not sent. Upgrade link included. |
| `limit_reached`, `ai_daily_limit`, `queue_limit`, `brand_limit_reached` | A plan limit: tell the user, give the included link once. |
| `duplicate_post` (409) | The same post went out in the last 2 minutes (or same `idempotency_key` in 24 h). Do not resend. |
| `rate_limited` | Wait `retry_after` seconds. `rate_limited_deferred` is fine: it is queued for right after. |
| `timed_out` | Status unknown: check the network or `get_post_status` before posting again. |
| Login expired on a network | `create_connect_link` for it; LinkedIn logins expire about every 60 days. |
| 503 `auth_unavailable` / `ai_unavailable` | On PostWire's side: retry in a minute; check https://postwire.io/status/ |

Every error code: https://postwire.io/docs/errors.md

## Links

Dashboard: https://postwire.io/dashboard.html · MCP reference: https://postwire.io/mcp.md · API: https://postwire.io/docs/index.md ·
Status: https://postwire.io/status/ · Security: https://postwire.io/security/ · Pricing: https://postwire.io/pricing.md ·
Everything for agents: https://postwire.io/llms.txt · Latest version of this file: https://postwire.io/SKILL.md
