@roomi-fields/notebooklm-mcp 1.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.
Files changed (67) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +548 -0
  3. package/dist/auth/auth-manager.d.ts +139 -0
  4. package/dist/auth/auth-manager.d.ts.map +1 -0
  5. package/dist/auth/auth-manager.js +981 -0
  6. package/dist/auth/auth-manager.js.map +1 -0
  7. package/dist/config.d.ts +89 -0
  8. package/dist/config.d.ts.map +1 -0
  9. package/dist/config.js +216 -0
  10. package/dist/config.js.map +1 -0
  11. package/dist/errors.d.ts +26 -0
  12. package/dist/errors.d.ts.map +1 -0
  13. package/dist/errors.js +41 -0
  14. package/dist/errors.js.map +1 -0
  15. package/dist/http-wrapper.d.ts +8 -0
  16. package/dist/http-wrapper.d.ts.map +1 -0
  17. package/dist/http-wrapper.js +221 -0
  18. package/dist/http-wrapper.js.map +1 -0
  19. package/dist/index.d.ts +32 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +499 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/library/notebook-library.d.ts +81 -0
  24. package/dist/library/notebook-library.d.ts.map +1 -0
  25. package/dist/library/notebook-library.js +362 -0
  26. package/dist/library/notebook-library.js.map +1 -0
  27. package/dist/library/types.d.ts +67 -0
  28. package/dist/library/types.d.ts.map +1 -0
  29. package/dist/library/types.js +8 -0
  30. package/dist/library/types.js.map +1 -0
  31. package/dist/session/browser-session.d.ts +108 -0
  32. package/dist/session/browser-session.d.ts.map +1 -0
  33. package/dist/session/browser-session.js +630 -0
  34. package/dist/session/browser-session.js.map +1 -0
  35. package/dist/session/session-manager.d.ts +76 -0
  36. package/dist/session/session-manager.d.ts.map +1 -0
  37. package/dist/session/session-manager.js +273 -0
  38. package/dist/session/session-manager.js.map +1 -0
  39. package/dist/session/shared-context-manager.d.ts +107 -0
  40. package/dist/session/shared-context-manager.d.ts.map +1 -0
  41. package/dist/session/shared-context-manager.js +447 -0
  42. package/dist/session/shared-context-manager.js.map +1 -0
  43. package/dist/tools/index.d.ts +225 -0
  44. package/dist/tools/index.d.ts.map +1 -0
  45. package/dist/tools/index.js +1396 -0
  46. package/dist/tools/index.js.map +1 -0
  47. package/dist/types.d.ts +82 -0
  48. package/dist/types.d.ts.map +1 -0
  49. package/dist/types.js +5 -0
  50. package/dist/types.js.map +1 -0
  51. package/dist/utils/cleanup-manager.d.ts +133 -0
  52. package/dist/utils/cleanup-manager.d.ts.map +1 -0
  53. package/dist/utils/cleanup-manager.js +673 -0
  54. package/dist/utils/cleanup-manager.js.map +1 -0
  55. package/dist/utils/logger.d.ts +61 -0
  56. package/dist/utils/logger.d.ts.map +1 -0
  57. package/dist/utils/logger.js +92 -0
  58. package/dist/utils/logger.js.map +1 -0
  59. package/dist/utils/page-utils.d.ts +54 -0
  60. package/dist/utils/page-utils.d.ts.map +1 -0
  61. package/dist/utils/page-utils.js +422 -0
  62. package/dist/utils/page-utils.js.map +1 -0
  63. package/dist/utils/stealth-utils.d.ts +135 -0
  64. package/dist/utils/stealth-utils.d.ts.map +1 -0
  65. package/dist/utils/stealth-utils.js +398 -0
  66. package/dist/utils/stealth-utils.js.map +1 -0
  67. package/package.json +71 -0
