xmcp exposes the X API OpenAPI spec as MCP tools using FastMCP with
streamable-http transport. Authentication is OAuth 2.0 Authorization Code + PKCE
through built-in remote OAuth endpoints.
- Architecture and operational docs:
docs/index.md
server.py- runtime entrypoint and compatibility exports for tests/importersxmcp/- runtime modules (env, OpenAPI filtering, HTTP retry/error handling, auth helpers)auth/- OAuth2 remote server, token persistence, client registry, and X token exchange helperstests/- unit and integration-style endpoint testsscripts/- manual smoke checksexamples/- manual credentialed scripts that are intentionally excluded from CI tests
- Python
3.12+ uvinstalled- X Developer OAuth2 app configured as a confidential client (web/bot style app)
Copy env.example to .env and set:
X_OAUTH2_CLIENT_IDX_OAUTH2_CLIENT_SECRETX_MCP_PUBLIC_URL(public HTTPS URL of this server)
Recommended:
X_OAUTH2_SCOPES=tweet.read tweet.write users.read offline.accessX_TOKEN_STORE_PATH=.tokens.jsonX_B3_FLAGS=1MCP_HOST=127.0.0.1MCP_PORT=8000
Optional:
X_BEARER_TOKEN— app-only Bearer Token for read-only GET requests (workaround for OAuth 402 bug)
X_CORS_ORIGINS is additive: default Claude/Anthropic origins are already allowed.
Startup validation is strict:
X_MCP_PUBLIC_URLmust be a validhttps://URLX_OAUTH2_SCOPESmust includeoffline.access
For your X app:
- OAuth2 app type must be confidential (with client secret)
- Redirect URI must include:
https://<your-public-domain>/x/callback
- OAuth client registrations (
POST /register) only accept:https://claude.ai/api/mcp/auth_callbackhttp://localhost:<port>/callback
- Scopes should match
X_OAUTH2_SCOPESand includeoffline.accessfor refresh
uv venv
uv pip install -e ".[dev]"
cp env.example .env
uv run python server.pyDefault MCP endpoint: http://127.0.0.1:8000/mcp
The server mounts:
GET /.well-known/oauth-authorization-serverPOST /registerGET /authorizeGET /x/callbackPOST /tokenGET /health
Quick checks:
curl -sS http://127.0.0.1:8000/health
curl -sS http://127.0.0.1:8000/.well-known/oauth-authorization-serverPOST /token returns opaque session tokens generated with secrets.token_urlsafe.
access_tokenlifetime:expires_in=7200(2 hours)refresh_token: session-level token, separate from X refresh token- refresh rotates session tokens and invalidates old ones
- session maps (
sessions_by_access,sessions_by_refresh) are in memory - pending auth/code state is in memory (
pending_auth,pending_codes) - restart invalidates active sessions and in-flight auth; users must re-authenticate
- X tokens are persisted in
TokenStore(default file path.tokens.json)
- Tool calls with invalid or expired credentials return
401(Unauthorized) - Session refresh with revoked/expired X refresh token returns:
401witherror=invalid_grant- error description instructing full re-authentication
docker-compose.yml runs with MCP_HOST=0.0.0.0 and persists token store to:
./.tokens.json:/app/.tokens.json
Run:
docker compose up --builduv run ruff check .
uv run ruff format --check .
uv run pytestRun the remote OAuth endpoint smoke checks against a running server:
scripts/smoke_oauth_remote.shOptional custom base URL:
scripts/smoke_oauth_remote.sh http://127.0.0.1:8000- Runtime transport is fixed to
streamable-http. - OpenAPI streaming/webhook operations are filtered out.
X_API_TOOL_ALLOWLIST,X_API_TOOL_DENYLIST, andX_API_TOOL_TAGSare applied at startup.- Manual credentialed Grok script remains at
examples/test_grok_mcp.py.
This repository is currently unlicensed (all rights reserved) until a license is added.