notebooklm-mcp-server 1.0.1 → 1.0.3
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 +36 -16
- package/build/index.js +334 -15
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -71,7 +71,7 @@ Before using the server, you must link it to your Google Account. This version u
|
|
|
71
71
|
|
|
72
72
|
1. Run the authentication command:
|
|
73
73
|
```bash
|
|
74
|
-
notebooklm-mcp-server auth
|
|
74
|
+
npx notebooklm-mcp-server auth
|
|
75
75
|
```
|
|
76
76
|
2. A browser window will open. Log in with your Google account.
|
|
77
77
|
3. Close the browser once you see your notebooks. Your session is now securely saved locally.
|
|
@@ -124,21 +124,36 @@ Since VS Code does not support MCP natively yet, you must use an extension:
|
|
|
124
124
|
|
|
125
125
|
### 🌌 Antigravity
|
|
126
126
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
127
|
+
Antigravity supports MCP natively. You can add the server by editing your global configuration file:
|
|
128
|
+
|
|
129
|
+
1. **Locate your `mcp.json`**:
|
|
130
|
+
- **Windows**: `%APPDATA%\antigravity\mcp.json`
|
|
131
|
+
- **macOS**: `~/Library/Application Support/antigravity/mcp.json`
|
|
132
|
+
- **Linux**: `~/.config/antigravity/mcp.json`
|
|
133
|
+
|
|
134
|
+
2. **Add the server** to the `mcpServers` object:
|
|
135
|
+
|
|
136
|
+
```json
|
|
137
|
+
{
|
|
138
|
+
"mcpServers": {
|
|
139
|
+
"notebooklm": {
|
|
140
|
+
"command": "npx",
|
|
141
|
+
"args": ["-y", "notebooklm-mcp-server", "start"]
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
3. **Restart Antigravity**: The new tools will appear in your sidebar instantly.
|
|
148
|
+
|
|
149
|
+
---
|
|
135
150
|
|
|
136
151
|
### 💎 Gemini CLI
|
|
137
152
|
|
|
138
153
|
Run the following command in your terminal to add the notebooklm skill:
|
|
139
154
|
|
|
140
155
|
```bash
|
|
141
|
-
gemini mcp add notebooklm -- npx -y notebooklm-mcp-server start
|
|
156
|
+
gemini mcp add notebooklm --scope user -- npx -y notebooklm-mcp-server start
|
|
142
157
|
```
|
|
143
158
|
|
|
144
159
|
---
|
|
@@ -155,12 +170,17 @@ claude skill add notebooklm -- "npx -y notebooklm-mcp-server start"
|
|
|
155
170
|
|
|
156
171
|
## 📖 Documentation
|
|
157
172
|
|
|
158
|
-
| Tool
|
|
159
|
-
|
|
|
160
|
-
| `list_notebooks`
|
|
161
|
-
| `create_notebook`
|
|
162
|
-
| `get_notebook`
|
|
163
|
-
| `query_notebook`
|
|
173
|
+
| Tool | Description |
|
|
174
|
+
| :------------------------ | :---------------------------------------------------- |
|
|
175
|
+
| `list_notebooks` | Lists all notebooks available in your account. |
|
|
176
|
+
| `create_notebook` | Creates a new notebook with an optional title. |
|
|
177
|
+
| `get_notebook` | Retrieves the details and sources of a notebook. |
|
|
178
|
+
| `query_notebook` | Asks a grounded question to a specific notebook. |
|
|
179
|
+
| `add_source_url` | Adds a website or YouTube video as a source. |
|
|
180
|
+
| `add_source_text` | Adds pasted text content as a source. |
|
|
181
|
+
| `generate_audio_overview` | Triggers the generation of an Audio Overview podcast. |
|
|
182
|
+
| `rename_notebook` | Renames an existing notebook. |
|
|
183
|
+
| `delete_notebook` | Deletes a notebook (Warning: Destructive). |
|
|
164
184
|
|
|
165
185
|
---
|
|
166
186
|
|
package/build/index.js
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
-
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
4
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, ErrorCode, McpError, } from "@modelcontextprotocol/sdk/types.js";
|
|
5
5
|
import { z } from "zod";
|
|
6
|
+
import { chromium } from "playwright";
|
|
7
|
+
import * as path from "path";
|
|
8
|
+
import * as os from "os";
|
|
6
9
|
import { Command } from "commander";
|
|
7
10
|
import { runAuth } from "./auth.js";
|
|
8
|
-
//
|
|
9
|
-
const ListNotebooksSchema = z.object({});
|
|
11
|
+
// Validation Schemas
|
|
10
12
|
const CreateNotebookSchema = z.object({
|
|
11
13
|
title: z.string().optional(),
|
|
12
14
|
});
|
|
15
|
+
const ListNotebooksSchema = z.object({});
|
|
13
16
|
const GetNotebookSchema = z.object({
|
|
14
17
|
notebookId: z.string(),
|
|
15
18
|
});
|
|
@@ -17,27 +20,73 @@ const QueryNotebookSchema = z.object({
|
|
|
17
20
|
notebookId: z.string(),
|
|
18
21
|
query: z.string(),
|
|
19
22
|
});
|
|
23
|
+
const AddSourceUrlSchema = z.object({
|
|
24
|
+
notebookId: z.string(),
|
|
25
|
+
url: z.string().url(),
|
|
26
|
+
});
|
|
27
|
+
const AddSourceTextSchema = z.object({
|
|
28
|
+
notebookId: z.string(),
|
|
29
|
+
title: z.string(),
|
|
30
|
+
text: z.string(),
|
|
31
|
+
});
|
|
32
|
+
const RenameNotebookSchema = z.object({
|
|
33
|
+
notebookId: z.string(),
|
|
34
|
+
newTitle: z.string(),
|
|
35
|
+
});
|
|
36
|
+
const DeleteNotebookSchema = z.object({
|
|
37
|
+
notebookId: z.string(),
|
|
38
|
+
});
|
|
39
|
+
const AudioOverviewSchema = z.object({
|
|
40
|
+
notebookId: z.string(),
|
|
41
|
+
});
|
|
20
42
|
class NotebookLMServer {
|
|
21
43
|
server;
|
|
22
44
|
browser = null;
|
|
45
|
+
userDataDir;
|
|
23
46
|
constructor() {
|
|
47
|
+
this.userDataDir = path.join(os.homedir(), ".notebooklm-mcp-auth");
|
|
24
48
|
this.server = new Server({
|
|
25
49
|
name: "notebooklm-mcp-server",
|
|
26
|
-
version: "1.0.
|
|
50
|
+
version: "1.0.2",
|
|
27
51
|
}, {
|
|
28
52
|
capabilities: {
|
|
29
53
|
tools: {},
|
|
30
54
|
},
|
|
31
55
|
});
|
|
32
56
|
this.setupTools();
|
|
57
|
+
// Error handling
|
|
58
|
+
this.server.onerror = (error) => console.error("[MCP Error]", error);
|
|
59
|
+
}
|
|
60
|
+
async cleanup() {
|
|
61
|
+
if (this.browser) {
|
|
62
|
+
await this.browser.close();
|
|
63
|
+
this.browser = null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
async getBrowser() {
|
|
67
|
+
if (!this.browser) {
|
|
68
|
+
this.browser = await chromium.launchPersistentContext(this.userDataDir, {
|
|
69
|
+
headless: true, // Standard for MCP servers
|
|
70
|
+
args: ["--no-sandbox", "--disable-setuid-sandbox"],
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
return this.browser;
|
|
74
|
+
}
|
|
75
|
+
async getPage(notebookId) {
|
|
76
|
+
const context = await this.getBrowser();
|
|
77
|
+
const page = await context.newPage();
|
|
78
|
+
const url = notebookId
|
|
79
|
+
? `https://notebooklm.google.com/notebook/${notebookId}`
|
|
80
|
+
: "https://notebooklm.google.com/";
|
|
81
|
+
await page.goto(url, { waitUntil: "networkidle", timeout: 30000 });
|
|
82
|
+
return page;
|
|
33
83
|
}
|
|
34
|
-
// ... (implementation same as before but including the setupTools)
|
|
35
84
|
setupTools() {
|
|
36
85
|
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
37
86
|
tools: [
|
|
38
87
|
{
|
|
39
88
|
name: "list_notebooks",
|
|
40
|
-
description: "Lists all your NotebookLM notebooks",
|
|
89
|
+
description: "Lists all your Google NotebookLM notebooks",
|
|
41
90
|
inputSchema: { type: "object", properties: {} },
|
|
42
91
|
},
|
|
43
92
|
{
|
|
@@ -46,16 +95,288 @@ class NotebookLMServer {
|
|
|
46
95
|
inputSchema: {
|
|
47
96
|
type: "object",
|
|
48
97
|
properties: {
|
|
98
|
+
title: {
|
|
99
|
+
type: "string",
|
|
100
|
+
description: "Optional title for the new notebook",
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
name: "get_notebook",
|
|
107
|
+
description: "Retrieves details, sources, and summaries of a notebook",
|
|
108
|
+
inputSchema: {
|
|
109
|
+
type: "object",
|
|
110
|
+
properties: {
|
|
111
|
+
notebookId: { type: "string" },
|
|
112
|
+
},
|
|
113
|
+
required: ["notebookId"],
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: "query_notebook",
|
|
118
|
+
description: "Asks a grounded question to a specific notebook",
|
|
119
|
+
inputSchema: {
|
|
120
|
+
type: "object",
|
|
121
|
+
properties: {
|
|
122
|
+
notebookId: { type: "string" },
|
|
123
|
+
query: { type: "string" },
|
|
124
|
+
},
|
|
125
|
+
required: ["notebookId", "query"],
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
name: "add_source_url",
|
|
130
|
+
description: "Adds a website or YouTube video as a source",
|
|
131
|
+
inputSchema: {
|
|
132
|
+
type: "object",
|
|
133
|
+
properties: {
|
|
134
|
+
notebookId: { type: "string" },
|
|
135
|
+
url: { type: "string", format: "uri" },
|
|
136
|
+
},
|
|
137
|
+
required: ["notebookId", "url"],
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
name: "add_source_text",
|
|
142
|
+
description: "Adds pasted text content as a source",
|
|
143
|
+
inputSchema: {
|
|
144
|
+
type: "object",
|
|
145
|
+
properties: {
|
|
146
|
+
notebookId: { type: "string" },
|
|
49
147
|
title: { type: "string" },
|
|
148
|
+
text: { type: "string" },
|
|
149
|
+
},
|
|
150
|
+
required: ["notebookId", "title", "text"],
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
name: "generate_audio_overview",
|
|
155
|
+
description: "Triggers the generation of an Audio Overview (Deep Dive podcast)",
|
|
156
|
+
inputSchema: {
|
|
157
|
+
type: "object",
|
|
158
|
+
properties: {
|
|
159
|
+
notebookId: { type: "string" },
|
|
160
|
+
},
|
|
161
|
+
required: ["notebookId"],
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
name: "rename_notebook",
|
|
166
|
+
description: "Renames a notebook",
|
|
167
|
+
inputSchema: {
|
|
168
|
+
type: "object",
|
|
169
|
+
properties: {
|
|
170
|
+
notebookId: { type: "string" },
|
|
171
|
+
newTitle: { type: "string" },
|
|
50
172
|
},
|
|
173
|
+
required: ["notebookId", "newTitle"],
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
name: "delete_notebook",
|
|
178
|
+
description: "Deletes a notebook by its ID (Warning: Destructive)",
|
|
179
|
+
inputSchema: {
|
|
180
|
+
type: "object",
|
|
181
|
+
properties: {
|
|
182
|
+
notebookId: { type: "string" },
|
|
183
|
+
},
|
|
184
|
+
required: ["notebookId"],
|
|
51
185
|
},
|
|
52
186
|
},
|
|
53
|
-
// ...
|
|
54
187
|
],
|
|
55
188
|
}));
|
|
56
189
|
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
57
|
-
|
|
58
|
-
|
|
190
|
+
const { name, arguments: args } = request.params;
|
|
191
|
+
try {
|
|
192
|
+
switch (name) {
|
|
193
|
+
case "list_notebooks": {
|
|
194
|
+
const page = await this.getPage();
|
|
195
|
+
await page
|
|
196
|
+
.locator('a[href*="/notebook/"]')
|
|
197
|
+
.first()
|
|
198
|
+
.waitFor({ state: "visible", timeout: 15000 });
|
|
199
|
+
const notebooks = await page.evaluate(() => {
|
|
200
|
+
const cards = Array.from(document.querySelectorAll('a[href*="/notebook/"]'));
|
|
201
|
+
return cards
|
|
202
|
+
.map((c) => {
|
|
203
|
+
const href = c.href;
|
|
204
|
+
const matches = href.match(/notebook\/([a-zA-Z0-9_-]+)/);
|
|
205
|
+
const id = matches ? matches[1] : "";
|
|
206
|
+
const title = c.textContent?.trim().split("\n")[0] || "Untitled";
|
|
207
|
+
return { id, title };
|
|
208
|
+
})
|
|
209
|
+
.filter((n) => n.id && n.id !== "notebook");
|
|
210
|
+
});
|
|
211
|
+
await page.close();
|
|
212
|
+
return {
|
|
213
|
+
content: [
|
|
214
|
+
{ type: "text", text: JSON.stringify(notebooks, null, 2) },
|
|
215
|
+
],
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
case "create_notebook": {
|
|
219
|
+
const page = await this.getPage();
|
|
220
|
+
// Look for the "Create new" button or the plus icon
|
|
221
|
+
const createBtn = page
|
|
222
|
+
.locator('text="Create new", [aria-label*="Create"]')
|
|
223
|
+
.first();
|
|
224
|
+
await createBtn.click();
|
|
225
|
+
await page.waitForURL(/notebook\/[a-zA-Z0-9_-]+/);
|
|
226
|
+
const id = page.url().match(/notebook\/([a-zA-Z0-9_-]+)/)?.[1] || "";
|
|
227
|
+
const { title } = CreateNotebookSchema.parse(args);
|
|
228
|
+
if (title) {
|
|
229
|
+
await page.waitForTimeout(2000);
|
|
230
|
+
// Try to find the title element and rename
|
|
231
|
+
const titleElem = page
|
|
232
|
+
.locator('input[aria-label*="title"], [contenteditable="true"]')
|
|
233
|
+
.first();
|
|
234
|
+
await titleElem.click();
|
|
235
|
+
await page.keyboard.press("Control+A");
|
|
236
|
+
await page.keyboard.type(title);
|
|
237
|
+
await page.keyboard.press("Enter");
|
|
238
|
+
}
|
|
239
|
+
await page.close();
|
|
240
|
+
return {
|
|
241
|
+
content: [
|
|
242
|
+
{
|
|
243
|
+
type: "text",
|
|
244
|
+
text: `Success: Notebook created with ID: ${id}`,
|
|
245
|
+
},
|
|
246
|
+
],
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
case "query_notebook": {
|
|
250
|
+
const { notebookId, query } = QueryNotebookSchema.parse(args);
|
|
251
|
+
const page = await this.getPage(notebookId);
|
|
252
|
+
const chatInput = page
|
|
253
|
+
.locator('textarea[placeholder*="Ask"], [role="textbox"][aria-label*="chat"]')
|
|
254
|
+
.first();
|
|
255
|
+
await chatInput.waitFor({ state: "visible" });
|
|
256
|
+
await chatInput.fill(query);
|
|
257
|
+
await page.keyboard.press("Enter");
|
|
258
|
+
// Wait for response bubble (Google usually uses specific indicators)
|
|
259
|
+
await page.waitForTimeout(3000);
|
|
260
|
+
await page.waitForSelector('[role="log"], .chat-bubble, .response-content', { timeout: 60000 });
|
|
261
|
+
const result = await page.evaluate(() => {
|
|
262
|
+
const bubbles = Array.from(document.querySelectorAll('[role="log"], .chat-bubble, .response-content'));
|
|
263
|
+
const last = bubbles[bubbles.length - 1];
|
|
264
|
+
return last?.textContent?.trim() || "No response received.";
|
|
265
|
+
});
|
|
266
|
+
await page.close();
|
|
267
|
+
return { content: [{ type: "text", text: result }] };
|
|
268
|
+
}
|
|
269
|
+
case "get_notebook": {
|
|
270
|
+
const { notebookId } = GetNotebookSchema.parse(args);
|
|
271
|
+
const page = await this.getPage(notebookId);
|
|
272
|
+
const info = await page.evaluate(() => {
|
|
273
|
+
const title = document.title.replace(" - NotebookLM", "");
|
|
274
|
+
const sources = Array.from(document.querySelectorAll('.source-card-title, [aria-label*="Source"]'))
|
|
275
|
+
.map((s) => s.textContent?.trim())
|
|
276
|
+
.filter(Boolean);
|
|
277
|
+
return { title, sources };
|
|
278
|
+
});
|
|
279
|
+
await page.close();
|
|
280
|
+
return {
|
|
281
|
+
content: [{ type: "text", text: JSON.stringify(info, null, 2) }],
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
case "add_source_url": {
|
|
285
|
+
const { notebookId, url } = AddSourceUrlSchema.parse(args);
|
|
286
|
+
const page = await this.getPage(notebookId);
|
|
287
|
+
await page.getByText("Add source", { exact: false }).click();
|
|
288
|
+
await page.getByText("Link", { exact: true }).click();
|
|
289
|
+
await page.locator('input[type="url"]').fill(url);
|
|
290
|
+
await page.getByRole("button", { name: /Add|Insert/i }).click();
|
|
291
|
+
await page.waitForTimeout(5000); // Give it time to start processing
|
|
292
|
+
await page.close();
|
|
293
|
+
return {
|
|
294
|
+
content: [
|
|
295
|
+
{
|
|
296
|
+
type: "text",
|
|
297
|
+
text: `Source ${url} added to notebook ${notebookId}.`,
|
|
298
|
+
},
|
|
299
|
+
],
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
case "add_source_text": {
|
|
303
|
+
const { notebookId, title, text } = AddSourceTextSchema.parse(args);
|
|
304
|
+
const page = await this.getPage(notebookId);
|
|
305
|
+
await page.getByText("Add source", { exact: false }).click();
|
|
306
|
+
await page.getByText("Text", { exact: true }).click();
|
|
307
|
+
await page.locator('input[placeholder*="Title"]').fill(title);
|
|
308
|
+
await page.locator("textarea").fill(text);
|
|
309
|
+
await page.getByRole("button", { name: /Add|Insert/i }).click();
|
|
310
|
+
await page.waitForTimeout(3000);
|
|
311
|
+
await page.close();
|
|
312
|
+
return {
|
|
313
|
+
content: [
|
|
314
|
+
{ type: "text", text: `Text source "${title}" added.` },
|
|
315
|
+
],
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
case "rename_notebook": {
|
|
319
|
+
const { notebookId, newTitle } = RenameNotebookSchema.parse(args);
|
|
320
|
+
const page = await this.getPage(notebookId);
|
|
321
|
+
const titleElem = page
|
|
322
|
+
.locator('input[aria-label*="title"], [contenteditable="true"]')
|
|
323
|
+
.first();
|
|
324
|
+
await titleElem.click();
|
|
325
|
+
await page.keyboard.press("Control+A");
|
|
326
|
+
await page.keyboard.type(newTitle);
|
|
327
|
+
await page.keyboard.press("Enter");
|
|
328
|
+
await page.waitForTimeout(1000);
|
|
329
|
+
await page.close();
|
|
330
|
+
return {
|
|
331
|
+
content: [
|
|
332
|
+
{ type: "text", text: `Notebook renamed to: ${newTitle}` },
|
|
333
|
+
],
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
case "delete_notebook": {
|
|
337
|
+
const { notebookId } = DeleteNotebookSchema.parse(args);
|
|
338
|
+
const page = await this.getPage(); // Go to home to delete from menu
|
|
339
|
+
// Find the notebook menu option (three dots) for this ID
|
|
340
|
+
const menuBtn = page
|
|
341
|
+
.locator(`a[href*="${notebookId}"]`)
|
|
342
|
+
.locator("..")
|
|
343
|
+
.locator('button[aria-label*="Menu"]')
|
|
344
|
+
.first();
|
|
345
|
+
await menuBtn.click();
|
|
346
|
+
await page.getByText("Delete", { exact: false }).click();
|
|
347
|
+
await page.getByRole("button", { name: /Delete/i }).click(); // Confirm dialog
|
|
348
|
+
await page.waitForTimeout(2000);
|
|
349
|
+
await page.close();
|
|
350
|
+
return {
|
|
351
|
+
content: [
|
|
352
|
+
{ type: "text", text: `Notebook ${notebookId} deleted.` },
|
|
353
|
+
],
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
case "generate_audio_overview": {
|
|
357
|
+
const { notebookId } = AudioOverviewSchema.parse(args);
|
|
358
|
+
const page = await this.getPage(notebookId);
|
|
359
|
+
await page.getByText("Notebook guide", { exact: false }).click();
|
|
360
|
+
await page.getByText("Audio Overview", { exact: false }).click();
|
|
361
|
+
await page.getByRole("button", { name: /Generate/i }).click();
|
|
362
|
+
await page.close();
|
|
363
|
+
return {
|
|
364
|
+
content: [
|
|
365
|
+
{ type: "text", text: "Audio overview generation triggered." },
|
|
366
|
+
],
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
default:
|
|
370
|
+
throw new McpError(ErrorCode.MethodNotFound, `Tool not found: ${name}`);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
catch (error) {
|
|
374
|
+
console.error(`Error in tool ${name}:`, error);
|
|
375
|
+
return {
|
|
376
|
+
content: [{ type: "text", text: `Error: ${error.message}` }],
|
|
377
|
+
isError: true,
|
|
378
|
+
};
|
|
379
|
+
}
|
|
59
380
|
});
|
|
60
381
|
}
|
|
61
382
|
async run() {
|
|
@@ -68,24 +389,22 @@ const program = new Command();
|
|
|
68
389
|
program
|
|
69
390
|
.name("notebooklm-mcp")
|
|
70
391
|
.description("MCP server for Google NotebookLM")
|
|
71
|
-
.version("1.0.
|
|
392
|
+
.version("1.0.2");
|
|
72
393
|
program
|
|
73
394
|
.command("start")
|
|
74
|
-
.description("Start the MCP server
|
|
395
|
+
.description("Start the MCP server")
|
|
75
396
|
.action(async () => {
|
|
76
397
|
const server = new NotebookLMServer();
|
|
77
398
|
await server.run();
|
|
78
399
|
});
|
|
79
400
|
program
|
|
80
401
|
.command("auth")
|
|
81
|
-
.description("Open
|
|
402
|
+
.description("Open browser for authentication")
|
|
82
403
|
.action(async () => {
|
|
83
404
|
await runAuth();
|
|
84
405
|
});
|
|
85
|
-
// Default to start if no command provided (for MCP clients)
|
|
86
406
|
if (process.argv.length <= 2) {
|
|
87
|
-
|
|
88
|
-
server.run().catch(console.error);
|
|
407
|
+
new NotebookLMServer().run().catch(console.error);
|
|
89
408
|
}
|
|
90
409
|
else {
|
|
91
410
|
program.parse(process.argv);
|