Twitter MCP服务器

一个轻量级的Twitter MCP服务器,支持读取和写入Twitter数据,适用于社交媒体管理和数据分析场景。

  • by armatrix
  • TypeScript
  • 本地部署
  • 2 星标
  • 更新于
在 GitHub 查看 armatrix/twitter-mcp

使用说明

twitter-mcp + xdata

X (Twitter) data access for Claude Code, split across two front ends over one set of adapters:

  • xdata CLI — all content reads. This is the entry point you want.
  • MCP server — writes, post-volume counts, and usage only.

Zero compilation — edit and run instantly with Bun.

Why reads live in the CLI, not in MCP

Both data providers bill per returned record, and an MCP tool result lands in the model's context in full. One 20-post search is 20–40k tokens of JSON. From a shell the same call can be trimmed, piped through jq, or written straight to disk with only a summary coming back.

Measured, not estimated:

tools schema in context a 20-post search in context
before (reads in MCP) 21 ~1,898 tok 20,000–40,000 tok
now (reads in CLI) 9 ~986 tok ~600 tok (--out: ~30 tok)

The 900-token schema saving is the small half. The point is that large payloads no longer have a path into the conversation at all.

Architecture

┌───────────────────────────┐   ┌──────────────────────────────────────┐
│  xdata CLI                │   │  MCP server (twitter-mcp v2.0)       │
│  all content reads        │   │  writes · counts · usage · feeds     │
└─────────────┬─────────────┘   └──────────────┬───────────────────────┘
              │                                │
              ▼                                ▼
   ┌──────────────────────┐         ┌────────────────────┐
   │ ReaderClient  (port) │         │ WriterClient       │  OAuth 1.0a
   ├──────────────────────┤         │ CookieClient       │  ct0 + auth_token
   │ primary              │         │ CountsClient       │  OAuth2 app-only
   │  twitterapi.io       │         └────────────────────┘
   │  $0.00015/post       │
   │       │ on 401/402/429/5xx or an HTTP-200 `status:error`
   │       ▼              │
   │ fallback             │   every fallback is tagged in the response —
   │  official X API v2   │   a silent switch would hide a 33x price jump
   │  $0.005/post         │
   └──────────────────────┘

Which channel for what

Need Channel Cost shape
Content (search, timelines, replies, profiles, graph) twitterapi.io $0.00015/post, $0.00018/profile
Same, when the primary is out of credits or down official v2 (auto, tagged) $0.005/post, $0.010/user — 33x
Volume over time (trends, ranking, spotting peaks) official counts $0.005 per request — any match volume, and it does not consume the 3M post-read cap
Writes (post/like/retweet/delete) official OAuth 1.0a $0.015/post ($0.20 with a link)
Replies the Free tier blocks (403) cookie GraphQL free

The cheap pattern is two-stage: counts first to find which query and which window matter (effectively free), then pull posts for only that window. Large blind searches are the expensive mistake.

Setup

git clone https://github.com/armatrix/twitter-mcp.git && cd twitter-mcp && bun install
mkdir -p ~/.twitter-mcp && cp config.example.json ~/.twitter-mcp/config.json && chmod 600 ~/.twitter-mcp/config.json

~/.twitter-mcp/config.json is the single source of truth for every credential:

{
  "reader":   { "provider": "twitterapi.io", "api_key": "new1_..." },
  "writer":   { "provider": "twitter-official", "api_key": "...", "api_secret_key": "...",
                "access_token": "...", "access_token_secret": "..." },
  "official": { "bearer": "AAAA..." },
  "cookie":   { "ct0": "...", "auth_token": "..." },
  "defaults": { "timeline_count": 20, "search_count": 20 }
}
  • reader.api_key — twitterapi.io. Optional: without it every read falls back to the official API and is tagged as such (33x the price, same capability).

  • writer.* — X Developer Portal → your App → Keys and Tokens.

  • official.bearer — derive it, don't go hunting for it. OAuth 1.0a is rejected by counts, location trends, usage and filtered stream; an app-only bearer is one call away from the consumer key/secret you already have:

    curl -s -u "<consumer_key>:<consumer_secret>" \
         -d 'grant_type=client_credentials' https://api.x.com/oauth2/token
    
  • cookie.* — DevTools → Application → Cookies → x.com → copy ct0 and auth_token. Optional; only powers the reply fallback. ct0 rotates every few hours.

Install the CLI and register the MCP server:

ln -sf "$PWD/bin/xdata" ~/.local/bin/xdata     # or point a wrapper at src/cli.ts
claude mcp add twitter -- bun run "$PWD/src/index.ts" -config ~/.twitter-mcp/config.json

CLI

