| name | chrome-bookmarks |
| title | Chrome Bookmarks Management |
| description | Read, deduplicate, check dead links, categorize, and archive Chrome bookmarks by manipulating the Bookmarks JSON file directly. |
| trigger | User asks to manage, organize, clean up, deduplicate, archive, or sort Chrome bookmarks. |
Chrome Bookmarks Management
Manage Chrome bookmarks programmatically by reading/writing the Bookmarks JSON file. This bypasses Chrome's MCP server and works even when mcp-chrome-bridge is down.
Bookmark File Location
- Default profile:
~/Library/Application Support/Google/Chrome/Default/Bookmarks
- Custom profile:
<user-data-dir>/Default/Bookmarks (e.g. ~/.hermes/chrome-wechat-profile/Default/Bookmarks)
- Format: JSON, uses
date_added as microseconds since 1601-01-01 (Windows epoch)
Workflow (in order)
0. Always Backup First
import shutil, time
shutil.copy2(path, path + ".bak." + time.strftime("%Y%m%d_%H%M%S"))
Keep multiple backups at each stage (before dedup, before dead-link removal, before categorization, before archive). Label them with suffix numbers (bak1, bak2, ...).
1. Read & Inspect
Use json.load() to read the file. Structure:
{
"roots": {
"bookmark_bar": { "children": [ ... ] },
"other": { "children": [ ... ] },
"synced": { "children": [ ... ] }
}
}
Each node has type ("url" or "folder"), name, url (for URLs), date_added, and children (for folders).
2. Deduplication
Remove bookmarks with identical URLs, keeping the first occurrence.
- Use a
set() to track seen URLs as you walk the tree
- Collect stats:
{"removed": N, "dupes": [(url, name), ...]}
- Per-folder scope: dedup within each folder separately (a link in "常用" and the same link in "归档" are legitimately different), OR global scope if the user wants true dedup across all folders. Ask if ambiguous.
3. Dead Link Checking
Check if each bookmark URL is still accessible:
- Use
concurrent.futures.ThreadPoolExecutor(max_workers=20-30) for speed
- Timeout: 5-8 seconds per URL
- SSL context:
ssl._create_unverified_context() to bypass cert errors
- HEAD first, fallback to GET if HEAD gets 405
- Classification rules for URL checker:
ALIVE (keep): 200-399, 401, 405, 429, 502-504 (temporary/server issues)
ALIVE (keep): 403 (anti-bot protection — works in real browser)
ALIVE (keep): 302, 307, 308 (redirects)
DEAD (remove): 404, 410 (gone permanently)
DEAD (remove): DNS failures, connection refused, TLS protocol errors
DEAD (remove): EOF violations on established connections
- 403 is NOT dead for Chinese sites (知乎, 百度贴吧, CSDN, etc. block bots)
- User-Agent header required:
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36"
4. Categorization
Define keyword-based category rules. Rules are checked in priority order — more specific rules first, generic fallback last.
Rule format:
def kw(name, name_kws=None, url_kws=None, domain_kws=None):
return (name, [k.lower()...], [k.lower()...], [k.lower()...])
def match(node, rule):
Recommended 11 categories (ordered):
⭐常用 — Google, GitHub, 知乎, B站, 邮箱, portals
🤖AI — ChatGPT, Claude, 文心一言, Ollama, API keys, etc.
☕Java/Spring — Java/JVM/并发, Spring Boot, MyBatis, JPA, Netty, Redis, Kafka
🌐前端 — Vue, React, HTML/CSS/JS, Hexo, Chrome extensions
📊大数据/数据库 — Hadoop, Spark, Flink, Hive, Kudu, MySQL, Oracle
🔗分布式/中间件 — 分布式理论, ZK, gRPC, Netty, Disruptor, 任务调度
🔧开发工具 — IDE, Maven, Git, Docker, Jenkins, Linux, 在线工具
📚学习/面试 — 面试题, LeetCode, 算法, 考研, MOOCs
💼工作 — 公司项目, 阿里云, 运维
🏠生活/娱乐 — 购物, 素材, 电子书, 贴吧, 汽车
📂其他 — Catch-all for uncategorized
Implementation note: Run categorization from execute_code (not terminal) so you can use urllib.parse.urlparse.
5. Archive by Year
6. Write Back & Notify User
with open(path, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
IMPORTANT: Chrome caches bookmarks in memory. User must fully quit Chrome (Cmd+Q) and restart to see changes.
Pitfalls
- Chrome MCP server may be down: Always have the direct JSON manipulation fallback ready. The MCP chrome-bridge uses CDP port 9222, but even when it's connected, bookmark operations may fail with "ClosedResourceError".
- Two MCP Chrome servers configured: In config.yaml, there may be both
chrome (mcp-chrome-bridge) and chrome_devtools (chrome-devtools-mcp). If bookmark search fails on one, don't waste time retrying — switch to direct JSON.
- Backup discipline: Save a backup before EVERY destructive operation (dedup, dead-link removal, categorization, archive). The user might ask to roll back.
- 403 is NOT dead: Chinese sites aggressively block HEAD/GET requests from Python. These work fine in a real browser with cookies. Always keep 403 links.
- date_added format: Chrome timestamps are microseconds since 1601-01-01 UTC, NOT Unix timestamps. Use the correct epoch.
- Empty names: Some bookmarks may have empty
name fields (corruption). Handle gracefully.
- Non-HTTP URLs: Skip
chrome://, about://, file://, data: URLs during dead-link checking.
- Rate limiting: Sleeping between batches is unnecessary if using
ThreadPoolExecutor — the concurrency itself spaces out requests naturally. But if 30+ workers trigger 403, reduce to 10-15.
- restore from backup: If a step corrupts the file, find the right
.bak* file and restore: shutil.copy2(bak_path, path). Match by timestamp — the most recent .bak before the corruption step.