Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Readwise MCP Serverfetch my daily review"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Readwise MCP Server
Minimal Python MCP server for Readwise integration - token-efficient, single-file implementation using FastMCP.
Features
Token-efficient: 10 essential tools (vs 13+ in Node.js version)
Single-file architecture: ~1,000 lines of code
Proven logic: Reuses battle-tested deduplication and pagination from backfill script
State compatibility: Preserves existing state file format
Smart optimization: Uses synced ranges to skip unnecessary API calls
Highlights import: Dedicated tools for importing all highlights with temporal organization
Installation
1. Clone repository
git clone https://github.com/ngpestelos/readwise-mcp-server.git
cd readwise-mcp-server2. Create virtual environment
python3 -m venv venv
source venv/bin/activate3. Install dependencies
pip install -r requirements.txt4. Configure environment variables
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)
Configuration
Update .mcp.json
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.
Tools Reference
1. 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()2. 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)3. 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")4. 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 ID
Returns:
{
"status": "success",
"count": 15,
"highlights": [...]
}Example:
Call readwise_book_highlights(title="Atomic Habits")5. 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")6. 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()7. 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()8. 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)9. 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
10. 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 format
Returns:
{
"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.md
State File Format
The 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 interrupted
Backward Compatibility: Existing state files without the highlights section will have it automatically created on first use.
Testing
Run the test suite:
source venv/bin/activate
pytest test_server.py -vTest Categories
Unit Tests:
State file reading/writing
Synced range optimization logic
Filename sanitization
ID extraction from URLs
Document scanning for deduplication
Integration Tests:
API calls with mocked responses
Markdown formatting
Document saving with collision handling
Highlights query tools (daily_review, book_highlights, search_highlights)
Highlights import and backfill with deduplication
Troubleshooting
Connection Issues
Check MCP connection:
/mcpShould show "Connected to readwise"
Verify environment variables are set correctly in
.mcp.jsonCheck logs in stderr output
State Issues
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()
Deduplication Issues
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
Architecture
Single-File Design
The server is intentionally kept to a single file (~350 lines) for:
Simplicity and maintainability
Easy deployment and updates
Minimal dependencies
Clear code organization
Reused Logic
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 extraction
Token Efficiency
Optimizations for reduced token usage:
8 tools vs 13+ in Node.js version (38% reduction)
Combined operations (fetch + dedupe + save in one call)
Smart defaults minimize required parameters
Tool descriptions limited to 20-30 words
Returns structured summaries, not full markdown dumps
Estimated token overhead reduction: ~60%
Comparison with Node.js Version
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 | ✓ | ✗ |
Development
Running locally for development
source venv/bin/activate
python server.pyAdding new tools
Add tool function using
@mcp.tool()decoratorFollow existing patterns for error handling and logging
Return structured dict with
statusfieldAdd tests to
test_server.pyUpdate README.md with tool documentation
License
MIT
Credits
Built with FastMCP by Anthropic
Based on proven logic from
.claude/scripts/readwise-backfill.pyDesigned for token efficiency and simplicity
This server cannot be installed
Resources
Looking for Admin?
Admins can modify the Dockerfile, update the server description, and track usage metrics. If you are the server author, to access the admin panel.