@@ -0,0 +1,1396 @@
1
+ /**
2
+ * MCP Tool Implementations
3
+ *
4
+ * Implements all MCP tools for NotebookLM integration:
5
+ * - ask_question: Ask NotebookLM with session support
6
+ * - list_sessions: List all active sessions
7
+ * - close_session: Close a specific session
8
+ * - reset_session: Reset session chat history
9
+ * - get_health: Server health check
10
+ * - setup_auth: Interactive authentication setup
11
+ *
12
+ * Based on the Python implementation from tools/*.py
13
+ */
14
+ import { CONFIG, applyBrowserOptions } from "../config.js";
15
+ import { log } from "../utils/logger.js";
16
+ import { RateLimitError } from "../errors.js";
17
+ import { CleanupManager } from "../utils/cleanup-manager.js";
18
+ /**
19
+ * Build dynamic tool description for ask_question based on active notebook or library
20
+ */
21
+ function buildAskQuestionDescription(library) {
22
+ const active = library.getActiveNotebook();
23
+ if (active) {
24
+ const topics = active.topics.join(", ");
25
+ const useCases = active.use_cases.map((uc) => ` - ${uc}`).join("\n");
26
+ return `# Conversational Research Partner (NotebookLM • Gemini 2.5 • Session RAG)
27
+
28
+ **Active Notebook:** ${active.name}
29
+ **Content:** ${active.description}
30
+ **Topics:** ${topics}
31
+
32
+ > Auth tip: If login is required, use the prompt 'notebooklm.auth-setup' and then verify with the 'get_health' tool. If authentication later fails (e.g., expired cookies), use the prompt 'notebooklm.auth-repair'.
33
+
34
+ ## What This Tool Is
35
+ - Full conversational research with Gemini (LLM) grounded on your notebook sources
36
+ - Session-based: each follow-up uses prior context for deeper, more precise answers
37
+ - Source-cited responses designed to minimize hallucinations
38
+
39
+ ## When To Use
40
+ ${useCases}
41
+
42
+ ## Rules (Important)
43
+ - Always prefer continuing an existing session for the same task
44
+ - If you start a new thread, create a new session and keep its session_id
45
+ - Ask clarifying questions before implementing; do not guess missing details
46
+ - If multiple notebooks could apply, propose the top 1–2 and ask which to use
47
+ - If task context changes, ask to reset the session or switch notebooks
48
+ - If authentication fails, use the prompts 'notebooklm.auth-repair' (or 'notebooklm.auth-setup') and verify with 'get_health'
49
+ - After every NotebookLM answer: pause, compare with the user's goal, and only respond if you are 100% sure the information is complete. Otherwise, plan the next NotebookLM question in the same session.
50
+
51
+ ## Session Flow (Recommended)
52
+ \`\`\`javascript
53
+ // 1) Start broad (no session_id → creates one)
54
+ ask_question({ question: "Give me an overview of [topic]" })
55
+ // ← Save: result.session_id
56
+
57
+ // 2) Go specific (same session)
58
+ ask_question({ question: "Key APIs/methods?", session_id })
59
+
60
+ // 3) Cover pitfalls (same session)
61
+ ask_question({ question: "Common edge cases + gotchas?", session_id })
62
+
63
+ // 4) Ask for production example (same session)
64
+ ask_question({ question: "Show a production-ready example", session_id })
65
+ \`\`\`
66
+
67
+ ## Automatic Multi-Pass Strategy (Host-driven)
68
+ - Simple prompts return once-and-done answers.
69
+ - For complex prompts, the host should issue follow-up calls:
70
+ 1. Implementation plan (APIs, dependencies, configuration, authentication).
71
+ 2. Pitfalls, gaps, validation steps, missing prerequisites.
72
+ - Keep the same session_id for all follow-ups, review NotebookLM's answer, and ask more questions until the problem is fully resolved.
73
+ - Before replying to the user, double-check: do you truly have everything? If not, queue another ask_question immediately.
74
+
75
+ ## 🔥 REAL EXAMPLE
76
+
77
+ Task: "Implement error handling in n8n workflow"
78
+
79
+ Bad (shallow):
80
+ \`\`\`
81
+ Q: "How do I handle errors in n8n?"
82
+ A: [basic answer]
83
+ → Implement → Probably missing edge cases!
84
+ \`\`\`
85
+
86
+ Good (deep):
87
+ \`\`\`
88
+ Q1: "What are n8n's error handling mechanisms?" (session created)
89
+ A1: [Overview of error handling]
90
+
91
+ Q2: "What's the recommended pattern for API errors?" (same session)
92
+ A2: [Specific patterns, uses context from Q1]
93
+
94
+ Q3: "How do I handle retry logic and timeouts?" (same session)
95
+ A3: [Detailed approach, builds on Q1+Q2]
96
+
97
+ Q4: "Show me a production example with all these patterns" (same session)
98
+ A4: [Complete example with full context]
99
+
100
+ → NOW implement with confidence!
101
+ \`\`\`
102
+
103
+ ## Notebook Selection
104
+ - Default: active notebook (${active.id})
105
+ - Or set notebook_id to use a library notebook
106
+ - Or set notebook_url for ad-hoc notebooks (not in library)
107
+ - If ambiguous which notebook fits, ASK the user which to use`;
108
+ }
109
+ else {
110
+ return `# Conversational Research Partner (NotebookLM • Gemini 2.5 • Session RAG)
111
+
112
+ ## No Active Notebook
113
+ - Visit https://notebooklm.google to create a notebook and get a share link
114
+ - Use **add_notebook** to add it to your library (explains how to get the link)
115
+ - Use **list_notebooks** to show available sources
116
+ - Use **select_notebook** to set one active
117
+
118
+ > Auth tip: If login is required, use the prompt 'notebooklm.auth-setup' and then verify with the 'get_health' tool. If authentication later fails (e.g., expired cookies), use the prompt 'notebooklm.auth-repair'.
119
+
120
+ Tip: Tell the user you can manage NotebookLM library and ask which notebook to use for the current task.`;
121
+ }
122
+ }
123
+ /**
124
+ * Build Tool Definitions with NotebookLibrary context
125
+ */
126
+ export function buildToolDefinitions(library) {
127
+ return [
128
+ {
129
+ name: "ask_question",
130
+ description: buildAskQuestionDescription(library),
131
+ inputSchema: {
132
+ type: "object",
133
+ properties: {
134
+ question: {
135
+ type: "string",
136
+ description: "The question to ask NotebookLM",
137
+ },
138
+ session_id: {
139
+ type: "string",
140
+ description: "Optional session ID for contextual conversations. If omitted, a new session is created.",
141
+ },
142
+ notebook_id: {
143
+ type: "string",
144
+ description: "Optional notebook ID from your library. If omitted, uses the active notebook. " +
145
+ "Use list_notebooks to see available notebooks.",
146
+ },
147
+ notebook_url: {
148
+ type: "string",
149
+ description: "Optional notebook URL (overrides notebook_id). Use this for ad-hoc queries to notebooks not in your library.",
150
+ },
151
+ show_browser: {
152
+ type: "boolean",
153
+ description: "Show browser window for debugging (simple version). " +
154
+ "For advanced control (typing speed, stealth, etc.), use browser_options instead.",
155
+ },
156
+ browser_options: {
157
+ type: "object",
158
+ description: "Optional browser behavior settings. Claude can control everything: " +
159
+ "visibility, typing speed, stealth mode, timeouts. Useful for debugging or fine-tuning.",
160
+ properties: {
161
+ show: {
162
+ type: "boolean",
163
+ description: "Show browser window (default: from ENV or false)",
164
+ },
165
+ headless: {
166
+ type: "boolean",
167
+ description: "Run browser in headless mode (default: true)",
168
+ },
169
+ timeout_ms: {
170
+ type: "number",
171
+ description: "Browser operation timeout in milliseconds (default: 30000)",
172
+ },
173
+ stealth: {
174
+ type: "object",
175
+ description: "Human-like behavior settings to avoid detection",
176
+ properties: {
177
+ enabled: {
178
+ type: "boolean",
179
+ description: "Master switch for all stealth features (default: true)",
180
+ },
181
+ random_delays: {
182
+ type: "boolean",
183
+ description: "Random delays between actions (default: true)",
184
+ },
185
+ human_typing: {
186
+ type: "boolean",
187
+ description: "Human-like typing patterns (default: true)",
188
+ },
189
+ mouse_movements: {
190
+ type: "boolean",
191
+ description: "Realistic mouse movements (default: true)",
192
+ },
193
+ typing_wpm_min: {
194
+ type: "number",
195
+ description: "Minimum typing speed in WPM (default: 160)",
196
+ },
197
+ typing_wpm_max: {
198
+ type: "number",
199
+ description: "Maximum typing speed in WPM (default: 240)",
200
+ },
201
+ delay_min_ms: {
202
+ type: "number",
203
+ description: "Minimum delay between actions in ms (default: 100)",
204
+ },
205
+ delay_max_ms: {
206
+ type: "number",
207
+ description: "Maximum delay between actions in ms (default: 400)",
208
+ },
209
+ },
210
+ },
211
+ viewport: {
212
+ type: "object",
213
+ description: "Browser viewport size",
214
+ properties: {
215
+ width: {
216
+ type: "number",
217
+ description: "Viewport width in pixels (default: 1920)",
218
+ },
219
+ height: {
220
+ type: "number",
221
+ description: "Viewport height in pixels (default: 1080)",
222
+ },
223
+ },
224
+ },
225
+ },
226
+ },
227
+ },
228
+ required: ["question"],
229
+ },
230
+ },
231
+ {
232
+ name: "add_notebook",
233
+ description: `PERMISSION REQUIRED — Only when user explicitly asks to add a notebook.
234
+
235
+ ## Conversation Workflow (Mandatory)
236
+ When the user says: "I have a NotebookLM with X"
237
+
238
+ 1) Ask URL: "What is the NotebookLM URL?"
239
+ 2) Ask content: "What knowledge is inside?" (1–2 sentences)
240
+ 3) Ask topics: "Which topics does it cover?" (3–5)
241
+ 4) Ask use cases: "When should we consult it?"
242
+ 5) Propose metadata and confirm:
243
+ - Name: [suggested]
244
+ - Description: [from user]
245
+ - Topics: [list]
246
+ - Use cases: [list]
247
+ "Add it to your library now?"
248
+ 6) Only after explicit "Yes" → call this tool
249
+
250
+ ## Rules
251
+ - Do not add without user permission
252
+ - Do not guess metadata — ask concisely
253
+ - Confirm summary before calling the tool
254
+
255
+ ## Example
256
+ User: "I have a notebook with n8n docs"
257
+ You: Ask URL → content → topics → use cases; propose summary
258
+ User: "Yes"
259
+ You: Call add_notebook
260
+
261
+ ## How to Get a NotebookLM Share Link
262
+
263
+ Visit https://notebooklm.google/ → Login (free: 100 notebooks, 50 sources each, 500k words, 50 daily queries)
264
+ 1) Click "+ New" (top right) → Upload sources (docs, knowledge)
265
+ 2) Click "Share" (top right) → Select "Anyone with the link"
266
+ 3) Click "Copy link" (bottom left) → Give this link to Claude
267
+
268
+ (Upgraded: Google AI Pro/Ultra gives 5x higher limits)`,
269
+ inputSchema: {
270
+ type: "object",
271
+ properties: {
272
+ url: {
273
+ type: "string",
274
+ description: "The NotebookLM notebook URL",
275
+ },
276
+ name: {
277
+ type: "string",
278
+ description: "Display name for the notebook (e.g., 'n8n Documentation')",
279
+ },
280
+ description: {
281
+ type: "string",
282
+ description: "What knowledge/content is in this notebook",
283
+ },
284
+ topics: {
285
+ type: "array",
286
+ items: { type: "string" },
287
+ description: "Topics covered in this notebook",
288
+ },
289
+ content_types: {
290
+ type: "array",
291
+ items: { type: "string" },
292
+ description: "Types of content (e.g., ['documentation', 'examples', 'best practices'])",
293
+ },
294
+ use_cases: {
295
+ type: "array",
296
+ items: { type: "string" },
297
+ description: "When should Claude use this notebook (e.g., ['Implementing n8n workflows'])",
298
+ },
299
+ tags: {
300
+ type: "array",
301
+ items: { type: "string" },
302
+ description: "Optional tags for organization",
303
+ },
304
+ },
305
+ required: ["url", "name", "description", "topics"],
306
+ },
307
+ },
308
+ {
309
+ name: "list_notebooks",
310
+ description: "List all library notebooks with metadata (name, topics, use cases, URL). " +
311
+ "Use this to present options, then ask which notebook to use for the task.",
312
+ inputSchema: {
313
+ type: "object",
314
+ properties: {},
315
+ },
316
+ },
317
+ {
318
+ name: "get_notebook",
319
+ description: "Get detailed information about a specific notebook by ID",
320
+ inputSchema: {
321
+ type: "object",
322
+ properties: {
323
+ id: {
324
+ type: "string",
325
+ description: "The notebook ID",
326
+ },
327
+ },
328
+ required: ["id"],
329
+ },
330
+ },
331
+ {
332
+ name: "select_notebook",
333
+ description: `Set a notebook as the active default (used when ask_question has no notebook_id).
334
+
335
+ ## When To Use
336
+ - User switches context: "Let's work on React now"
337
+ - User asks explicitly to activate a notebook
338
+ - Obvious task change requires another notebook
339
+
340
+ ## Auto-Switching
341
+ - Safe to auto-switch if the context is clear and you announce it:
342
+ "Switching to React notebook for this task..."
343
+ - If ambiguous, ask: "Switch to [notebook] for this task?"
344
+
345
+ ## Example
346
+ User: "Now let's build the React frontend"
347
+ You: "Switching to React notebook..." (call select_notebook)`,
348
+ inputSchema: {
349
+ type: "object",
350
+ properties: {
351
+ id: {
352
+ type: "string",
353
+ description: "The notebook ID to activate",
354
+ },
355
+ },
356
+ required: ["id"],
357
+ },
358
+ },
359
+ {
360
+ name: "update_notebook",
361
+ description: `Update notebook metadata based on user intent.
362
+
363
+ ## Pattern
364
+ 1) Identify target notebook and fields (topics, description, use_cases, tags, url)
365
+ 2) Propose the exact change back to the user
366
+ 3) After explicit confirmation, call this tool
367
+
368
+ ## Examples
369
+ - User: "React notebook also covers Next.js 14"
370
+ You: "Add 'Next.js 14' to topics for React?"
371
+ User: "Yes" → call update_notebook
372
+
373
+ - User: "Include error handling in n8n description"
374
+ You: "Update the n8n description to mention error handling?"
375
+ User: "Yes" → call update_notebook
376
+
377
+ Tip: You may update multiple fields at once if requested.`,
378
+ inputSchema: {
379
+ type: "object",
380
+ properties: {
381
+ id: {
382
+ type: "string",
383
+ description: "The notebook ID to update",
384
+ },
385
+ name: {
386
+ type: "string",
387
+ description: "New display name",
388
+ },
389
+ description: {
390
+ type: "string",
391
+ description: "New description",
392
+ },
393
+ topics: {
394
+ type: "array",
395
+ items: { type: "string" },
396
+ description: "New topics list",
397
+ },
398
+ content_types: {
399
+ type: "array",
400
+ items: { type: "string" },
401
+ description: "New content types",
402
+ },
403
+ use_cases: {
404
+ type: "array",
405
+ items: { type: "string" },
406
+ description: "New use cases",
407
+ },
408
+ tags: {
409
+ type: "array",
410
+ items: { type: "string" },
411
+ description: "New tags",
412
+ },
413
+ url: {
414
+ type: "string",
415
+ description: "New notebook URL",
416
+ },
417
+ },
418
+ required: ["id"],
419
+ },
420
+ },
421
+ {
422
+ name: "remove_notebook",
423
+ description: `Dangerous — requires explicit user confirmation.
424
+
425
+ ## Confirmation Workflow
426
+ 1) User requests removal ("Remove the React notebook")
427
+ 2) Look up full name to confirm
428
+ 3) Ask: "Remove '[notebook_name]' from your library? (Does not delete the actual NotebookLM notebook)"
429
+ 4) Only on explicit "Yes" → call remove_notebook
430
+
431
+ Never remove without permission or based on assumptions.
432
+
433
+ Example:
434
+ User: "Delete the old React notebook"
435
+ You: "Remove 'React Best Practices' from your library?"
436
+ User: "Yes" → call remove_notebook`,
437
+ inputSchema: {
438
+ type: "object",
439
+ properties: {
440
+ id: {
441
+ type: "string",
442
+ description: "The notebook ID to remove",
443
+ },
444
+ },
445
+ required: ["id"],
446
+ },
447
+ },
448
+ {
449
+ name: "search_notebooks",
450
+ description: "Search library by query (name, description, topics, tags). " +
451
+ "Use to propose relevant notebooks for the task and then ask which to use.",
452
+ inputSchema: {
453
+ type: "object",
454
+ properties: {
455
+ query: {
456
+ type: "string",
457
+ description: "Search query",
458
+ },
459
+ },
460
+ required: ["query"],
461
+ },
462
+ },
463
+ {
464
+ name: "get_library_stats",
465
+ description: "Get statistics about your notebook library (total notebooks, usage, etc.)",
466
+ inputSchema: {
467
+ type: "object",
468
+ properties: {},
469
+ },
470
+ },
471
+ {
472
+ name: "list_sessions",
473
+ description: "List all active sessions with stats (age, message count, last activity). " +
474
+ "Use to continue the most relevant session instead of starting from scratch.",
475
+ inputSchema: {
476
+ type: "object",
477
+ properties: {},
478
+ },
479
+ },
480
+ {
481
+ name: "close_session",
482
+ description: "Close a specific session by session ID. Ask before closing if the user might still need it.",
483
+ inputSchema: {
484
+ type: "object",
485
+ properties: {
486
+ session_id: {
487
+ type: "string",
488
+ description: "The session ID to close",
489
+ },
490
+ },
491
+ required: ["session_id"],
492
+ },
493
+ },
494
+ {
495
+ name: "reset_session",
496
+ description: "Reset a session's chat history (keep same session ID). " +
497
+ "Use for a clean slate when the task changes; ask the user before resetting.",
498
+ inputSchema: {
499
+ type: "object",
500
+ properties: {
501
+ session_id: {
502
+ type: "string",
503
+ description: "The session ID to reset",
504
+ },
505
+ },
506
+ required: ["session_id"],
507
+ },
508
+ },
509
+ {
510
+ name: "get_health",
511
+ description: "Get server health status including authentication state, active sessions, and configuration. " +
512
+ "Use this to verify the server is ready before starting research workflows.\n\n" +
513
+ "If authenticated=false and having persistent issues:\n" +
514
+ "Consider running cleanup_data(preserve_library=true) + setup_auth for fresh start with clean browser session.",
515
+ inputSchema: {
516
+ type: "object",
517
+ properties: {},
518
+ },
519
+ },
520
+ {
521
+ name: "setup_auth",
522
+ description: "Google authentication for NotebookLM access - opens a browser window for manual login to your Google account. " +
523
+ "Returns immediately after opening the browser. You have up to 10 minutes to complete the login. " +
524
+ "Use 'get_health' tool afterwards to verify authentication was saved successfully. " +
525
+ "Use this for first-time authentication or when auto-login credentials are not available. " +
526
+ "For switching accounts or rate-limit workarounds, use 're_auth' tool instead.\n\n" +
527
+ "TROUBLESHOOTING for persistent auth issues:\n" +
528
+ "If setup_auth fails or you encounter browser/session issues:\n" +
529
+ "1. Ask user to close ALL Chrome/Chromium instances\n" +
530
+ "2. Run cleanup_data(confirm=true, preserve_library=true) to clean old data\n" +
531
+ "3. Run setup_auth again for fresh start\n" +
532
+ "This helps resolve conflicts from old browser sessions and installation data.",
533
+ inputSchema: {
534
+ type: "object",
535
+ properties: {
536
+ show_browser: {
537
+ type: "boolean",
538
+ description: "Show browser window (simple version). Default: true for setup. " +
539
+ "For advanced control, use browser_options instead.",
540
+ },
541
+ browser_options: {
542
+ type: "object",
543
+ description: "Optional browser settings. Control visibility, timeouts, and stealth behavior.",
544
+ properties: {
545
+ show: {
546
+ type: "boolean",
547
+ description: "Show browser window (default: true for setup)",
548
+ },
549
+ headless: {
550
+ type: "boolean",
551
+ description: "Run browser in headless mode (default: false for setup)",
552
+ },
553
+ timeout_ms: {
554
+ type: "number",
555
+ description: "Browser operation timeout in milliseconds (default: 30000)",
556
+ },
557
+ },
558
+ },
559
+ },
560
+ },
561
+ },
562
+ {
563
+ name: "re_auth",
564
+ description: "Switch to a different Google account or re-authenticate. " +
565
+ "Use this when:\n" +
566
+ "- NotebookLM rate limit is reached (50 queries/day for free accounts)\n" +
567
+ "- You want to switch to a different Google account\n" +
568
+ "- Authentication is broken and needs a fresh start\n\n" +
569
+ "This will:\n" +
570
+ "1. Close all active browser sessions\n" +
571
+ "2. Delete all saved authentication data (cookies, Chrome profile)\n" +
572
+ "3. Open browser for fresh Google login\n\n" +
573
+ "After completion, use 'get_health' to verify authentication.\n\n" +
574
+ "TROUBLESHOOTING for persistent auth issues:\n" +
575
+ "If re_auth fails repeatedly:\n" +
576
+ "1. Ask user to close ALL Chrome/Chromium instances\n" +
577
+ "2. Run cleanup_data(confirm=false, preserve_library=true) to preview old files\n" +
578
+ "3. Run cleanup_data(confirm=true, preserve_library=true) to clean everything except library\n" +
579
+ "4. Run re_auth again for completely fresh start\n" +
580
+ "This removes old installation data and browser sessions that can cause conflicts.",
581
+ inputSchema: {
582
+ type: "object",
583
+ properties: {
584
+ show_browser: {
585
+ type: "boolean",
586
+ description: "Show browser window (simple version). Default: true for re-auth. " +
587
+ "For advanced control, use browser_options instead.",
588
+ },
589
+ browser_options: {
590
+ type: "object",
591
+ description: "Optional browser settings. Control visibility, timeouts, and stealth behavior.",
592
+ properties: {
593
+ show: {
594
+ type: "boolean",
595
+ description: "Show browser window (default: true for re-auth)",
596
+ },
597
+ headless: {
598
+ type: "boolean",
599
+ description: "Run browser in headless mode (default: false for re-auth)",
600
+ },
601
+ timeout_ms: {
602
+ type: "number",
603
+ description: "Browser operation timeout in milliseconds (default: 30000)",
604
+ },
605
+ },
606
+ },
607
+ },
608
+ },
609
+ },
610
+ {
611
+ name: "cleanup_data",
612
+ description: "ULTRATHINK Deep Cleanup - Scans entire system for ALL NotebookLM MCP data files across 8 categories. Always runs in deep mode, shows categorized preview before deletion.\n\n" +
613
+ "⚠️ CRITICAL: Close ALL Chrome/Chromium instances BEFORE running this tool! Open browsers can prevent cleanup and cause issues.\n\n" +
614
+ "Categories scanned:\n" +
615
+ "1. Legacy Installation (notebooklm-mcp-nodejs) - Old paths with -nodejs suffix\n" +
616
+ "2. Current Installation (notebooklm-mcp) - Active data, browser profiles, library\n" +
617
+ "3. NPM/NPX Cache - Cached installations from npx\n" +
618
+ "4. Claude CLI MCP Logs - MCP server logs from Claude CLI\n" +
619
+ "5. Temporary Backups - Backup directories in system temp\n" +
620
+ "6. Claude Projects Cache - Project-specific cache (optional)\n" +
621
+ "7. Editor Logs (Cursor/VSCode) - MCP logs from code editors (optional)\n" +
622
+ "8. Trash Files - Deleted notebooklm files in system trash (optional)\n\n" +
623
+ "Works cross-platform (Linux, Windows, macOS). Safe by design: shows detailed preview before deletion, requires explicit confirmation.\n\n" +
624
+ "LIBRARY PRESERVATION: Set preserve_library=true to keep your notebook library.json file while cleaning everything else.\n\n" +
625
+ "RECOMMENDED WORKFLOW for fresh start:\n" +
626
+ "1. Ask user to close ALL Chrome/Chromium instances\n" +
627
+ "2. Run cleanup_data(confirm=false, preserve_library=true) to preview\n" +
628
+ "3. Run cleanup_data(confirm=true, preserve_library=true) to execute\n" +
629
+ "4. Run setup_auth or re_auth for fresh browser session\n\n" +
630
+ "Use cases: Clean reinstall, troubleshooting auth issues, removing all traces before uninstall, cleaning old browser sessions and installation data.",
631
+ inputSchema: {
632
+ type: "object",
633
+ properties: {
634
+ confirm: {
635
+ type: "boolean",
636
+ description: "Confirmation flag. Tool shows preview first, then user confirms deletion. " +
637
+ "Set to true only after user has reviewed the preview and explicitly confirmed.",
638
+ },
639
+ preserve_library: {
640
+ type: "boolean",
641
+ description: "Preserve library.json file during cleanup. Default: false. " +
642
+ "Set to true to keep your notebook library while deleting everything else (browser data, caches, logs).",
643
+ default: false,
644
+ },
645
+ },
646
+ required: ["confirm"],
647
+ },
648
+ },
649
+ ];
650
+ }
651
+ /**
652
+ * MCP Tool Handlers
653
+ */
654
+ export class ToolHandlers {
655
+ sessionManager;
656
+ authManager;
657
+ library;
658
+ constructor(sessionManager, authManager, library) {
659
+ this.sessionManager = sessionManager;
660
+ this.authManager = authManager;
661
+ this.library = library;
662
+ }
663
+ /**
664
+ * Handle ask_question tool
665
+ */
666
+ async handleAskQuestion(args, sendProgress) {
667
+ const { question, session_id, notebook_id, notebook_url, show_browser, browser_options } = args;
668
+ log.info(`🔧 [TOOL] ask_question called`);
669
+ log.info(` Question: "${question.substring(0, 100)}..."`);
670
+ if (session_id) {
671
+ log.info(` Session ID: ${session_id}`);
672
+ }
673
+ if (notebook_id) {
674
+ log.info(` Notebook ID: ${notebook_id}`);
675
+ }
676
+ if (notebook_url) {
677
+ log.info(` Notebook URL: ${notebook_url}`);
678
+ }
679
+ try {
680
+ // Resolve notebook URL
681
+ let resolvedNotebookUrl = notebook_url;
682
+ if (!resolvedNotebookUrl && notebook_id) {
683
+ const notebook = this.library.incrementUseCount(notebook_id);
684
+ if (!notebook) {
685
+ const allNotebooks = this.library.listNotebooks();
686
+ if (allNotebooks.length === 0) {
687
+ throw new Error(`Notebook not found: '${notebook_id}'\n\n` +
688
+ `❌ No notebooks configured in library.\n\n` +
689
+ `To add a notebook:\n` +
690
+ ` POST /notebooks with { url, name, description, topics }\n\n` +
691
+ `Or use notebook_url directly in your request:\n` +
692
+ ` { "question": "...", "notebook_url": "https://notebooklm.google.com/notebook/..." }`);
693
+ }
694
+ else {
695
+ const availableIds = allNotebooks.map(n => n.id).join(', ');
696
+ throw new Error(`Notebook not found: '${notebook_id}'\n\n` +
697
+ `Available notebooks: ${availableIds}\n\n` +
698
+ `To list all notebooks: GET /notebooks\n` +
699
+ `To add a new notebook: POST /notebooks`);
700
+ }
701
+ }
702
+ resolvedNotebookUrl = notebook.url;
703
+ log.info(` Resolved notebook: ${notebook.name}`);
704
+ }
705
+ else if (!resolvedNotebookUrl) {
706
+ const active = this.library.getActiveNotebook();
707
+ if (active) {
708
+ const notebook = this.library.incrementUseCount(active.id);
709
+ if (!notebook) {
710
+ throw new Error(`Active notebook not found: ${active.id}`);
711
+ }
712
+ resolvedNotebookUrl = notebook.url;
713
+ log.info(` Using active notebook: ${notebook.name}`);
714
+ }
715
+ else {
716
+ // No notebook_url, no notebook_id, and no active notebook
717
+ const allNotebooks = this.library.listNotebooks();
718
+ if (allNotebooks.length === 0) {
719
+ throw new Error(`❌ No notebook specified and no notebooks configured in library.\n\n` +
720
+ `Please either:\n` +
721
+ `1. Add a notebook to the library:\n` +
722
+ ` POST /notebooks with { url, name, description, topics }\n\n` +
723
+ `2. Or specify notebook_url in your request:\n` +
724
+ ` { "question": "...", "notebook_url": "https://notebooklm.google.com/notebook/..." }\n\n` +
725
+ `3. Or specify notebook_id from existing notebooks:\n` +
726
+ ` GET /notebooks to list available notebooks`);
727
+ }
728
+ else {
729
+ const availableIds = allNotebooks.map(n => `${n.id} (${n.name})`).join('\n - ');
730
+ throw new Error(`❌ No notebook specified.\n\n` +
731
+ `Available notebooks:\n - ${availableIds}\n\n` +
732
+ `Please specify one of:\n` +
733
+ ` - notebook_id: "${allNotebooks[0].id}"\n` +
734
+ ` - notebook_url: "https://notebooklm.google.com/notebook/..."\n\n` +
735
+ `Or set an active notebook: PUT /notebooks/${allNotebooks[0].id}/activate`);
736
+ }
737
+ }
738
+ }
739
+ // Progress: Getting or creating session
740
+ await sendProgress?.("Getting or creating browser session...", 1, 5);
741
+ // Apply browser options temporarily
742
+ const originalConfig = { ...CONFIG };
743
+ const effectiveConfig = applyBrowserOptions(browser_options, show_browser);
744
+ Object.assign(CONFIG, effectiveConfig);
745
+ // Calculate overrideHeadless parameter for session manager
746
+ // show_browser takes precedence over browser_options.headless
747
+ let overrideHeadless = undefined;
748
+ if (show_browser !== undefined) {
749
+ overrideHeadless = show_browser;
750
+ }
751
+ else if (browser_options?.show !== undefined) {
752
+ overrideHeadless = browser_options.show;
753
+ }
754
+ else if (browser_options?.headless !== undefined) {
755
+ overrideHeadless = !browser_options.headless;
756
+ }
757
+ try {
758
+ // Get or create session (with headless override to handle mode changes)
759
+ const session = await this.sessionManager.getOrCreateSession(session_id, resolvedNotebookUrl, overrideHeadless);
760
+ // Progress: Asking question
761
+ await sendProgress?.("Asking question to NotebookLM...", 2, 5);
762
+ // Ask the question (pass progress callback)
763
+ const rawAnswer = await session.ask(question, sendProgress);
764
+ // Note: FOLLOW_UP_REMINDER removed for cleaner responses
765
+ const answer = rawAnswer.trimEnd();
766
+ // Get session info
767
+ const sessionInfo = session.getInfo();
768
+ const result = {
769
+ status: "success",
770
+ question,
771
+ answer,
772
+ session_id: session.sessionId,
773
+ notebook_url: session.notebookUrl,
774
+ session_info: {
775
+ age_seconds: sessionInfo.age_seconds,
776
+ message_count: sessionInfo.message_count,
777
+ last_activity: sessionInfo.last_activity,
778
+ },
779
+ };
780
+ // Progress: Complete
781
+ await sendProgress?.("Question answered successfully!", 5, 5);
782
+ log.success(`✅ [TOOL] ask_question completed successfully`);
783
+ return {
784
+ success: true,
785
+ data: result,
786
+ };
787
+ }
788
+ finally {
789
+ // Restore original CONFIG
790
+ Object.assign(CONFIG, originalConfig);
791
+ }
792
+ }
793
+ catch (error) {
794
+ const errorMessage = error instanceof Error ? error.message : String(error);
795
+ // Special handling for rate limit errors
796
+ if (error instanceof RateLimitError || errorMessage.toLowerCase().includes("rate limit")) {
797
+ log.error(`🚫 [TOOL] Rate limit detected`);
798
+ return {
799
+ success: false,
800
+ error: "NotebookLM rate limit reached (50 queries/day for free accounts).\n\n" +
801
+ "You can:\n" +
802
+ "1. Use the 're_auth' tool to login with a different Google account\n" +
803
+ "2. Wait until tomorrow for the quota to reset\n" +
804
+ "3. Upgrade to Google AI Pro/Ultra for 5x higher limits\n\n" +
805
+ `Original error: ${errorMessage}`,
806
+ };
807
+ }
808
+ log.error(`❌ [TOOL] ask_question failed: ${errorMessage}`);
809
+ return {
810
+ success: false,
811
+ error: errorMessage,
812
+ };
813
+ }
814
+ }
815
+ /**
816
+ * Handle list_sessions tool
817
+ */
818
+ async handleListSessions() {
819
+ log.info(`🔧 [TOOL] list_sessions called`);
820
+ try {
821
+ const stats = this.sessionManager.getStats();
822
+ const sessions = this.sessionManager.getAllSessionsInfo();
823
+ const result = {
824
+ active_sessions: stats.active_sessions,
825
+ max_sessions: stats.max_sessions,
826
+ session_timeout: stats.session_timeout,
827
+ oldest_session_seconds: stats.oldest_session_seconds,
828
+ total_messages: stats.total_messages,
829
+ sessions: sessions.map((info) => ({
830
+ id: info.id,
831
+ created_at: info.created_at,
832
+ last_activity: info.last_activity,
833
+ age_seconds: info.age_seconds,
834
+ inactive_seconds: info.inactive_seconds,
835
+ message_count: info.message_count,
836
+ notebook_url: info.notebook_url,
837
+ })),
838
+ };
839
+ log.success(`✅ [TOOL] list_sessions completed (${result.active_sessions} sessions)`);
840
+ return {
841
+ success: true,
842
+ data: result,
843
+ };
844
+ }
845
+ catch (error) {
846
+ const errorMessage = error instanceof Error ? error.message : String(error);
847
+ log.error(`❌ [TOOL] list_sessions failed: ${errorMessage}`);
848
+ return {
849
+ success: false,
850
+ error: errorMessage,
851
+ };
852
+ }
853
+ }
854
+ /**
855
+ * Handle close_session tool
856
+ */
857
+ async handleCloseSession(args) {
858
+ const { session_id } = args;
859
+ log.info(`🔧 [TOOL] close_session called`);
860
+ log.info(` Session ID: ${session_id}`);
861
+ try {
862
+ const closed = await this.sessionManager.closeSession(session_id);
863
+ if (closed) {
864
+ log.success(`✅ [TOOL] close_session completed`);
865
+ return {
866
+ success: true,
867
+ data: {
868
+ status: "success",
869
+ message: `Session ${session_id} closed successfully`,
870
+ session_id,
871
+ },
872
+ };
873
+ }
874
+ else {
875
+ log.warning(`⚠️ [TOOL] Session ${session_id} not found`);
876
+ return {
877
+ success: false,
878
+ error: `Session ${session_id} not found`,
879
+ };
880
+ }
881
+ }
882
+ catch (error) {
883
+ const errorMessage = error instanceof Error ? error.message : String(error);
884
+ log.error(`❌ [TOOL] close_session failed: ${errorMessage}`);
885
+ return {
886
+ success: false,
887
+ error: errorMessage,
888
+ };
889
+ }
890
+ }
891
+ /**
892
+ * Handle reset_session tool
893
+ */
894
+ async handleResetSession(args) {
895
+ const { session_id } = args;
896
+ log.info(`🔧 [TOOL] reset_session called`);
897
+ log.info(` Session ID: ${session_id}`);
898
+ try {
899
+ const session = this.sessionManager.getSession(session_id);
900
+ if (!session) {
901
+ log.warning(`⚠️ [TOOL] Session ${session_id} not found`);
902
+ return {
903
+ success: false,
904
+ error: `Session ${session_id} not found`,
905
+ };
906
+ }
907
+ await session.reset();
908
+ log.success(`✅ [TOOL] reset_session completed`);
909
+ return {
910
+ success: true,
911
+ data: {
912
+ status: "success",
913
+ message: `Session ${session_id} reset successfully`,
914
+ session_id,
915
+ },
916
+ };
917
+ }
918
+ catch (error) {
919
+ const errorMessage = error instanceof Error ? error.message : String(error);
920
+ log.error(`❌ [TOOL] reset_session failed: ${errorMessage}`);
921
+ return {
922
+ success: false,
923
+ error: errorMessage,
924
+ };
925
+ }
926
+ }
927
+ /**
928
+ * Handle get_health tool
929
+ */
930
+ async handleGetHealth() {
931
+ log.info(`🔧 [TOOL] get_health called`);
932
+ try {
933
+ // Check authentication status
934
+ const statePath = await this.authManager.getValidStatePath();
935
+ const authenticated = statePath !== null;
936
+ // Get session stats
937
+ const stats = this.sessionManager.getStats();
938
+ const result = {
939
+ status: "ok",
940
+ authenticated,
941
+ notebook_url: CONFIG.notebookUrl || "not configured",
942
+ active_sessions: stats.active_sessions,
943
+ max_sessions: stats.max_sessions,
944
+ session_timeout: stats.session_timeout,
945
+ total_messages: stats.total_messages,
946
+ headless: CONFIG.headless,
947
+ auto_login_enabled: CONFIG.autoLoginEnabled,
948
+ stealth_enabled: CONFIG.stealthEnabled,
949
+ // Add troubleshooting tip if not authenticated
950
+ ...((!authenticated) && {
951
+ troubleshooting_tip: "For fresh start with clean browser session: Close all Chrome instances → " +
952
+ "cleanup_data(confirm=true, preserve_library=true) → setup_auth"
953
+ }),
954
+ };
955
+ log.success(`✅ [TOOL] get_health completed`);
956
+ return {
957
+ success: true,
958
+ data: result,
959
+ };
960
+ }
961
+ catch (error) {
962
+ const errorMessage = error instanceof Error ? error.message : String(error);
963
+ log.error(`❌ [TOOL] get_health failed: ${errorMessage}`);
964
+ return {
965
+ success: false,
966
+ error: errorMessage,
967
+ };
968
+ }
969
+ }
970
+ /**
971
+ * Handle setup_auth tool
972
+ *
973
+ * Opens a browser window for manual login with live progress updates.
974
+ * The operation waits synchronously for login completion (up to 10 minutes).
975
+ */
976
+ async handleSetupAuth(args, sendProgress) {
977
+ const { show_browser, browser_options } = args;
978
+ // CRITICAL: Send immediate progress to reset timeout from the very start
979
+ await sendProgress?.("Initializing authentication setup...", 0, 10);
980
+ log.info(`🔧 [TOOL] setup_auth called`);
981
+ if (show_browser !== undefined) {
982
+ log.info(` Show browser: ${show_browser}`);
983
+ }
984
+ const startTime = Date.now();
985
+ // Apply browser options temporarily
986
+ const originalConfig = { ...CONFIG };
987
+ const effectiveConfig = applyBrowserOptions(browser_options, show_browser);
988
+ Object.assign(CONFIG, effectiveConfig);
989
+ try {
990
+ // Progress: Starting
991
+ await sendProgress?.("Preparing authentication browser...", 1, 10);
992
+ log.info(` 🌐 Opening browser for interactive login...`);
993
+ // Progress: Opening browser
994
+ await sendProgress?.("Opening browser window...", 2, 10);
995
+ // Perform setup with progress updates (uses CONFIG internally)
996
+ const success = await this.authManager.performSetup(sendProgress);
997
+ const durationSeconds = (Date.now() - startTime) / 1000;
998
+ if (success) {
999
+ // Progress: Complete
1000
+ await sendProgress?.("Authentication saved successfully!", 10, 10);
1001
+ log.success(`✅ [TOOL] setup_auth completed (${durationSeconds.toFixed(1)}s)`);
1002
+ return {
1003
+ success: true,
1004
+ data: {
1005
+ status: "authenticated",
1006
+ message: "Successfully authenticated and saved browser state",
1007
+ authenticated: true,
1008
+ duration_seconds: durationSeconds,
1009
+ },
1010
+ };
1011
+ }
1012
+ else {
1013
+ log.error(`❌ [TOOL] setup_auth failed (${durationSeconds.toFixed(1)}s)`);
1014
+ return {
1015
+ success: false,
1016
+ error: "Authentication failed or was cancelled",
1017
+ };
1018
+ }
1019
+ }
1020
+ catch (error) {
1021
+ const errorMessage = error instanceof Error ? error.message : String(error);
1022
+ const durationSeconds = (Date.now() - startTime) / 1000;
1023
+ log.error(`❌ [TOOL] setup_auth failed: ${errorMessage} (${durationSeconds.toFixed(1)}s)`);
1024
+ return {
1025
+ success: false,
1026
+ error: errorMessage,
1027
+ };
1028
+ }
1029
+ finally {
1030
+ // Restore original CONFIG
1031
+ Object.assign(CONFIG, originalConfig);
1032
+ }
1033
+ }
1034
+ /**
1035
+ * Handle re_auth tool
1036
+ *
1037
+ * Performs a complete re-authentication:
1038
+ * 1. Closes all active browser sessions
1039
+ * 2. Deletes all saved authentication data (cookies, Chrome profile)
1040
+ * 3. Opens browser for fresh Google login
1041
+ *
1042
+ * Use for switching Google accounts or recovering from rate limits.
1043
+ */
1044
+ async handleReAuth(args, sendProgress) {
1045
+ const { show_browser, browser_options } = args;
1046
+ await sendProgress?.("Preparing re-authentication...", 0, 12);
1047
+ log.info(`🔧 [TOOL] re_auth called`);
1048
+ if (show_browser !== undefined) {
1049
+ log.info(` Show browser: ${show_browser}`);
1050
+ }
1051
+ const startTime = Date.now();
1052
+ // Apply browser options temporarily
1053
+ const originalConfig = { ...CONFIG };
1054
+ const effectiveConfig = applyBrowserOptions(browser_options, show_browser);
1055
+ Object.assign(CONFIG, effectiveConfig);
1056
+ try {
1057
+ // 1. Close all active sessions
1058
+ await sendProgress?.("Closing all active sessions...", 1, 12);
1059
+ log.info(" 🛑 Closing all sessions...");
1060
+ await this.sessionManager.closeAllSessions();
1061
+ log.success(" ✅ All sessions closed");
1062
+ // 2. Clear all auth data
1063
+ await sendProgress?.("Clearing authentication data...", 2, 12);
1064
+ log.info(" 🗑️ Clearing all auth data...");
1065
+ await this.authManager.clearAllAuthData();
1066
+ log.success(" ✅ Auth data cleared");
1067
+ // 3. Perform fresh setup
1068
+ await sendProgress?.("Starting fresh authentication...", 3, 12);
1069
+ log.info(" 🌐 Starting fresh authentication setup...");
1070
+ const success = await this.authManager.performSetup(sendProgress);
1071
+ const durationSeconds = (Date.now() - startTime) / 1000;
1072
+ if (success) {
1073
+ await sendProgress?.("Re-authentication complete!", 12, 12);
1074
+ log.success(`✅ [TOOL] re_auth completed (${durationSeconds.toFixed(1)}s)`);
1075
+ return {
1076
+ success: true,
1077
+ data: {
1078
+ status: "authenticated",
1079
+ message: "Successfully re-authenticated with new account. All previous sessions have been closed.",
1080
+ authenticated: true,
1081
+ duration_seconds: durationSeconds,
1082
+ },
1083
+ };
1084
+ }
1085
+ else {
1086
+ log.error(`❌ [TOOL] re_auth failed (${durationSeconds.toFixed(1)}s)`);
1087
+ return {
1088
+ success: false,
1089
+ error: "Re-authentication failed or was cancelled",
1090
+ };
1091
+ }
1092
+ }
1093
+ catch (error) {
1094
+ const errorMessage = error instanceof Error ? error.message : String(error);
1095
+ const durationSeconds = (Date.now() - startTime) / 1000;
1096
+ log.error(`❌ [TOOL] re_auth failed: ${errorMessage} (${durationSeconds.toFixed(1)}s)`);
1097
+ return {
1098
+ success: false,
1099
+ error: errorMessage,
1100
+ };
1101
+ }
1102
+ finally {
1103
+ // Restore original CONFIG
1104
+ Object.assign(CONFIG, originalConfig);
1105
+ }
1106
+ }
1107
+ /**
1108
+ * Handle add_notebook tool
1109
+ */
1110
+ async handleAddNotebook(args) {
1111
+ log.info(`🔧 [TOOL] add_notebook called`);
1112
+ log.info(` Name: ${args.name}`);
1113
+ try {
1114
+ const notebook = await this.library.addNotebook(args);
1115
+ log.success(`✅ [TOOL] add_notebook completed: ${notebook.id}`);
1116
+ return {
1117
+ success: true,
1118
+ data: { notebook },
1119
+ };
1120
+ }
1121
+ catch (error) {
1122
+ const errorMessage = error instanceof Error ? error.message : String(error);
1123
+ log.error(`❌ [TOOL] add_notebook failed: ${errorMessage}`);
1124
+ return {
1125
+ success: false,
1126
+ error: errorMessage,
1127
+ };
1128
+ }
1129
+ }
1130
+ /**
1131
+ * Handle list_notebooks tool
1132
+ */
1133
+ async handleListNotebooks() {
1134
+ log.info(`🔧 [TOOL] list_notebooks called`);
1135
+ try {
1136
+ const notebooks = this.library.listNotebooks();
1137
+ const activeNotebook = this.library.getActiveNotebook();
1138
+ const active_notebook_id = activeNotebook ? activeNotebook.id : null;
1139
+ log.success(`✅ [TOOL] list_notebooks completed (${notebooks.length} notebooks, active: ${active_notebook_id || 'none'})`);
1140
+ return {
1141
+ success: true,
1142
+ data: {
1143
+ notebooks,
1144
+ active_notebook_id,
1145
+ },
1146
+ };
1147
+ }
1148
+ catch (error) {
1149
+ const errorMessage = error instanceof Error ? error.message : String(error);
1150
+ log.error(`❌ [TOOL] list_notebooks failed: ${errorMessage}`);
1151
+ return {
1152
+ success: false,
1153
+ error: errorMessage,
1154
+ };
1155
+ }
1156
+ }
1157
+ /**
1158
+ * Handle get_notebook tool
1159
+ */
1160
+ async handleGetNotebook(args) {
1161
+ log.info(`🔧 [TOOL] get_notebook called`);
1162
+ log.info(` ID: ${args.id}`);
1163
+ try {
1164
+ const notebook = this.library.getNotebook(args.id);
1165
+ if (!notebook) {
1166
+ log.warning(`⚠️ [TOOL] Notebook not found: ${args.id}`);
1167
+ return {
1168
+ success: false,
1169
+ error: `Notebook not found: ${args.id}`,
1170
+ };
1171
+ }
1172
+ log.success(`✅ [TOOL] get_notebook completed: ${notebook.name}`);
1173
+ return {
1174
+ success: true,
1175
+ data: { notebook },
1176
+ };
1177
+ }
1178
+ catch (error) {
1179
+ const errorMessage = error instanceof Error ? error.message : String(error);
1180
+ log.error(`❌ [TOOL] get_notebook failed: ${errorMessage}`);
1181
+ return {
1182
+ success: false,
1183
+ error: errorMessage,
1184
+ };
1185
+ }
1186
+ }
1187
+ /**
1188
+ * Handle select_notebook tool
1189
+ */
1190
+ async handleSelectNotebook(args) {
1191
+ log.info(`🔧 [TOOL] select_notebook called`);
1192
+ log.info(` ID: ${args.id}`);
1193
+ try {
1194
+ const notebook = this.library.selectNotebook(args.id);
1195
+ log.success(`✅ [TOOL] select_notebook completed: ${notebook.name}`);
1196
+ return {
1197
+ success: true,
1198
+ data: { notebook },
1199
+ };
1200
+ }
1201
+ catch (error) {
1202
+ const errorMessage = error instanceof Error ? error.message : String(error);
1203
+ log.error(`❌ [TOOL] select_notebook failed: ${errorMessage}`);
1204
+ return {
1205
+ success: false,
1206
+ error: errorMessage,
1207
+ };
1208
+ }
1209
+ }
1210
+ /**
1211
+ * Handle update_notebook tool
1212
+ */
1213
+ async handleUpdateNotebook(args) {
1214
+ log.info(`🔧 [TOOL] update_notebook called`);
1215
+ log.info(` ID: ${args.id}`);
1216
+ try {
1217
+ const notebook = this.library.updateNotebook(args);
1218
+ log.success(`✅ [TOOL] update_notebook completed: ${notebook.name}`);
1219
+ return {
1220
+ success: true,
1221
+ data: { notebook },
1222
+ };
1223
+ }
1224
+ catch (error) {
1225
+ const errorMessage = error instanceof Error ? error.message : String(error);
1226
+ log.error(`❌ [TOOL] update_notebook failed: ${errorMessage}`);
1227
+ return {
1228
+ success: false,
1229
+ error: errorMessage,
1230
+ };
1231
+ }
1232
+ }
1233
+ /**
1234
+ * Handle remove_notebook tool
1235
+ */
1236
+ async handleRemoveNotebook(args) {
1237
+ log.info(`🔧 [TOOL] remove_notebook called`);
1238
+ log.info(` ID: ${args.id}`);
1239
+ try {
1240
+ const notebook = this.library.getNotebook(args.id);
1241
+ if (!notebook) {
1242
+ log.warning(`⚠️ [TOOL] Notebook not found: ${args.id}`);
1243
+ return {
1244
+ success: false,
1245
+ error: `Notebook not found: ${args.id}`,
1246
+ };
1247
+ }
1248
+ const removed = this.library.removeNotebook(args.id);
1249
+ if (removed) {
1250
+ const closedSessions = await this.sessionManager.closeSessionsForNotebook(notebook.url);
1251
+ log.success(`✅ [TOOL] remove_notebook completed`);
1252
+ return {
1253
+ success: true,
1254
+ data: { removed: true, closed_sessions: closedSessions },
1255
+ };
1256
+ }
1257
+ else {
1258
+ log.warning(`⚠️ [TOOL] Notebook not found: ${args.id}`);
1259
+ return {
1260
+ success: false,
1261
+ error: `Notebook not found: ${args.id}`,
1262
+ };
1263
+ }
1264
+ }
1265
+ catch (error) {
1266
+ const errorMessage = error instanceof Error ? error.message : String(error);
1267
+ log.error(`❌ [TOOL] remove_notebook failed: ${errorMessage}`);
1268
+ return {
1269
+ success: false,
1270
+ error: errorMessage,
1271
+ };
1272
+ }
1273
+ }
1274
+ /**
1275
+ * Handle search_notebooks tool
1276
+ */
1277
+ async handleSearchNotebooks(args) {
1278
+ log.info(`🔧 [TOOL] search_notebooks called`);
1279
+ log.info(` Query: "${args.query}"`);
1280
+ try {
1281
+ const notebooks = this.library.searchNotebooks(args.query);
1282
+ log.success(`✅ [TOOL] search_notebooks completed (${notebooks.length} results)`);
1283
+ return {
1284
+ success: true,
1285
+ data: { notebooks },
1286
+ };
1287
+ }
1288
+ catch (error) {
1289
+ const errorMessage = error instanceof Error ? error.message : String(error);
1290
+ log.error(`❌ [TOOL] search_notebooks failed: ${errorMessage}`);
1291
+ return {
1292
+ success: false,
1293
+ error: errorMessage,
1294
+ };
1295
+ }
1296
+ }
1297
+ /**
1298
+ * Handle get_library_stats tool
1299
+ */
1300
+ async handleGetLibraryStats() {
1301
+ log.info(`🔧 [TOOL] get_library_stats called`);
1302
+ try {
1303
+ const stats = this.library.getStats();
1304
+ log.success(`✅ [TOOL] get_library_stats completed`);
1305
+ return {
1306
+ success: true,
1307
+ data: stats,
1308
+ };
1309
+ }
1310
+ catch (error) {
1311
+ const errorMessage = error instanceof Error ? error.message : String(error);
1312
+ log.error(`❌ [TOOL] get_library_stats failed: ${errorMessage}`);
1313
+ return {
1314
+ success: false,
1315
+ error: errorMessage,
1316
+ };
1317
+ }
1318
+ }
1319
+ /**
1320
+ * Handle cleanup_data tool
1321
+ *
1322
+ * ULTRATHINK Deep Cleanup - scans entire system for ALL NotebookLM MCP files
1323
+ */
1324
+ async handleCleanupData(args) {
1325
+ const { confirm, preserve_library = false } = args;
1326
+ log.info(`🔧 [TOOL] cleanup_data called`);
1327
+ log.info(` Confirm: ${confirm}`);
1328
+ log.info(` Preserve Library: ${preserve_library}`);
1329
+ const cleanupManager = new CleanupManager();
1330
+ try {
1331
+ // Always run in deep mode
1332
+ const mode = "deep";
1333
+ if (!confirm) {
1334
+ // Preview mode - show what would be deleted
1335
+ log.info(` 📋 Generating cleanup preview (mode: ${mode})...`);
1336
+ const preview = await cleanupManager.getCleanupPaths(mode, preserve_library);
1337
+ const platformInfo = cleanupManager.getPlatformInfo();
1338
+ log.info(` Found ${preview.totalPaths.length} items (${cleanupManager.formatBytes(preview.totalSizeBytes)})`);
1339
+ log.info(` Platform: ${platformInfo.platform}`);
1340
+ return {
1341
+ success: true,
1342
+ data: {
1343
+ status: "preview",
1344
+ mode,
1345
+ preview: {
1346
+ categories: preview.categories,
1347
+ totalPaths: preview.totalPaths.length,
1348
+ totalSizeBytes: preview.totalSizeBytes,
1349
+ },
1350
+ },
1351
+ };
1352
+ }
1353
+ else {
1354
+ // Cleanup mode - actually delete files
1355
+ log.info(` 🗑️ Performing cleanup (mode: ${mode})...`);
1356
+ const result = await cleanupManager.performCleanup(mode, preserve_library);
1357
+ if (result.success) {
1358
+ log.success(`✅ [TOOL] cleanup_data completed - deleted ${result.deletedPaths.length} items`);
1359
+ }
1360
+ else {
1361
+ log.warning(`⚠️ [TOOL] cleanup_data completed with ${result.failedPaths.length} errors`);
1362
+ }
1363
+ return {
1364
+ success: result.success,
1365
+ data: {
1366
+ status: result.success ? "completed" : "partial",
1367
+ mode,
1368
+ result: {
1369
+ deletedPaths: result.deletedPaths,
1370
+ failedPaths: result.failedPaths,
1371
+ totalSizeBytes: result.totalSizeBytes,
1372
+ categorySummary: result.categorySummary,
1373
+ },
1374
+ },
1375
+ };
1376
+ }
1377
+ }
1378
+ catch (error) {
1379
+ const errorMessage = error instanceof Error ? error.message : String(error);
1380
+ log.error(`❌ [TOOL] cleanup_data failed: ${errorMessage}`);
1381
+ return {
1382
+ success: false,
1383
+ error: errorMessage,
1384
+ };
1385
+ }
1386
+ }
1387
+ /**
1388
+ * Cleanup all resources (called on server shutdown)
1389
+ */
1390
+ async cleanup() {
1391
+ log.info(`🧹 Cleaning up tool handlers...`);
1392
+ await this.sessionManager.closeAllSessions();
1393
+ log.success(`✅ Tool handlers cleanup complete`);
1394
+ }
1395
+ }
1396
+ //# sourceMappingURL=index.js.map