chirp
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@chirpwhat are the top trends on X right now?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
X retired its free v1.1 API and the paid tiers are prohibitively expensive for personal agents. This project re-uses the same GraphQL endpoint the x.com web app calls, wraps it in a small MCP server, and exposes eight tools your agent can call directly: read your timeline, search, check trends, read a profile — and post or reply.
┌──────────────┐ MCP (HTTP) ┌──────────────────┐
│ AI agent │ ───────────────────────▶ │ chirp │
│ (Claude, │ /mcp (stateless │ (Node, :8899) │
│ kelivo, │ streamable HTTP) │ │
│ Cursor …) │ │ ┌─────────────┐ │
└──────────────┘ │ │ read path │──┼──▶ twitter-scraper
│ └─────────────┘ │ (cookie auth)
│ ┌─────────────┐ │
│ │ write path │──┼──▶ CreateTweet GraphQL
│ └─────────────┘ │ (direct fetch)
└──────────────────┘
│
cookies.json
(auth_token + ct0)Why this project is a little unusual
There are two ways people build "free Twitter bots":
A scraper library (
twitter-scraper,agent-twitter-client, …). These are great for reading, but the write path in most libraries pins a stale GraphQL query id. X rotates these ids periodically, and when it does, posting stops working — the endpoint returns HTTP 200 with an emptytweet_results: {}and no error.A full web-driver (Playwright/Selenium driving the real site). This always works but is heavy, slow, and fragile to DOM changes.
This project splits the difference:
Read uses
@the-convocation/twitter-scraper(stable, cookie-authenticated).Write is a single direct
fetchto theCreateTweetGraphQL mutation, with the query id and auth header shape kept as plain configuration so you can re-point them when X rotates them — no library rewrite.
That decision is the whole reason this server keeps working after X changes its API: the two fragile constants (CREATE_TWEET_QUERY_ID and the bearer token) are environment variables, and the README below tells you exactly how to re-capture them from your own browser.
Related MCP server: Twitter MCP
Tools
Tool | Action | Read/Write |
| Home timeline — For You feed ( | read |
| Search tweets by keyword | read |
| Current trending topics | read |
| Read one tweet by id | read |
| A user's recent tweets | read |
| A user's profile (followers / following / bio) | read |
| Post a new tweet | write |
| Reply to a tweet | write |
All tools return plain JSON, so any MCP-compatible client can call them.
Requirements
Node.js ≥ 18 (uses global
fetch).An X account you're willing to use from a datacenter IP (see the rate-limit note in Troubleshooting).
Quick start (local)
git clone https://github.com/<you>/chirp.git
cd chirp
npm install
# 1. Capture your X session cookies (opens a browser for you to log in)
npm i -D playwright && npx playwright install chromium
npm run capture-cookies # → writes cookies.json
# 2. Set your handle (required for x_read_timeline)
cp .env.example .env # then edit X_USERNAME
# 3. Run
npm start # → listening on :8899Verify it's up:
npm run smoke-test
# initialize: { name: 'chirp', version: '1.0.0' }
# tools: x_read_timeline, x_search, x_trends, x_get_tweet, x_get_user_tweets, x_profile, x_post, x_replyThen connect it to your MCP client. For Claude Code:
claude mcp add chirp --transport http http://127.0.0.1:8899/mcpConfiguration
Copy .env.example to .env. Every value is optional except X_USERNAME (for the timeline tool).
Variable | Default | Purpose |
|
| HTTP listen port |
|
| Path to captured session cookies |
| (empty) | Public hostname when behind a tunnel/proxy — required for remote use (see below) |
| (empty) | If set, clients must send |
| (empty) | Your handle without |
| (built-in) | Override when X rotates the web-app bearer token |
| (built-in) | Override when X rotates the CreateTweet query id |
| (built-in) | Override when X rotates the HomeTimeline (For You) query id |
| (built-in) | Override when X rotates the HomeLatestTimeline (Following) query id |
Deploy to a VPS (with Cloudflare Tunnel)
For a phone or any client that isn't on your LAN, run the server on a VPS and expose it over HTTPS. The steps below assume Ubuntu and Cloudflare Tunnel.
1. Install and run the server
# on the VPS
sudo apt update && sudo apt install -y nodejs npm
# (if the distro's Node is <18, install via nvm or nodesource)
git clone https://github.com/<you>/chirp.git && cd chirp
npm install
npm run capture-cookies # needs a display; or run it locally and scp cookies.json up
cp .env.example .env
# PUBLIC_HOST=chirp.example.com ← your tunnel hostname
# MCP_AUTH_KEY=<openssl rand -hex 24>
npm i -g pm2
pm2 start ecosystem.config.example.cjs --name chirp # after copying to ecosystem.config.cjs2. Expose it with Cloudflare Tunnel
cloudflared tunnel create chirp
cloudflared tunnel route dns chirp chirp.example.com
# in ~/.cloudflared/config.yml:
# tunnel: <tunnel-id>
# credentials-file: /home/<user>/.cloudflared/<tunnel-id>.json
# ingress:
# - hostname: chirp.example.com
# service: http://localhost:8899
# - service: http_status:404
pm2 start cloudflared -- tunnel --config ~/.cloudflared/config.yml⚠️ The DNS-rebinding gotcha
The MCP SDK's createMcpExpressApp() enables DNS-rebinding protection and only allows localhost by default. Behind a tunnel the Host header is your public domain, so requests fail with "Invalid Host". That's what PUBLIC_HOST is for — the server adds your hostname to the allow-list:
// src/server.mjs
const allowedHosts = config.publicHost
? [config.publicHost, 'localhost', '127.0.0.1']
: ['localhost', '127.0.0.1'];Set PUBLIC_HOST to your tunnel hostname and this error disappears.
3. Point your client at the public endpoint
claude mcp add chirp --transport http https://chirp.example.com/mcp \
--header "Authorization: Bearer <MCP_AUTH_KEY>"Note on region. If your VPS is behind a network that blocks x.com (e.g. a mainland-China IP), the write path will fail. Run the server on a VPS whose IP can reach x.com directly.
Survival guide: keeping the write path alive
The write path is reverse-engineered from the x.com web app, so X can break it at any time. Every failure you'll hit is one of the four below, and each has a known fix.
1. Empty result — "posting returns nothing" (rotated query id)
Symptom: x_post succeeds (no error) but returns { ok: true, id: "", url: "" }, and the tweet never appears. The raw response is HTTP 200 with tweet_results: {}.
Cause: X rotated the CreateTweet GraphQL query id. Your X_CREATE_TWEET_QUERY_ID is stale.
Fix — re-capture it from your browser:
Log in to x.com in a desktop browser, open DevTools → Network.
Type a tweet and press Post.
Filter for
graphql. Find the request tohttps://x.com/i/api/graphql/<ID>/CreateTweet.Copy the
<ID>(a 22-character token) and set it asX_CREATE_TWEET_QUERY_ID.
2. Error 226 — "looks automated"
Symptom: X returns error code 226.
Cause: The write path sent a full cookie jar that includes stale Cloudflare tokens (cf_clearance, __cf_bm). Those stale tokens mark the request as automated.
Fix: This server already filters the write-path cookie string to only auth_token, ct0, twid, and lang. If you call the GraphQL endpoint yourself, don't send the Cloudflare cookies.
3. Error 344 — daily post limit
Symptom: X returns error code 344 ("daily limit for sending Tweets").
Cause: New or low-activity accounts get a very small daily post quota. It resets after ~24h.
Fix: None in code — this is an account-level limit. Posting regularly and gaining followers raises the quota over time.
4. Error 32 — authentication failed
Symptom: X returns error code 32.
Cause: auth_token is expired or invalid.
Fix: Re-run npm run capture-cookies to refresh the session.
Also: the auth header shape
Writes must send x-twitter-auth-type: OAuth2Session — not the OAuth2Client value many write libraries use. The wrong value is the #1 reason third-party write libraries return empty results. The server handles this for you; it's documented here because it's the detail most people get wrong when they reimplement the call.
Troubleshooting
Symptom | Likely cause | Fix |
|
| Set |
| Client didn't send | Use a real MCP client; for curl, add the header |
| Forgot the capture step | Run |
| Expired cookies | Re-capture cookies |
Read works, write returns empty | Rotated query id | See survival guide #1 |
Security
cookies.json,.env, and anyMCP_AUTH_KEYare secrets. They are git-ignored — never commit them.The X password is never stored anywhere. Only the session cookies are captured and used.
If you expose the server publicly, set
MCP_AUTH_KEY. Without it, anyone who can reach the port can post as you.Use HTTPS (the Cloudflare Tunnel does this for you). Never expose the raw HTTP port to the internet.
Disclaimer
This project is not affiliated with, endorsed by, or sponsored by X Corp / Twitter. It uses X's internal, undocumented API, which may change without notice and which your use of may violate X's Terms of Service. You are responsible for your own account and for complying with X's rules. Use at your own risk; the authors assume no liability.
License
AGPL-3.0 — free software. If you run a modified version as a network service, the AGPL requires you to offer its source to your users.
中文详细教程见 README.zh-CN.md。
This server cannot be deployed
Maintenance
Related MCP Connectors
Twitter/X read-only MCP server — 12 tools: search, users, tweets, followers, timelines, trends.
Driflyte MCP server which lets AI assistants query topic-specific knowledge from web and GitHub.
An MCP server that gives your AI access to the source code and docs of all public github repos
Official MCP server for Agentwork — delegate tasks to AI agents with human-in-the-loop
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceAn MCP server for interacting with the Twitter/X API v2, enabling AI assistants to retrieve tweets, post content, reply, quote, and more programmatically.1,026 npm13MIT
- AlicenseAqualityCmaintenanceMCP server for Twitter/X enabling AI agents to search, post, reply, and engage with tweets.147 npm2MIT
- AlicenseNot gradedqualityCmaintenanceAn MCP server that enables posting tweets, reading timelines, searching posts, and interacting with X (Twitter) API.6 npm2MIT
- AlicenseBqualityAmaintenanceAn MCP server that enables AI assistants to interact with Twitter/X through a single authenticated session without an official API key, providing 27 tools for reading and writing tweets, user management, timeline access, trends, and direct messages.2712 PyPI1MIT