Skip to main content
Glama
hellocybernetics

pyX-MCP

README.md
# X (Twitter) API Client

This is a Python client for integrating with the X (Twitter) API. You can operate the X API from AI assistants (such as Claude, Gemini, etc.) via MCP.

## Role of this Library

This library is a **client for the X API**. Although it also functions as an MCP server, the name `x_client` means it is a client for the X (Twitter) server.

```mermaid
graph TD
    A["AI Agent<br>Claude / Gemini / Codex etc."] -->|MCP Protocol over stdio| B["MCP Server Entrypoint<br>(x_client.integrations.mcp_server)"]

    %% Subgraph: Stabilize parsing by separating ID and display name
    subgraph x_client_library["x_client Library — Single Python Process"]
        B -->|Internal Call| C["XMCPAdapter"]
        C -->|Internal Call| D["Service Layer<br>(PostService, MediaService)"]
        D -->|Internal Call| E["Client Layer<br>(TweepyClient)"]
        D -->|Internal Call| F["Client Layer<br>(OriginalClient ...future)"]
    end

    E -->|X API HTTP / REST| G["X Server<br>Twitter (X)"]
    F -->|X API HTTP / REST| G["X Server<br>Twitter (X)"]

    style B fill:#e1f5ff
    style C fill:#e1f5ff
    style D fill:#e1f5ff
    style E fill:#e1f5ff
    style F fill:#e1f5ff
    style A fill:#fff4e6
    style G fill:#f3e5f5

```

**Clarification of Roles**:
- **AI Agent (MCP Client)**: AI assistants like Claude Code, Claude Desktop, Gemini.
- **MCP Server**: The MCP protocol-compatible server provided by this library.
- **X Client**: The core function of this library. A client for the X API.
- **X Server**: The main server of Twitter/X.

In other words, this library has two faces:
1.  **From the MCP perspective**: It acts as an MCP **server** providing tools to AI agents.
2.  **From the X API perspective**: It acts as an X API **client** communicating with the X server.

It is also possible to use the X API as a library without MCP (see bottom of README.md).