xdata usage                                    # both providers: balance, spend, cap
xdata counts '<query>' --day|--hour [--all]    # per-request billing → trends & ranking
xdata search '<q> min_faves:30' --top -n 20    # min_faves is server-side (official v2 lacks it)
xdata user|about|timeline|mentions <handle>
xdata replies|quotes|thread|retweeters <tweetId>
xdata tweets <id,id,...>                       # batch by id
xdata batch-users <id,id,...>                  # bulk profiles, cheaper per user
xdata followers|followings <handle>
xdata follower-ids <handle>                    # IDs only: 0.45cr vs 18cr per profile — 40x cheaper
xdata verified-followers <handle>              # $0.3/1000
xdata usersearch <query>                       # no official equivalent exists
xdata follows <a> <b>                          # mutual-follow check (100cr)
xdata article <tweetId>                        # long-form body (no official equivalent)
xdata trends [woeid]

Output control — this is where the token savings are:

--out FILE   full JSON to disk, one summary line to stdout
--json       raw          --jsonl   one record per line (pipe to jq)
-n N         max records  -w N      text column width      -q  no summary line

Every command prints cost≈, because both providers bill per record and it is otherwise invisible until the dashboard catches up.

MCP tools (9)

Tool Channel Notes
post_tweet OAuth 1.0a → cookie Up to 4 images; auto-falls back to cookie auth on a reply 403
like_tweet unlike_tweet retweet delete_tweet OAuth 1.0a
get_tweet_counts official app-only Per-request billing; counts only (no text/authors)
get_api_usage official app-only Post reads against the monthly cap + estimated spend
get_for_you_feed get_following_feed cookie The authenticated account's own timelines

There are deliberately no content-read tools here. The server says so on startup.

Reply fallback

post_tweet(text, reply_to_tweet_id) → OAuth 1.0a POST /2/tweets
  ├─ 200 → done (method: "oauth")
  └─ 403 (Free tier reply restriction) → cookie GraphQL CreateTweet (method: "cookie")
                                       → or an error if no cookie is configured

Endpoint index

docs/endpoint-index.md — 264 endpoints across both providers, generated from their machine-readable specs (api.x.com/2/openapi.json v2.168 / 157 paths + twitterapi.io / 76 paths), grouped by function with auth requirements and billing per row. Consult it before assuming a capability is missing.

⚠️ A spec is a lead, not a fact. /twitter/user/tweet_timeline is documented with a top-level tweets array but actually answers data.tweets; the adapter accepts both, because reading only one shape yields an empty array indistinguishable from "no results".

Known limitations

Issue Reality
/twitter/user/following looks broken It isn't a route — the path is followings (plural). Wrong URLs answer HTTP 200 status:error msg:"user not found"
last_tweets ignores count No such parameter; a page is always ~20 and you are billed per returned tweet. Narrow with cursor, and prefer the stream API for monitoring — the endpoint docs say polling it frequently "will cost you a lot"
Official counts/trends/usage/stream return 403 They reject OAuth 1.0a. Use official.bearer
Official v2 has no user search, and no long-form article body Only twitterapi.io has these
firehose / sample streams 403 — Enterprise tier
Filtered stream billing Not in the pricing page; rule slots are visible (cap_per_client_app: 5, cap_per_project: 15) but per-delivery cost is unverified
Free-tier OAuth blocks replies to non-interacted posts Cookie fallback handles it
ct0 rotates every few hours Re-copy from the browser when cookie auth starts failing

Project structure

src/
├── cli.ts                      # xdata — all content reads
├── index.ts                    # MCP server — writes, counts, usage, feeds
├── config.ts
├── clients/
│   ├── reader.ts               # port + router (primary → tagged fallback)
│   ├── reader-twitterapi.ts    # primary adapter
│   ├── reader-official.ts      # fallback adapter (X API v2, OAuth 1.0a)
│   ├── counts.ts               # app-only bearer: counts / trends / usage
│   ├── writer.ts               # OAuth 1.0a writes + media upload
│   └── cookie.ts               # GraphQL via browser session
├── lib/
│   ├── normalize-tweet.ts      # GraphQL → NormalizedTweet
│   └── v2-to-normalized.ts     # X API v2 → NormalizedTweet
└── tools/
    ├── discovery.ts            # get_tweet_counts, get_api_usage, cookie feeds
    └── write.ts                # post/like/unlike/retweet/delete
docs/
├── endpoint-index.md           # both providers, generated from specs
PATCH-reader-dual-channel.md    # why it is built this way + what was learned the hard way

Development

No build step. Edit and restart the server (stdio servers do not hot-reload — /mcp reconnect after changing anything the server loads).

bun run src/index.ts -config ~/.twitter-mcp/config.json
bunx tsc --noEmit

Tech stack

Bun · @modelcontextprotocol/sdk · oauth-1.0a · bun:sqlite · X internal GraphQL (no deps)

License

MIT