twitter-mcp
README.md
# Twitter MCP Server
TypeScript [Model Context Protocol](https://modelcontextprotocol.io) server for the Twitter/X API v2.
- **Transport:** Streamable HTTP (stateless)
- **Auth:** OAuth user access token via the `access-token` header — required only for **tool calls**, not for list/initialize
- **Runtime:** Node.js 20+, Express, `@modelcontextprotocol/sdk`
- **Docker:** multi-stage production image included
This server does **not** run the OAuth dance itself. Obtain a user access token from your OAuth 2.0 (or OAuth 1.0a user-context) flow and send it when invoking tools.
## Endpoints
| Method | Path | Description |
|--------|-----------|--------------------------------------------------|
| `POST` | `/mcp` | MCP Streamable HTTP endpoint (tools) |
| `GET` | `/health` | Liveness probe |
| `GET` | `/mcp` | `405` — not used in stateless mode |
| `DELETE` | `/mcp` | `405` — no sessions to delete |
## Authentication
`access-token` is **not** required for protocol/list methods (`initialize`, `tools/list`, `ping`, etc.). It is **required** when calling a tool that hits the Twitter API.
```http
POST /mcp HTTP/1.1
Host: localhost:3000
Content-Type: application/json
Accept: application/json, text/event-stream
access-token: <OAUTH_USER_ACCESS_TOKEN>
```
Fallback (also accepted):
```http
Authorization: Bearer <OAUTH_USER_ACCESS_TOKEN>
```
If a tool is called without a token, the tool returns an error:
```text
Missing access token. Tool calls require an OAuth user access token via the `access-token` header …
```
Suggested OAuth 2.0 scopes for full tool coverage:
```
tweet.read tweet.write users.read follows.read follows.write media.write offline.access
```
> **Media / video posting** requires `media.write` in addition to `tweet.write`. Re-authorize users after adding the scope so tokens include it.
## Tools
| Tool | Description |
|------|-------------|
| `get_me` | Authenticated user profile |
| `get_user_by_username` | User lookup by handle |
| `get_user_by_id` | User lookup by ID |
| `get_tweet` | Single tweet by ID |
| `get_user_timeline` | User's recent posts |
| `get_user_mentions` | Mentions timeline |
| `search_recent_tweets` | Recent search (last 7 days) |
| `upload_media` | Download a URL and upload image / GIF / video (returns `media_id`) |
| `post_tweet` | Create a post (optional reply / quote / media) |
| `delete_tweet` | Delete own post |
| `like_tweet` / `unlike_tweet` | Like management |
| `retweet` / `undo_retweet` | Retweet management |
| `follow_user` / `unfollow_user` | Follow management |
### Posting media
1. Call **`upload_media`** with `media_url` (HTTP(S) URL). Type and category are detected from the file.
2. Wait for `{ "media_id": "..." }` (videos are processed before return).
3. Call **`post_tweet`** with `media_ids: ["<media_id>"]` and optional `text`.
X post media ([official limits](https://docs.x.com/x-api/media/quickstart/best-practices)):
| Kind | Formats | Size | Per post |
|------|---------|------|----------|
| Image | JPEG, PNG, WEBP | 5 MB each | up to 4 |
| GIF | GIF | 15 MB (≤1280×1080, ≤350 frames) | 1 |
| Video | H.264 MP4 or MOV, AAC audio | 0.5 s–20 min (125 min Premium); this server loads up to 512 MB | 1 |
Do not mix types. Audio (MP3 / WAV / M4A) is not supported — mux it into an MP4 first.
## Local development
```bash
npm install
npm run dev
# → http://0.0.0.0:3000/mcp
```
Build & run production:
```bash
npm run build
npm start
```
Environment variables:
| Variable | Default | Description |
|----------|-----------|--------------------|
| `PORT` | `3000` | HTTP port |
| `HOST` | `0.0.0.0` | Bind address |
## Docker
```bash
# Build
docker build -t twitter-mcp .
# Run
docker run --rm -p 3000:3000 twitter-mcp
# Health check
curl http://localhost:3000/health
```
## Example MCP initialize (curl)
No token needed for initialize / tools/list:
```bash
curl -sS http://localhost:3000/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-03-26",
"capabilities": {},
"clientInfo": { "name": "curl", "version": "1.0.0" }
}
}'
```
Tool calls need the token:
```bash
curl -sS http://localhost:3000/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-H 'access-token: YOUR_TOKEN' \
-d '{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": { "name": "get_me", "arguments": {} }
}'
```
## Architecture notes
1. **Stateless Streamable HTTP** — each `POST /mcp` creates a new `McpServer` + `StreamableHTTPServerTransport` with `sessionIdGenerator: undefined`, then tears them down when the response finishes. Safe for horizontal scaling and containers.
2. **Per-request token isolation** — `AsyncLocalStorage` holds the optional access token for the duration of the request so concurrent calls never mix credentials. Missing tokens only fail when tools call `getAccessToken()`.
3. **Twitter client** — `twitter-api-v2` is constructed with the user access token and used as OAuth 2.0 user-context (Bearer).
## License
MIT
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues