loading…
Search for a command to run...
loading…
A lightweight Python-based server that enables users to manage and retrieve their Readwise highlights and daily reviews through a token-efficient interface. It
A lightweight Python-based server that enables users to manage and retrieve their Readwise highlights and daily reviews through a token-efficient interface. It supports features like document importing, historical backfilling, and highlight searching with built-in deduplication and state management.
Minimal Python MCP server for Readwise integration - token-efficient, single-file implementation using FastMCP.
git clone https://github.com/ngpestelos/readwise-mcp-server.git
cd readwise-mcp-server
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
Set these in your .mcp.json file:
READWISE_TOKEN: Your Readwise API tokenVAULT_PATH: Path to your PARA vault (e.g., /path/to/your/vault)Add or update the readwise entry in your vault's .mcp.json:
{
"mcpServers": {
"readwise": {
"command": "/absolute/path/to/readwise-mcp-server/venv/bin/python",
"args": ["/absolute/path/to/readwise-mcp-server/server.py"],
"env": {
"READWISE_TOKEN": "your_token_here",
"VAULT_PATH": "/absolute/path/to/your/vault"
}
}
}
}
Replace the paths with your actual installation locations.
readwise_daily_review()Fetch today's highlights and save to Daily Reviews directory.
Parameters: None
Returns:
{
"status": "success",
"count": 42,
"file": "/path/to/daily-review.md"
}
Example:
Call readwise_daily_review()
readwise_import_recent(category="tweet", limit=20)Import recent documents since last import with automatic deduplication.
Parameters:
category (string, optional): Document category (default: "tweet")limit (int, optional): Maximum documents to fetch (default: 20)Returns:
{
"status": "success",
"imported": 5,
"skipped": 15,
"total_analyzed": 20
}
Example:
Call readwise_import_recent(category="article", limit=50)
readwise_backfill(target_date, category="tweet")Paginate backwards to target date with synced range optimization.
Parameters:
target_date (string, required): Target date in YYYY-MM-DD formatcategory (string, optional): Document category (default: "tweet")Returns:
{
"status": "success",
"imported": 67,
"skipped": 433,
"pages": 10,
"reached_target": true
}
Example:
Call readwise_backfill(target_date="2026-01-01")
readwise_book_highlights(title=None, book_id=None)Get highlights for a specific book.
Parameters:
title (string, optional): Book title to search forbook_id (string, optional): Specific book IDReturns:
{
"status": "success",
"count": 15,
"highlights": [...]
}
Example:
Call readwise_book_highlights(title="Atomic Habits")
readwise_search_highlights(query, limit=50)Search highlights by text query.
Parameters:
query (string, required): Search querylimit (int, optional): Maximum results (default: 50)Returns:
{
"status": "success",
"count": 8,
"highlights": [...]
}
Example:
Call readwise_search_highlights(query="productivity")
readwise_state_info()Show current import state and synced ranges.
Parameters: None
Returns:
{
"status": "success",
"last_import": "2026-01-22T04:29:12Z",
"oldest_imported": "2026-01-01",
"synced_ranges": [...],
"backfill_in_progress": false,
"documents_on_disk": 1044,
"documents_with_ids": 614
}
Example:
Call readwise_state_info()
readwise_init_ranges()Scan filesystem to build synced_ranges from existing documents.
Parameters: None
Returns:
{
"status": "success",
"range": {
"start": "2026-01-01T00:00:00Z",
"end": "2026-01-21T00:00:00Z",
"doc_count": 614
},
"documents_analyzed": 614
}
Example:
Call readwise_init_ranges()
readwise_reset_state(clear_ranges=False)Clear state file (optionally preserve synced_ranges).
Parameters:
clear_ranges (bool, optional): Whether to clear synced ranges (default: False)Returns:
{
"status": "success",
"message": "State reset",
"cleared_ranges": false
}
Example:
Call readwise_reset_state(clear_ranges=True)
readwise_import_recent_highlights(limit=100)Import recent highlights across all sources since last import.
Parameters:
limit (int, optional): Maximum highlights to fetch (default: 100)Returns:
{
"status": "success",
"imported": 42,
"skipped": 58,
"total_analyzed": 100
}
Example:
Call readwise_import_recent_highlights(limit=200)
Saves to: 2 Resources/Readwise/Highlights/YYYYMMDD-HHMMSS [Source Title] highlight.md
readwise_backfill_highlights(target_date)Paginate highlights backwards to target date with synced range optimization.
Parameters:
target_date (string, required): Target date in YYYY-MM-DD formatReturns:
{
"status": "success",
"imported": 127,
"skipped": 873,
"pages": 15,
"reached_target": true
}
Example:
Call readwise_backfill_highlights(target_date="2026-01-01")
Saves to: 2 Resources/Readwise/Highlights/YYYYMMDD-HHMMSS [Source Title] highlight.md
Filename Format: Highlights are saved with temporal prefix for chronological sorting:
20260130-143020 [Building a Second Brain] highlight.md20260130-142815 [The Phoenix Project] highlight.mdThe server maintains state at .claude/state/readwise-import.json:
{
"last_import_timestamp": "2026-01-22T04:29:12.864733Z",
"oldest_imported_date": "2026-01-01",
"synced_ranges": [
{
"start": "2026-01-01T06:17:43.693000+00:00",
"end": "2026-01-21T02:33:27.975000+00:00",
"doc_count": 614,
"verified_at": "2026-01-21T10:43:56.626549Z"
}
],
"backfill_in_progress": false,
"highlights": {
"last_import_timestamp": "2026-01-30T05:00:00Z",
"synced_ranges": [],
"backfill_in_progress": false
}
}
Highlights State: The highlights section tracks separate import state for highlights:
last_import_timestamp: Last highlights import timesynced_ranges: Optimized ranges for highlights backfillbackfill_in_progress: Whether a highlights backfill was interruptedBackward Compatibility: Existing state files without the highlights section will have it automatically created on first use.
Run the test suite:
source venv/bin/activate
pytest test_server.py -v
Unit Tests:
Integration Tests:
Check MCP connection:
/mcp
Should show "Connected to readwise"
Verify environment variables are set correctly in .mcp.json
Check logs in stderr output
If state file appears corrupted:
View current state:
Call readwise_state_info()
Reset state (preserve ranges):
Call readwise_reset_state()
Rebuild ranges from filesystem:
Call readwise_init_ranges()
If documents are being imported multiple times:
Rebuild synced ranges:
Call readwise_init_ranges()
Check filesystem for duplicate filenames manually
Verify readwise_url frontmatter is present in existing documents
The server is intentionally kept to a single file (~350 lines) for:
Key functions reused from .claude/scripts/readwise-backfill.py:
load_state() / write_state() - State managementoptimize_backfill() - Synced range optimizationscan_existing_documents() - Filesystem deduplicationsanitize_filename() - Safe filename generationextract_id_from_url() - ID extractionOptimizations for reduced token usage:
Estimated token overhead reduction: ~60%
| Feature | Python MCP | Node.js MCP |
|---|---|---|
| Lines of code | ~1,000 | ~6,749 |
| Tool count | 10 | 13+ |
| Dependencies | 4 | 10+ |
| State compatibility | ✓ | ✓ |
| Token efficiency | High | Medium |
| Maintenance | Simple | Complex |
| Highlights import | ✓ | ✗ |
source venv/bin/activate
python server.py
@mcp.tool() decoratorstatus fieldtest_server.pyMIT
.claude/scripts/readwise-backfill.pyДобавь это в claude_desktop_config.json и перезапусти Claude Desktop.
{
"mcpServers": {
"readwise-mcp-server": {
"command": "npx",
"args": []
}
}
}