1. Conduid
  2. Social
  3. Chirp
MCP server · Social

Chirp

An MCP server that gives an AI agent read + write access to X (Twitter) via the internal web API — no paid API key required.

Unclaimed AGPL-3.0 social
39Low

Scored 4 days ago · breakdown

About Chirp

Chirp is an MCP server published by yuyuki-guyu in the Social category: an MCP server that gives an AI agent read + write access to X (Twitter) via the internal web API — no paid API key required.

The repository has 36 stars and 5 forks, with the last commit 26 days ago. Recent commit activity is the strongest signal that reported issues will get attention.

Install

From source
git clone https://github.com/yuyuki-guyu/chirp

This server has no ConduID identity, so agent calls to it are not receipted. Pin the version you install and review the source before granting it credentials.

Ask AI

✦

Ask AI about Chirp

Powered by Claude · Grounded in docs

I know everything about Chirp. Ask me about installation, configuration, usage, or troubleshooting.

Security checks

Static checks derived from the repository 2 days ago. Heuristics on the source, not an audit.

  • ✓README presentThe repository ships documentation.
  • ✓License declaredUsable under a stated license.
  • !Tests presentNo tests found. Behaviour changes may go unnoticed between versions.
  • !Dependencies pinnedNo lockfile. Installs can pull different code over time.
  • ✓No dynamic code executionSource scan found no eval, dynamic Function, or shell execution.

1 check has not been run yet.

README

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":

  1. 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 empty tweet_results: {} and no error.
  2. 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 fetch to the CreateTweet GraphQL 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.

Tools

Tool Action Read/Write
x_read_timeline Home timeline — For You feed (tab="foryou", default) or Following feed (tab="following") read
x_search Search tweets by keyword read
x_trends Current trending topics read
x_get_tweet Read one tweet by id read
x_get_user_tweets A user's recent tweets read
x_profile A user's profile (followers / following / bio) read
x_post Post a new tweet write
x_reply 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 :8899

Verify 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_reply

Then connect it to your MCP client. For Claude Code:

claude mcp add chirp --transport http http://127.0.0.1:8899/mcp

Configuration

Copy .env.example to .env. Every value is optional except X_USERNAME (for the timeline tool).

Variable Default Purpose
PORT 8899 HTTP listen port
COOKIES_FILE ./cookies.json Path to captured session cookies
PUBLIC_HOST (empty) Public hostname when behind a tunnel/proxy — required for remote use (see below)
MCP_AUTH_KEY (empty) If set, clients must send Authorization: Bearer <key> or x-api-key: <key>
X_USERNAME (empty) Your handle without @, for x_read_timeline
X_BEARER_TOKEN (built-in) Override when X rotates the web-app bearer token
X_CREATE_TWEET_QUERY_ID (built-in) Override when X rotates the CreateTweet query id
X_HOME_TIMELINE_QUERY_ID (built-in) Override when X rotates the HomeTimeline (For You) query id
X_HOME_LATEST_TIMELINE_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.cjs

2. 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:

  1. Log in to x.com in a desktop browser, open DevTools → Network.
  2. Type a tweet and press Post.
  3. Filter for graphql. Find the request to https://x.com/i/api/graphql/<ID>/CreateTweet.
  4. Copy the <ID> (a 22-character token) and set it as X_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
Invalid Host: <domain> PUBLIC_HOST not set Set PUBLIC_HOST to your tunnel hostname
Not Acceptable (406) Client didn't send Accept: application/json, text/event-stream Use a real MCP client; for curl, add the header
cookies.json not found Forgot the capture step Run npm run capture-cookies
isLoggedIn=false Expired cookies Re-capture cookies
Read works, write returns empty Rotated query id See survival guide #1

Security

  • cookies.json, .env, and any MCP_AUTH_KEY are 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。

README fetched from the repository 2 days ago. The original is authoritative.

Questions

About Chirp

How do I install Chirp?

No package has been published for this server, so install it from source: clone the repository and follow its README.

Is Chirp safe to use with an AI agent?

Its trust score is 39 out of 100 (low). It passes 3 of the 5 static security checks run so far; the failures are listed above. It has no ConduID identity yet, so agent calls to it are not receipted.

Is Chirp still maintained?

Yes — the last commit was 26 days ago. The repository has 36 stars and 5 forks.