An MCP (Model Context Protocol) server for X (Twitter). Built in Rust using OAuth 1.0a and the X API v2.
Communicates via stdio using JSON-RPC 2.0.
| Tool | Description |
|---|---|
post_tweet |
Post a tweet with optional media (up to 4 images, 1 video, or 1 GIF) |
post_thread |
Post a thread of up to 25 tweets, each with optional media |
delete_tweet |
Delete a tweet by ID or URL |
upload_media |
Upload media for later attachment (returns a media_id) |
search_tweets |
Search recent tweets (last 7 days) with Twitter operators |
get_timeline |
Get your home timeline in reverse chronological order |
get_me |
Get the authenticated user's profile |
lookup_user |
Look up any user by @username or numeric ID |
get_followers |
List your followers |
get_following |
List who you follow |
like_tweet |
Like a tweet by ID or URL |
unlike_tweet |
Unlike a tweet by ID or URL |
retweet |
Retweet a tweet by ID or URL |
unretweet |
Undo a retweet by ID or URL |
get_dm_events |
Get recent direct messages across all conversations |
send_dm |
Send a direct message to a conversation |
cargo build --releaseProduces target/release/post-x (optimized with LTO, stripped).
Create the config file:
mkdir -p ~/.config/mcp-server-post-xCreate ~/.config/mcp-server-post-x/config.toml:
api_key = "your-api-key"
api_key_secret = "your-api-key-secret"
access_token = "your-access-token"
access_token_secret = "your-access-token-secret"Secure it:
chmod 700 ~/.config/mcp-server-post-x
chmod 600 ~/.config/mcp-server-post-x/config.tomlSee Getting credentials below for how to obtain these.
Claude Code (~/.claude.json):
{
"mcpServers": {
"post-x": {
"command": "/path/to/post-x"
}
}
}Then ask Claude things like:
- "Post a tweet saying hello world"
- "Search for tweets about Rust"
- "Show me my timeline"
- "Like this tweet: https://x.com/someone/status/123456"
- "Who are my followers?"
- "Look up @elonmusk"
| Param | Type | Required | Description |
|---|---|---|---|
text |
string | yes | Tweet text (max 280 characters) |
media |
array | no | Media to upload and attach. Each item: { path, alt_text? }. Max 4 images, or 1 video, or 1 GIF. |
media_ids |
array | no | Pre-uploaded media IDs to attach (max 4). Mutually exclusive with media. |
| Param | Type | Required | Description |
|---|---|---|---|
tweets |
array | yes | Array of tweets (max 25). Each: { text, media? } |
| Param | Type | Required | Description |
|---|---|---|---|
tweet_id |
string | yes | Tweet ID or full tweet URL |
All accept URLs like https://x.com/user/status/123456 — the ID is extracted automatically.
| Param | Type | Required | Description |
|---|---|---|---|
path |
string | yes | Local file path. Supported: jpeg/png/webp (max 5MB), gif (max 15MB), mp4 (max 512MB) |
alt_text |
string | no | Alt text (images and GIFs only, not video) |
Returns a media_id to use with post_tweet's media_ids param.
| Param | Type | Required | Description |
|---|---|---|---|
query |
string | yes | Search query. Supports: from:user, #hashtag, @mention, "exact phrase", -exclude, lang:en |
max_results |
integer | no | 10-100 (default 10) |
sort_order |
string | no | recency or relevancy |
pagination_token |
string | no | Next page token from previous response |
| Param | Type | Required | Description |
|---|---|---|---|
max_results |
integer | no | 1-100 (default 20) |
exclude |
string | no | replies, retweets, or both comma-separated |
pagination_token |
string | no | Next page token |
| Param | Type | Required | Description |
|---|---|---|---|
user |
string | yes | Username (with or without @) or numeric user ID |
| Param | Type | Required | Description |
|---|---|---|---|
max_results |
integer | no | 1-100 (default 20) |
pagination_token |
string | no | Next page token |
| Param | Type | Required | Description |
|---|---|---|---|
max_results |
integer | no | 1-100 (default 20) |
pagination_token |
string | no | Next page token |
| Param | Type | Required | Description |
|---|---|---|---|
conversation_id |
string | yes | DM conversation ID (get from get_dm_events) |
text |
string | yes | Message text |
No parameters. Returns your user ID, display name, and @username.
- Go to developer.x.com and sign up for a developer account
- Create a Project and an App in the Developer Console
- In your App settings, set up User authentication:
- App permissions: Read and write (and Direct Messages if you want DM support)
- Type: Web App, Automated App or Bot
- Callback URL:
https://example.com(not used, but required) - Website URL: any valid URL
- Go to Keys and tokens and generate:
- API Key and API Key Secret (under Consumer Keys)
- Access Token and Access Token Secret (under Authentication Tokens)
- Copy all four values into your
config.toml
The server validates credentials at startup. If you get persistent 401 errors, regenerate your tokens at developer.x.com.
cargo build # debug build
cargo run # run in dev mode
RUST_LOG=debug cargo run # debug logging (credentials are redacted)- Auth: OAuth 1.0a with HMAC-SHA1 signatures (RFC 5849, RFC 3986 percent-encoding)
- Tweet API: X API v2 (
api.x.com/2/) - Media upload: v1.1 chunked upload (
upload.twitter.com/1.1/media/upload.json) — INIT/APPEND/FINALIZE/STATUS flow for video/GIF, simple multipart for images - Media limits: JPEG/PNG/WebP up to 5MB, GIF up to 15MB, MP4 up to 512MB
- Media validation: Max 4 images OR 1 video OR 1 GIF per tweet (no mixing)
- Thread posting: 500ms delay between tweets, chained via
in_reply_to_tweet_id - Retry logic: Automatic retry with exponential backoff on 503 errors
- Rate limits: 429 responses include reset timestamp in error message (no auto-retry — the caller decides)
src/
main.rs — entry point, config loading, tracing, stdio transport
server.rs — MCP tool handlers, response formatting, cached auth state
api.rs — X API client: OAuth signing, tweet/media/user/DM endpoints
params.rs — tool parameter types (serde + JSON Schema)