notebooklm-mcp-server 1.0.9 → 1.1.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/dist/auth.js CHANGED
@@ -22,39 +22,25 @@ export class AuthManager {
22
22
  if (onStatus)
23
23
  onStatus('Waiting for Google Login...');
24
24
  // Wait for the user to be logged in.
25
- // We check for either the URL pattern or the presence of session cookies
26
25
  let isDone = false;
27
26
  try {
28
27
  await Promise.race([
29
- page.waitForURL('**/notebook/**', { timeout: 300000 }).then(() => { isDone = true; }),
28
+ // Wait for redirect to dashboard or notebook
29
+ page.waitForURL(url => url.origin === 'https://notebooklm.google.com' &&
30
+ (url.pathname.includes('/notebook') || url.pathname === '/'), { timeout: 300000 }).then(() => { isDone = true; }),
31
+ // Wait for user icon or other logged-in indicia
32
+ page.waitForSelector('button[aria-label*="Account"], img[src*="googleusercontent.com"]', { timeout: 300000 })
33
+ .then(() => { isDone = true; }),
34
+ // Safety fallback: if we see the landing page, we might already be logged in
30
35
  page.waitForFunction(() => {
36
+ const body = document.body.innerText;
31
37
  return window.location.href.includes('notebooklm.google.com') &&
32
- !document.body.innerText.includes('Sign in');
38
+ !body.includes('Sign in') &&
39
+ (body.includes('Notebooks') || body.includes('Create new'));
33
40
  }, { polling: 2000, timeout: 300000 }).then(() => { isDone = true; }),
34
- // Fallback: check if we have the critical cookies
35
- new Promise((resolve) => {
36
- const checkCookies = async () => {
37
- if (isDone)
38
- return;
39
- try {
40
- const cookies = await context.cookies();
41
- const sessionCookie = cookies.find(c => c.name === '__Secure-3PSID' || c.name === 'SID');
42
- if (sessionCookie && page.url().includes('notebooklm.google.com')) {
43
- isDone = true;
44
- resolve(true);
45
- }
46
- else {
47
- if (!isDone)
48
- setTimeout(checkCookies, 2000);
49
- }
50
- }
51
- catch (e) {
52
- isDone = true;
53
- }
54
- };
55
- checkCookies();
56
- })
57
41
  ]);
42
+ // Small delay to ensure all session cookies are synced
43
+ await page.waitForTimeout(2000);
58
44
  }
59
45
  catch (e) {
60
46
  throw new Error('Authentication timed out or browser was closed.');
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ const program = new Command();
4
4
  program
5
5
  .name('notebooklm-mcp-server')
6
6
  .description('NotebookLM MCP Server (Node.js)')
7
- .version('1.0.9');
7
+ .version('1.1.0');
8
8
  program
9
9
  .command('server')
10
10
  .description('Start the MCP server (default)')
package/dist/server.js CHANGED
@@ -6,7 +6,7 @@ import { AuthManager } from "./auth.js";
6
6
  import chalk from "chalk";
7
7
  const server = new Server({
8
8
  name: "notebooklm-mcp-server",
9
- version: "1.0.9",
9
+ version: "1.1.0",
10
10
  }, {
11
11
  capabilities: {
12
12
  tools: {},
@@ -262,6 +262,11 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
262
262
  required: ["notebook_id"],
263
263
  },
264
264
  },
265
+ {
266
+ name: "refresh_auth",
267
+ description: "Open browser to refresh Google Session cookies (interactive)",
268
+ inputSchema: { type: "object", properties: {} },
269
+ },
265
270
  ],
266
271
  };
267
272
  });
