Skip to main content
Glama
Andre-wyap

Threads MCP Server

by Andre-wyap
README.md
# Safe Threads Auto-Poster & Planner

A local, safety-first scheduler for [Threads](https://www.threads.net) posts using Meta's official Threads API.

- **Autoposter** (`threads_autoposter.py`) — publishes due posts from a CSV schedule. Locked in dry-run mode until you explicitly approve live posting.
- **Planner dashboard** (`threads_dashboard.py`) — a local web UI with calendar and list views for adding, editing, skipping, and tracking single posts and multi-part thread chains. No Terminal or CSV editing needed day to day.
- **MCP server** (`threads_mcp_server.py`) — lets AI assistants such as **Claude** (Claude Desktop / Claude Code) or **Codex** manage your schedule with natural language.

Everything runs from one folder and one CSV file (`threads_schedule.csv`), so the dashboard, the autoposter, and the AI assistant always see the same schedule.

## Requirements

- Python 3.10 or newer (the autoposter and dashboard use only the standard library)
- `mcp` package, only if you want the Claude/Codex integration: `pip install -r requirements.txt`
- A Meta Developer account and a Meta app with the **Threads use case** (see step 2)

## Step-by-step setup

### 1. Get the code

```sh
git clone https://github.com/<your-username>/<your-repo>.git
cd <your-repo>
cp .env.example .env
```

`.env` holds your private settings and tokens. It is listed in `.gitignore` and must never be committed. Never paste tokens or app secrets into an AI chat either — put them only in your local `.env`.

### 2. Create a Meta app and get Threads API credentials

Meta's current flow (verified July 2026):

1. In the [Meta Developer dashboard](https://developers.facebook.com), create an app with the **Threads use case**. Meta issues separate Threads app credentials; use the Threads app ID and its matching secret.
2. Add your Threads account as a **Threads Tester** in the app dashboard, then accept the invitation in your Threads account settings (Website permissions → Invitations). Accounts without an app/tester role need approved permissions through App Review and a published app.
3. Authorize the account with the official OAuth flow using these permissions:
   - `threads_basic` (required for everything)
   - `threads_content_publish` (publishing)
   - `threads_manage_replies` (needed for multi-part thread chains)
4. Exchange the short-lived token (1 hour) for a **long-lived token** (60 days). Refresh it before expiry by running `python3 refresh_threads_token.py` (it calls Meta's official refresh endpoint and updates `.env` in place). Public profiles can extend grants by refreshing; private profiles must re-authorize after expiry.
5. Note your **Threads user ID** — you'll need it together with the token.

Official references: [Threads API](https://developers.facebook.com/documentation/threads) · [Get Started](https://developers.facebook.com/documentation/threads/get-started) · [Create Posts](https://developers.facebook.com/documentation/threads/posts) · [Create Replies](https://developers.facebook.com/documentation/threads/retrieve-and-manage-replies/create-replies) · [Rate Limits](https://developers.facebook.com/documentation/threads/overview#rate-limiting)

### 3. Fill in `.env`

Open `.env` and set at minimum:

```dotenv
THREADS_ACCESS_TOKEN=your-long-lived-token
THREADS_USER_ID=your-threads-user-id
```

Leave the safety locks as they are (`DRY_RUN=true`, `APPROVED_TO_POST=false`). See `.env.example` for every field and what it does.

### 4. Set your schedule start date and dry-run

```sh
python3 threads_autoposter.py --set-start-date YYYY-MM-DD
python3 threads_autoposter.py
```

The first command fills in dates for every calendar item. The second is a **dry run** — it selects the next due post and logs exactly what it would publish, without any API call.

Inspect what would happen at a specific moment:

```sh
python3 threads_autoposter.py --now 2026-08-01T09:01:00+08:00
```

Run the test suite any time:

```sh
python3 -m unittest -v test_threads_autoposter.py
```

### 5. Open the planner dashboard

```sh
python3 threads_dashboard.py
```

(On macOS you can instead double-click `Open Threads Planner.command`.) The dashboard opens at `http://127.0.0.1:8765` (change with `--host`/`--port`). Keep the Terminal window open while using it; press Control+C to stop.

The planner supports calendar and upcoming-list views, single posts and multi-part chains, topics and filtering, scheduled/skipped/error/published statuses, UTF-8 byte checks for every post part, and safe deletion of unpublished entries. Published rows are read-only, and the dashboard never edits `.env` or enables live posting.

### 6. Approve live posting (deliberately)

Live posting requires **all three** settings in `.env`:

```dotenv
DRY_RUN=false
APPROVED_TO_POST=true
LIVE_POSTING_CONFIRMATION=I approve live Threads posting.
```

Test one specific row first:

```dotenv
TEST_MODE=true
TEST_POST_ID=D01-AM
```

Test mode selects only that row but does not bypass any safety lock. After a successful test, set `TEST_MODE=false`.

Pause posting at any time by setting `DRY_RUN=true` or `APPROVED_TO_POST=false`.

### 7. Run it on a schedule

Run the autoposter every 15 minutes from the project folder:

```sh
cd /absolute/path/to/this/folder && python3 threads_autoposter.py
```

Each run processes at most one due item, uses a local lock so overlapping runs exit safely, and respects all safety settings.

You can host the scheduler on your own computer (macOS `launchd` or Linux `cron`) or on an always-on VPS with systemd — full walkthroughs for both, including the systemd unit files, private dashboard access over SSH, and monthly token refresh, are in [DEPLOYMENT.md](DEPLOYMENT.md).

Keep the automation disabled or dry-run-only until the schedule has a start date and you have reviewed the dry-run output. **Run the scheduler in exactly one place** — post statuses live in the local CSV and do not sync between machines.

## Using it with Claude or Codex (MCP)

`threads_mcp_server.py` exposes the planner over the Model Context Protocol via stdio. It edits the same CSV as the dashboard and never exposes `.env` secrets. Tools available to the assistant:

`safety_status`, `list_posts`, `get_post`, `add_post`, `update_post`, `reschedule_post`, `skip_post`, `mark_published`, `run_scheduler_once` (obeys all DRY_RUN/approval locks).

Install the dependency first: `pip install "mcp[cli]>=1.27,<2"`.

### Claude Desktop

Add to `claude_desktop_config.json` (Settings → Developer → Edit Config):

```json
{
  "mcpServers": {
    "threads-planner": {
      "command": "python3",
      "args": ["/absolute/path/to/this/folder/threads_mcp_server.py"]
    }
  }
}
```

If the planner lives on a remote server, run it over SSH instead — see `claude_desktop_mcp_config.example.json` for both variants.

### Claude Code

```sh
claude mcp add threads-planner -- python3 /absolute/path/to/this/folder/threads_mcp_server.py
```

### Codex CLI

Add to `~/.codex/config.toml`:

```toml
[mcp_servers.threads-planner]
command = "python3"
args = ["/absolute/path/to/this/folder/threads_mcp_server.py"]
```

Then ask things like: *"List next week's scheduled threads"*, *"Add a 3-part chain about client onboarding on Friday at 9am"*, or *"Skip post D14-PM"*.

## The schedule CSV

`threads_schedule.csv` is the single source of truth. You can also edit it in a spreadsheet (UTF-8, keep the exact column names, close the editor before the scheduler runs). Valid statuses:

- `scheduled` — eligible when due
- `published` — always skipped
- `skipped` — always skipped
- `publishing` — safety marker while a chain is being sent
- `error` — skipped until you inspect the error and deliberately reset it

`post_text` holds a JSON list of strings — one entry per post in the chain. Logs are written to `logs/threads_autoposter.log`; every selection, dry run, container, publish, retry, and error is recorded. Tokens are never logged.

## Threads API limits (as of July 2026)

- Text: **500 UTF-8 bytes** per post segment (emoji count by bytes)
- API-published posts: 250 per profile per rolling 24 hours; replies: 1,000
- General call quota: `4,800 × impressions` per rolling 24 hours (minimum 10 impressions in the formula)
- Check your usage with `GET /{threads-user-id}/threads_publishing_limit`

There is no native future-time scheduling in the Threads publishing flow — this local scheduler decides when to call the API. A typical two-chains-per-day calendar (2 roots + 8 replies) sits comfortably below the limits.

## Troubleshooting

- **Blank dates** — run `--set-start-date YYYY-MM-DD`.
- **401 / invalid token** — renew the OAuth token (or refresh the long-lived token) and update only your local `.env`.
- **Permission error** — confirm the tester invitation was accepted (or App Review passed) and all three permissions were granted.
- **Container timeout** — the row is marked `error` and partial published IDs are retained to prevent duplicates; inspect the logged Meta error.
- **429 / rate limit** — leave the row in `error`, wait for the rolling window, check quota usage, then reset deliberately.
- **Partial chain** — don't reset blindly. Check `threads_post_id` (a JSON list of already-published IDs) and finish manually or edit the remaining content before rescheduling.
- **CSV locked or damaged** — restore the exact header listed above using a UTF-8 CSV editor.

## Security notes

- `.env` is git-ignored; keep it that way. Set file permissions to `600` on shared machines.
- Before exposing the dashboard beyond localhost, set `DASHBOARD_USERNAME` and `DASHBOARD_PASSWORD` in `.env` (never reuse your Meta password).
- Meta describes publishing as acting on a person's behalf. External users (no app role) require App Review and a published app — check Meta's current Platform Terms before running this for accounts other than your own.