4.3 KiB
Agent handshake — Bookmarks API for Scripting and AI
An API add-on for Firefox to allow agentic and script-based management of user bookmarks.
Use this when working in this repo. The shipped product is extension/ only.
Do not edit places.sqlite while Firefox is open if this API is reachable.
Preconditions
- Add-on loaded. Secrets are issued and stored in the add-on (toolbar → Manage clients). The options page does not need the native host.
- Native host installed only if scripts should call in:
powershell -NoProfile -File tools/install-native-host.ps1 - Firefox running for those calls
If http://127.0.0.1:17634/health fails, the host is not up. If /health works but calls 401, you do not have a current FAB_TOKEN.
Auth
This is a normal API token, not an SSH key.
- In Firefox, open the Bookmarks API toolbar icon → Manage clients.
- Name the client (e.g.
cursor-agent) and click Generate secret. - Copy the
fab_…value once. Store it however you already store secrets: env var, Vaultwarden,.env, CI secret, Cursor env. - Give tooling only that string:
set FAB_TOKEN=fab_…
python tools/client.py meta.methods
FAB_TOKEN_FILE pointing at a file that contains only the token is also fine. --token on the CLI overrides both.
The add-on stores SHA-256(token) only. Revoke from the same page. A local process that never received the secret cannot call the API.
Preferred caller
python tools/client.py METHOD [ARGS_JSON]
ARGS_JSON is a JSON array matching the WebExtension function arguments.
python tools/client.py bookmarks.search "[{\"title\":\"10.132.x.x\"}]"
HTTP:
POST /v1/call
Authorization: Bearer fab_…
Content-Type: application/json
{"method": "bookmarks.search", "args": [{"title": "10.132.x.x"}]}
Allowlisted methods (v0.2)
| Method | Args | Notes |
|---|---|---|
meta.ping |
(none) | Bridge alive |
meta.methods |
(none) | This table |
bookmarks.getTree |
(none) | Full tree |
bookmarks.getSubTree |
id |
|
bookmarks.getChildren |
id |
|
bookmarks.get |
id or [id, …] |
|
bookmarks.search |
{title? url? query?} or string |
|
bookmarks.create |
{parentId?, title, url?} |
Omit url to create a folder |
bookmarks.update |
id, {title? url?} |
|
bookmarks.move |
id, {parentId? index?} |
|
bookmarks.remove |
id |
Bookmark or empty folder |
bookmarks.removeTree |
id |
Folder and descendants |
bookmarks.getRecent |
numberOfItems |
Do not invent other browser.* names. Adding an API means editing ALLOWED in extension/background.js and reloading the extension.
REST aliases use the same Bearer token. GET /health has no token (loopback liveness only).
Typical folder replace
bookmarks.search{title: "…"}— take the hit without aurl. Fail if zero or many.bookmarks.getChildrenon that id.bookmarks.removeeach bookmark;bookmarks.removeTreeeach child folder.bookmarks.createeach new item withparentIdset.
examples/replace_named_folder.py does that (FAB_TOKEN must be set).
Failure modes
| Symptom | Cause | What to do |
|---|---|---|
Connection refused on :17634 |
Firefox closed or add-on/host missing | Open Firefox; load add-on; install native host |
401 Bearer token required |
Unsigned request | Set FAB_TOKEN |
401 unknown token |
Revoked, typo, or never generated | Manage clients → generate again |
502 method not allowed |
Typo or API not in ALLOWED |
meta.methods |
| Temporary add-on gone after restart | Unsigned on Firefox Release | Load again, or sign via AMO |
Security boundaries
- No
runtime.onMessageExternal. Other add-ons cannot call the dispatcher. - Native host
allowed_extensionsis pinned to this add-on's gecko id. - HTTP is
127.0.0.1only; browserOriginheaders are rejected. - Secrets are ordinary Bearer tokens. Do not write them under
%LOCALAPPDATA%\firefox-agent-bridge. - Another add-on with the
bookmarkspermission already has Places. - Do not add
tabs,history,<all_urls>, oronMessageExternalwithout a new threat review.
What this project is not
- Not a bidirectional sync implementation
- Not a general Firefox remote-control surface
- Not a reason to keep editing
places.sqliteon a live profile