bookmark-bridge 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -36
- package/bin/bookmark-bridge.mjs +7 -5
- package/package.json +16 -4
package/README.md
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
# Bookmark Bridge
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
An MCP client can read and reorganize your Chrome bookmarks through this.
|
|
4
|
+
Every change lands in git first, so anything can be rolled back.
|
|
5
5
|
|
|
6
6
|
```
|
|
7
7
|
MCP client --stdio--> bookmark-bridge --ws(127.0.0.1)-- Chrome extension --chrome.bookmarks--> Chrome
|
|
8
8
|
```
|
|
9
9
|
|
|
10
|
-
The extension
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
The extension dials out rather than listening, because an extension cannot open
|
|
11
|
+
a port. A second MCP process that finds the port taken joins the first as a peer
|
|
12
|
+
and relays through it, so several sessions share one browser.
|
|
13
13
|
|
|
14
14
|
## Install
|
|
15
15
|
|
|
16
|
-
Two pieces: this server, and
|
|
16
|
+
Two pieces: this server, and the Chrome extension that dials into it.
|
|
17
17
|
|
|
18
|
-
**1. Register the server with your MCP client.** It
|
|
19
|
-
|
|
18
|
+
**1. Register the server with your MCP client.** It downloads itself on first
|
|
19
|
+
run, so you do not need Rust. macOS, Linux and Windows.
|
|
20
20
|
|
|
21
21
|
<details open>
|
|
22
22
|
<summary><b>Claude Code</b></summary>
|
|
@@ -72,19 +72,25 @@ Install the bookmark-bridge MCP server for me.
|
|
|
72
72
|
extension from https://github.com/semanticist21/bookmark-bridge and then
|
|
73
73
|
check again.
|
|
74
74
|
|
|
75
|
-
|
|
76
|
-
a prebuilt binary.
|
|
75
|
+
Do not build from source; the npx package fetches a prebuilt binary.
|
|
77
76
|
```
|
|
78
77
|
|
|
79
78
|
</details>
|
|
80
79
|
|
|
81
|
-
Your client starts and stops
|
|
80
|
+
Your client starts and stops it for you. Nothing to leave running. `git` must be
|
|
81
|
+
on PATH — the snapshots are a real git repository.
|
|
82
82
|
|
|
83
83
|
**2. Install the extension.** From the Chrome Web Store, or load `extension/`
|
|
84
84
|
at `chrome://extensions` with developer mode on.
|
|
85
85
|
|
|
86
|
-
That is
|
|
87
|
-
|
|
86
|
+
That is all of it. The extension's popup goes green once the two find each other.
|
|
87
|
+
|
|
88
|
+
The extension sleeps when you are not using it. Chrome ends an idle service
|
|
89
|
+
worker, and this one is not woken on a timer — tidying bookmarks is occasional
|
|
90
|
+
work, and polling a socket nobody is using is what Manifest V3 set out to stop.
|
|
91
|
+
It wakes on its own when the browser starts, when a setting changes, when you
|
|
92
|
+
touch a bookmark, and when you open its popup. If an agent reports that the
|
|
93
|
+
extension is not connected, clicking the toolbar icon reconnects it.
|
|
88
94
|
|
|
89
95
|
<details>
|
|
90
96
|
<summary>From source instead</summary>
|
|
@@ -108,26 +114,26 @@ extension's storage. `BOOKMARK_BRIDGE_PORT` changes the port.
|
|
|
108
114
|
|
|
109
115
|
## Safety model
|
|
110
116
|
|
|
111
|
-
Three
|
|
117
|
+
Three mechanisms, in the order they engage.
|
|
112
118
|
|
|
113
119
|
**Snapshots.** Every mutating call commits the whole tree to a git repository at
|
|
114
|
-
`~/.local/state/bookmark-bridge/history
|
|
115
|
-
before-snapshot
|
|
116
|
-
|
|
120
|
+
`~/.local/state/bookmark-bridge/history`, before and after. A failed
|
|
121
|
+
before-snapshot refuses the change: a mutation nobody can undo is not worth
|
|
122
|
+
making. `force: true` overrides that.
|
|
117
123
|
|
|
118
124
|
**Compare-and-swap.** Any operation may carry `expect: {title?, url?, parentId?}`.
|
|
119
|
-
If the
|
|
120
|
-
|
|
121
|
-
|
|
125
|
+
If the live value differs, that one operation is skipped and reported with what
|
|
126
|
+
was actually there. This is what survives a user edit landing between planning
|
|
127
|
+
and applying.
|
|
122
128
|
|
|
123
|
-
**Drift detection.** `history_drift`
|
|
124
|
-
the commit marking the agent's last
|
|
125
|
-
someone else
|
|
126
|
-
`
|
|
129
|
+
**Drift detection.** `history_drift` diffs the live tree against `agent-head`,
|
|
130
|
+
the commit marking the agent's last finished operation. Whatever differs is
|
|
131
|
+
someone else's work. The extension also logs bookmark events tagged `agent` or
|
|
132
|
+
`user`; that ledger is evidence, the diff is the authority.
|
|
127
133
|
|
|
128
134
|
## Known limits
|
|
129
135
|
|
|
130
|
-
|
|
136
|
+
Read these before trusting the output.
|
|
131
137
|
|
|
132
138
|
- **Reading list is invisible.** `chrome.readingList` is a separate API whose
|
|
133
139
|
items never appear in the bookmark tree. Neither layer sees them.
|
|
@@ -150,12 +156,14 @@ These are real. Read them before trusting the output.
|
|
|
150
156
|
|
|
151
157
|
## Tests
|
|
152
158
|
|
|
153
|
-
`cd mcp && cargo test`
|
|
154
|
-
WebSocket client holding an in-memory tree
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
+
`cd mcp && cargo test` runs the real binary against a fake extension: a
|
|
160
|
+
WebSocket client holding an in-memory tree. The destructive paths are covered
|
|
161
|
+
without a browser, including restoring a deleted subtree under new ids, refusing
|
|
162
|
+
to mutate when no snapshot can be taken, and two sessions sharing one extension
|
|
163
|
+
through the peer relay.
|
|
164
|
+
|
|
165
|
+
Two release-blocking defects came out of writing these, both invisible on a
|
|
166
|
+
machine that had already been set up once.
|
|
159
167
|
|
|
160
168
|
## Tools
|
|
161
169
|
|
|
@@ -169,11 +177,11 @@ extension through the peer relay.
|
|
|
169
177
|
|
|
170
178
|
## Privacy
|
|
171
179
|
|
|
172
|
-
The extension holds `bookmarks
|
|
173
|
-
`127.0.0.1` and
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
180
|
+
The extension holds `bookmarks` and `storage`. It connects only to
|
|
181
|
+
`127.0.0.1` and turns away handshakes carrying an `http(s)` origin, so a web page
|
|
182
|
+
cannot reach it. Nothing goes anywhere else. The token stops a page; it does not
|
|
183
|
+
stop another process on this machine, which can read the token file just as
|
|
184
|
+
easily.
|
|
177
185
|
|
|
178
186
|
## License
|
|
179
187
|
|
package/bin/bookmark-bridge.mjs
CHANGED
|
@@ -20,8 +20,11 @@ const TARGETS = {
|
|
|
20
20
|
'darwin-x64': 'x86_64-apple-darwin',
|
|
21
21
|
'linux-x64': 'x86_64-unknown-linux-gnu',
|
|
22
22
|
'linux-arm64': 'aarch64-unknown-linux-gnu',
|
|
23
|
+
'win32-x64': 'x86_64-pc-windows-msvc',
|
|
23
24
|
};
|
|
24
25
|
|
|
26
|
+
const EXE = process.platform === 'win32' ? '.exe' : '';
|
|
27
|
+
|
|
25
28
|
function die(message) {
|
|
26
29
|
process.stderr.write(`bookmark-bridge: ${message}\n`);
|
|
27
30
|
process.exit(1);
|
|
@@ -30,15 +33,13 @@ function die(message) {
|
|
|
30
33
|
const key = `${process.platform}-${process.arch}`;
|
|
31
34
|
const target = TARGETS[key];
|
|
32
35
|
if (!target) {
|
|
33
|
-
die(`no prebuilt server for ${key}.
|
|
34
|
-
+ `so Windows is not supported. Build from source: `
|
|
35
|
-
+ `https://github.com/${REPO}`);
|
|
36
|
+
die(`no prebuilt server for ${key}. Build from source: https://github.com/${REPO}`);
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
// Cached per version, so upgrading the package fetches a matching server rather
|
|
39
40
|
// than silently reusing an old one.
|
|
40
41
|
const cacheDir = join(here, '..', '.bin', pkg.version);
|
|
41
|
-
const binary = join(cacheDir,
|
|
42
|
+
const binary = join(cacheDir, `bookmark-bridge${EXE}`);
|
|
42
43
|
|
|
43
44
|
async function exists(path) {
|
|
44
45
|
try { await stat(path); return true; } catch { return false; }
|
|
@@ -86,7 +87,8 @@ async function install() {
|
|
|
86
87
|
// directory.
|
|
87
88
|
await rm(cacheDir, {recursive: true, force: true});
|
|
88
89
|
await rename(join(staging, `bookmark-bridge-${target}`), cacheDir);
|
|
89
|
-
|
|
90
|
+
// Windows has no execute bit; the archive already carries it elsewhere.
|
|
91
|
+
if (process.platform !== 'win32') await chmod(binary, 0o755);
|
|
90
92
|
} finally {
|
|
91
93
|
await rm(staging, {recursive: true, force: true});
|
|
92
94
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bookmark-bridge",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Chrome bookmarks as MCP tools, with every change recorded in git so it can be rolled back",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -11,10 +11,22 @@
|
|
|
11
11
|
"bin": {
|
|
12
12
|
"bookmark-bridge": "bin/bookmark-bridge.mjs"
|
|
13
13
|
},
|
|
14
|
-
"files": [
|
|
14
|
+
"files": [
|
|
15
|
+
"bin/",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
15
18
|
"engines": {
|
|
16
19
|
"node": ">=18"
|
|
17
20
|
},
|
|
18
|
-
"os": [
|
|
19
|
-
|
|
21
|
+
"os": [
|
|
22
|
+
"darwin",
|
|
23
|
+
"linux",
|
|
24
|
+
"win32"
|
|
25
|
+
],
|
|
26
|
+
"keywords": [
|
|
27
|
+
"mcp",
|
|
28
|
+
"model-context-protocol",
|
|
29
|
+
"bookmarks",
|
|
30
|
+
"chrome"
|
|
31
|
+
]
|
|
20
32
|
}
|