{"_id":"@bintangtimurlangit/threads-mcp","_rev":"4-23ccc3ff4c319e39ddbef3000b1d82cd","name":"@bintangtimurlangit/threads-mcp","dist-tags":{"latest":"0.2.0"},"versions":{"0.1.0":{"name":"@bintangtimurlangit/threads-mcp","version":"0.1.0","keywords":["mcp","model-context-protocol","threads","meta","instagram","social-media","automation"],"license":"MIT","_id":"@bintangtimurlangit/threads-mcp@0.1.0","maintainers":[{"name":"bintangtimurlangit","email":"btimurlangit@gmail.com"}],"homepage":"https://github.com/bintangtimurlangit/threads-mcp#readme","bugs":{"url":"https://github.com/bintangtimurlangit/threads-mcp/issues"},"bin":{"threads-mcp":"build/index.js","threads-mcp-login":"build/login.js"},"dist":{"shasum":"085a2ba9eda9b8b1b41c5eab2791c0abd5ca54ac","tarball":"https://registry.npmjs.org/@bintangtimurlangit/threads-mcp/-/threads-mcp-0.1.0.tgz","fileCount":68,"integrity":"sha512-hsUIUcDa8vhp6PV1HWIpsqLQA59To1D/LaGWOx+cK2ipv8/qAhgdO7v9HwhgWVAf74YzC4Te6jZmUfqdqMGQWA==","signatures":[{"sig":"MEUCIQCxjPpjPLGEtq8U13Pq0fpF9T1lUjjrVjX/ZFYVhq2kXwIgJiQxUYPu/m2fSmPmwkgi6AqbRfmquTCCV1Nw4cNgj9o=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":235781},"type":"module","engines":{"node":">=18.0.0"},"gitHead":"6e1e30794b133ce3405e46ab5229fcbceb3a5b77","scripts":{"dev":"tsx watch src/index.ts","lint":"eslint .","test":"tsx test/smoke.ts","build":"tsc","clean":"node -e \"require('fs').rmSync('build',{recursive:true,force:true})\"","login":"tsx src/login.ts","start":"node build/index.js","format":"prettier --write .","prepare":"husky","lint:fix":"eslint . --fix","prebuild":"npm run clean","typecheck":"tsc --noEmit","format:check":"prettier --check .","prepublishOnly":"npm run build"},"_npmUser":{"name":"bintangtimurlangit","email":"btimurlangit@gmail.com"},"repository":{"url":"git+https://github.com/bintangtimurlangit/threads-mcp.git","type":"git"},"_npmVersion":"11.16.0","description":"An MCP server for Meta's Threads via a logged-in browser session — read profiles, posts, replies, timeline & search, and post/reply/like/repost/follow. No developer account: it uses your own cookie session.","directories":{},"lint-staged":{"*.ts":"eslint --fix","*.{ts,js,json,md,yml,yaml}":"prettier --write"},"_nodeVersion":"24.16.0","dependencies":{"zod":"^3.25.76","dotenv":"^17.4.2","playwright":"^1.49.0","cloakbrowser":"^0.4.10","@modelcontextprotocol/sdk":"^1.29.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.23.0","husky":"^9.1.7","eslint":"^9.17.0","prettier":"^3.4.2","@eslint/js":"^9.17.0","typescript":"^5.9.0","@types/node":"^24.0.0","lint-staged":"^15.3.0","@commitlint/cli":"^19.6.1","typescript-eslint":"^8.19.0","eslint-config-prettier":"^9.1.0","@commitlint/config-conventional":"^19.6.0"},"_npmOperationalInternal":{"tmp":"tmp/threads-mcp_0.1.0_1784396016875_0.08792812909833714","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@bintangtimurlangit/threads-mcp","version":"0.1.1","keywords":["mcp","model-context-protocol","threads","meta","instagram","social-media","automation"],"author":{"name":"bintangtimurlangit"},"license":"MIT","_id":"@bintangtimurlangit/threads-mcp@0.1.1","maintainers":[{"name":"bintangtimurlangit","email":"btimurlangit@gmail.com"}],"homepage":"https://github.com/bintangtimurlangit/threads-mcp#readme","bugs":{"url":"https://github.com/bintangtimurlangit/threads-mcp/issues"},"bin":{"threads-mcp":"build/index.js","threads-mcp-login":"build/login.js"},"dist":{"shasum":"509163e58962406cf77f6374699e1799ec261080","tarball":"https://registry.npmjs.org/@bintangtimurlangit/threads-mcp/-/threads-mcp-0.1.1.tgz","fileCount":68,"integrity":"sha512-0en3uQEP9xU/yaabZ4wCsQxO7HWG9F6n5rMGUYudIV3s0R6Cn3NxMiwgs3DiIQvtbtjJ88xzA9JyuvQriV5y7Q==","signatures":[{"sig":"MEQCIFXmrWfZKjl4HXz7SfkLMV5WJXmGiG6uFzCS8HckvQW6AiA5+mxzDX2sK24wZPMT7NUj/p6LPTLco6XbwT1uGWx+cA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bintangtimurlangit%2fthreads-mcp@0.1.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":236601},"type":"module","engines":{"node":">=18.0.0"},"gitHead":"2b253bc6ceb1683ff2b734ac55fd19dd4ee070a7","scripts":{"dev":"tsx watch src/index.ts","lint":"eslint .","test":"tsx test/smoke.ts","build":"tsc","clean":"node -e \"require('fs').rmSync('build',{recursive:true,force:true})\"","login":"tsx src/login.ts","start":"node build/index.js","format":"prettier --write .","prepare":"husky","lint:fix":"eslint . --fix","prebuild":"npm run clean","typecheck":"tsc --noEmit","format:check":"prettier --check .","prepublishOnly":"npm run build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:f886e520-feb8-4cc0-9e8c-e8105d2946c0"}},"repository":{"url":"git+https://github.com/bintangtimurlangit/threads-mcp.git","type":"git"},"_npmVersion":"11.16.0","description":"An MCP server for Meta's Threads via a logged-in browser session — read profiles, posts, replies, timeline & search, and post/reply/like/repost/follow. No developer account: it uses your own cookie session.","directories":{},"lint-staged":{"*.ts":"eslint --fix","*.{ts,js,json,md,yml,yaml}":"prettier --write"},"_nodeVersion":"24.18.0","dependencies":{"zod":"^3.25.76","dotenv":"^17.4.2","playwright":"^1.49.0","cloakbrowser":"^0.4.12","@modelcontextprotocol/sdk":"^1.29.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.23.0","husky":"^9.1.7","eslint":"^10.7.0","prettier":"^3.4.2","@eslint/js":"^10.0.1","typescript":"^5.9.0","@types/node":"^24.0.0","lint-staged":"^17.1.0","@commitlint/cli":"^21.2.1","typescript-eslint":"^8.65.0","eslint-config-prettier":"^10.1.8","@commitlint/config-conventional":"^21.2.0"},"_npmOperationalInternal":{"tmp":"tmp/threads-mcp_0.1.1_1784596050031_0.46868753593695023","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@bintangtimurlangit/threads-mcp","version":"0.1.2","keywords":["mcp","model-context-protocol","threads","meta","instagram","social-media","automation"],"author":{"name":"bintangtimurlangit"},"license":"MIT","_id":"@bintangtimurlangit/threads-mcp@0.1.2","maintainers":[{"name":"bintangtimurlangit","email":"btimurlangit@gmail.com"}],"homepage":"https://github.com/bintangtimurlangit/threads-mcp#readme","bugs":{"url":"https://github.com/bintangtimurlangit/threads-mcp/issues"},"bin":{"threads-mcp":"build/index.js","threads-mcp-login":"build/login.js"},"dist":{"shasum":"f6d5ddc71661d95e9cff38af9a9b4052e13b5136","tarball":"https://registry.npmjs.org/@bintangtimurlangit/threads-mcp/-/threads-mcp-0.1.2.tgz","fileCount":68,"integrity":"sha512-5QbQJZtjzL/fuuo45Srf9OUpTdIGLdJS6FkDnBkTI9O2RX/1bncw4QMJvxi7COHYVM6NtqQjew9YpU70WeNE4Q==","signatures":[{"sig":"MEQCIEf0r38aI8C49UqvPedQyLIp8Vk6xwBaMxSX4UlEseIDAiAB0sf8Y4GZ2JFdv9FzDD2FmEQc38kEDJNTaQZJllZXqQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bintangtimurlangit%2fthreads-mcp@0.1.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":238991},"type":"module","engines":{"node":">=20.0.0"},"gitHead":"5a74efaa361617f195f89ef489b02724089ffc9a","scripts":{"dev":"tsx watch src/index.ts","lint":"eslint .","test":"tsx test/smoke.ts","build":"tsc","clean":"node -e \"require('fs').rmSync('build',{recursive:true,force:true})\"","login":"tsx src/login.ts","start":"node build/index.js","format":"prettier --write .","prepare":"husky","lint:fix":"eslint . --fix","prebuild":"npm run clean","typecheck":"tsc --noEmit","format:check":"prettier --check .","prepublishOnly":"npm run build"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:f886e520-feb8-4cc0-9e8c-e8105d2946c0"}},"repository":{"url":"git+https://github.com/bintangtimurlangit/threads-mcp.git","type":"git"},"_npmVersion":"11.16.0","description":"An MCP server for Meta's Threads via a logged-in browser session — read profiles, posts, replies, timeline & search, and post/reply/like/repost/follow. No developer account: it uses your own cookie session.","directories":{},"lint-staged":{"*.ts":"eslint --fix","*.{ts,js,json,md,yml,yaml}":"prettier --write"},"_nodeVersion":"24.18.0","dependencies":{"zod":"^3.25.76","dotenv":"^17.4.2","playwright":"^1.49.0","cloakbrowser":"^0.5.2","@modelcontextprotocol/sdk":"^1.29.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.23.0","husky":"^9.1.7","eslint":"^10.7.0","prettier":"^3.4.2","@eslint/js":"^10.0.1","typescript":"^5.9.0","@types/node":"^24.0.0","lint-staged":"^17.1.0","@commitlint/cli":"^21.2.1","typescript-eslint":"^8.65.0","eslint-config-prettier":"^10.1.8","@commitlint/config-conventional":"^21.2.0"},"_npmOperationalInternal":{"tmp":"tmp/threads-mcp_0.1.2_1785216585680_0.6629813153472397","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@bintangtimurlangit/threads-mcp","version":"0.2.0","description":"An MCP server for Meta's Threads via a logged-in browser session — read profiles, posts, replies, timeline & search, and post/reply/like/repost/follow. No developer account: it uses your own cookie session.","author":{"name":"bintangtimurlangit"},"license":"MIT","type":"module","publishConfig":{"access":"public"},"repository":{"type":"git","url":"git+https://github.com/bintangtimurlangit/threads-mcp.git"},"bugs":{"url":"https://github.com/bintangtimurlangit/threads-mcp/issues"},"homepage":"https://github.com/bintangtimurlangit/threads-mcp#readme","bin":{"threads-mcp":"build/index.js","threads-mcp-login":"build/login.js","threads-mcp-import-session":"build/import-session.js"},"scripts":{"clean":"node -e \"require('fs').rmSync('build',{recursive:true,force:true})\"","prebuild":"npm run clean","build":"tsc","login":"tsx src/login.ts","import-session":"tsx src/import-session.ts","dev":"tsx watch src/index.ts","start":"node build/index.js","typecheck":"tsc --noEmit","lint":"eslint .","lint:fix":"eslint . --fix","format":"prettier --write .","format:check":"prettier --check .","test":"node --import tsx --test test/unit/*.test.ts","test:live":"tsx test/smoke.ts","bench":"tsx test/capture-bench.ts","graph":"graphify . --code-only && graphify cluster-only .","graph:update":"graphify update .","prepare":"husky","prepublishOnly":"npm run build"},"lint-staged":{"*.{ts,js,json,md,yml,yaml}":"prettier --write","*.ts":"eslint --fix"},"keywords":["mcp","model-context-protocol","threads","meta","instagram","social-media","automation"],"dependencies":{"@modelcontextprotocol/sdk":"^1.29.0","cloakbrowser":"^0.5.2","dotenv":"^17.4.2","playwright":"^1.49.0","zod":"^3.25.76"},"devDependencies":{"@commitlint/cli":"^21.2.1","@commitlint/config-conventional":"^21.2.0","@eslint/js":"^10.0.1","@types/node":"^24.0.0","eslint":"^10.7.0","eslint-config-prettier":"^10.1.8","husky":"^9.1.7","lint-staged":"^17.1.0","prettier":"^3.4.2","tsx":"^4.23.0","typescript":"^5.9.0","typescript-eslint":"^8.65.0"},"engines":{"node":">=20.0.0"},"gitHead":"08d9bba724fd586f92f637a9b4ec99fcd721e22e","_id":"@bintangtimurlangit/threads-mcp@0.2.0","_nodeVersion":"24.18.0","_npmVersion":"11.16.0","dist":{"integrity":"sha512-+4M7Bgv01QWwyAyoAwcc7HBQRirk1pp/3LAgaIxQsXNEFFMnnOWJLu72QZXXSzf88TdwApWfVX9YlKkhs2iErw==","shasum":"e8a074a8e131a553a6a20a5b93e0fd5eb9131878","tarball":"https://registry.npmjs.org/@bintangtimurlangit/threads-mcp/-/threads-mcp-0.2.0.tgz","fileCount":88,"unpackedSize":379817,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bintangtimurlangit%2fthreads-mcp@0.2.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCdUUajhY9h8pezr8sYl2ORmprNU74CZyR5/5Gz6n2qDwIhALhqjKS+GVcfFdF8sOp0DToiTNOjiYBifBzrtignjC4r"}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:f886e520-feb8-4cc0-9e8c-e8105d2946c0"}},"directories":{},"maintainers":[{"name":"bintangtimurlangit","email":"btimurlangit@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/threads-mcp_0.2.0_1785221967449_0.6650596207447643"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-18T17:33:36.719Z","modified":"2026-07-28T06:59:28.176Z","0.1.0":"2026-07-18T17:33:37.023Z","0.1.1":"2026-07-21T01:07:30.176Z","0.1.2":"2026-07-28T05:29:45.890Z","0.2.0":"2026-07-28T06:59:27.605Z"},"bugs":{"url":"https://github.com/bintangtimurlangit/threads-mcp/issues"},"author":{"name":"bintangtimurlangit"},"license":"MIT","homepage":"https://github.com/bintangtimurlangit/threads-mcp#readme","keywords":["mcp","model-context-protocol","threads","meta","instagram","social-media","automation"],"repository":{"type":"git","url":"git+https://github.com/bintangtimurlangit/threads-mcp.git"},"description":"An MCP server for Meta's Threads via a logged-in browser session — read profiles, posts, replies, timeline & search, and post/reply/like/repost/follow. No developer account: it uses your own cookie session.","maintainers":[{"name":"bintangtimurlangit","email":"btimurlangit@gmail.com"}],"readme":"# threads-mcp\n\n[![npm](https://img.shields.io/npm/v/@bintangtimurlangit/threads-mcp?style=flat-square)](https://www.npmjs.com/package/@bintangtimurlangit/threads-mcp)\n[![license](https://img.shields.io/github/license/bintangtimurlangit/threads-mcp?style=flat-square)](./LICENSE)\n[![CI](https://img.shields.io/github/actions/workflow/status/bintangtimurlangit/threads-mcp/ci.yml?branch=main&style=flat-square)](https://github.com/bintangtimurlangit/threads-mcp/actions)\n[![GitHub Repo](https://img.shields.io/badge/GitHub-threads--mcp-24292f?style=flat-square&logo=github)](https://github.com/bintangtimurlangit/threads-mcp)\n\nAn MCP server for **Meta's Threads** that acts as _your own account_ — read profiles, posts, replies, your timeline & search, and post / reply / quote / like / repost / follow / schedule — from any MCP client (Claude Desktop, Claude Code, etc.).\n\n> **No developer account.** Unlike the official [Threads Graph API](https://developers.facebook.com/docs/threads) approach (which needs an app, OAuth, and an Instagram Business account), this server drives a **real logged-in browser session** using your own cookies.\n\n**Contents:** [Rate limits](#️-behave-for-rate-limits) · [Tools](#tools) · [Media](#media) · [Scheduling](#scheduling) · [Setup](#setup) · [Run as a daemon](#running-as-a-persistent-daemon) · [Config](#configuration) · [How it works](#how-it-works) · [Troubleshooting](#troubleshooting)\n\n**Full reference:** [Documentation](./docs/README.md) · **Changelog:** [CHANGELOG.md](./CHANGELOG.md) · **Versioning & releases:** [docs/RELEASES.md](./docs/RELEASES.md)\n\n---\n\n## ⚠️ Behave for rate limits\n\nYou are automating a **real Threads account**. Meta rate-limits aggressively and can **restrict or ban** accounts that behave like bots — bursty posting, rapid follow/unfollow, like loops. This server helps, but the discipline is on you:\n\n- Writes are **spaced ≥ `THREADS_MIN_ACTION_INTERVAL_MS` (default 8s) apart**, enforced server-side.\n- Treat `create_thread`, `follow_user`, `like_thread` as **scarce actions**, not loops.\n- If you hit a `🐢 rate-limited` message, **stop for several minutes** — don't retry immediately.\n- Reads are cheaper but still hit a real session; results are cached briefly.\n\n---\n\n## Tools\n\n**24 tools.** Posts are identified by a full `url` **or** `handle` + `code` (the shortcode in `.../@user/post/CODE`).\n\n### Read\n\n| Tool                 | What it returns                                                                           |\n| -------------------- | ----------------------------------------------------------------------------------------- |\n| `whoami`             | Which account you're signed in as (handle, user id, name, follower/following).            |\n| `get_profile`        | A user's bio, follower count, verified status + recent posts. Omit `handle` for your own. |\n| `get_user_threads`   | A user's recent posts (their profile feed).                                               |\n| `get_thread`         | A single post with its like/reply/repost counts.                                          |\n| `get_thread_replies` | Replies under a post.                                                                     |\n| `get_timeline`       | Your \"For you\" home feed.                                                                 |\n| `search`             | Search Threads for `posts` or `users`.                                                    |\n| `get_followers`      | A partial sample of a user's followers.                                                   |\n| `get_following`      | A partial sample of who a user follows.                                                   |\n| `get_notifications`  | Your Activity feed — follows, replies, mentions, suggestions. Filterable by `kind`.       |\n\n### Write &nbsp;(rate-limited — real account)\n\n| Tool                                | Action                                                                  |\n| ----------------------------------- | ----------------------------------------------------------------------- |\n| `create_thread`                     | Post a new thread — text and/or media, optionally a multi-post `chain`. |\n| `reply_to_thread`                   | Reply to a post — text and/or media.                                    |\n| `quote_thread`                      | Quote-post (repost with your own comment + optional media).             |\n| `delete_thread`                     | Delete one of your own posts (permanent).                               |\n| `like_thread` / `unlike_thread`     | Like / remove a like.                                                   |\n| `repost_thread` / `unrepost_thread` | Repost / remove a repost.                                               |\n| `follow_user` / `unfollow_user`     | Follow / unfollow a user.                                               |\n\n### Schedule\n\n| Tool               | Action                                                                     |\n| ------------------ | -------------------------------------------------------------------------- |\n| `schedule_thread`  | Queue a text/media post to publish later (`at` ISO time or `in` duration). |\n| `list_scheduled`   | List scheduled posts and their status.                                     |\n| `cancel_scheduled` | Cancel a pending scheduled post by id.                                     |\n\n> **Note:** reposting your _own_ post is a no-op on Threads (it silently does nothing) — that's Threads' behavior, not a bug.\n\n### Tool annotations\n\nPer the [MCP annotations spec](https://modelcontextprotocol.io/) — side effects at a glance. Write tools act on your **real** account.\n\n| Tool                                                                                                                                                                       | Read-only | Idempotent | Destructive |\n| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------: | :--------: | :---------: |\n| `whoami`, `get_profile`, `get_user_threads`, `get_thread`, `get_thread_replies`, `get_timeline`, `search`, `get_followers`, `get_following`, `get_notifications`, `doctor` |     ✓     |     ✓      |      –      |\n| `create_thread`, `reply_to_thread`, `quote_thread`                                                                                                                         |     –     |     –      |      –      |\n| `like_thread` / `unlike_thread`, `repost_thread` / `unrepost_thread`, `follow_user` / `unfollow_user`                                                                      |     –     |     ✓      |      –      |\n| `delete_thread`                                                                                                                                                            |     –     |     ✓      |      ✓      |\n| `schedule_thread`                                                                                                                                                          |     –     |     –      |      –      |\n| `list_scheduled`                                                                                                                                                           |     ✓     |     ✓      |      –      |\n| `cancel_scheduled`                                                                                                                                                         |     –     |     ✓      |      –      |\n\n---\n\n## Media\n\n`create_thread`, `reply_to_thread`, `quote_thread`, and `schedule_thread` take an optional `media` array — **local file paths and/or `http(s)` URLs** (URLs are downloaded to a temp file first, then cleaned up). Supported: images (`jpg/png/webp/avif`) and video (`mp4/mov/webm`). **Multiple images post as a carousel.** Either `text` or `media` is required.\n\n```jsonc\n// text + single image\ncreate_thread { \"text\": \"hello\", \"media\": [\"/path/to/pic.jpg\"] }\n\n// carousel (multiple images, mix local + URL)\ncreate_thread { \"text\": \"trip 🧵\", \"media\": [\"a.jpg\", \"b.jpg\", \"https://…/c.jpg\"] }\n\n// image-only reply\nreply_to_thread { \"handle\": \"someone\", \"code\": \"ABC123\", \"media\": [\"reaction.png\"] }\n\n// quote with a comment + image\nquote_thread { \"url\": \"https://www.threads.com/@x/post/ABC\", \"text\": \"this 👇\", \"media\": [\"chart.png\"] }\n```\n\n### Multi-post threads\n\n`create_thread` takes an optional `chain` — extra posts published as one\nconnected thread, the format Threads calls \"Add to thread\". Posting them\nseparately instead produces unlinked standalone threads.\n\n```jsonc\ncreate_thread {\n  \"text\": \"Three things I learned shipping this 🧵\",\n  \"chain\": [\"1. Meta detects headless.\", \"2. Cache invalidation is still hard.\", \"3. Ship it.\"]\n}\n```\n\nOn your profile a chain appears as a **single** entry; the later parts are\nreachable via `get_thread_replies` on the first post.\n\n---\n\n## Scheduling\n\nThreads' **web UI has no native scheduling** (it's a mobile / Meta Business Suite feature), so this server runs its own scheduler: jobs are **persisted** to `~/.threads-mcp/scheduled.json` and a poll loop publishes them when due, through the same code path as `create_thread`.\n\n```jsonc\n// absolute time (local timezone unless you add an offset like +07:00 or Z)\nschedule_thread { \"text\": \"launch 🚀\", \"at\": \"2026-07-20T09:00\" }\n\n// relative delay\nschedule_thread { \"text\": \"in a bit\", \"in\": \"2h\", \"media\": [\"teaser.jpg\"] }\n\nlist_scheduled {}                 // → ids + status (pending / done / failed / canceled)\ncancel_scheduled { \"id\": \"b9ec…\" }\n```\n\n### The one hard limit\n\nA cookie/browser approach can only post **while this server process is running** — there's no Threads-side scheduler to hand the job to. So:\n\n- **Short horizons / same session** — works while your MCP client keeps the server alive.\n- **Past-due jobs** — fire on the **next startup** (better late than never).\n- **Long horizons (days out)** — run the server as an **[always-on daemon](#running-as-a-persistent-daemon)** so it's alive when the job is due.\n\nLocal media paths must **still exist** when the job fires (URLs are re-downloaded at fire time).\n\n---\n\n## Setup\n\n### From npm (recommended)\n\n```bash\nnpm install -g @bintangtimurlangit/threads-mcp   # downloads the CloakBrowser binary (~200 MB, cached)\n```\n\nThis puts two commands on your PATH: **`threads-mcp`** (the server) and **`threads-mcp-login`** (one-time login). Or run without installing: `npx -y @bintangtimurlangit/threads-mcp`.\n\n### From source\n\n```bash\ngit clone https://github.com/bintangtimurlangit/threads-mcp.git\ncd threads-mcp\nnpm install          # also downloads the CloakBrowser binary (~200 MB, cached)\nnpm run build\n```\n\n### 1. Log in once\n\n```bash\nthreads-mcp-login    # global install — or, from a source checkout:  npm run login\n```\n\nOpens a CloakBrowser window — log into Threads, then press Enter. Saves your session to `~/.threads-mcp/chrome-profile`. Re-run only when it expires.\n\n### 2. Register with your MCP client\n\nThe server launches a **headed** browser, so it needs a display. On a headless machine wrap it with `xvfb-run`:\n\n```json\n{\n  \"mcpServers\": {\n    \"threads\": {\n      \"command\": \"xvfb-run\",\n      \"args\": [\"-a\", \"threads-mcp\"]\n    }\n  }\n}\n```\n\nOn a machine with a real display, drop `xvfb-run`: `\"command\": \"threads-mcp\"`, `\"args\": []`. From a source checkout, use `\"command\": \"node\"`, `\"args\": [\"/absolute/path/to/threads-mcp/build/index.js\"]` (wrapped in `xvfb-run` on a headless box).\n\n---\n\n## Running as a persistent daemon\n\nFor **reliable scheduling** (and to avoid re-launching the browser each session), run the server always-on under a virtual display. Example with **systemd** on Linux:\n\n```ini\n# ~/.config/systemd/user/threads-mcp.service\n[Unit]\nDescription=threads-mcp (Threads MCP server)\nAfter=network-online.target\n\n[Service]\nExecStart=/usr/bin/xvfb-run -a /usr/bin/node /absolute/path/to/threads-mcp/build/index.js\nRestart=on-failure\nEnvironment=DEBUG=false\n\n[Install]\nWantedBy=default.target\n```\n\n```bash\nsystemctl --user enable --now threads-mcp\nloginctl enable-linger \"$USER\"     # keep it running after logout\n```\n\nOr with **pm2**: `pm2 start \"xvfb-run -a node build/index.js\" --name threads-mcp`.\n\n### Signing in without a display\n\n`npm run login` needs a visible browser, which is the main obstacle to running\nthis anywhere without a desktop — a VPS, a container, CI. The session is only\ncookies, so move it instead of trying to log in headlessly:\n\n1. On a machine with a display, sign in to Threads normally.\n2. Open devtools → Application → Cookies → `threads.com` and copy `sessionid`\n   and `ds_user_id`.\n3. On the server:\n\n```bash\nTHREADS_SESSIONID=… THREADS_DS_USER_ID=… npx threads-mcp-import-session\nnpm run test:live      # confirm it worked\n```\n\n> ⚠️ A `sessionid` is a bearer credential for your **entire account** — whoever\n> holds it is you. Prefer the environment-variable form so it stays out of shell\n> history, never commit it, and revoke it by logging out of Threads if it leaks.\n\n### Chromium system libraries\n\nOn a fresh server Chromium needs system libraries that are not installed by\ndefault. If the browser fails to launch with a missing `.so`:\n\n```bash\nnpx playwright install-deps chromium\n# or, Debian/Ubuntu, without Playwright's helper:\nsudo apt-get install -y libnss3 libatk1.0-0 libatk-bridge2.0-0 libcups2 \\\n  libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 libxfixes3 libxrandr2 \\\n  libgbm1 libasound2 libpango-1.0-0 libcairo2\n```\n\n> **Why not a Docker image?** It would mainly pin those libraries — the one line\n> above. Against that, MCP over stdio means the client owns the process, so a\n> container turns `npx threads-mcp` into `docker run -i` with a volume for the\n> profile; and a containerised Chromium on a virtual display has no GPU and\n> reports renderer strings that match no real desktop, which cuts directly\n> against the fingerprint work this server depends on. Running it natively under\n> `xvfb` is both simpler and less detectable.\n\n> Note: MCP over stdio expects the client to own the process. Running a standalone daemon is specifically for the **scheduler** to survive between client sessions — the scheduled-post queue is shared via `~/.threads-mcp/scheduled.json`.\n\n---\n\n## Configuration\n\nAll optional — see `.env.example`, copy to `.env` to override.\n\n| Variable                         | Default                         | Purpose                                                                   |\n| -------------------------------- | ------------------------------- | ------------------------------------------------------------------------- |\n| `THREADS_DOMAIN`                 | `threads.com`                   | Threads domain (`threads.net` redirects here).                            |\n| `THREADS_PROFILE_DIR`            | `~/.threads-mcp/chrome-profile` | Where the saved login lives.                                              |\n| `THREADS_HEADLESS`               | `false`                         | Keep `false` — headless is detected.                                      |\n| `THREADS_MIN_ACTION_INTERVAL_MS` | `8000`                          | Minimum gap between write actions. Raise to be safer.                     |\n| `CACHE_TTL_MS`                   | `30000`                         | In-memory read-cache lifetime.                                            |\n| `THREADS_LOCK_TIMEOUT_MS`        | `120000`                        | Ceiling on one browser operation; on timeout the page resets.             |\n| `THREADS_MAX_MEDIA_BYTES`        | `67108864`                      | Largest accepted media file (64 MB).                                      |\n| `THREADS_MEDIA_TIMEOUT_MS`       | `60000`                         | Per-download timeout for `http(s)` media.                                 |\n| `DEBUG`                          | `false`                         | Log startup, captured GraphQL op names, and scheduler activity to stderr. |\n\nState lives under `~/.threads-mcp/`: `chrome-profile/` (your login) and `scheduled.json` (the post queue).\n\n---\n\n## How it works\n\nThreads' web app talks to Meta's **Relay GraphQL gateway** with per-session tokens (`fb_dtsg`, `lsd`) and anti-automation fingerprinting. A hand-rolled `fetch` gets rejected, and operation IDs churn. So this server drives **[CloakBrowser](https://github.com/CloakHQ/cloakbrowser)** — a fingerprint-patched Chromium — against a persistent profile you log into once, and:\n\n- **Reads** — collects the data the app renders: the server-side JSON embedded in each page's `<script>` tags, plus every `/api/graphql` **and** `/graphql/query` response (the home feed uses the latter). A defensive walker pulls posts/users out of _whatever_ comes back, so it survives Meta renaming operations.\n- **Writes** — drive the real composer and action buttons so Meta's own client mints the tokens. Icon buttons are clicked at the DOM level (a humanized pointer click misses them). Replies use the inline composer's Ctrl+Enter; reply-with-media promotes it to the full dialog via \"Expand composer\".\n- **Scheduling** — a persisted queue + poll loop, delegating to the same publish path as `create_thread`.\n\nThe browser runs **headed** (Meta detects headless); on a server use a virtual display (`xvfb`).\n\n---\n\n## Development\n\n```bash\nnpm run typecheck\nnpm test             # unit tests (no browser, no login — runs in CI)\nnpm run test:live    # live READ-only smoke test (needs login + a display)\nnpm run dev          # tsx watch\n```\n\n`DEBUG=true` logs every GraphQL operation name the app fires and each scheduler tick — useful if Meta reshuffles a surface and a reader comes back empty.\n\n---\n\n## When things break\n\nMeta ships UI changes without notice. Because writes drive the real interface,\na moved button shows up as a vague \"couldn't confirm\" from whichever tool\nhappened to use it — not as an obvious failure.\n\nRun `doctor` first. It checks the session and every DOM anchor the write tools\ndepend on, and tells you what each failure breaks:\n\n```\n✅ session — session cookie present\n✅ composer-add-to-thread — present\n❌ post-repost — NOT FOUND\n\n**Impact:**\n- `post-repost` → repost_thread / quote_thread / unrepost_thread\n```\n\nAnchors are declared in [`src/browser/selectors.ts`](./src/browser/selectors.ts);\nupdate the ones that moved. `doctor { \"deep\": true }` also checks a real post\npage and the activity feed.\n\n---\n\n## Troubleshooting\n\n| Symptom                                   | Likely cause / fix                                                                                                                                  |\n| ----------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `🔒 Not signed in` on every tool          | No/expired session → run `npm run login`.                                                                                                           |\n| A read returns empty for a public account | Try again (feed/timeline is lazy-loaded); run with `DEBUG=true` to see the GraphQL ops. Private/blocked accounts yield nothing.                     |\n| A write says it couldn't find its button  | Meta changed the UI, or a promo interstitial got in the way (the server tries to dismiss those). Retry; if persistent, the selector needs updating. |\n| `🐢 rate-limited`                         | Stop for several minutes, then slow down.                                                                                                           |\n| Scheduled post never fired                | The server wasn't running when it was due — see [Run as a daemon](#running-as-a-persistent-daemon). It'll fire on next startup.                     |\n| Headless / server has no display          | Wrap the command in `xvfb-run -a …`.                                                                                                                |\n\n---\n\n## Caveats\n\n- **Login required.** No session → tools return a friendly \"run `npm run login`\" prompt.\n- **Anti-bot is a moving target.** The free CloakBrowser binary can go stale as Meta updates detection; CloakBrowser Pro ships newer patches. Writes rely on UI selectors Meta can change.\n- **Reads are resilient** to GraphQL renames (they parse whatever the app fetches), but a private/blocked account yields nothing, and just-posted content can be briefly stale on read-back.\n- **Scheduling only fires while the server runs** (see above).\n- Respect Threads' Terms of Service and the rate-limit guidance above. This is for personal use of your own account, not scraping or automation at scale.\n\n## Contributing & security\n\n[CONTRIBUTING.md](./CONTRIBUTING.md) · [SECURITY.md](./SECURITY.md) · [Code of Conduct](./CODE_OF_CONDUCT.md)\n\n## License\n\n[MIT](./LICENSE)\n\n---\n\n## Disclaimer\n\nThis is an **unofficial** project. It is **not affiliated with, authorized, maintained, sponsored, or endorsed by Meta, Threads, or Instagram**.\n\nIt works by driving a real logged-in browser session against Threads' web app, which can change without notice — a tool may break when Meta updates its site or anti-bot behavior. It automates **your own** account and performs only the actions you invoke.\n\nYou are responsible for using this software in compliance with [Threads' / Meta's Terms of Service](https://help.instagram.com/769983657850450) and applicable law. Keep request and write volumes reasonable. All product names, logos, and brands are property of their respective owners.\n","readmeFilename":"README.md"}