Architecture Overview¶
Tab Lifecycle Manager is a monorepo with three packages that work together but are independently functional.
System Diagram¶
graph LR
subgraph Claude["Claude Code"]
AI["AI Agent"]
end
subgraph MCP["MCP Plugin"]
Tools["9 MCP Tools"]
Client["HTTP Client"]
end
subgraph Bridge["Bridge Server"]
Express["Express API"]
WS["WebSocket"]
FS["JSON Storage"]
end
subgraph Chrome["Chrome Browser"]
SW["Service Worker"]
Popup["Popup UI"]
CS["Content Scripts"]
Storage["Chrome Storage"]
end
AI -->|tool calls| Tools
Tools --> Client
Client -->|HTTP| Express
Express <-->|read/write| FS
WS <-->|commands| SW
SW -->|HTTP sync| Express
Popup <-->|messages| SW
CS -->|element data| SW
SW <-->|read/write| Storage
Data Flow¶
Extension (Primary)¶
Chrome Storage is the source of truth. The service worker manages all state transitions through a mutex-protected read-modify-write pattern. The popup and options pages communicate with the service worker via chrome.runtime.sendMessage.
Bridge (Sync/Backup)¶
Every minute, the extension syncs its state to the bridge via HTTP. The bridge stores a copy in JSON files at ~/.tab-manager/. This enables MCP access without requiring the extension to be active.
The bridge also sends commands back via WebSocket (e.g., wake a tab from Claude Code).
MCP (AI Interface)¶
The MCP plugin is a thin HTTP client over the bridge API. It exposes 9 tools that Claude Code can call to list, snooze, queue, watch, and manage tabs.
Key Design Decisions¶
| Decision | Rationale |
|---|---|
| Chrome Storage as primary | Extension works fully standalone without the bridge |
| Mutex on storage writes | Prevents race conditions from concurrent message handlers |
| Chrome Alarms for snooze | Survives service worker suspension, Chrome fires missed alarms on restart |
| CSS selector targeting | Users click to select what to watch, no manual selector writing |
| Content script extraction | Pages render in a real browser context, no fetch-based scraping |
| localhost-only bridge | Security boundary, prevents SSRF and data exfiltration |
| No authentication on bridge | Localhost-only + CORS origin check is sufficient for single-user |
Security Model¶
- URL validation: all
chrome.tabs.createcalls are guarded byisAllowedUrl()(http/https only) - Bridge SSRF: bridge URL restricted to localhost/127.0.0.1 in both extension and MCP
- CORS: bridge rejects requests from non-
chrome-extension://origins - CSS.escape(): selector generation escapes IDs and class names
- textContent over innerHTML: watch extraction uses text content to avoid processing scripts
- Input validation: bridge validates URLs, wakeAt timestamps, cssSelector length, tab payloads