{
  "markdown": "<!-- mcp-name: io.github.nsozturk/tweetkit-x -->\n\n<p align=\"center\">\n  <img src=\"assets/header.png\" alt=\"tweetkit-x — post, delete and read on X (Twitter) from Python & MCP using your own web session\" width=\"100%\">\n</p>\n\n<h1 align=\"center\">tweetkit-x</h1>\n\n<p align=\"center\">\n  <b>Post, delete &amp; read on X (Twitter) — from Python <i>and</i> as an MCP server — using your own logged-in browser session.</b><br>\n  No paid API. No developer app. No OAuth dance. Just your session cookie.\n</p>\n\n<p align=\"center\">\n  <img alt=\"PyPI\" src=\"https://img.shields.io/pypi/v/tweetkit-x?style=flat-square&color=1d9bf0&label=pypi\">\n  <img alt=\"Python\" src=\"https://img.shields.io/badge/python-3.10+-1d9bf0?style=flat-square\">\n  <img alt=\"License\" src=\"https://img.shields.io/badge/license-MIT-3fb950?style=flat-square\">\n  <img alt=\"MCP\" src=\"https://img.shields.io/badge/MCP-server%20included-7856ff?style=flat-square\">\n  <img alt=\"X API\" src=\"https://img.shields.io/badge/X%20API-not%20required-3fb950?style=flat-square\">\n  <img alt=\"Runs with\" src=\"https://img.shields.io/badge/runs%20with-Claude%20%7C%20Cursor%20%7C%20uvx-111318?style=flat-square\">\n</p>\n\n<p align=\"center\">\n  <a href=\"#quick-start\">Quick start</a> ·\n  <a href=\"#getting-your-session-cookie\">Get your cookie</a> ·\n  <a href=\"#python-usage\">Python</a> ·\n  <a href=\"#cli-usage\">CLI</a> ·\n  <a href=\"#mcp-server\">MCP server</a> ·\n  <a href=\"#how-it-works\">How it works</a> ·\n  <a href=\"#disclaimer\">Disclaimer</a>\n</p>\n\n---\n\n## Why\n\nSince **February 6, 2026**, X's official API has **no free tier for new developers** — it's pay-per-use: **~$0.015 per post, ~$0.20 per post with a link**, plus per-read charges. For publishing *your own* tweets (a bot posting your content, a scheduled thread, a cleanup script that deletes old posts), that's a recurring bill for something your browser already does for free.\n\n**tweetkit-x** skips the API entirely. It replays the exact internal HTTP calls your browser makes when you click **Post**, **Delete**, or scroll your profile — authenticated with the session cookie you already have. Post, delete, and read the full timeline, at **zero API cost**.\n\n> ⚠️ Automating your web session is **against X's Terms of Service**. This is a grey-area tool for personal automation. **Account risk is entirely yours.** See the [Disclaimer](#disclaimer).\n\n---\n\n## Features\n\n- ✅ **Post** — text, **up to 4 images**, **GIF &amp; video** (with **alt text**), replies, **threads**, **quote-tweets**, **long-form note tweets**, and **scheduled** tweets\n- 🗑️ **Delete** — one id, a list of ids, or \"find matching &amp; delete\" cleanup loops\n- ❤️ **Engage** — like, retweet, bookmark, **follow, block, mute, pin** — each with an undo\n- 🌐 **Search all of X** — the real `SearchTimeline` (operators like `from:`, `min_faves:`, `filter:media`), plus local regex over your own tweets\n- 📖 **Read** — your **home feed**, any timeline, a tweet's **replies/conversation**, **followers/following**, **likers/retweeters**, **bookmarks**, a user's **likes**, **notifications**, and full **profiles**\n- 🔌 **MCP server** — **40 tools**; drop into Claude Desktop / Claude Code / Cursor; tools that **explain the cookie** and set it up with one paste\n- 🔑 **Frictionless auth** — paste a `Cookie:` header, drop a **HAR** file, or a **storage-dump zip**; store in a file or the macOS **Keychain**\n- 🐍 **Python API** *and* a **CLI** (`tweetkit …`)\n- 🪶 Tiny footprint — no headless browser, no Selenium\n\n---\n\n## Quick start\n\n```bash\npip install \"tweetkit-x[mcp]\"      # or: pip install tweetkit-x   (library + CLI only)\n\n# 1. Import your session cookie (three ways — see the next section)\ntweetkit import --paste                       # paste the Cookie: header, Ctrl-D\n#   or:  tweetkit import --file x.com.har\n#   or:  tweetkit import --file storagedump_x.com.zip --keychain tweetkit\n\n# 2. Verify (no network call)\ntweetkit whoami\n# {\"ready\": true, \"user_id\": \"1986...\", \"ct0_len\": 160}\n\n# 3. Use it\ntweetkit post \"hello world 👋\"\ntweetkit search \"xmr|monero\" --regex          # your tweets that match\ntweetkit delete 2063240404980445603\n```\n\n---\n\n## Getting your session cookie\n\nThe **only** secret tweetkit needs is your X **session cookie** — the same `Cookie:` header your logged-in browser sends. It must contain **at least two** values:\n\n| Cookie | What it is | Required | HttpOnly? |\n|---|---|---|---|\n| `auth_token` | your login/session token | **yes** | **yes** (hidden from `document.cookie`) |\n| `ct0` | CSRF token (sent back as `x-csrf-token`) | **yes** | no |\n| `twid` | your user id (`u=<id>`) — lets tweetkit read *your* timeline with no extra lookup | recommended | no |\n| `guest_id`, `kdt`, `att`, `personalization_id` | misc session state | optional | mixed |\n\n> ❗ `auth_token` is **HttpOnly**, so a `document.cookie` copy in the JS console will **NOT** include it. Use one of the reliable methods below.\n\n### Method A — copy the `Cookie:` header (most reliable, one paste)\n\n1. Open **x.com** while logged in.\n2. Open **DevTools (F12)** → the **Network** tab.\n3. Click any request to `x.com` (e.g. `HomeTimeline`).\n4. Under **Request Headers**, find **`cookie:`** and copy the **entire** value.\n5. Feed it in:\n\n```bash\ntweetkit import --paste\n# paste the cookie header, then press Ctrl-D\n```\n\n### Method B — an F12 \"Preserve log\" HAR file\n\n1. Open **x.com** logged in → **DevTools (F12)** → **Network**.\n2. Tick **\"Preserve log\"**, then reload / click around so requests are captured.\n3. **Right-click any request → \"Save all as HAR\"** (or use the ⬇ export icon).\n4. Import it:\n\n```bash\ntweetkit import --file ~/Downloads/x.com.har\n```\n\n> Modern Chrome can **redact cookies** from exported HARs. If tweetkit reports `auth_token` missing, either enable **\"Allow to generate HAR with sensitive data\"** in the Network settings, or fall back to **Method A** / **Method C**.\n\n### Method C — the **storagedump** browser extension (zip)\n\nThe easiest no-DevTools route. **[storagedump](https://chromewebstore.google.com/detail/storagedump/kihoghfekemdccfnpjefmggehpgnjnab)** (a Chrome extension built by this project's author) exports a tab's browser storage — including the **HttpOnly** `auth_token`, which `document.cookie` can't reach — as a `.zip` containing `cookies.json`. On x.com (logged in), click the extension → export, then:\n\n```bash\ntweetkit import --file storagedump_x.com_2026-07-05.zip\n```\n\ntweetkit reads the extension's native format, `{ \"data\": [ { \"key\": \"...\", \"value\": \"...\" }, ... ] }`, and also accepts a plain list or a `{name: value}` map. Cookie-Editor / EditThisCookie JSON exports (`--file export.json`) work too.\n\n> Install: **[storagedump on the Chrome Web Store](https://chromewebstore.google.com/detail/storagedump/kihoghfekemdccfnpjefmggehpgnjnab)**.\n\n### Where the cookie is stored\n\nBy default `tweetkit import` writes **`~/.config/tweetkit/cookie.txt`** (chmod `600`; also git-ignored). Prefer the **macOS Keychain**? Add `--keychain <slug>`:\n\n```bash\ntweetkit import --paste --keychain tweetkit\nexport TWEETKIT_COOKIE_KEYCHAIN=tweetkit\n```\n\nResolution order at runtime (first hit wins): explicit arg → `TWEETKIT_COOKIE` → `TWEETKIT_COOKIE_FILE` → `./cookie.txt` → `~/.config/tweetkit/cookie.txt` → Keychain (`TWEETKIT_COOKIE_KEYCHAIN`).\n\n---\n\n## Python usage\n\n```python\nfrom tweetkit_x import TweetKit\n\ntk = TweetKit(keychain_slug=\"tweetkit\")          # or cookie=\"auth_token=...; ct0=...\"\n\ntk.whoami()                                       # {'ready': True, 'user_id': '...', 'ct0_len': 160}\n\n# post\ntk.post(\"hello world\")\ntk.post(\"with a picture\", image_path=\"chart.png\")\ntk.post(\"a reply\", reply_to=\"1800000000000000000\")\n\n# thread — strings or (text, image_path) tuples; each replies to the previous\ntk.post_thread([\"1/ intro\", (\"2/ chart\", \"chart.png\"), \"3/ outro\"])\n\n# read\nmine = tk.get_tweets(limit=100)                   # your tweets, newest first\ntheirs = tk.get_tweets(username=\"jack\", limit=50) # anyone's\n\n# search (local filter over the timeline — no paid search API)\nhits = tk.search(\"monero\")                        # substring\nhits = tk.search(r\"xmr|monero\", regex=True)       # regex\n\n# delete\ntk.delete(\"1800000000000000000\")\ntk.delete_many([t[\"id\"] for t in hits])\n\n# quote-tweet\ntk.quote(\"this is great\", quote_tweet_id=\"1800000000000000000\")\n\n# engage (each has an undo)\ntk.like(\"1800000000000000000\");     tk.unlike(\"1800000000000000000\")\ntk.retweet(\"1800000000000000000\");  tk.unretweet(\"1800000000000000000\")\ntk.bookmark(\"1800000000000000000\"); tk.unbookmark(\"1800000000000000000\")\n\n# read a single tweet, and search ALL of X (real search + operators)\ntk.get_tweet(\"1800000000000000000\")\ntk.search_x(\"mcp server\", latest=True, limit=40)\ntk.search_x(\"from:jack min_faves:1000\")     # X search operators work\n```\n\nEvery write returns `{'ok': True, 'id': '...', 'url': '...'}` on success, or `{'ok': False, 'status': ..., 'error': '...'}` on failure — easy to log or retry.\n\nSee [`examples/`](examples/) for a full \"find my $XMR tweets and delete them\" cleanup loop.\n\n---\n\n## CLI usage\n\n```bash\ntweetkit whoami\ntweetkit post \"just some text\"\ntweetkit post \"text + media\" --image pic.png\ntweetkit post \"a reply\" --reply-to 1800000000000000000\ntweetkit thread thread.txt                 # tweets separated by blank lines\ntweetkit delete 1800000000000000000 1800000000000000001\ntweetkit tweets --limit 100                # your tweets\ntweetkit tweets @jack --limit 50           # someone else's\ntweetkit search monero                     # your tweets containing \"monero\"\ntweetkit search \"xmr|monero\" --regex\ntweetkit searchx \"mcp server\" --latest     # search ALL of X (real search + operators)\ntweetkit get 1800000000000000000           # one tweet by id\ntweetkit quote \"great thread\" 1800000000000000000\ntweetkit like 1800000000000000000          # also: unlike / retweet / unretweet / bookmark / unbookmark\n```\n\nA `thread.txt` is just tweets separated by **blank lines**.\n\n---\n\n## MCP server\n\ntweetkit ships a first-class **MCP server** so an AI client can post/delete/read for you — and it **walks the user through auth**, explaining what the cookie is and setting it up with a single paste.\n\n### Configure your client\n\n**Claude Desktop / Claude Code / Cursor** — add to your MCP config (`claude_desktop_config.json`, `.mcp.json`, …):\n\n```jsonc\n{\n  \"mcpServers\": {\n    \"tweetkit\": {\n      \"command\": \"uvx\",\n      \"args\": [\"--from\", \"tweetkit-x[mcp]\", \"tweetkit-mcp\"],\n      \"env\": { \"TWEETKIT_COOKIE_KEYCHAIN\": \"tweetkit\" }\n    }\n  }\n}\n```\n\nAlready `pip install`ed? Use the console script directly:\n\n```jsonc\n{ \"mcpServers\": { \"tweetkit\": { \"command\": \"tweetkit-mcp\",\n  \"env\": { \"TWEETKIT_COOKIE_KEYCHAIN\": \"tweetkit\" } } } }\n```\n\nIf you don't set any cookie env var, that's fine — the server starts unauthenticated and asks the user to set the cookie from inside the chat (below).\n\n### Frictionless auth, from inside the chat\n\nThe server **describes each value** and offers the lowest-effort path. A typical first run:\n\n1. The client calls **`auth_status`** → *not authenticated*.\n2. The server’s instructions tell the assistant to ask you for your cookie and explain **what `auth_token` and `ct0` are** and how to copy them.\n3. You paste the `Cookie:` header once → **`set_session_cookie(\"<paste>\")`** → stored (Keychain or file) and activated. Or point **`import_cookie_from_file(\"~/Downloads/x.com.har\")`** at a HAR / storage-dump zip.\n\nThe raw cookie is never echoed back.\n\n### Tools\n\n| Tool | What it does |\n|---|---|\n| `auth_status` | Is a valid session loaded? (call first) |\n| `set_session_cookie(cookie_header)` | Set auth from a pasted `Cookie:` header — explains exactly what to copy |\n| `import_cookie_from_file(path)` | Set auth from a HAR / storage-dump `.zip` / cookie JSON |\n| `post_tweet(text, image_path?, reply_to?)` | Post a tweet (optional image / reply) |\n| `post_thread(tweets[])` | Post a thread |\n| `quote_tweet(text, quote_tweet_id, image_path?)` | Quote-tweet an existing tweet |\n| `delete_tweet(tweet_id)` / `delete_tweets(ids[])` | Delete tweets |\n| `like_tweet` / `unlike_tweet` / `retweet` / `unretweet` / `bookmark_tweet` / `unbookmark_tweet` | Engage (each reversible) |\n| `get_my_tweets(limit?)` / `get_user_tweets(username, limit?)` | Read a timeline |\n| `get_tweet(tweet_id)` | Fetch one tweet by id |\n| `search_my_tweets(query, regex?, limit?)` | Find your own tweets (local filter — great for cleanup) |\n| `search_x(query, latest?, limit?)` | Search **all of X** (real search, supports operators) |\n| `post_note` / `schedule_tweet` / `unschedule_tweet` | Long-form note tweets; schedule / cancel |\n| `follow_user` / `unfollow_user` / `block_user` / `unblock_user` / `mute_user` / `unmute_user` | Social-graph actions |\n| `pin_tweet` / `unpin_tweet` | Pin / unpin to your profile |\n| `get_home_timeline` / `get_replies` / `get_notifications` / `get_bookmarks` | Feeds & conversations |\n| `get_user_profile` / `get_followers` / `get_following` / `get_likers` / `get_retweeters` / `get_user_likes` | People & profiles |\n\n> **Not (yet) supported:** DMs, polls (create/vote), Lists, profile editing, Spaces, hide-reply. These need a captured HAR of that exact action to pin down the endpoint — easy to add on request.\n\n---\n\n## How it works\n\nWhen you act in the browser, x.com calls a handful of internal endpoints. tweetkit reproduces them 1:1:\n\n1. **`x-client-transaction-id`** — X requires an anti-bot header derived from the page's `ondemand.s` JS and a home fetch. Generated with the standalone [`x-client-transaction-id`](https://pypi.org/project/x-client-transaction-id/) package.\n2. **Media upload** (images) — `POST upload.x.com/i/media/upload.json` as `INIT` → `APPEND` → `FINALIZE`, yielding a `media_id`.\n3. **Write / delete** — `POST /i/api/graphql/<queryId>/CreateTweet` and `.../DeleteTweet`.\n4. **Read** — `GET /i/api/graphql/<queryId>/UserTweets` (paginated by cursor), plus `UserByScreenName` to resolve `@handles`.\n\nAuth is just your **cookie** + the public web **bearer** token (the same non-secret bearer embedded in x.com for every visitor) + the **`ct0`** CSRF value echoed as `x-csrf-token`.\n\n```\nyour cookie ──► x-client-transaction-id ──► CreateTweet / DeleteTweet / UserTweets ──► result\n```\n\n> The GraphQL **query IDs** live in [`tweetkit_x/constants.py`](tweetkit_x/constants.py). X rotates them every few weeks; if a call starts failing, refresh them from a fresh HAR (the file has step-by-step notes).\n\n---\n\n## Security\n\n- The cookie is the only secret. It's stored **chmod 600** in `~/.config/tweetkit/cookie.txt` or the macOS **Keychain**, and `.gitignore` blocks `cookie.txt`, `.env`, `*.har`, and `*.zip`.\n- tweetkit **never prints** the cookie value — importers report only which cookies were found and their lengths.\n- The bundled **bearer** is X's public web bearer, identical for every visitor — not a secret.\n\n---\n\n## Disclaimer\n\nThis project automates your **own** X web session for **personal** use. Automating the web session is **against X's Terms of Service**, and using it may put your account at risk of rate-limiting, suspension, or ban. **You are solely responsible** for how you use it. Provided **as-is**, without warranty. Not affiliated with X Corp.\n\n## License\n\n[MIT](LICENSE) © 2026 ns0bj\n",
  "bytes": 15218,
  "sha": "4e7b852ed91122235560d6e2c2b6425ea2186770dd9b254b0e6a76adbe83d64f",
  "repo_slug": "nsozturk/tweetkit-x",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_nsozturk_tweetkit_x_05d4da2b/readme"
}