XBookmark
MCP server that syncs X (Twitter) bookmarks to an Obsidian vault. Multi-account support, OAuth 2.0 PKCE authentication, and structured JSON storage.
Features
- Multi-account support — unlimited X accounts via profiles
- One-click OAuth login — browser opens, callback captured, tokens saved automatically
- Centralized storage — all bookmarks in a single
bookmarks.json(atomic writes) - Thread detection — automatic thread fetching for multi-tweet threads
- Cross-account deduplication — same tweet bookmarked from 2 accounts = 1 entry
- Rate limit handling — sequential processing with automatic retry
- Two-step workflow — sync first, delete later (safe for testing)
- AI-powered digest —
/xbookmark-digestClaude Code skill for analysis & categorization
Available Tools
| Tool | Description |
|---|---|
| Auth | |
xbookmark_auth_login(profile) | One-click OAuth — browser + callback + token exchange |
xbookmark_auth_url(profile) | Get OAuth authorization URL (manual fallback) |
xbookmark_auth_callback(code, state) | Complete authorization (manual fallback) |
xbookmark_auth_status(profile) | Check auth status (None = all profiles) |
xbookmark_auth_logout(profile) | Clear stored tokens |
xbookmark_list_profiles() | List all profiles and auth status |
| Sync | |
xbookmark_sync(profile) | Sync bookmarks to bookmarks.json |
xbookmark_delete(profile, tweet_ids, limit) | Delete synced bookmarks from X (selective or all) |
xbookmark_list(profile) | List bookmarks from X API |
xbookmark_get_tweet(tweet, profile) | Fetch single tweet by ID or URL (with thread) |
xbookmark_sync_all() | Sync ALL profiles with deduplication |
Quick Install (Claude Code Plugin)
The easiest way to use XBookmark with Claude Code:
-
Set X API credentials (add to
~/.bashrcor~/.zshrc):export X_CLIENT_ID="your_oauth2_client_id" export X_CLIENT_SECRET="your_client_secret" # optional export X_REDIRECT_URI="http://localhost:8080/callback" # optional -
Install the plugin (in Claude Code):
/plugin marketplace add isezen/XBookmarks /plugin install xbookmark@XBookmarks -
Restart Claude Code. The MCP server is auto-installed on first start.
-
Use it:
/xbookmark:digest # Analyze all bookmarks /xbookmark:digest https://x.com/.../12345 # Process single tweet
Developer Installation
Prerequisites
- Python 3.10+
- uv package manager (recommended) or pip
- X Developer Account with OAuth 2.0 enabled
Setup
-
Clone and install
git clone git@github-isezen:isezen/XBookmarks.git cd XBookmarks uv sync -
Set environment variables
cp .env.example .envEdit
.envwith your X API credentials:X_CLIENT_ID="your_client_id" X_CLIENT_SECRET="your_client_secret" X_REDIRECT_URI="http://localhost:8080/callback" -
Create config (optional — defaults work for most setups)
mkdir -p ~/.config/XBookmark cat > ~/.config/XBookmark/config.json << 'EOF' { "obsidian_vault_path": "/path/to/your/obsidian/vault", "xbookmarks_folder": "XBookmarks" } EOFOr set
XBOOKMARK_HOMEenv var to use a custom config directory.
X API Setup
Get OAuth 2.0 Credentials
- Go to X Developer Portal
- Create a Project and an App
- Under App Settings, enable OAuth 2.0 with:
- Type: Confidential client (or Public)
- Redirect URI:
http://localhost:8080/callback
- Required scopes:
bookmark.read,bookmark.write,tweet.read,users.read,offline.access - Copy Client ID and Client Secret to your
.envfile
Note: Bookmark endpoints require Basic tier access or pay-per-usage credits.
Usage with MCP Clients
Claude Code / Claude Desktop
Add to your MCP settings:
{
"mcpServers": {
"xbookmark": {
"command": "/path/to/XBookmarks/.venv/bin/xbookmark-mcp"
}
}
}
Workflow
Step 1: Authenticate
> "Login to my X account"
The agent calls xbookmark_auth_login() — browser opens, you authorize, tokens are saved.
For multiple accounts, use profiles:
> "Login to my work X account as 'work'"
Step 2: Sync Bookmarks
> "Sync my X bookmarks"
The agent calls xbookmark_sync_all() which:
- Downloads from all authenticated profiles
- Deduplicates across accounts
- Saves to
XBookmarks/bookmarks.json - Does NOT delete from X
Step 3: Process & Categorize
Use the /xbookmark-digest skill (or manually):
- Read bookmark entries from
bookmarks.json - Analyze tweet content, fetch linked URLs
- Create enriched notes in Obsidian topic folders
- Track processed tweets in
index.json
Step 4: Delete from X (optional)
> "Delete processed bookmarks from X"
The agent calls xbookmark_delete() (with no profile argument, which deletes
from all profiles) and cleans up bookmarks.json.
Data Storage
bookmarks.json
All synced bookmarks are stored in XBookmarks/bookmarks.json as a flat JSON array.
Each entry contains tweet text, author info, entities, media, thread replies,
and source profiles. Writes use atomic tmp-then-rename for crash safety.
index.json
Tracks which tweets have been processed by the digest skill.
Maps tweet_id to {path, date, tags} for Obsidian cross-references.
Development
Quick Commands
# Install with dev dependencies
uv sync --extra dev
# Run all tests
.venv/bin/pytest
# Run single test
.venv/bin/pytest tests/test_config.py::TestLoadConfig -v
# Coverage report
.venv/bin/pytest --cov=src/xbookmark
# Lint & format
.venv/bin/ruff check . && .venv/bin/ruff format .
# Run MCP server
.venv/bin/xbookmark-mcp
# Debug with MCP Inspector
npx @modelcontextprotocol/inspector .venv/bin/xbookmark-mcp
Project Structure
XBookmarks/
├── .claude-plugin/
│ └── marketplace.json # Claude Code plugin marketplace
├── plugins/xbookmark/ # Claude Code plugin
│ ├── .claude-plugin/plugin.json
│ ├── .mcp.json # MCP server config
│ ├── commands/
│ │ └── digest.md # AI digest skill (source of truth)
│ ├── hooks/hooks.json # SessionStart auto-install hook
│ └── scripts/ensure-installed.sh
├── .claude/commands/
│ └── xbookmark-digest.md # Symlink → plugins/xbookmark/commands/digest.md
├── src/xbookmark/
│ ├── config.py # Config loading, lazy singleton
│ ├── mcp/
│ │ ├── server.py # FastMCP entry point
│ │ ├── tools_auth.py # 6 OAuth 2.0 PKCE auth tools
│ │ └── tools_sync.py # 6 sync/delete/list tools
│ ├── x_api/
│ │ ├── auth.py # OAuth 2.0 PKCE flow, token storage
│ │ ├── callback_server.py # Local HTTP server for one-click login
│ │ ├── client.py # Async HTTP client with retry
│ │ ├── bookmarks.py # Bookmark CRUD (X API v2)
│ │ └── tweets.py # Tweet/thread fetching
│ └── obsidian/
│ └── writer.py # Markdown generation, JSON I/O
├── tests/
│ ├── conftest.py # Shared fixtures
│ ├── test_config.py
│ ├── test_writer.py
│ └── test_tools.py
├── docs/ # Architecture & API docs
├── pyproject.toml
├── CLAUDE.md # Claude Code guidance
└── README.md
Testing
- Framework: pytest with
asyncio_mode = "auto" - HTTP mocking:
respxlibrary - Fixtures:
tests/conftest.py(mock_config, mock_oauth_manager, mock_tweet, etc.) - Tests organized as classes per module
Code Style
- Python 3.10+, ruff rules: E/F/I/N/W/UP
- Modern type hints:
str | None,list[str] - Double quotes, trailing commas in multi-line structures
License
MIT