Short version: connect a social media scheduler's MCP server to Claude Code with one claude mcp add command. Then ask Claude to write the announcement from your changelog, check it against each platform's limits, and schedule it. Claude writes and makes the calls; the scheduler holds the post and publishes it on time, whether or not your terminal is still open. This walkthrough uses seenpaid's server and four of its real tools: list_accounts, validate_post, schedule_post and list_posts.
seenpaid is mine, so read with that in mind. Every tool name, argument and response below comes from its live server: I ran list_accounts and validate_post against my own account while writing this on 24 September 2026. I didn't schedule the example post, so for schedule_post I show the arguments and the fields it returns, not a live result. Any other scheduler with an MCP server works the same way with its own tool names.
What you need
- Claude Code, installed and signed in.
- A seenpaid account with X, LinkedIn and Bluesky connected. X is bring-your-own-key: you connect your own X developer app, and X charges you per post on that account.
- An API key from Settings → AI agents & API. It starts with sp_.
Step 1: connect the server
Put the key in an environment variable, then add the server:
export SEENPAID_API_KEY=sp_your_key claude mcp add --transport http seenpaid https://api.seenpaid.com/mcp \ --header "Authorization: Bearer $SEENPAID_API_KEY"
Your shell fills in $SEENPAID_API_KEY when you run the command, so the key itself is what gets saved. With no --scope flag, Claude Code uses local scope: the server is stored in ~/.claude.json for the current project only, and never in your repo. Add --scope user to have it in every project. Don't use --scope project for this one. That writes .mcp.json, which is meant to be committed, and your key would go with it.
Check it with claude mcp list, or /mcp inside a session. seenpaid should show ✔ Connected. If a session was already open, start a new one so it picks up the tools.
Step 2: check what's connected
First request: "Which of my social accounts are connected?" Claude calls list_accounts, which takes no arguments and returns every account with its id, platform, name and status. Mine, trimmed to the three that matter here (ids shortened):
{
"count": 11,
"accounts": [
{ "id": "1517f63b-…", "platform": "x",
"name": "@JustinasRoland0", "status": "active" },
{ "id": "00af1c12-…", "platform": "linkedin",
"name": "Justinas Rolando", "status": "active" },
{ "id": "de993be7-…", "platform": "bluesky",
"name": "@justinasrolando.bsky.social", "status": "active" }
]
}This step matters more than it looks. schedule_post called with no platforms and no account ids posts to every active account. On my account that's eleven of them. So the rule is: always name the platforms.
Step 3: have Claude write it from the release
In the repo you just released from, ask in plain words: "Read the changelog entry for v2.3 and draft an announcement for X, LinkedIn and Bluesky. One version for LinkedIn, and a shorter one that fits X and Bluesky." Claude already has the diff, the commit messages and the changelog in front of it, which is the whole reason to do this from Claude Code rather than a chat window.
Step 4: validate before scheduling
validate_post dry-runs a caption against the platforms you name, without publishing. It takes caption, platforms and an optional has_media. Here's what it returned for a 441-character LinkedIn-length draft sent to all three (trimmed):
{
"allClear": false,
"results": [
{ "platform": "x", "wouldPublish": false,
"problems": ["Caption is 441 characters, 161 over x's 280 limit."] },
{ "platform": "linkedin", "wouldPublish": true, "problems": [] },
{ "platform": "bluesky", "wouldPublish": false,
"problems": ["Caption is 441 characters, 141 over bluesky's 300 limit."] }
]
}That's the useful kind of failure. The fix isn't to cut the LinkedIn version down; it's to give X and Bluesky their own text. The same call with a 173-character version came back allClear: true on all three.
Two other things came back with it. Every X result carries a caveat that X charges for API posting on your own developer account, and without credits there the post fails. And the response had a signals block from Jev, an advisory score for whether the caption reads as spam or has a clear opening. It never blocks anything.
Step 5: schedule it
schedule_post takes the caption, platforms (or account_ids), and schedule_for: an ISO-8601 time in the future, with its offset. Leave schedule_for out and it publishes immediately, which for a release announcement is rarely what you want. For different text per platform, per_account_captions maps an account id from list_accounts to its own caption. So one call carries the LinkedIn text as the main caption and the short version for X and Bluesky:
{
"caption": "v2.3 is out. Two things people asked for: …",
"platforms": ["x", "linkedin", "bluesky"],
"schedule_for": "2026-09-29T09:00:00+02:00",
"per_account_captions": {
"<x account id>": "v2.3 is out. Scheduled posts now …",
"<bluesky account id>": "v2.3 is out. Scheduled posts now …"
}
}That's Tuesday at 9am Berlin time. It returns ok, postId, status, scheduledFor and postedTo, the platforms it actually targeted. Check postedTo against what you asked for.
Step 6: read the queue back
Then ask "What's going out on Tuesday?" list_posts takes status, from, to and limit. For one day that's status scheduled, from 2026-09-29T00:00:00+02:00, to 2026-09-29T23:59:59+02:00. Each post comes back with its id, caption, status, schedule time, platforms and any publish errors, soonest first.
After it goes out, the same call with status published or failed tells you whether all three made it, with the platform's own error if one didn't. Make Claude do this every time instead of trusting a "done".
Make it ask before it posts
schedule_post writes something that ends up public, so I keep it on ask: Claude Code stops and shows me the arguments before the call goes out. The reads can run without asking. In .claude/settings.json, or ~/.claude/settings.json for every project:
{
"permissions": {
"allow": [
"mcp__seenpaid__list_accounts",
"mcp__seenpaid__validate_post",
"mcp__seenpaid__list_posts"
],
"ask": [
"mcp__seenpaid__schedule_post",
"mcp__seenpaid__bulk_schedule",
"mcp__seenpaid__update_post",
"mcp__seenpaid__delete_post"
]
}
}Claude Code checks deny rules first, then ask, then allow, so an ask rule still prompts even if something broader allows the server's tools.
What goes wrong
- No platforms named: it goes to every active account. The one to remember.
- A time without an offset: schedule_for is rejected unless it ends in Z or an offset like +02:00. Say the time zone in your request so Claude doesn't guess it.
- A time in the past: refused with "schedule_for must be in the future".
- X fails with a credits error: that's X's pay-per-use API on your own developer account, not the scheduler.
- The key stops working: create a new one in Settings → AI agents & API, run claude mcp remove seenpaid, then add it again.
The honest limits
seenpaid has no free plan and the 7-day trial asks for a card; Solo is $19.99 a month for 5 workspaces (brands), each connecting every platform with no cap on accounts. X is bring-your-own-key, so posting there costs you X's per-post price on top. If you only announce on Bluesky, you don't need a scheduler at all: its API is free, and Claude Code can write the script for you. Where seenpaid earns its place is the queue across several platforms, and, if you sell something, telling you later which announcement actually brought in money.
Setup guides and further reading
Sources, checked 24 September 2026: code.claude.com/docs/en/mcp (claude mcp add, scopes, claude mcp list) and code.claude.com/docs/en/permissions (MCP rule names, deny → ask → allow order); seenpaid's own MCP tool definitions, and live list_accounts and validate_post calls against my account on the same day.