Content
# mcp-twitterapi
A local [MCP](https://modelcontextprotocol.io) server that fetches tweets from a specific X (Twitter) user via [twitterapi.io](https://twitterapi.io).
Build with
```
░█░█░█░█░█▀▀░█▀█░█▀▄░▀█▀░░░█▀█░█░░░█░█░█▀▀░▀█▀░█▀█░█▀▀
░█▀▄░█░█░▀▀█░█▀█░█▀▄░░█░░░░█▀▀░█░░░█░█░█░█░░█░░█░█░▀▀█
░▀░▀░▀▀▀░▀▀▀░▀░▀░▀░▀░▀▀▀░░░▀░░░▀▀▀░▀▀▀░▀▀▀░▀▀▀░▀░▀░▀▀▀
```
Build with [create-mcp@kusari-plugin](https://github.com/kusarixyz/kusari-plugins) skill.
## Requirements
- Node.js 22+
- A twitterapi.io API key ([get one here](https://twitterapi.io/dashboard))
## Install
The package is on [npm](https://www.npmjs.com/package/@0x50b/mcp-twitterapi) but you don't have to install it. Using npx is perfectly fine.
Add to your Claude Desktop config:
| Platform | Config path |
|---|---|
| macOS | `~/Library/Application Support/Claude/claude_desktop_config.json` |
| Windows | `%APPDATA%\Claude\claude_desktop_config.json` |
| Claude Code | project-local `.mcp.json` (also set `enableAllProjectMcpServers: true` in `.claude/settings.local.json`) |
```json
{
"mcpServers": {
"twitter": {
"command": "npx",
"args": ["-y", "@0x50b/mcp-twitterapi"],
"env": {
"TWITTERAPI_IO_API_KEY": "your-key-here"
}
}
}
}
```
Restart Claude Desktop fully (⌘Q on macOS, not just close the window).
### Run from a local clone
If you'd rather run your own checkout (e.g. for a forked version, an unpublished change), build the server first:
```sh
git clone https://github.com/designoor/mcp-twitterapi.git
cd mcp-twitterapi
pnpm install
pnpm build
```
Then point your client config at the built `dist/index.js` using an **absolute path**:
```json
{
"mcpServers": {
"twitter": {
"command": "node",
"args": ["/absolute/path/to/mcp-twitterapi/dist/index.js"],
"env": {
"TWITTERAPI_IO_API_KEY": "your-key-here"
}
}
}
}
```
Re-run `pnpm build` after any source change, then restart the client.
## Tools
### `fetch_tweets`
Fetch tweets from a specific X (Twitter) user via twitterapi.io's advanced search. Combines `from:{username}` with optional time bounds, extra query terms, and content filters. Returns up to `limit` tweets in reverse-chronological order, trimmed to high-signal fields (text, counts, author, quoted/retweeted content, attached media).
#### Parameters
| Param | Type | Default | Description |
|---|---|---|---|
| `username` | string | **required** | X handle without `@` |
| `since` | string | — | ISO 8601 or relative (`30s`, `45m`, `6h`, `2d`, `1w`) |
| `until` | string | now | ISO 8601 or relative |
| `limit` | int 1–2000 | `200` | max tweets returned in this call |
| `query` | string | — | extra terms AND-combined with `from:{username}`; supports quoted phrases and `OR` |
| `include_retweets` | bool | `true` | when false, appends `-filter:retweets` |
| `include_quotes` | bool | `true` | when false, appends `-filter:quote` |
| `include_replies` | bool | `true` | when false, appends `-filter:replies` |
| `query_type` | `Latest` \| `Top` | `Latest` | sort order |
| `lang` | ISO 639-1 | — | e.g. `en`, `es` |
| `min_faves` | int ≥ 0 | — | minimum like count |
#### Response
| Field | Type | Meaning |
|---|---|---|
| `tweets` | array | Trimmed tweet objects in reverse-chronological order |
| `count` | int | Number of tweets returned |
| `window` | `{ since, until }` | Resolved absolute bounds of the requested window |
| `fetched` | `{ newest, oldest }` \| `null` | Oldest/newest returned tweet timestamps; `null` when `count === 0` |
| `hasMore` | bool | `false` = window fully fetched. `true` = more exist earlier |
| `nextCall` | `{ since, until }` \| `null` | Exact params for the follow-up call when `hasMore: true` |
| `hint` | string | Human-readable guidance (what was returned, what to do next) |
| `queryString` | string | Echo of the query sent to twitterapi.io — for debugging |
Each tweet includes `hasMedia` (only when true) and `media[]` with `{type, url, videoUrl?, altText?}`:
| Field | Type | Meaning |
|---|---|---|
| `type` | `photo` \| `video` \| `animated_gif` | media kind |
| `url` | string | image URL (photo) or thumbnail/poster (video, gif) |
| `videoUrl` | string | highest-bitrate mp4 — video and gif only |
| `altText` | string | author-provided accessibility description, when present |
Quoted and retweeted tweets surface only `hasMedia` (not the full media array) to keep nesting cheap.
#### Response size cap
Responses are capped at ~450KB per call. The server declares `anthropic/maxResultSizeChars: 500000` in its tool metadata so Claude Code honors the 500K-character hard ceiling instead of the default 25K-token cap. When the byte budget trims a call mid-fetch, two extra fields appear:
| Field | Type | Meaning |
|---|---|---|
| `fetchedTotal` | int | Tweets fetched before trimming |
| `droppedForSize` | int | `fetchedTotal − count` (tweets discarded to fit under the cap) |
The `hint` then spells out the covered timespan and drop count — so you can continue with `nextCall` or lower `limit`.
#### Continuation pattern
When a window contains more than `limit` tweets, iterate:
```
fetch_tweets({ username: "elonmusk", since: "30d", limit: 200 })
→ hasMore: true, nextCall: { since: "...", until: "2026-04-15T10:23:00Z" }
fetch_tweets({ username: "elonmusk", since: "...", until: "2026-04-15T10:23:00Z", limit: 200 })
→ hasMore: true, nextCall: { ... }
... until hasMore: false.
```
<details>
<summary>How pagination works internally</summary>
Each call paginates by **walking the `until` bound backward** — per twitterapi.io's own guidance (their cursor is documented as unreliable). After each API page, the tool narrows `until` to one second before the oldest tweet it just received, queries again, and stops when the API reports no more tweets, when a page yields zero new IDs, or when the caller's `limit` / byte budget is reached. Duplicates are filtered by tweet ID as a safety net for boundary tweets. A pagination safety ceiling of 110 API calls per invocation prevents runaway cost on pathological windows; if it fires, `hasMore` stays `true` and the returned `nextCall` lets you resume from the oldest fetched tweet.
</details>
### `fetch_tweet_by_id`
Fetch one or more tweets by their numeric IDs via twitterapi.io. Returns the same trimmed tweet shape as `fetch_tweets` (text, counts, author, media). Does **not** search or filter — use `fetch_tweets` for user timeline search.
#### Parameters
| Param | Type | Default | Description |
|---|---|---|---|
| `tweet_ids` | string[] | **required** | Array of numeric tweet ID strings (e.g. `["1846987139428634858"]`). Max 100 per call. |
#### Response
A flat array of trimmed tweet objects (same shape as individual items in the `fetch_tweets` `tweets` array). Returns an error when none of the requested IDs resolve to a tweet.
## Skills
This repo also ships an optional Claude Code skill, [`fetch-tweets`](skills/fetch-tweets/SKILL.md), that wraps `fetch_tweets` with a `[since, until]` pagination loop and prompt-injection guidance for tweet content. It is not bundled with the MCP server — see [`skills/`](skills/) for what's available and how to copy it into your own skills directory.
## Local development (contributors only)
```bash
git clone https://github.com/designoor/mcp-twitterapi.git
cd mcp-twitterapi
pnpm install
pnpm typecheck # tsc --noEmit
pnpm test # vitest run
pnpm build # compiles to dist/
pnpm dev # runs via tsx without a build step
TWITTERAPI_IO_API_KEY=... node dist/index.js # manual stdio run
```
To test changes against Claude Desktop locally, point the config at your built `dist/index.js` via its absolute path instead of `npx`.
MCP Config
Below is the configuration for this MCP Server. You can copy it directly to Cursor or other MCP clients.
mcp.json
Connection Info
You Might Also Like
cc-switch
All-in-One Assistant for Claude Code, Codex & Gemini CLI across platforms.
awesome-mcp-servers
A collection of MCP servers.
git
A Model Context Protocol server for Git automation and interaction.
oh-my-opencode
Background agents · Curated agents like oracle, librarians, frontend...
TrendRadar
TrendRadar: Your hotspot assistant for real news in just 30 seconds.
Appwrite
Build like a team of hundreds