bookmark-bridge 0.1.2 → 2.0.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 +52 -17
- package/bin/bookmark-bridge.mjs +19 -41
- package/package.json +8 -1
- package/release-assets/SHA256SUMS +5 -0
- package/release-assets/bookmark-bridge-aarch64-apple-darwin.tar.gz +0 -0
- package/release-assets/bookmark-bridge-aarch64-unknown-linux-gnu.tar.gz +0 -0
- package/release-assets/bookmark-bridge-x86_64-apple-darwin.tar.gz +0 -0
- package/release-assets/bookmark-bridge-x86_64-pc-windows-msvc.tar.gz +0 -0
- package/release-assets/bookmark-bridge-x86_64-unknown-linux-gnu.tar.gz +0 -0
package/README.md
CHANGED
|
@@ -13,7 +13,8 @@ and relays through it, so several sessions share one browser.
|
|
|
13
13
|
|
|
14
14
|
## Install
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
The default package uses the existing WebSocket connection. For the opt-in
|
|
17
|
+
Native Messaging release, see [Native Messaging](#native-messaging).
|
|
17
18
|
|
|
18
19
|
**1. Register the server with your MCP client.** It downloads itself on first
|
|
19
20
|
run, so you do not need Rust. macOS, Linux and Windows.
|
|
@@ -80,8 +81,9 @@ Do not build from source; the npx package fetches a prebuilt binary.
|
|
|
80
81
|
Your client starts and stops it for you. Nothing to leave running. `git` must be
|
|
81
82
|
on PATH — the snapshots are a real git repository.
|
|
82
83
|
|
|
83
|
-
**2. Install the extension.**
|
|
84
|
-
|
|
84
|
+
**2. Install the extension.** Get [Bridgey from the Chrome Web Store](https://chromewebstore.google.com/detail/ekokljgbbianihlpahackkfdendobngd)
|
|
85
|
+
and leave its mode set to **Legacy (WebSocket)**. For the unpacked legacy
|
|
86
|
+
extension, use `extension/` from the `v0.1.2` tag.
|
|
85
87
|
|
|
86
88
|
That is all of it. The extension's popup goes green once the two find each other.
|
|
87
89
|
|
|
@@ -93,7 +95,9 @@ touch a bookmark, and when you open its popup. If an agent reports that the
|
|
|
93
95
|
extension is not connected, clicking the toolbar icon reconnects it.
|
|
94
96
|
|
|
95
97
|
<details>
|
|
96
|
-
<summary>
|
|
98
|
+
<summary>Build the legacy server from source</summary>
|
|
99
|
+
|
|
100
|
+
Use the `v0.1.2` tag for the legacy server.
|
|
97
101
|
|
|
98
102
|
```sh
|
|
99
103
|
cd mcp && cargo build --release
|
|
@@ -112,6 +116,42 @@ extension's storage. `BOOKMARK_BRIDGE_PORT` changes the port.
|
|
|
112
116
|
|
|
113
117
|
</details>
|
|
114
118
|
|
|
119
|
+
## Native Messaging
|
|
120
|
+
|
|
121
|
+
The v2 server is available on the npm `native` channel. The standard
|
|
122
|
+
`bookmark-bridge` command still installs the legacy server.
|
|
123
|
+
|
|
124
|
+
1. In your MCP client, change the command arguments from `-y bookmark-bridge`
|
|
125
|
+
to `-y bookmark-bridge@native`, then restart its MCP connection. With Claude
|
|
126
|
+
Code, the command for a new entry is
|
|
127
|
+
`claude mcp add bookmark-bridge -- npx -y bookmark-bridge@native`.
|
|
128
|
+
2. Open Bridgey's popup and choose **Native Messaging**. Approve Chrome's
|
|
129
|
+
permission prompt. If setup fails, the extension stays in Legacy mode.
|
|
130
|
+
3. Run `bridge_status` from your MCP client to confirm the connection.
|
|
131
|
+
|
|
132
|
+
The Native npm package contains the prebuilt servers and their checksums.
|
|
133
|
+
The first server startup installs the native host in a user data directory
|
|
134
|
+
outside the npm cache and registers it with Chrome. Chrome launches that host
|
|
135
|
+
when the extension connects. The host and the MCP process communicate over an
|
|
136
|
+
authenticated loopback connection. Keep `git` on the MCP client's PATH. The
|
|
137
|
+
server remembers its absolute path so the Chrome-launched host can use it.
|
|
138
|
+
|
|
139
|
+
Bridgey serves one Native Messaging browser profile at a time. Each profile's
|
|
140
|
+
v2 snapshots live under `native-v2/<profileId>` inside the existing history
|
|
141
|
+
state directory. Legacy snapshots remain available in Legacy mode.
|
|
142
|
+
|
|
143
|
+
To switch back, choose **Legacy (WebSocket)** in the popup, restore the MCP
|
|
144
|
+
command arguments to `-y bookmark-bridge`, and restart its MCP connection.
|
|
145
|
+
Restarting Chrome or reconnecting the popup refreshes the browser connection.
|
|
146
|
+
Restart the MCP connection after updating the server to refresh its tool list.
|
|
147
|
+
|
|
148
|
+
For an unpacked development extension, build the v2 binary and run
|
|
149
|
+
`bookmark-bridge register-native <extension-id>` with that binary before
|
|
150
|
+
selecting Native Messaging. It remembers the ID alongside the published one.
|
|
151
|
+
On macOS and Linux, set `BOOKMARK_BRIDGE_CHROME_USER_DATA_DIR` to the absolute
|
|
152
|
+
user data directory when registering for a Chrome test profile outside the
|
|
153
|
+
default location.
|
|
154
|
+
|
|
115
155
|
## Safety model
|
|
116
156
|
|
|
117
157
|
Three mechanisms, in the order they engage.
|
|
@@ -156,20 +196,16 @@ Read these before trusting the output.
|
|
|
156
196
|
|
|
157
197
|
## Tests
|
|
158
198
|
|
|
159
|
-
`cd mcp && cargo test`
|
|
160
|
-
|
|
161
|
-
|
|
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.
|
|
199
|
+
`cd mcp && cargo test --locked` exercises the MCP server against a fake browser
|
|
200
|
+
extension with an in-memory tree. The test harness uses temporary state and
|
|
201
|
+
does not read your real Chrome bookmarks.
|
|
167
202
|
|
|
168
203
|
## Tools
|
|
169
204
|
|
|
170
205
|
| | |
|
|
171
206
|
| --- | --- |
|
|
172
207
|
| `bookmarks_tree` `_search` `_children` | read |
|
|
208
|
+
| `bookmarks_find_duplicates` | groups identical URLs and lists each bookmark's ID, title and parent folder ID; pass `folderId` to scan a folder and its descendants |
|
|
173
209
|
| `bookmarks_update` `_move` `_create` `_remove` `_removeTree` | single change, each accepts `expect` |
|
|
174
210
|
| `bookmarks_batch` | up to 200 changes at once, per-item `expect` |
|
|
175
211
|
| `history_log` `_restore` `_drift` | snapshots, rollback, what changed since the agent last ran |
|
|
@@ -177,11 +213,10 @@ machine that had already been set up once.
|
|
|
177
213
|
|
|
178
214
|
## Privacy
|
|
179
215
|
|
|
180
|
-
The extension holds `bookmarks` and `storage`.
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
easily.
|
|
216
|
+
The extension holds `bookmarks` and `storage`. Native Messaging also requires
|
|
217
|
+
an optional permission and uses Chrome's extension allowlist. Legacy mode uses
|
|
218
|
+
`127.0.0.1` and rejects handshakes carrying a web origin. Both transports keep
|
|
219
|
+
bookmark data on this computer.
|
|
185
220
|
|
|
186
221
|
## License
|
|
187
222
|
|
package/bin/bookmark-bridge.mjs
CHANGED
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
//
|
|
3
|
-
//
|
|
2
|
+
// Verifies and unpacks the bundled server, then hands over to it.
|
|
3
|
+
// An MCP client launches this over stdio, so nothing may be written to
|
|
4
4
|
// stdout that is not protocol: progress goes to stderr.
|
|
5
5
|
import {spawn} from 'node:child_process';
|
|
6
6
|
import {createHash} from 'node:crypto';
|
|
7
|
-
import {createWriteStream} from 'node:fs';
|
|
8
7
|
import {chmod, mkdir, mkdtemp, readFile, rename, rm, stat} from 'node:fs/promises';
|
|
9
|
-
import {tmpdir} from 'node:os';
|
|
10
8
|
import {dirname, join} from 'node:path';
|
|
11
|
-
import {pipeline} from 'node:stream/promises';
|
|
12
9
|
import {fileURLToPath} from 'node:url';
|
|
13
10
|
|
|
14
11
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
@@ -36,7 +33,7 @@ if (!target) {
|
|
|
36
33
|
die(`no prebuilt server for ${key}. Build from source: https://github.com/${REPO}`);
|
|
37
34
|
}
|
|
38
35
|
|
|
39
|
-
// Cached per version, so upgrading the package
|
|
36
|
+
// Cached per version, so upgrading the package unpacks a matching server rather
|
|
40
37
|
// than silently reusing an old one.
|
|
41
38
|
const cacheDir = join(here, '..', '.bin', pkg.version);
|
|
42
39
|
const binary = join(cacheDir, `bookmark-bridge${EXE}`);
|
|
@@ -45,50 +42,31 @@ async function exists(path) {
|
|
|
45
42
|
try { await stat(path); return true; } catch { return false; }
|
|
46
43
|
}
|
|
47
44
|
|
|
48
|
-
async function fetchOrDie(url, what) {
|
|
49
|
-
const res = await fetch(url, {redirect: 'follow'});
|
|
50
|
-
if (!res.ok) die(`could not download ${what} (HTTP ${res.status})\n ${url}`);
|
|
51
|
-
return res;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
45
|
async function install() {
|
|
55
|
-
const
|
|
46
|
+
const assets = join(here, '..', 'release-assets');
|
|
56
47
|
const name = `bookmark-bridge-${target}.tar.gz`;
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const
|
|
48
|
+
const sums = await readFile(join(assets, 'SHA256SUMS'), 'utf8');
|
|
49
|
+
const expected = sums.split('\n').map(line => line.trim().split(/\s+/))
|
|
50
|
+
.find(([, file]) => file === name)?.[0];
|
|
51
|
+
if (!expected) throw new Error(`no checksum bundled for ${name}`);
|
|
52
|
+
const archive = join(assets, name);
|
|
53
|
+
const actual = createHash('sha256').update(await readFile(archive)).digest('hex');
|
|
54
|
+
if (actual !== expected) throw new Error(`checksum mismatch for ${name}`);
|
|
55
|
+
|
|
56
|
+
await mkdir(dirname(cacheDir), {recursive: true});
|
|
57
|
+
const staging = await mkdtemp(join(dirname(cacheDir), '.install-'));
|
|
67
58
|
try {
|
|
68
|
-
await pipeline(
|
|
69
|
-
(await fetchOrDie(`${base}/${name}`, 'the server')).body,
|
|
70
|
-
createWriteStream(archive),
|
|
71
|
-
);
|
|
72
|
-
|
|
73
|
-
// Never run a binary that does not match what the release published.
|
|
74
|
-
const actual = createHash('sha256').update(await readFile(archive)).digest('hex');
|
|
75
|
-
if (actual !== expected) {
|
|
76
|
-
die(`checksum mismatch for ${name}\n expected ${expected}\n got ${actual}`);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
59
|
await new Promise((resolve, reject) => {
|
|
80
60
|
const tar = spawn('tar', ['-xzf', archive, '-C', staging], {stdio: 'inherit'});
|
|
81
61
|
tar.on('error', reject);
|
|
82
62
|
tar.on('exit', (code) => code === 0 ? resolve() : reject(new Error(`tar exited ${code}`)));
|
|
83
63
|
});
|
|
84
64
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
// directory.
|
|
88
|
-
await
|
|
89
|
-
|
|
90
|
-
// Windows has no execute bit; the archive already carries it elsewhere.
|
|
91
|
-
if (process.platform !== 'win32') await chmod(binary, 0o755);
|
|
65
|
+
const unpacked = join(staging, `bookmark-bridge-${target}`);
|
|
66
|
+
if (process.platform !== 'win32') await chmod(join(unpacked, `bookmark-bridge${EXE}`), 0o755);
|
|
67
|
+
// Publish the whole directory. Another client may already have installed it.
|
|
68
|
+
try { await rename(unpacked, cacheDir); }
|
|
69
|
+
catch (e) { if (!(await exists(binary))) throw e; }
|
|
92
70
|
} finally {
|
|
93
71
|
await rm(staging, {recursive: true, force: true});
|
|
94
72
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bookmark-bridge",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "2.0.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": {
|
|
@@ -13,8 +13,15 @@
|
|
|
13
13
|
},
|
|
14
14
|
"files": [
|
|
15
15
|
"bin/",
|
|
16
|
+
"release-assets/",
|
|
16
17
|
"README.md"
|
|
17
18
|
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"prepublishOnly": "node -e \"if (process.env.npm_config_tag !== 'native' || !require('fs').existsSync('release-assets/SHA256SUMS')) { console.error('Publish the bundled release with npm publish --tag native.'); process.exit(1); }\""
|
|
21
|
+
},
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"tag": "native"
|
|
24
|
+
},
|
|
18
25
|
"engines": {
|
|
19
26
|
"node": ">=18"
|
|
20
27
|
},
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
af0dc501199feb693a68cc1d433499e67a633f495fbce1e6f43c5e165f6b99a3 bookmark-bridge-aarch64-apple-darwin.tar.gz
|
|
2
|
+
35968f8114af290d81638e97d5482c23457b0b72c5a6004eabec78a3739a13c8 bookmark-bridge-aarch64-unknown-linux-gnu.tar.gz
|
|
3
|
+
67fae0592491c25bbb8caca61630dd4213d26fe83e09e2f03bf75a130c70efd6 bookmark-bridge-x86_64-apple-darwin.tar.gz
|
|
4
|
+
d443279e1a1380f442b69df9c86072d40bb5980d550aba8050e6c84aa3fcf184 bookmark-bridge-x86_64-pc-windows-msvc.tar.gz
|
|
5
|
+
3cc3b1480fd7fb6a39d7407d48bf528debe1216791bade50f1e95cfd5dd237b9 bookmark-bridge-x86_64-unknown-linux-gnu.tar.gz
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|