ai-bookmarks-mcp 1.0.0
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/LICENSE +21 -0
- package/README.md +63 -0
- package/dist/index.js +363 -0
- package/package.json +39 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 AI Bookmark Manager Authors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# ai-bookmarks-mcp
|
|
2
|
+
|
|
3
|
+
Model Context Protocol server that exposes your Chrome bookmarks to AI agents —
|
|
4
|
+
Claude Desktop, [Antigravity](https://antigravity.google/), Cursor, Cline, or
|
|
5
|
+
any other MCP client. Ask your agent to find bookmarks by topic, list your
|
|
6
|
+
folder taxonomy, or look up a specific saved page.
|
|
7
|
+
|
|
8
|
+
Companion to [AI Bookmark Manager](https://github.com/serguei9090/AI-Bookmark-Manager),
|
|
9
|
+
a Chrome extension that tags, summarizes, and organizes bookmarks with AI —
|
|
10
|
+
but this server works standalone: it reads Chrome's native `Bookmarks` file
|
|
11
|
+
directly, no extension required.
|
|
12
|
+
|
|
13
|
+
## Tools
|
|
14
|
+
|
|
15
|
+
- **search_bookmarks** — keyword search across title/URL/summary/tags. If the
|
|
16
|
+
extension's "Semantic Search" toggle is on and bookmarks have been indexed
|
|
17
|
+
with the Local ONNX embedding provider, results are ranked by vector
|
|
18
|
+
similarity instead, so a query can match by *concept* rather than exact
|
|
19
|
+
words. Semantic scoring runs entirely locally (a small ONNX model,
|
|
20
|
+
downloaded and cached on first use) — no API key, no cloud calls.
|
|
21
|
+
- **list_folders** — the full folder hierarchy and each folder's AI prompt
|
|
22
|
+
context.
|
|
23
|
+
- **get_bookmark** — full details (tags, summary, dates) for one bookmark by
|
|
24
|
+
ID or URL.
|
|
25
|
+
|
|
26
|
+
## Setup
|
|
27
|
+
|
|
28
|
+
No installation needed — run it with `npx`:
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"mcpServers": {
|
|
33
|
+
"ai-bookmarks": {
|
|
34
|
+
"command": "npx",
|
|
35
|
+
"args": ["-y", "ai-bookmarks-mcp"]
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Paste that into:
|
|
42
|
+
- **Claude Desktop**: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
43
|
+
- **Antigravity**: your user or project `mcp_config.json`
|
|
44
|
+
- **Cursor / Cline**: your `mcp.json`
|
|
45
|
+
|
|
46
|
+
By default it auto-detects your Chrome (or Edge/Brave) `Bookmarks` file. To
|
|
47
|
+
point it at a specific file instead, set `BOOKMARKS_DATA_PATH`:
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"mcpServers": {
|
|
52
|
+
"ai-bookmarks": {
|
|
53
|
+
"command": "npx",
|
|
54
|
+
"args": ["-y", "ai-bookmarks-mcp"],
|
|
55
|
+
"env": { "BOOKMARKS_DATA_PATH": "C:\\path\\to\\Bookmarks" }
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## License
|
|
62
|
+
|
|
63
|
+
MIT
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Model Context Protocol (MCP) Server
|
|
4
|
+
*
|
|
5
|
+
* Exposes three primary tools for AI Assistants:
|
|
6
|
+
* 1. search_bookmarks (hybrid keyword and semantic vector search, when the
|
|
7
|
+
* extension's semantic search toggle is on and bookmarks are indexed)
|
|
8
|
+
* 2. list_folders (returns taxonomy and promptContexts)
|
|
9
|
+
* 3. get_bookmark (retrieves tags, summary, and metadata by ID or URL)
|
|
10
|
+
*/
|
|
11
|
+
import * as fs from "node:fs";
|
|
12
|
+
import * as path from "node:path";
|
|
13
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
14
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
15
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
16
|
+
const CHUNK_PREFIX = "bm_meta_v1_";
|
|
17
|
+
const METADATA_FOLDER_NAME = ".ai_bookmark_metadata";
|
|
18
|
+
/** Must match LOCAL_MODEL_ID in src/services/embeddingService.ts so query
|
|
19
|
+
* vectors and synced bookmark vectors live in the same embedding space. */
|
|
20
|
+
const LOCAL_MODEL_ID = "Xenova/all-MiniLM-L6-v2";
|
|
21
|
+
function getDefaultChromeBookmarksPath() {
|
|
22
|
+
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
23
|
+
const localAppData = process.env.LOCALAPPDATA || "";
|
|
24
|
+
const candidates = [
|
|
25
|
+
path.join(localAppData, "Google", "Chrome", "User Data", "Default", "Bookmarks"),
|
|
26
|
+
path.join(home, "Library", "Application Support", "Google", "Chrome", "Default", "Bookmarks"),
|
|
27
|
+
path.join(home, ".config", "google-chrome", "Default", "Bookmarks"),
|
|
28
|
+
path.join(localAppData, "Microsoft", "Edge", "User Data", "Default", "Bookmarks"),
|
|
29
|
+
path.join(localAppData, "BraveSoftware", "Brave-Browser", "User Data", "Default", "Bookmarks"),
|
|
30
|
+
];
|
|
31
|
+
for (const p of candidates) {
|
|
32
|
+
if (p && fs.existsSync(p))
|
|
33
|
+
return p;
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
function parseChromeBookmarksFile(filePath) {
|
|
38
|
+
const raw = fs.readFileSync(filePath, "utf-8");
|
|
39
|
+
const data = JSON.parse(raw);
|
|
40
|
+
let metadataFolder = null;
|
|
41
|
+
const findMetaFolder = (node) => {
|
|
42
|
+
if (node.type === "folder" && node.name === METADATA_FOLDER_NAME) {
|
|
43
|
+
metadataFolder = node;
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (node.children) {
|
|
47
|
+
for (const child of node.children)
|
|
48
|
+
findMetaFolder(child);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
for (const root of Object.values(data.roots || {})) {
|
|
52
|
+
findMetaFolder(root);
|
|
53
|
+
}
|
|
54
|
+
let syncedMeta = { bookmarks: {}, folders: {} };
|
|
55
|
+
if (metadataFolder && metadataFolder.children) {
|
|
56
|
+
const chunks = [];
|
|
57
|
+
for (const child of metadataFolder.children || []) {
|
|
58
|
+
if (child.name?.startsWith(CHUNK_PREFIX)) {
|
|
59
|
+
const headerEnd = child.name.indexOf(":::");
|
|
60
|
+
if (headerEnd !== -1) {
|
|
61
|
+
const header = child.name.slice(0, headerEnd);
|
|
62
|
+
const content = child.name.slice(headerEnd + 3);
|
|
63
|
+
const parts = header.replace(CHUNK_PREFIX, "").split("_");
|
|
64
|
+
const index = Number.parseInt(parts[0], 10);
|
|
65
|
+
if (!Number.isNaN(index)) {
|
|
66
|
+
chunks.push({ index, data: content });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
chunks.sort((a, b) => a.index - b.index);
|
|
72
|
+
const fullJson = chunks.map((c) => c.data).join("");
|
|
73
|
+
if (fullJson) {
|
|
74
|
+
try {
|
|
75
|
+
syncedMeta = JSON.parse(fullJson);
|
|
76
|
+
}
|
|
77
|
+
catch (e) {
|
|
78
|
+
console.error("[MCP] Error parsing shadow metadata:", e);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const bookmarksList = [];
|
|
83
|
+
const foldersList = [];
|
|
84
|
+
const traverse = (node, parentId) => {
|
|
85
|
+
if (node.name === METADATA_FOLDER_NAME)
|
|
86
|
+
return;
|
|
87
|
+
if (node.type === "folder") {
|
|
88
|
+
if (node.id !== "0") {
|
|
89
|
+
const meta = syncedMeta.folders[node.id] || {};
|
|
90
|
+
foldersList.push({
|
|
91
|
+
id: node.id,
|
|
92
|
+
parentId,
|
|
93
|
+
name: node.name,
|
|
94
|
+
promptContext: meta.promptContext || "",
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
if (node.children) {
|
|
98
|
+
for (const child of node.children)
|
|
99
|
+
traverse(child, node.id);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
else if (node.type === "url") {
|
|
103
|
+
const meta = syncedMeta.bookmarks[node.id] || {};
|
|
104
|
+
bookmarksList.push({
|
|
105
|
+
id: node.id,
|
|
106
|
+
title: node.name,
|
|
107
|
+
url: node.url || "",
|
|
108
|
+
folderId: parentId,
|
|
109
|
+
tags: meta.tags || [],
|
|
110
|
+
summary: meta.summary || "",
|
|
111
|
+
dateAdded: Number(node.date_added) || Date.now(),
|
|
112
|
+
embedding: meta.embedding,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
for (const root of Object.values(data.roots || {})) {
|
|
117
|
+
traverse(root, null);
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
bookmarks: bookmarksList,
|
|
121
|
+
folders: foldersList,
|
|
122
|
+
searchSettings: syncedMeta.searchSettings,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
function loadBookmarkData() {
|
|
126
|
+
// 1. If an explicit file is specified, check if it's Chrome format or JSON export
|
|
127
|
+
const explicitPath = process.env.BOOKMARKS_DATA_PATH;
|
|
128
|
+
if (explicitPath && fs.existsSync(explicitPath)) {
|
|
129
|
+
try {
|
|
130
|
+
const raw = fs.readFileSync(explicitPath, "utf-8");
|
|
131
|
+
const parsed = JSON.parse(raw);
|
|
132
|
+
if (parsed.roots) {
|
|
133
|
+
return parseChromeBookmarksFile(explicitPath);
|
|
134
|
+
}
|
|
135
|
+
if (parsed.bookmarks && parsed.folders) {
|
|
136
|
+
return parsed;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
catch (e) {
|
|
140
|
+
console.error("[MCP] Error reading explicit bookmarks data path:", e);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
// 2. Local exported json file fallback (for development/testing)
|
|
144
|
+
const localExport = path.join(process.cwd(), "bookmarks-data.json");
|
|
145
|
+
if (fs.existsSync(localExport)) {
|
|
146
|
+
try {
|
|
147
|
+
const raw = fs.readFileSync(localExport, "utf-8");
|
|
148
|
+
return JSON.parse(raw);
|
|
149
|
+
}
|
|
150
|
+
catch (e) {
|
|
151
|
+
console.error("[MCP] Error reading local bookmarks-data.json:", e);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// 3. Auto-detect native Chrome Bookmarks file (Zero configuration!)
|
|
155
|
+
const chromePath = getDefaultChromeBookmarksPath();
|
|
156
|
+
if (chromePath && fs.existsSync(chromePath)) {
|
|
157
|
+
try {
|
|
158
|
+
return parseChromeBookmarksFile(chromePath);
|
|
159
|
+
}
|
|
160
|
+
catch (e) {
|
|
161
|
+
console.error("[MCP] Error auto-parsing Chrome Bookmarks:", e);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return { bookmarks: [], folders: [] };
|
|
165
|
+
}
|
|
166
|
+
/** Cosine similarity between two equal-length float vectors. */
|
|
167
|
+
function cosineSimilarity(a, b) {
|
|
168
|
+
if (!a || !b || a.length !== b.length || a.length === 0)
|
|
169
|
+
return 0;
|
|
170
|
+
let dot = 0;
|
|
171
|
+
let normA = 0;
|
|
172
|
+
let normB = 0;
|
|
173
|
+
for (let i = 0; i < a.length; i++) {
|
|
174
|
+
dot += a[i] * b[i];
|
|
175
|
+
normA += a[i] * a[i];
|
|
176
|
+
normB += b[i] * b[i];
|
|
177
|
+
}
|
|
178
|
+
if (normA === 0 || normB === 0)
|
|
179
|
+
return 0;
|
|
180
|
+
return dot / (Math.sqrt(normA) * Math.sqrt(normB));
|
|
181
|
+
}
|
|
182
|
+
let localExtractorPromise = null;
|
|
183
|
+
/**
|
|
184
|
+
* Lazily loads the local MiniLM model. MCP is called by external agents
|
|
185
|
+
* (Claude Desktop, Antigravity, Cursor, ...) that need a self-contained
|
|
186
|
+
* search tool — it deliberately never depends on the bookmark extension's
|
|
187
|
+
* own AI provider/API keys (those are for a separate concern: auto-sort and
|
|
188
|
+
* tagging). The query is always embedded locally, at no cost and no
|
|
189
|
+
* network dependency once this model is cached.
|
|
190
|
+
*/
|
|
191
|
+
function getLocalExtractor() {
|
|
192
|
+
if (!localExtractorPromise) {
|
|
193
|
+
localExtractorPromise = import("@huggingface/transformers").then(async ({ pipeline, env }) => {
|
|
194
|
+
env.allowLocalModels = false;
|
|
195
|
+
const extractor = await pipeline("feature-extraction", LOCAL_MODEL_ID);
|
|
196
|
+
return extractor;
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
return localExtractorPromise;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Embeds the search query locally so results can be scored against the
|
|
203
|
+
* bookmark vectors synced from the shadow folder. Returns null (never a
|
|
204
|
+
* fabricated vector) when semantic search is off or embedding fails, so
|
|
205
|
+
* callers fall back to plain keyword matching.
|
|
206
|
+
*
|
|
207
|
+
* Note: this only produces matches against bookmarks that were themselves
|
|
208
|
+
* indexed with the Local ONNX provider (384-dim, same model). Bookmarks
|
|
209
|
+
* indexed via the extension's Gemini provider (768-dim) won't line up with
|
|
210
|
+
* this query vector — they're excluded by the dimension check in the caller
|
|
211
|
+
* and fall back to keyword matching for those specific bookmarks.
|
|
212
|
+
*/
|
|
213
|
+
async function embedQueryIfEnabled(query, searchSettings) {
|
|
214
|
+
if (!searchSettings?.semanticSearchEnabled)
|
|
215
|
+
return null;
|
|
216
|
+
try {
|
|
217
|
+
const extractor = await getLocalExtractor();
|
|
218
|
+
const output = await extractor(query, { pooling: "mean", normalize: true });
|
|
219
|
+
return Array.from(output.data);
|
|
220
|
+
}
|
|
221
|
+
catch (e) {
|
|
222
|
+
console.error("[MCP] Local query embedding failed:", e);
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
const server = new Server({
|
|
227
|
+
name: "ai-bookmarks",
|
|
228
|
+
version: "1.0.0",
|
|
229
|
+
}, {
|
|
230
|
+
capabilities: {
|
|
231
|
+
tools: {},
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
235
|
+
return {
|
|
236
|
+
tools: [
|
|
237
|
+
{
|
|
238
|
+
name: "search_bookmarks",
|
|
239
|
+
description: "Search bookmarks by keywords, concepts, tags, or summaries. Uses " +
|
|
240
|
+
"semantic vector search (matching the embedding provider configured " +
|
|
241
|
+
"in the extension) when semantic search is enabled there and " +
|
|
242
|
+
"bookmarks have been indexed; otherwise falls back to keyword matching.",
|
|
243
|
+
inputSchema: {
|
|
244
|
+
type: "object",
|
|
245
|
+
properties: {
|
|
246
|
+
query: { type: "string", description: "Search query or topic" },
|
|
247
|
+
limit: {
|
|
248
|
+
type: "number",
|
|
249
|
+
description: "Maximum results (default: 10)",
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
required: ["query"],
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
name: "list_folders",
|
|
257
|
+
description: "Lists all bookmark folders and their AI-organization prompt contexts.",
|
|
258
|
+
inputSchema: {
|
|
259
|
+
type: "object",
|
|
260
|
+
properties: {},
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
name: "get_bookmark",
|
|
265
|
+
description: "Gets full details of a specific bookmark by ID or URL.",
|
|
266
|
+
inputSchema: {
|
|
267
|
+
type: "object",
|
|
268
|
+
properties: {
|
|
269
|
+
id: { type: "string", description: "Bookmark ID or URL" },
|
|
270
|
+
},
|
|
271
|
+
required: ["id"],
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
],
|
|
275
|
+
};
|
|
276
|
+
});
|
|
277
|
+
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
278
|
+
const data = loadBookmarkData();
|
|
279
|
+
if (req.params.name === "list_folders") {
|
|
280
|
+
return {
|
|
281
|
+
content: [
|
|
282
|
+
{
|
|
283
|
+
type: "text",
|
|
284
|
+
text: JSON.stringify(data.folders, null, 2),
|
|
285
|
+
},
|
|
286
|
+
],
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
if (req.params.name === "get_bookmark") {
|
|
290
|
+
const id = String(req.params.arguments?.id || "");
|
|
291
|
+
const found = data.bookmarks.find((b) => b.id === id || b.url === id);
|
|
292
|
+
if (!found) {
|
|
293
|
+
return {
|
|
294
|
+
content: [
|
|
295
|
+
{
|
|
296
|
+
type: "text",
|
|
297
|
+
text: `No bookmark found matching '${id}'`,
|
|
298
|
+
},
|
|
299
|
+
],
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
return {
|
|
303
|
+
content: [
|
|
304
|
+
{
|
|
305
|
+
type: "text",
|
|
306
|
+
text: JSON.stringify(found, null, 2),
|
|
307
|
+
},
|
|
308
|
+
],
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
if (req.params.name === "search_bookmarks") {
|
|
312
|
+
const rawQuery = String(req.params.arguments?.query || "");
|
|
313
|
+
const query = rawQuery.toLowerCase();
|
|
314
|
+
const limit = Number(req.params.arguments?.limit || 10);
|
|
315
|
+
const keywordMatch = (bm) => bm.title.toLowerCase().includes(query) ||
|
|
316
|
+
bm.url.toLowerCase().includes(query) ||
|
|
317
|
+
bm.summary.toLowerCase().includes(query) ||
|
|
318
|
+
bm.tags.some((t) => t.toLowerCase().includes(query));
|
|
319
|
+
const queryEmbedding = await embedQueryIfEnabled(rawQuery, data.searchSettings);
|
|
320
|
+
let matches;
|
|
321
|
+
if (queryEmbedding && queryEmbedding.length > 0) {
|
|
322
|
+
matches = data.bookmarks
|
|
323
|
+
.map((bm) => {
|
|
324
|
+
const keywordScore = keywordMatch(bm) ? 0.5 : 0;
|
|
325
|
+
const vectorScore = bm.embedding && bm.embedding.length === queryEmbedding.length
|
|
326
|
+
? Math.max(0, cosineSimilarity(queryEmbedding, bm.embedding))
|
|
327
|
+
: 0;
|
|
328
|
+
return { bm, score: Math.max(keywordScore, vectorScore) };
|
|
329
|
+
})
|
|
330
|
+
.filter((item) => item.score > 0.15)
|
|
331
|
+
.sort((a, b) => b.score - a.score)
|
|
332
|
+
.map((item) => item.bm);
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
matches = data.bookmarks.filter(keywordMatch);
|
|
336
|
+
}
|
|
337
|
+
return {
|
|
338
|
+
content: [
|
|
339
|
+
{
|
|
340
|
+
type: "text",
|
|
341
|
+
text: JSON.stringify(matches.slice(0, limit), null, 2),
|
|
342
|
+
},
|
|
343
|
+
],
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
return {
|
|
347
|
+
content: [
|
|
348
|
+
{
|
|
349
|
+
type: "text",
|
|
350
|
+
text: `Unknown tool: ${req.params.name}`,
|
|
351
|
+
},
|
|
352
|
+
],
|
|
353
|
+
};
|
|
354
|
+
});
|
|
355
|
+
async function main() {
|
|
356
|
+
const transport = new StdioServerTransport();
|
|
357
|
+
await server.connect(transport);
|
|
358
|
+
console.error("AI Bookmarks MCP server running via stdio");
|
|
359
|
+
}
|
|
360
|
+
main().catch((err) => {
|
|
361
|
+
console.error("Fatal error in MCP server:", err);
|
|
362
|
+
process.exit(1);
|
|
363
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ai-bookmarks-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Model Context Protocol server exposing your Chrome bookmarks (keyword + local semantic vector search) to AI agents like Claude Desktop, Antigravity, Cursor, and Cline.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/serguei9090/AI-Bookmark-Manager.git",
|
|
10
|
+
"directory": "mcp-server"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/serguei9090/AI-Bookmark-Manager#readme",
|
|
13
|
+
"keywords": [
|
|
14
|
+
"mcp",
|
|
15
|
+
"model-context-protocol",
|
|
16
|
+
"bookmarks",
|
|
17
|
+
"chrome-bookmarks",
|
|
18
|
+
"semantic-search",
|
|
19
|
+
"claude"
|
|
20
|
+
],
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"bin": {
|
|
25
|
+
"ai-bookmarks-mcp": "./dist/index.js"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "tsc",
|
|
29
|
+
"start": "node dist/index.js"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@huggingface/transformers": "^4.2.0",
|
|
33
|
+
"@modelcontextprotocol/sdk": "^1.5.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/node": "^22.14.0",
|
|
37
|
+
"typescript": "^5.8.2"
|
|
38
|
+
}
|
|
39
|
+
}
|