## Requirements
- Python 3.11 or higher
- X (Twitter) developer account and a set of API keys
- Package management tool [uv](https://docs.astral.sh/uv/) (recommended)

## Usage with MCP (Model Context Protocol)

You can operate the X API from AI assistants (such as Claude Code, Claude Desktop, codex-cli, Gemini, etc.).

### 🚀 Recommended Setup: Unified Execution with uvx

By using **uvx** in all environments, you can get automatic dependency management and always stay up-to-date.

### Configuration

Describe the following in the MCP configuration file of each AI tool:

**TOML format (Codex-CLI, etc.)**:
*   Published on PyPI
```toml
[mcp.servers.x_client]
command = "uvx"
args = ["--from", "pyx-mcp", "x-mcp-server"]

[mcp.servers.x_client.env]
X_API_KEY = "your-api-key"
X_API_SECRET = "your-api-secret"
X_ACCESS_TOKEN = "your-access-token"
X_ACCESS_TOKEN_SECRET = "your-access-token-secret"
```

*   Latest from GitHub
```toml
[mcp.servers.x_client]
command = "uvx"
args = ["--from", "git+https://github.com/hellocybernetics/pyX-MCP", "x-mcp-server"]

[mcp.servers.x_client.env]
X_API_KEY = "your-api-key"
X_API_SECRET = "your-api-secret"
X_ACCESS_TOKEN = "your-access-token"
X_ACCESS_TOKEN_SECRET = "your-access-token-secret"
```

**JSON format (Claude Code, Gemini CLI, etc.)**:
*   Published on PyPI
```json
{
  "mcpServers": {
    "x_client": {
      "command": "uvx",
      "args": ["--from", "pyx-mcp", "x-mcp-server"],
      "env": {
        "X_API_KEY": "your-api-key",
        "X_API_SECRET": "your-api-secret",
        "X_ACCESS_TOKEN": "your-access-token",
        "X_ACCESS_TOKEN_SECRET": "your-access-token-secret"
      }
    }
  }
}
```
*   Latest from GitHub
```json
{
  "mcpServers": {
    "x_client": {
      "command": "uvx",
      "args": ["--from", "git+https://github.com/hellocybernetics/pyX-MCP", "x-mcp-server"],
      "env": {
        "X_API_KEY": "your-api-key",
        "X_API_SECRET": "your-api-secret",
        "X_ACCESS_TOKEN": "your-access-token",
        "X_ACCESS_TOKEN_SECRET": "your-access-token-secret"
      }
    }
  }
}
```


**Important**: Please restart your AI tool completely after setting it up.

### Operation Check

Ask your AI assistant as follows:

```
"List the available X API tools"
```

or

```
"Post 'Hello from MCP!'"
```

### Benefits of uvx setup

- ✅ **Environment Independent**: No Node.js required, works only with a Python environment
- ✅ **Automatic Dependency Management**: uv automatically builds and caches virtual environments
- ✅ **Always Up-to-date**: `--from pyx-mcp` automatically gets the latest version from PyPI
- ✅ **Unified Configuration**: Same configuration method for all AI assistants

---

## Provided Features

The following tools are available via MCP:

### Posting Features
- **create_post**: Text posts, posts with images/videos, replies, quote posts
- **delete_post**: Delete a post
- **get_post**: Get a post by its ID
- **create_thread**: Automatically split long text into a thread post

### Repost Features
- **repost_post**: Repost a post
- **undo_repost**: Undo a repost

### Search Features
- **search_recent_posts**: Search for posts from the last 7 days (with author information)

### Media Upload
- **upload_image**: Upload an image (JPEG/PNG/WebP/GIF, max 5MB)
- **upload_video**: Upload a video (MP4, max 512MB, chunked upload supported)

### Authentication and Status Check
- **get_auth_status**: Get authentication status and rate limit information

### Usage Examples

```
You: "Post 'Hello from Claude via MCP!'"

Claude: Using the create_post tool...
       Post completed! Post ID: 1234567890
```

```
You: "Search for recent posts about 'MCP protocol'"

Claude: Using the search_recent_posts tool...
       Found 3 posts:
       1. @user1: I tried using MCP...
       2. @user2: Model Context Protocol is...
```

### Architecture

```
AI Assistant ↔ MCP Server (stdio) ↔ XMCPAdapter ↔ Service Layer ↔ X API
```

### Error Handling

- **ConfigurationError**: Missing authentication information. Check `.env` and environment variables.
- **AuthenticationError**: Token expired. Re-run the OAuth flow.
- **RateLimitExceeded**: Rate limit reached. Back off with reference to `reset_at`.
- **MediaProcessingTimeout/Failed**: Timed out waiting for video processing to complete. Adjust `timeout` and video quality.

### Troubleshooting

- **Missing credentials**: Check environment variables with `echo $X_API_KEY`. Check if `.env` is saved with `0o600`.
- **Invalid token**: Re-run the OAuth flow to update authentication information.
- **Video timeout**: Extend the `timeout` of `upload_video` or re-encode with `ffmpeg`.

---

## Using as a Library

It can also be called directly from Python code.

### Installation

```bash
uv add pyx-mcp
```

### How to get authentication information

To use this library, you need to get the following four pieces of authentication information from your X (Twitter) developer account.

1.  **Access the X Developer Portal**:
    -   Go to [https://developer.x.com/en/portal/dashboard](https://developer.x.com/en/portal/dashboard) and log in.

2.  **Select or create an application**:
    -   Select an existing application or create a new one.

3.  **Check keys and tokens**:
    -   On the application dashboard, go to the "Keys and Tokens" tab.

4.  **Generate and set permissions**:
    -   **API Key and Secret**: Check or regenerate in the "Consumer Keys" section.
    -   **Access Token and Secret**: In the "Authentication Tokens" section, generate an access token and secret with **Read and Write** permissions.

Set these retrieved values in the environment variables or `.env` file described below.

## Setting Authentication Information

Set the authentication information with environment variables or an `.env` file:

```bash
export X_API_KEY="your_api_key"
export X_API_SECRET="your_api_secret"
export X_ACCESS_TOKEN="your_access_token"
export X_ACCESS_TOKEN_SECRET="your_access_token_secret"
export X_BEARER_TOKEN="your_bearer_token"  # for v2 API (optional)
```

Or in an `.env` file (placed in the project root):
```bash
X_API_KEY=your_api_key
X_API_SECRET=your_api_secret
X_ACCESS_TOKEN=your_access_token
X_ACCESS_TOKEN_SECRET=your_access_token_secret
X_BEARER_TOKEN=your_bearer_token
```

`.env` is automatically set to `0o600` (owner read/write only). `.env*` is `.gitignore`d.

---

### Basic Usage

```python
from x_client.config import ConfigManager
from x_client.factory import XClientFactory
from x_client.services.post_service import PostService
from x_client.services.media_service import MediaService

# 1. Load authentication information
config = ConfigManager()
client = XClientFactory.create_from_config(config)

# 2. Initialize the service layer
post_service = PostService(client)
media_service = MediaService(client)

# 3. Create a post
post = post_service.create_post(text="Hello from x_client!")
print(f"Post created: {post.id}")

# 4. Post with an image
from pathlib import Path
media_result = media_service.upload_image(Path("image.png"))
post = post_service.create_post(
    text="Check out this image!",
    media_ids=[media_result.media_id]
)

# 5. Post a long thread
thread = post_service.create_thread(
    '''Python 3.11 highlights... (long text)''',
    chunk_limit=200,
)
for idx, segment_post in enumerate(thread.posts, start=1):
    print(f"Segment {idx}: {segment_post.id}")
if not thread.succeeded:
    print("Thread failed", thread.error)

# 6. Repost operation
repost_state = post_service.repost_post(post.id)
print("Reposted:", repost_state.reposted)

undo_state = post_service.undo_repost(post.id)
print("Repost removed:", not undo_state.reposted)

# 7. Search with author information
search_results = post_service.search_recent(
    "from:twitterdev",
    expansions=["author_id"],
    user_fields=["username", "verified"],
    post_fields=["created_at"],
)
for item in search_results:
    author = item.author.username if item.author else "unknown"
    print(author, item.text)
```

### Usage via MCP Adapter (a simplified version of the above API)

It can also be called directly from other than MCP clients:

```python
from x_client.integrations.mcp_adapter import XMCPAdapter

adapter = XMCPAdapter()  # Authentication information is automatically loaded by ConfigManager

post = adapter.create_post({"text": "Hello from MCP!"})
print(post)

media = adapter.upload_image({"path": "/path/to/image.png"})
adapter.create_post({"text": "Image post", "media_ids": [media["media_id"]]})
```

### Logging and Observability

`PostService` has structured logging and event hooks built in:

```python
import logging
from x_client.config import ConfigManager
from x_client.factory import XClientFactory
from x_client.services.post_service import PostService

logging.basicConfig(level=logging.INFO)

client = XClientFactory.create_from_config(ConfigManager())

def metrics_hook(event: str, payload: dict[str, object]) -> None:
    # Integration point for Prometheus / OpenTelemetry, etc.
    print("metrics", event, payload)

post_service = PostService(client, event_hook=metrics_hook)
post_service.create_post("observability ready!")
```

The event hook consolidates both success and failure into a single callback, making it easy to send metrics and integrate with distributed tracing.

---

## Usage in a Development Environment

### Setup

```bash
uv sync
```

This will create the `x-mcp-server` command in `.venv/bin/`.

### Running the MCP Server with a Local Path

To run the MCP server under development directly:

```json
{
  "mcpServers": {
    "x-client": {
      "command": "/absolute/path/to/twitter/.venv/bin/x-mcp-server",
      "env": {
        "X_API_KEY": "your-api-key",
        "X_API_SECRET": "your-api-secret",
        "X_ACCESS_TOKEN": "your-access-token",
        "X_ACCESS_TOKEN_SECRET": "your-access-token-secret"
      }
    }
  }
}
```

<details>
<summary>Alternative methods (click to expand)</summary>

**Method 2: Direct use of uv**
```json
{
  "mcpServers": {
    "x-client": {
      "command": "uv",
      "args": ["run", "--directory", "/absolute/path/to/twitter", "python", "-m", "x_client.integrations.mcp_server"],
      "env": {
        "X_API_KEY": "your-api-key",
        "X_API_SECRET": "your-api-secret",
        "X_ACCESS_TOKEN": "your-access-token",
        "X_ACCESS_TOKEN_SECRET": "your-access-token-secret"
      }
    }
  }
}
```

**Method 3: Launcher script**
```json
{
  "mcpServers": {
    "x-client": {
      "command": "/absolute/path/to/twitter/scripts/run_mcp_server.sh",
      "env": { "X_API_KEY": "...", "X_API_SECRET": "...", "X_ACCESS_TOKEN": "...", "X_ACCESS_TOKEN_SECRET": "..." }
    }
  }
}
```
</details>

**Important**: Replace `/absolute/path/to/twitter` with the actual project path.

---

## Usage with CLI

You can easily post from the command line using `examples/create_post.py`.

### Basic Usage

```bash
# Text only
python examples/create_post.py "Hello from x_client!"

# With image
python examples/create_post.py "Check out this image!" --image path/to/image.png

# With video (max 512MB, chunked upload supported)
python examples/create_post.py "Check out this video!" --video path/to/video.mp4

# Use .env from a different path
python examples/create_post.py "Hello with custom env" --dotenv /secure/path/.env
```

### Thread Posting

```bash
# Long thread post (auto-split with chunk_limit=180)
python examples/create_post.py "Long form update..." --thread --chunk-limit 180

# Post a thread from a file (assuming UTF-8 text)
python examples/create_post.py --thread-file docs/thread_draft.txt

# Example of a long Japanese thread (break lines appropriately under 280 characters)
python examples/create_post.py --thread-file examples/long_thread_ja.txt --chunk-limit 180

# Example of a long English thread (maintaining sentence breaks)
python examples/create_post.py --thread-file examples/long_thread_en.txt --chunk-limit 240

# Wait 8 seconds between each post to avoid rate limits
python examples/create_post.py --thread-file examples/long_thread_en.txt --segment-pause 8

# Choose a split strategy (simple | sentence | paragraph)
python examples/create_post.py --thread-file examples/long_thread_en.txt \
  --chunk-limit 240 --split-strategy sentence
```

### Other Operations

```bash
# Delete the first tweet of a failed thread (used to resolve duplicate errors)
python examples/create_post.py --delete 1234567890123456789

# Repost / Undo repost
python examples/create_post.py --repost 1234567890
python examples/create_post.py --undo-repost 1234567890
```

### Language-specific considerations

- **Japanese**: If there are many full-width characters, filling up to the 280-character limit can make it difficult to read, so keep `--chunk-limit` to around 150-200 characters to maintain chunks for each phrase. Also, since splitting immediately after a punctuation mark can break the context, it is safe to insert a blank line for each paragraph on the text file side.

- **English**: When including URLs or emojis, Twitter counts them as 23 characters, so set `--chunk-limit` with a margin. If you add a line break for each sentence, it will be easier to read after splitting.

### Notes

- When re-posting a thread, if you post the exact same body text within 24 hours, you will get a **Duplicate content** error due to X's specifications. Please delete the previously posted thread or add a unique phrase such as a timestamp to the text.

- The X API may return HTTP 429 (Too Many Requests) if you post continuously in a short period of time. This library detects `RateLimitExceeded` and waits according to `x-rate-limit-reset` in the response header before retrying, but if a 429 occurs, please wait 2-3 minutes before re-executing the command. Setting `--segment-pause` to about 5–10 seconds makes it easier to avoid 429 in advance.


---

## Tests

```bash
# Test MCP server operation
uv run python scripts/test_mcp_server.py

# Unit tests
uv run pytest tests/unit/test_mcp_adapter.py -v

# Run all tests
uv run pytest

# Run with coverage
uv run pytest --cov=x_client --cov-report=html

# Verbose mode
uv run pytest -v

# Specific test file
uv run pytest tests/unit/test_tweepy_client.py
```

---

## Main Features

- Dual client configuration: tweepy.Client (v2) for posts, tweepy.API (v1.1) for media
- Secure authentication information management using `.env` and OAuth flow integration
- High-level API with `PostService` / `MediaService`
- Long thread posting utility and automatic reply chain construction
- Repost/undo API and MCP tools
- Support for specifying expansions/fields in the search API and resolving author information
- Structured logging and event hooks built into the service layer
- Operation from AI assistants via MCP (Model Context Protocol) integration


---

## Support

Please report bugs and suggestions for improvement via issues or pull requests. For details on project policies and design, please refer to `docs/` and add comments as necessary.

TDQS

A3.7/5.0

Scored across 10 tools

Disambiguation5/5

All tools have clearly distinct purposes: create, delete, get, repost, undo repost, search, auth status, and two media uploads differentiated by type/size. No overlap or ambiguity.

Naming Consistency5/5

All tool names follow a consistent verb_noun pattern in snake_case (e.g., create_post, delete_post, upload_image). Even 'undo_repost' and 'get_auth_status' fit the pattern well.

Tool Count5/5

With 10 tools covering posting, reading, searching, media uploads, reposts, and auth status, the count is well-scoped for a social media client. No unnecessary or missing tools.

Completeness4/5

Core operations are covered (CRUD for posts, search, media upload, repost). Missing edit post and timeline listing are minor gaps that agents can work around.

Maintenance

ActivityInactive
ResponsivenessNo issues