@@ -271,76 +276,82 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
271
276
  switch (name) {
272
277
  case "notebook_list":
273
278
  const notebooks = await client.listNotebooks();
274
- return { content: [{ type: "text", text: JSON.stringify(notebooks, null, 2) }] };
279
+ return { content: [{ type: "text", text: JSON.stringify({ notebooks }, null, 2) }] };
275
280
  case "notebook_create":
276
281
  const newId = await client.createNotebook(args?.title);
277
- return { content: [{ type: "text", text: `Created notebook with ID: ${newId}` }] };
282
+ return { content: [{ type: "text", text: JSON.stringify({ status: "success", notebook_id: newId }) }] };
278
283
  case "notebook_delete":
279
284
  await client.deleteNotebook(args?.notebook_id);
280
- return { content: [{ type: "text", text: `Deleted notebook ${args?.notebook_id}` }] };
285
+ return { content: [{ type: "text", text: JSON.stringify({ status: "success", message: `Deleted notebook ${args?.notebook_id}` }) }] };
281
286
  case "notebook_rename":
282
287
  await client.renameNotebook(args?.notebook_id, args?.title);
283
- return { content: [{ type: "text", text: `Renamed notebook to ${args?.title}` }] };
288
+ return { content: [{ type: "text", text: JSON.stringify({ status: "success", message: `Renamed notebook to ${args?.title}` }) }] };
284
289
  case "notebook_add_url":
285
290
  const sourceIdUrl = await client.addUrlSource(args?.notebook_id, args?.url);
286
- return { content: [{ type: "text", text: `Added source with ID: ${sourceIdUrl}` }] };
291
+ return { content: [{ type: "text", text: JSON.stringify({ status: "success", source_id: sourceIdUrl }) }] };
287
292
  case "notebook_add_text":
288
293
  const sourceIdText = await client.addTextSource(args?.notebook_id, args?.title, args?.content);
289
- return { content: [{ type: "text", text: `Added text source with ID: ${sourceIdText}` }] };
294
+ return { content: [{ type: "text", text: JSON.stringify({ status: "success", source_id: sourceIdText }) }] };
290
295
  case "notebook_query":
291
296
  const queryResult = await client.query(args?.notebook_id, args?.query, undefined, args?.conversation_id);
292
297
  return {
293
298
  content: [
294
- { type: "text", text: queryResult.answer },
295
- { type: "text", text: `\n\n(Conversation ID: ${queryResult.conversation_id})` }
299
+ { type: "text", text: JSON.stringify(queryResult, null, 2) }
296
300
  ]
297
301
  };
298
302
  case "research_start":
299
303
  const taskInfo = await client.startResearch(args?.notebook_id, args?.query, args?.source || 'web', args?.mode || 'fast');
300
- return { content: [{ type: "text", text: `Started research task: ${JSON.stringify(taskInfo)}` }] };
304
+ return { content: [{ type: "text", text: JSON.stringify({ status: "success", task: taskInfo }, null, 2) }] };
301
305
  case "research_poll":
302
306
  const researchResult = await client.pollResearch(args?.notebook_id);
303
307
  return { content: [{ type: "text", text: JSON.stringify(researchResult, null, 2) }] };
304
308
  case "research_import":
305
309
  const imported = await client.importResearchSources(args?.notebook_id, args?.task_id, args?.sources);
306
- return { content: [{ type: "text", text: `Imported ${imported.length} sources: ${JSON.stringify(imported)}` }] };
310
+ return { content: [{ type: "text", text: JSON.stringify({ status: "success", imported_count: imported.length, sources: imported }, null, 2) }] };
307
311
  case "mind_map_generate":
308
312
  const mmData = await client.generateMindMap(args?.source_ids);
309
313
  return { content: [{ type: "text", text: JSON.stringify(mmData, null, 2) }] };
310
314
  case "mind_map_save":
311
315
  const savedMm = await client.saveMindMap(args?.notebook_id, args?.mind_map_json, args?.source_ids, args?.title);
312
- return { content: [{ type: "text", text: `Mind Map saved: ${JSON.stringify(savedMm)}` }] };
316
+ return { content: [{ type: "text", text: JSON.stringify({ status: "success", mind_map: savedMm }, null, 2) }] };
313
317
  case "mind_map_list":
314
318
  const maps = await client.listMindMaps(args?.notebook_id);
315
- return { content: [{ type: "text", text: JSON.stringify(maps, null, 2) }] };
319
+ return { content: [{ type: "text", text: JSON.stringify({ mind_maps: maps }, null, 2) }] };
316
320
  case "mind_map_delete":
317
321
  await client.deleteMindMap(args?.notebook_id, args?.mind_map_id);
318
- return { content: [{ type: "text", text: "Mind Map deleted." }] };
322
+ return { content: [{ type: "text", text: JSON.stringify({ status: "success", message: "Mind Map deleted." }) }] };
319
323
  case "notebook_add_local_file":
320
324
  const fileSourceId = await client.uploadLocalFile(args?.notebook_id, args?.path);
321
- return { content: [{ type: "text", text: `Successfully uploaded and added file. Source ID: ${fileSourceId}` }] };
325
+ return { content: [{ type: "text", text: JSON.stringify({ status: "success", source_id: fileSourceId }) }] };
322
326
  case "notebook_add_drive":
323
327
  const driveId = await client.addDriveSource(args?.notebook_id, args?.file_id);
324
- return { content: [{ type: "text", text: `Successfully added Drive source. ID: ${driveId}` }] };
328
+ return { content: [{ type: "text", text: JSON.stringify({ status: "success", source_id: driveId }) }] };
325
329
  case "source_delete":
326
330
  await client.deleteSource(args?.notebook_id, args?.source_id);
327
- return { content: [{ type: "text", text: `Source ${args?.source_id} deleted successfully.` }] };
331
+ return { content: [{ type: "text", text: JSON.stringify({ status: "success", message: `Source ${args?.source_id} deleted successfully.` }) }] };
328
332
  case "source_sync":
329
333
  await client.syncDriveSource(args?.notebook_id, args?.source_id);
330
- return { content: [{ type: "text", text: `Sync triggered for source ${args?.source_id}.` }] };
334
+ return { content: [{ type: "text", text: JSON.stringify({ status: "success", message: `Sync triggered for source ${args?.source_id}.` }) }] };
331
335
  case "audio_overview_create":
332
336
  const audioInfo = await client.createAudioOverview(args?.notebook_id, args?.source_ids || [], args?.language || 'en');
333
- return { content: [{ type: "text", text: `Audio Overview generation started: ${JSON.stringify(audioInfo)}` }] };
337
+ return { content: [{ type: "text", text: JSON.stringify({ status: "success", audio_info: audioInfo }, null, 2) }] };
334
338
  case "studio_poll":
335
339
  const studioStatus = await client.pollStudioStatus(args?.notebook_id);
336
340
  return { content: [{ type: "text", text: JSON.stringify(studioStatus, null, 2) }] };
341
+ case "refresh_auth":
342
+ const auth = new AuthManager();
343
+ await auth.runAuthentication();
344
+ // After auth is successful, we need to update our client
345
+ const newCookies = auth.getSavedCookies();
346
+ client = new NotebookLMClient(newCookies);
347
+ return { content: [{ type: "text", text: JSON.stringify({ status: "success", message: "Authentication refreshed successfully." }) }] };
337
348
  default:
338
349
  throw new Error(`Unknown tool: ${name}`);
339
350
  }
340
351
  }
341
352
  catch (error) {
342
353
  return {
343
- content: [{ type: "text", text: `Error: ${error.message}` }],
354
+ content: [{ type: "text", text: JSON.stringify({ status: "error", error: error.message }) }],
344
355
  isError: true,
345
356
  };
346
357
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "notebooklm-mcp-server",
3
- "version": "1.0.9",
3
+ "version": "1.1.0",
4
4
  "description": "Node.js Model Context Protocol server for Google NotebookLM",
5
5
  "type": "module",
6
6
  "repository": {