Skip to main content
Glama
README.md
# x-mcp

MCP server in front of the X (Twitter) API v2. Image: `ghcr.io/robertdwhite/x-mcp`.
Manifests in whitehouse-rke2 (`apps/ai/x-mcp/`).

Reads are free. **Writes are two-step** — `draft_post` returns a preview plus a
single-use `confirm_token`, and nothing is published until `publish_post` is
called with that exact token. An agent cannot post in one call, so a human-visible
preview always exists first. That matters when the output is public and permanent.

There is deliberately **no delete tool**. Deletion is destructive and
irreversible; remove posts from the X app.

## Tools

| Tool | Kind | Notes |
| --- | --- | --- |
| `whoami` | read | Which account the credentials act as |
| `get_tweet` | read | One post by id |
| `get_user_posts` | read | Recent posts from a handle |
| `get_mentions` | read | Mentions of the authenticated account |
| `get_home_timeline` | read | Reverse-chronological home timeline |
| `search_recent` | read | Last-7-days search (plan-dependent) |
| `draft_post` | **stage** | Validates + previews. Publishes nothing. |
| `list_drafts` | read | Staged drafts and their remaining TTL |
| `publish_post` | **write** | Publishes a draft. Public and permanent. |

Drafts are in-memory: a restart clears them, so a stale confirm token never
outlives the process that showed the preview.

## Endpoints

- `POST /mcp` — streamable-HTTP MCP, gated on `Authorization: Bearer $MCP_TOKEN`
- `GET /healthz` — open, for liveness/readiness

Every other path returns **404, not 401** — including the `/.well-known/oauth-*`
probes MCP clients make on startup. A 401 there makes clients attempt an
interactive OAuth flow, which hangs headless clients as a connection timeout.

## Configuration

| Env | Required | Purpose |
| --- | --- | --- |
| `MCP_TOKEN` | recommended | Bearer for `/mcp`. Unset = **unauthenticated**. |
| `X_API_KEY` | yes | Consumer key |
| `X_API_SECRET` | yes | Consumer secret |
| `X_ACCESS_TOKEN` | yes | Access token (user context) |
| `X_ACCESS_TOKEN_SECRET` | yes | Access token secret |
| `X_MAX_CHARS` | no | Length limit, default `280`. Raise for premium. |
| `X_DRAFT_TTL` | no | Draft lifetime in seconds, default `600` |
| `X_TIMEOUT` | no | HTTP timeout, default `30` |

Auth to X is **OAuth 1.0a user context**, signed per request. App-only bearer
tokens cannot post, because publishing acts *as* the account. Get the four
values from an X app with **Read and Write** permission: consumer keys plus an
access token/secret pair generated for your own account.

If the credentials are missing, every tool returns a clear "not configured"
error instead of a stack trace — so the service can be deployed before the
credentials exist.

## Note on X API plans

Write access is gated by X's paid API tiers, and the free tier is heavily
capped. A `429` from this server surfaces the rate-limit reset when X provides
it. Check your plan at developer.x.com if writes fail immediately.

## Local run

```sh
pip install -r requirements.txt
MCP_TOKEN=dev X_API_KEY=... X_API_SECRET=... \
  X_ACCESS_TOKEN=... X_ACCESS_TOKEN_SECRET=... python server.py
```