Bookmarks-Aware MCP Server - Project Specification
Project Identity
A Python MCP (Model Context Protocol) server that provides full access to Chrome bookmarks with search, enrichment, and organization capabilities. Enables LLMs to search, enrich with AI metadata, and reorganize bookmarks via natural language.
Tech Stack
- •Python 3.11 (see
.python-version) - •mcp SDK (
mcp>=0.1.0) - Model Context Protocol implementation - •aiosqlite - Async SQLite for metadata storage
- •httpx - Async HTTP client for page fetching
- •trafilatura - Content extraction from HTML
- •websockets - WebSocket server for Chrome extension bridge
- •Transport: stdio-based MCP protocol
- •Build: Makefile (
make setup,make run)
No LLM dependencies -- enrichment is agent-driven (see Enrichment Architecture below).
Module Map
src/ ├── main.py # Entry point - runs asyncio.run(main()) ├── server.py # MCP server, 17 tool definitions, bridge-first writes ├── bookmarks_store.py # Chrome bookmarks read/write/add (cross-platform) ├── search.py # Search engine with metadata support ├── metadata_store.py # SQLite store for summaries and tags ├── change_tracker.py # Change history + undo (bookmark_changes table in same DB) ├── chrome_bridge.py # WebSocket bridge to Chrome extension for live writes ├── enrichment.py # Page fetching + content extraction (no LLM) ├── config.py # Configuration (enrichment settings, Chrome profile, bridge port) └── bookmarks_reader.py # (legacy, kept for compatibility)
src/bookmarks_store.py
- •
get_chrome_bookmarks_path(profile)- Platform-specific path - •
read_chrome_bookmarks(path)- Returns bookmarks withid,url,title,folder - •
add_bookmark(url, title, folder_path)- Add new bookmark - •
move_bookmark(url, target_folder)- Move bookmark to folder - •
rename_bookmark(url, new_title)- Rename bookmark - •
delete_bookmark(url)- Delete bookmark - •
create_folder(name, parent)- Create folder - •
bulk_move_bookmarks(moves)- Batch reorganization - •
backup_bookmarks()- Creates.bakbefore writes
src/metadata_store.py
- •
MetadataStore- Async SQLite wrapper for~/.bookmarks-mcp/metadata.db - •
get_metadata(url)/upsert_metadata(...)- CRUD operations - •
search_by_tags(tags)- Find bookmarks by tags - •
get_urls_needing_enrichment(urls)- Find stale/missing metadata
src/enrichment.py
- •
fetch_page_content(url)- HTTP fetch + trafilatura text extraction - •
compute_content_hash(content)- SHA256 hash for change detection
src/change_tracker.py
- •
ChangeTracker- Tracks bookmark changes inbookmark_changesSQLite table - •
record_change(action, url, details)- Record a change with before/after state - •
get_history(limit)- Get recent changes (newest first) - •
get_last_revertable()- Get most recent non-reverted change - •
mark_reverted(change_id)- Mark a change as undone
src/config.py
- •
EnrichmentConfig- Rate limits, timeouts, content length limits - •
Config.from_env()- Load from environment variables (incl.BOOKMARKS_CHROME_PROFILE)
src/search.py
- •
SearchEngine(Protocol) - Interface with metadata support - •
KeywordSearchEngine.search(query, bookmarks, tags_filter, metadata)- Enhanced search
Enrichment Architecture
Enrichment uses an agent-driven pattern: the MCP server handles fetching/storage, while the calling agent (the LLM) does the intelligence work.
Agent calls fetch_page_content(url) -> MCP fetches page, extracts text, returns content to agent Agent reads content, generates summary + tags using its own model Agent calls store_bookmark_metadata(url, summary, tags) -> MCP stores metadata in SQLite
Enrichment is never optional -- tool descriptions use MUST language to ensure agents always enrich:
- •
add_bookmarkauto-fetches page content and returns it with the add confirmation - •
enrich_allbatch-processes unenriched bookmarks (configurable batch_size, max 10) - •
health_checkdirects the agent to callenrich_allwhen unenriched bookmarks exist - •
get_bookmarksinstructs the agent to enrich any result missing metadata
This means:
- •No LLM dependencies in the server
- •Uses whatever model the agent is already running on
- •No API keys or local model setup needed
- •Better quality (frontier model vs local 3B)
- •Enrichment happens automatically on add, and on demand via enrich_all
Current Tools (17)
| Tool | Description |
|---|---|
health_check | Diagnostic: Chrome file, bookmark count, DB status, issues. Directs agent to enrich_all if needed. |
list_bookmarks | List ALL bookmarks (optional folder filter) |
get_bookmarks | Search bookmarks with metadata, optional tag filtering |
fetch_page_content | Fetch URL and extract text (agent then summarizes) |
store_bookmark_metadata | Store agent-generated summary + tags |
get_bookmark_metadata | Get stored summary/tags for a URL |
search_by_tags | Find bookmarks by tag |
enrich_all | Batch-fetch unenriched bookmarks for agent summarization (batched, with remaining count) |
add_bookmark | Add new bookmark + auto-fetch content for immediate enrichment |
move_bookmark | Move bookmark to different folder |
rename_bookmark | Rename a bookmark |
delete_bookmark | Delete a bookmark |
create_folder | Create new folder |
get_folder_structure | View folder hierarchy |
bulk_reorganize | Batch move multiple bookmarks |
get_change_history | View recent changes with before/after state |
revert_last_change | Undo the most recent non-reverted change |
Design Decisions
- •Agent-driven enrichment: No LLM in the server; the calling agent does summarization
- •Read and Write: Full bookmark management (with automatic backups)
- •Change tracking with undo: Every write records before/after state; any change is revertable
- •Extensible search:
SearchEngineprotocol for swappable implementations - •Local-first metadata: SQLite at
~/.bookmarks-mcp/metadata.db - •Configurable Chrome profile:
BOOKMARKS_CHROME_PROFILEenv var - •Chrome extension bridge: WebSocket bridge routes writes through
chrome.bookmarksAPI for live editing; falls back to file when disconnected - •Graceful errors: Missing files return safe defaults with warnings
- •Cross-platform: Handles Windows, macOS, Linux, and Chromium paths
- •Rate limiting: Configurable limits for page fetching
For full reasoning, tradeoffs, and rejected alternatives, see decisions.md.
Coding Conventions
- •Type hints: Always use type annotations
- •Docstrings: Google-style docstrings on all public functions
- •Protocol-based interfaces: Use
typing.Protocolfor swappable components - •No external LLM APIs: Intelligence comes from the calling agent
- •Error handling: Catch specific exceptions, log to stderr, return safe defaults
Adding a New Tool
- •
Define the tool handler in
server.py:pythonasync def my_tool(arg: str) -> list[TextContent]: # implementation return [TextContent(type="text", text=result)] - •
Register in
list_tools():pythonTool( name="my_tool", description="...", inputSchema={"type": "object", "properties": {...}, "required": [...]} ) - •
Add case in
call_tool():pythonif name == "my_tool": return await my_tool(arguments.get("arg"))
Adding a New Search Implementation
- •
Create a class implementing the
SearchEngineprotocol:pythonclass SemanticSearchEngine: def search(self, query: str, bookmarks: List[Dict[str, str]], limit: int = 10) -> List[Dict[str, str]]: # implementation - •
Replace the global
_search_engineinserver.py:python_search_engine: SearchEngine = SemanticSearchEngine()
Running the Server
make setup # Install dependencies make run # Run the server
Or directly:
python src/main.py
Configuration
MCP Client Configuration
Configure in MCP client (e.g., ~/.cursor/mcp.json):
{
"mcpServers": {
"bookmarks-aware-mcp": {
"command": "python3",
"args": ["/absolute/path/to/src/main.py"]
}
}
}
Environment Variables
| Variable | Default | Description |
|---|---|---|
BOOKMARKS_RATE_LIMIT | 2.0 | Requests per second for page fetching |
BOOKMARKS_MAX_CONCURRENT | 5 | Max concurrent page fetches |
BOOKMARKS_MAX_CONTENT | 50000 | Max chars to extract from a page |
BOOKMARKS_TIMEOUT | 30.0 | HTTP request timeout (seconds) |
BOOKMARKS_METADATA_DB | ~/.bookmarks-mcp/metadata.db | Custom DB path |
BOOKMARKS_CHROME_PROFILE | Default | Chrome profile name (e.g., Profile 1) |
BOOKMARKS_BRIDGE_PORT | 8765 | WebSocket port for Chrome extension bridge |