@pan-sec/notebooklm-mcp 1.4.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.
Files changed (145) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +289 -0
  3. package/SECURITY.md +539 -0
  4. package/dist/auth/auth-manager.d.ts +137 -0
  5. package/dist/auth/auth-manager.d.ts.map +1 -0
  6. package/dist/auth/auth-manager.js +984 -0
  7. package/dist/auth/auth-manager.js.map +1 -0
  8. package/dist/auth/mcp-auth.d.ts +102 -0
  9. package/dist/auth/mcp-auth.d.ts.map +1 -0
  10. package/dist/auth/mcp-auth.js +286 -0
  11. package/dist/auth/mcp-auth.js.map +1 -0
  12. package/dist/config.d.ts +89 -0
  13. package/dist/config.d.ts.map +1 -0
  14. package/dist/config.js +216 -0
  15. package/dist/config.js.map +1 -0
  16. package/dist/errors.d.ts +26 -0
  17. package/dist/errors.d.ts.map +1 -0
  18. package/dist/errors.js +41 -0
  19. package/dist/errors.js.map +1 -0
  20. package/dist/index.d.ts +32 -0
  21. package/dist/index.d.ts.map +1 -0
  22. package/dist/index.js +371 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/library/notebook-library.d.ts +70 -0
  25. package/dist/library/notebook-library.d.ts.map +1 -0
  26. package/dist/library/notebook-library.js +279 -0
  27. package/dist/library/notebook-library.js.map +1 -0
  28. package/dist/library/types.d.ts +67 -0
  29. package/dist/library/types.d.ts.map +1 -0
  30. package/dist/library/types.js +8 -0
  31. package/dist/library/types.js.map +1 -0
  32. package/dist/resources/resource-handlers.d.ts +22 -0
  33. package/dist/resources/resource-handlers.d.ts.map +1 -0
  34. package/dist/resources/resource-handlers.js +216 -0
  35. package/dist/resources/resource-handlers.js.map +1 -0
  36. package/dist/session/browser-session.d.ts +108 -0
  37. package/dist/session/browser-session.d.ts.map +1 -0
  38. package/dist/session/browser-session.js +621 -0
  39. package/dist/session/browser-session.js.map +1 -0
  40. package/dist/session/session-manager.d.ts +77 -0
  41. package/dist/session/session-manager.d.ts.map +1 -0
  42. package/dist/session/session-manager.js +314 -0
  43. package/dist/session/session-manager.js.map +1 -0
  44. package/dist/session/session-timeout.d.ts +122 -0
  45. package/dist/session/session-timeout.d.ts.map +1 -0
  46. package/dist/session/session-timeout.js +281 -0
  47. package/dist/session/session-timeout.js.map +1 -0
  48. package/dist/session/shared-context-manager.d.ts +107 -0
  49. package/dist/session/shared-context-manager.d.ts.map +1 -0
  50. package/dist/session/shared-context-manager.js +447 -0
  51. package/dist/session/shared-context-manager.js.map +1 -0
  52. package/dist/tools/definitions/ask-question.d.ts +8 -0
  53. package/dist/tools/definitions/ask-question.d.ts.map +1 -0
  54. package/dist/tools/definitions/ask-question.js +211 -0
  55. package/dist/tools/definitions/ask-question.js.map +1 -0
  56. package/dist/tools/definitions/notebook-management.d.ts +3 -0
  57. package/dist/tools/definitions/notebook-management.d.ts.map +1 -0
  58. package/dist/tools/definitions/notebook-management.js +243 -0
  59. package/dist/tools/definitions/notebook-management.js.map +1 -0
  60. package/dist/tools/definitions/session-management.d.ts +3 -0
  61. package/dist/tools/definitions/session-management.d.ts.map +1 -0
  62. package/dist/tools/definitions/session-management.js +41 -0
  63. package/dist/tools/definitions/session-management.js.map +1 -0
  64. package/dist/tools/definitions/system.d.ts +3 -0
  65. package/dist/tools/definitions/system.d.ts.map +1 -0
  66. package/dist/tools/definitions/system.js +143 -0
  67. package/dist/tools/definitions/system.js.map +1 -0
  68. package/dist/tools/definitions.d.ts +12 -0
  69. package/dist/tools/definitions.d.ts.map +1 -0
  70. package/dist/tools/definitions.js +26 -0
  71. package/dist/tools/definitions.js.map +1 -0
  72. package/dist/tools/handlers.d.ts +213 -0
  73. package/dist/tools/handlers.d.ts.map +1 -0
  74. package/dist/tools/handlers.js +813 -0
  75. package/dist/tools/handlers.js.map +1 -0
  76. package/dist/tools/index.d.ts +8 -0
  77. package/dist/tools/index.d.ts.map +1 -0
  78. package/dist/tools/index.js +8 -0
  79. package/dist/tools/index.js.map +1 -0
  80. package/dist/types.d.ts +82 -0
  81. package/dist/types.d.ts.map +1 -0
  82. package/dist/types.js +5 -0
  83. package/dist/types.js.map +1 -0
  84. package/dist/utils/audit-logger.d.ts +140 -0
  85. package/dist/utils/audit-logger.d.ts.map +1 -0
  86. package/dist/utils/audit-logger.js +361 -0
  87. package/dist/utils/audit-logger.js.map +1 -0
  88. package/dist/utils/cert-pinning.d.ts +97 -0
  89. package/dist/utils/cert-pinning.d.ts.map +1 -0
  90. package/dist/utils/cert-pinning.js +328 -0
  91. package/dist/utils/cert-pinning.js.map +1 -0
  92. package/dist/utils/cleanup-manager.d.ts +133 -0
  93. package/dist/utils/cleanup-manager.d.ts.map +1 -0
  94. package/dist/utils/cleanup-manager.js +673 -0
  95. package/dist/utils/cleanup-manager.js.map +1 -0
  96. package/dist/utils/cli-handler.d.ts +16 -0
  97. package/dist/utils/cli-handler.d.ts.map +1 -0
  98. package/dist/utils/cli-handler.js +102 -0
  99. package/dist/utils/cli-handler.js.map +1 -0
  100. package/dist/utils/crypto.d.ts +175 -0
  101. package/dist/utils/crypto.d.ts.map +1 -0
  102. package/dist/utils/crypto.js +612 -0
  103. package/dist/utils/crypto.js.map +1 -0
  104. package/dist/utils/logger.d.ts +61 -0
  105. package/dist/utils/logger.d.ts.map +1 -0
  106. package/dist/utils/logger.js +92 -0
  107. package/dist/utils/logger.js.map +1 -0
  108. package/dist/utils/page-utils.d.ts +54 -0
  109. package/dist/utils/page-utils.d.ts.map +1 -0
  110. package/dist/utils/page-utils.js +405 -0
  111. package/dist/utils/page-utils.js.map +1 -0
  112. package/dist/utils/response-validator.d.ts +98 -0
  113. package/dist/utils/response-validator.d.ts.map +1 -0
  114. package/dist/utils/response-validator.js +352 -0
  115. package/dist/utils/response-validator.js.map +1 -0
  116. package/dist/utils/secrets-scanner.d.ts +126 -0
  117. package/dist/utils/secrets-scanner.d.ts.map +1 -0
  118. package/dist/utils/secrets-scanner.js +443 -0
  119. package/dist/utils/secrets-scanner.js.map +1 -0
  120. package/dist/utils/secure-memory.d.ts +130 -0
  121. package/dist/utils/secure-memory.d.ts.map +1 -0
  122. package/dist/utils/secure-memory.js +279 -0
  123. package/dist/utils/secure-memory.js.map +1 -0
  124. package/dist/utils/security.d.ts +83 -0
  125. package/dist/utils/security.d.ts.map +1 -0
  126. package/dist/utils/security.js +272 -0
  127. package/dist/utils/security.js.map +1 -0
  128. package/dist/utils/settings-manager.d.ts +37 -0
  129. package/dist/utils/settings-manager.d.ts.map +1 -0
  130. package/dist/utils/settings-manager.js +125 -0
  131. package/dist/utils/settings-manager.js.map +1 -0
  132. package/dist/utils/stealth-utils.d.ts +135 -0
  133. package/dist/utils/stealth-utils.d.ts.map +1 -0
  134. package/dist/utils/stealth-utils.js +398 -0
  135. package/dist/utils/stealth-utils.js.map +1 -0
  136. package/dist/utils/tool-validation.d.ts +93 -0
  137. package/dist/utils/tool-validation.d.ts.map +1 -0
  138. package/dist/utils/tool-validation.js +277 -0
  139. package/dist/utils/tool-validation.js.map +1 -0
  140. package/docs/SECURITY_IMPLEMENTATION_PLAN.md +437 -0
  141. package/docs/configuration.md +94 -0
  142. package/docs/tools.md +34 -0
  143. package/docs/troubleshooting.md +59 -0
  144. package/docs/usage-guide.md +245 -0
  145. package/package.json +82 -0
@@ -0,0 +1,813 @@
1
+ /**
2
+ * MCP Tool Handlers
3
+ *
4
+ * Implements the logic for all MCP tools.
5
+ */
6
+ import { CONFIG, applyBrowserOptions } from "../config.js";
7
+ import { log } from "../utils/logger.js";
8
+ import { RateLimitError } from "../errors.js";
9
+ import { validateNotebookUrl, validateNotebookId, validateSessionId, validateQuestion, sanitizeForLogging, RateLimiter, SecurityError, } from "../utils/security.js";
10
+ import { audit } from "../utils/audit-logger.js";
11
+ import { validateResponse } from "../utils/response-validator.js";
12
+ import { CleanupManager } from "../utils/cleanup-manager.js";
13
+ const FOLLOW_UP_REMINDER = "\n\nEXTREMELY IMPORTANT: Is that ALL you need to know? You can always ask another question using the same session ID! Think about it carefully: before you reply to the user, review their original request and this answer. If anything is still unclear or missing, ask me another question first.";
14
+ /**
15
+ * MCP Tool Handlers
16
+ */
17
+ export class ToolHandlers {
18
+ sessionManager;
19
+ authManager;
20
+ library;
21
+ rateLimiter;
22
+ constructor(sessionManager, authManager, library) {
23
+ this.sessionManager = sessionManager;
24
+ this.authManager = authManager;
25
+ this.library = library;
26
+ // Rate limit: 100 requests per minute per session (protective limit)
27
+ this.rateLimiter = new RateLimiter(100, 60000);
28
+ }
29
+ /**
30
+ * Handle ask_question tool
31
+ */
32
+ async handleAskQuestion(args, sendProgress) {
33
+ const { show_browser, browser_options } = args;
34
+ const startTime = Date.now();
35
+ log.info(`๐Ÿ”ง [TOOL] ask_question called`);
36
+ // === SECURITY: Input validation ===
37
+ let safeQuestion;
38
+ let safeSessionId;
39
+ let safeNotebookId;
40
+ let safeNotebookUrl;
41
+ try {
42
+ // Validate question (required)
43
+ safeQuestion = validateQuestion(args.question);
44
+ log.info(` Question: "${sanitizeForLogging(safeQuestion.substring(0, 100))}"...`);
45
+ // Validate optional session_id
46
+ if (args.session_id) {
47
+ safeSessionId = validateSessionId(args.session_id);
48
+ log.info(` Session ID: ${safeSessionId}`);
49
+ }
50
+ // Validate optional notebook_id
51
+ if (args.notebook_id) {
52
+ safeNotebookId = validateNotebookId(args.notebook_id);
53
+ log.info(` Notebook ID: ${safeNotebookId}`);
54
+ }
55
+ // Validate optional notebook_url (CRITICAL - prevents URL injection)
56
+ if (args.notebook_url) {
57
+ safeNotebookUrl = validateNotebookUrl(args.notebook_url);
58
+ log.info(` Notebook URL: ${safeNotebookUrl}`);
59
+ }
60
+ // Rate limiting check
61
+ const rateLimitKey = safeSessionId || 'global';
62
+ if (!this.rateLimiter.isAllowed(rateLimitKey)) {
63
+ log.warning(`๐Ÿšซ Rate limit exceeded for ${rateLimitKey}`);
64
+ await audit.security("rate_limit_exceeded", "warning", {
65
+ session_id: rateLimitKey,
66
+ remaining: this.rateLimiter.getRemaining(rateLimitKey),
67
+ });
68
+ await audit.tool("ask_question", args, false, Date.now() - startTime, "Rate limit exceeded");
69
+ return {
70
+ success: false,
71
+ error: `Rate limit exceeded. Please wait before making more requests. Remaining: ${this.rateLimiter.getRemaining(rateLimitKey)}`,
72
+ };
73
+ }
74
+ }
75
+ catch (error) {
76
+ if (error instanceof SecurityError) {
77
+ log.error(`๐Ÿ›ก๏ธ [SECURITY] Validation failed: ${error.message}`);
78
+ await audit.security("validation_failed", "error", {
79
+ tool: "ask_question",
80
+ error: error.message,
81
+ });
82
+ await audit.tool("ask_question", args, false, Date.now() - startTime, error.message);
83
+ return {
84
+ success: false,
85
+ error: `Security validation failed: ${error.message}`,
86
+ };
87
+ }
88
+ throw error;
89
+ }
90
+ try {
91
+ // Resolve notebook URL (using validated values)
92
+ let resolvedNotebookUrl = safeNotebookUrl;
93
+ if (!resolvedNotebookUrl && safeNotebookId) {
94
+ const notebook = this.library.incrementUseCount(safeNotebookId);
95
+ if (!notebook) {
96
+ throw new Error(`Notebook not found in library: ${safeNotebookId}`);
97
+ }
98
+ resolvedNotebookUrl = notebook.url;
99
+ log.info(` Resolved notebook: ${notebook.name}`);
100
+ }
101
+ else if (!resolvedNotebookUrl) {
102
+ const active = this.library.getActiveNotebook();
103
+ if (active) {
104
+ const notebook = this.library.incrementUseCount(active.id);
105
+ if (!notebook) {
106
+ throw new Error(`Active notebook not found: ${active.id}`);
107
+ }
108
+ resolvedNotebookUrl = notebook.url;
109
+ log.info(` Using active notebook: ${notebook.name}`);
110
+ }
111
+ }
112
+ // Progress: Getting or creating session
113
+ await sendProgress?.("Getting or creating browser session...", 1, 5);
114
+ // Apply browser options temporarily
115
+ const originalConfig = { ...CONFIG };
116
+ const effectiveConfig = applyBrowserOptions(browser_options, show_browser);
117
+ Object.assign(CONFIG, effectiveConfig);
118
+ // Calculate overrideHeadless parameter for session manager
119
+ // show_browser takes precedence over browser_options.headless
120
+ let overrideHeadless = undefined;
121
+ if (show_browser !== undefined) {
122
+ overrideHeadless = show_browser;
123
+ }
124
+ else if (browser_options?.show !== undefined) {
125
+ overrideHeadless = browser_options.show;
126
+ }
127
+ else if (browser_options?.headless !== undefined) {
128
+ overrideHeadless = !browser_options.headless;
129
+ }
130
+ try {
131
+ // Get or create session (with headless override to handle mode changes)
132
+ const session = await this.sessionManager.getOrCreateSession(safeSessionId, resolvedNotebookUrl, overrideHeadless);
133
+ // Progress: Asking question
134
+ await sendProgress?.("Asking question to NotebookLM...", 2, 5);
135
+ // Ask the question (pass progress callback) - using validated question
136
+ const rawAnswer = await session.ask(safeQuestion, sendProgress);
137
+ // === SECURITY: Validate response for prompt injection & malicious content ===
138
+ await sendProgress?.("Validating response security...", 4, 5);
139
+ const validationResult = await validateResponse(rawAnswer);
140
+ // Use sanitized response if issues were found
141
+ let finalAnswer;
142
+ let securityWarnings = [];
143
+ if (!validationResult.safe) {
144
+ log.warning(`๐Ÿ›ก๏ธ Response contained blocked content, using sanitized version`);
145
+ finalAnswer = validationResult.sanitized;
146
+ securityWarnings = validationResult.blocked;
147
+ }
148
+ else if (validationResult.warnings.length > 0) {
149
+ log.info(`โš ๏ธ Response had ${validationResult.warnings.length} warnings`);
150
+ finalAnswer = rawAnswer;
151
+ securityWarnings = validationResult.warnings;
152
+ }
153
+ else {
154
+ finalAnswer = rawAnswer;
155
+ }
156
+ const answer = `${finalAnswer.trimEnd()}${FOLLOW_UP_REMINDER}`;
157
+ // Get session info
158
+ const sessionInfo = session.getInfo();
159
+ const result = {
160
+ status: "success",
161
+ question: safeQuestion,
162
+ answer,
163
+ session_id: session.sessionId,
164
+ notebook_url: session.notebookUrl,
165
+ session_info: {
166
+ age_seconds: sessionInfo.age_seconds,
167
+ message_count: sessionInfo.message_count,
168
+ last_activity: sessionInfo.last_activity,
169
+ },
170
+ // Include security warnings if any
171
+ ...(securityWarnings.length > 0 && { security_warnings: securityWarnings }),
172
+ };
173
+ // Progress: Complete
174
+ await sendProgress?.("Question answered successfully!", 5, 5);
175
+ log.success(`โœ… [TOOL] ask_question completed successfully`);
176
+ // Audit: successful tool call
177
+ await audit.tool("ask_question", {
178
+ question_length: safeQuestion.length,
179
+ session_id: safeSessionId,
180
+ notebook_id: safeNotebookId,
181
+ }, true, Date.now() - startTime);
182
+ return {
183
+ success: true,
184
+ data: result,
185
+ };
186
+ }
187
+ finally {
188
+ // Restore original CONFIG
189
+ Object.assign(CONFIG, originalConfig);
190
+ }
191
+ }
192
+ catch (error) {
193
+ const errorMessage = error instanceof Error ? error.message : String(error);
194
+ // Special handling for rate limit errors
195
+ if (error instanceof RateLimitError || errorMessage.toLowerCase().includes("rate limit")) {
196
+ log.error(`๐Ÿšซ [TOOL] Rate limit detected`);
197
+ await audit.security("notebooklm_rate_limit", "warning", {
198
+ session_id: safeSessionId,
199
+ });
200
+ await audit.tool("ask_question", args, false, Date.now() - startTime, "NotebookLM rate limit");
201
+ return {
202
+ success: false,
203
+ error: "NotebookLM rate limit reached (50 queries/day for free accounts).\n\n" +
204
+ "You can:\n" +
205
+ "1. Use the 're_auth' tool to login with a different Google account\n" +
206
+ "2. Wait until tomorrow for the quota to reset\n" +
207
+ "3. Upgrade to Google AI Pro/Ultra for 5x higher limits\n\n" +
208
+ `Original error: ${errorMessage}`,
209
+ };
210
+ }
211
+ log.error(`โŒ [TOOL] ask_question failed: ${errorMessage}`);
212
+ await audit.tool("ask_question", args, false, Date.now() - startTime, errorMessage);
213
+ return {
214
+ success: false,
215
+ error: errorMessage,
216
+ };
217
+ }
218
+ }
219
+ /**
220
+ * Handle list_sessions tool
221
+ */
222
+ async handleListSessions() {
223
+ log.info(`๐Ÿ”ง [TOOL] list_sessions called`);
224
+ try {
225
+ const stats = this.sessionManager.getStats();
226
+ const sessions = this.sessionManager.getAllSessionsInfo();
227
+ const result = {
228
+ active_sessions: stats.active_sessions,
229
+ max_sessions: stats.max_sessions,
230
+ session_timeout: stats.session_timeout,
231
+ oldest_session_seconds: stats.oldest_session_seconds,
232
+ total_messages: stats.total_messages,
233
+ sessions: sessions.map((info) => ({
234
+ id: info.id,
235
+ created_at: info.created_at,
236
+ last_activity: info.last_activity,
237
+ age_seconds: info.age_seconds,
238
+ inactive_seconds: info.inactive_seconds,
239
+ message_count: info.message_count,
240
+ notebook_url: info.notebook_url,
241
+ })),
242
+ };
243
+ log.success(`โœ… [TOOL] list_sessions completed (${result.active_sessions} sessions)`);
244
+ return {
245
+ success: true,
246
+ data: result,
247
+ };
248
+ }
249
+ catch (error) {
250
+ const errorMessage = error instanceof Error ? error.message : String(error);
251
+ log.error(`โŒ [TOOL] list_sessions failed: ${errorMessage}`);
252
+ return {
253
+ success: false,
254
+ error: errorMessage,
255
+ };
256
+ }
257
+ }
258
+ /**
259
+ * Handle close_session tool
260
+ */
261
+ async handleCloseSession(args) {
262
+ const { session_id } = args;
263
+ log.info(`๐Ÿ”ง [TOOL] close_session called`);
264
+ log.info(` Session ID: ${session_id}`);
265
+ try {
266
+ const closed = await this.sessionManager.closeSession(session_id);
267
+ if (closed) {
268
+ log.success(`โœ… [TOOL] close_session completed`);
269
+ return {
270
+ success: true,
271
+ data: {
272
+ status: "success",
273
+ message: `Session ${session_id} closed successfully`,
274
+ session_id,
275
+ },
276
+ };
277
+ }
278
+ else {
279
+ log.warning(`โš ๏ธ [TOOL] Session ${session_id} not found`);
280
+ return {
281
+ success: false,
282
+ error: `Session ${session_id} not found`,
283
+ };
284
+ }
285
+ }
286
+ catch (error) {
287
+ const errorMessage = error instanceof Error ? error.message : String(error);
288
+ log.error(`โŒ [TOOL] close_session failed: ${errorMessage}`);
289
+ return {
290
+ success: false,
291
+ error: errorMessage,
292
+ };
293
+ }
294
+ }
295
+ /**
296
+ * Handle reset_session tool
297
+ */
298
+ async handleResetSession(args) {
299
+ const { session_id } = args;
300
+ log.info(`๐Ÿ”ง [TOOL] reset_session called`);
301
+ log.info(` Session ID: ${session_id}`);
302
+ try {
303
+ const session = this.sessionManager.getSession(session_id);
304
+ if (!session) {
305
+ log.warning(`โš ๏ธ [TOOL] Session ${session_id} not found`);
306
+ return {
307
+ success: false,
308
+ error: `Session ${session_id} not found`,
309
+ };
310
+ }
311
+ await session.reset();
312
+ log.success(`โœ… [TOOL] reset_session completed`);
313
+ return {
314
+ success: true,
315
+ data: {
316
+ status: "success",
317
+ message: `Session ${session_id} reset successfully`,
318
+ session_id,
319
+ },
320
+ };
321
+ }
322
+ catch (error) {
323
+ const errorMessage = error instanceof Error ? error.message : String(error);
324
+ log.error(`โŒ [TOOL] reset_session failed: ${errorMessage}`);
325
+ return {
326
+ success: false,
327
+ error: errorMessage,
328
+ };
329
+ }
330
+ }
331
+ /**
332
+ * Handle get_health tool
333
+ */
334
+ async handleGetHealth() {
335
+ log.info(`๐Ÿ”ง [TOOL] get_health called`);
336
+ try {
337
+ // Check authentication status
338
+ const statePath = await this.authManager.getValidStatePath();
339
+ const authenticated = statePath !== null;
340
+ // Get session stats
341
+ const stats = this.sessionManager.getStats();
342
+ const result = {
343
+ status: "ok",
344
+ authenticated,
345
+ notebook_url: CONFIG.notebookUrl || "not configured",
346
+ active_sessions: stats.active_sessions,
347
+ max_sessions: stats.max_sessions,
348
+ session_timeout: stats.session_timeout,
349
+ total_messages: stats.total_messages,
350
+ headless: CONFIG.headless,
351
+ auto_login_enabled: CONFIG.autoLoginEnabled,
352
+ stealth_enabled: CONFIG.stealthEnabled,
353
+ // Add troubleshooting tip if not authenticated
354
+ ...((!authenticated) && {
355
+ troubleshooting_tip: "For fresh start with clean browser session: Close all Chrome instances โ†’ " +
356
+ "cleanup_data(confirm=true, preserve_library=true) โ†’ setup_auth"
357
+ }),
358
+ };
359
+ log.success(`โœ… [TOOL] get_health completed`);
360
+ return {
361
+ success: true,
362
+ data: result,
363
+ };
364
+ }
365
+ catch (error) {
366
+ const errorMessage = error instanceof Error ? error.message : String(error);
367
+ log.error(`โŒ [TOOL] get_health failed: ${errorMessage}`);
368
+ return {
369
+ success: false,
370
+ error: errorMessage,
371
+ };
372
+ }
373
+ }
374
+ /**
375
+ * Handle setup_auth tool
376
+ *
377
+ * Opens a browser window for manual login with live progress updates.
378
+ * The operation waits synchronously for login completion (up to 10 minutes).
379
+ */
380
+ async handleSetupAuth(args, sendProgress) {
381
+ const { show_browser, browser_options } = args;
382
+ // CRITICAL: Send immediate progress to reset timeout from the very start
383
+ await sendProgress?.("Initializing authentication setup...", 0, 10);
384
+ log.info(`๐Ÿ”ง [TOOL] setup_auth called`);
385
+ if (show_browser !== undefined) {
386
+ log.info(` Show browser: ${show_browser}`);
387
+ }
388
+ const startTime = Date.now();
389
+ // Apply browser options temporarily
390
+ const originalConfig = { ...CONFIG };
391
+ const effectiveConfig = applyBrowserOptions(browser_options, show_browser);
392
+ Object.assign(CONFIG, effectiveConfig);
393
+ try {
394
+ // Progress: Starting
395
+ await sendProgress?.("Preparing authentication browser...", 1, 10);
396
+ log.info(` ๐ŸŒ Opening browser for interactive login...`);
397
+ // Progress: Opening browser
398
+ await sendProgress?.("Opening browser window...", 2, 10);
399
+ // Perform setup with progress updates (uses CONFIG internally)
400
+ const success = await this.authManager.performSetup(sendProgress);
401
+ const durationSeconds = (Date.now() - startTime) / 1000;
402
+ if (success) {
403
+ // Progress: Complete
404
+ await sendProgress?.("Authentication saved successfully!", 10, 10);
405
+ log.success(`โœ… [TOOL] setup_auth completed (${durationSeconds.toFixed(1)}s)`);
406
+ // Audit: successful authentication
407
+ await audit.auth("setup_auth", true, { duration_seconds: durationSeconds });
408
+ await audit.tool("setup_auth", {}, true, Date.now() - startTime);
409
+ return {
410
+ success: true,
411
+ data: {
412
+ status: "authenticated",
413
+ message: "Successfully authenticated and saved browser state",
414
+ authenticated: true,
415
+ duration_seconds: durationSeconds,
416
+ },
417
+ };
418
+ }
419
+ else {
420
+ log.error(`โŒ [TOOL] setup_auth failed (${durationSeconds.toFixed(1)}s)`);
421
+ // Audit: failed authentication
422
+ await audit.auth("setup_auth", false, { reason: "cancelled_or_failed" });
423
+ await audit.tool("setup_auth", {}, false, Date.now() - startTime, "Authentication failed or was cancelled");
424
+ return {
425
+ success: false,
426
+ error: "Authentication failed or was cancelled",
427
+ };
428
+ }
429
+ }
430
+ catch (error) {
431
+ const errorMessage = error instanceof Error ? error.message : String(error);
432
+ const durationSeconds = (Date.now() - startTime) / 1000;
433
+ log.error(`โŒ [TOOL] setup_auth failed: ${errorMessage} (${durationSeconds.toFixed(1)}s)`);
434
+ // Audit: auth error
435
+ await audit.auth("setup_auth", false, { error: errorMessage });
436
+ await audit.tool("setup_auth", {}, false, Date.now() - startTime, errorMessage);
437
+ return {
438
+ success: false,
439
+ error: errorMessage,
440
+ };
441
+ }
442
+ finally {
443
+ // Restore original CONFIG
444
+ Object.assign(CONFIG, originalConfig);
445
+ }
446
+ }
447
+ /**
448
+ * Handle re_auth tool
449
+ *
450
+ * Performs a complete re-authentication:
451
+ * 1. Closes all active browser sessions
452
+ * 2. Deletes all saved authentication data (cookies, Chrome profile)
453
+ * 3. Opens browser for fresh Google login
454
+ *
455
+ * Use for switching Google accounts or recovering from rate limits.
456
+ */
457
+ async handleReAuth(args, sendProgress) {
458
+ const { show_browser, browser_options } = args;
459
+ await sendProgress?.("Preparing re-authentication...", 0, 12);
460
+ log.info(`๐Ÿ”ง [TOOL] re_auth called`);
461
+ if (show_browser !== undefined) {
462
+ log.info(` Show browser: ${show_browser}`);
463
+ }
464
+ const startTime = Date.now();
465
+ // Apply browser options temporarily
466
+ const originalConfig = { ...CONFIG };
467
+ const effectiveConfig = applyBrowserOptions(browser_options, show_browser);
468
+ Object.assign(CONFIG, effectiveConfig);
469
+ try {
470
+ // 1. Close all active sessions
471
+ await sendProgress?.("Closing all active sessions...", 1, 12);
472
+ log.info(" ๐Ÿ›‘ Closing all sessions...");
473
+ await this.sessionManager.closeAllSessions();
474
+ log.success(" โœ… All sessions closed");
475
+ // 2. Clear all auth data
476
+ await sendProgress?.("Clearing authentication data...", 2, 12);
477
+ log.info(" ๐Ÿ—‘๏ธ Clearing all auth data...");
478
+ await this.authManager.clearAllAuthData();
479
+ log.success(" โœ… Auth data cleared");
480
+ // 3. Perform fresh setup
481
+ await sendProgress?.("Starting fresh authentication...", 3, 12);
482
+ log.info(" ๐ŸŒ Starting fresh authentication setup...");
483
+ const success = await this.authManager.performSetup(sendProgress);
484
+ const durationSeconds = (Date.now() - startTime) / 1000;
485
+ if (success) {
486
+ await sendProgress?.("Re-authentication complete!", 12, 12);
487
+ log.success(`โœ… [TOOL] re_auth completed (${durationSeconds.toFixed(1)}s)`);
488
+ // Audit: successful re-auth
489
+ await audit.auth("re_auth", true, { duration_seconds: durationSeconds });
490
+ await audit.tool("re_auth", {}, true, Date.now() - startTime);
491
+ return {
492
+ success: true,
493
+ data: {
494
+ status: "authenticated",
495
+ message: "Successfully re-authenticated with new account. All previous sessions have been closed.",
496
+ authenticated: true,
497
+ duration_seconds: durationSeconds,
498
+ },
499
+ };
500
+ }
501
+ else {
502
+ log.error(`โŒ [TOOL] re_auth failed (${durationSeconds.toFixed(1)}s)`);
503
+ // Audit: failed re-auth
504
+ await audit.auth("re_auth", false, { reason: "cancelled_or_failed" });
505
+ await audit.tool("re_auth", {}, false, Date.now() - startTime, "Re-authentication failed or was cancelled");
506
+ return {
507
+ success: false,
508
+ error: "Re-authentication failed or was cancelled",
509
+ };
510
+ }
511
+ }
512
+ catch (error) {
513
+ const errorMessage = error instanceof Error ? error.message : String(error);
514
+ const durationSeconds = (Date.now() - startTime) / 1000;
515
+ log.error(`โŒ [TOOL] re_auth failed: ${errorMessage} (${durationSeconds.toFixed(1)}s)`);
516
+ // Audit: re-auth error
517
+ await audit.auth("re_auth", false, { error: errorMessage });
518
+ await audit.tool("re_auth", {}, false, Date.now() - startTime, errorMessage);
519
+ return {
520
+ success: false,
521
+ error: errorMessage,
522
+ };
523
+ }
524
+ finally {
525
+ // Restore original CONFIG
526
+ Object.assign(CONFIG, originalConfig);
527
+ }
528
+ }
529
+ /**
530
+ * Handle add_notebook tool
531
+ */
532
+ async handleAddNotebook(args) {
533
+ log.info(`๐Ÿ”ง [TOOL] add_notebook called`);
534
+ log.info(` Name: ${args.name}`);
535
+ try {
536
+ const notebook = this.library.addNotebook(args);
537
+ log.success(`โœ… [TOOL] add_notebook completed: ${notebook.id}`);
538
+ return {
539
+ success: true,
540
+ data: { notebook },
541
+ };
542
+ }
543
+ catch (error) {
544
+ const errorMessage = error instanceof Error ? error.message : String(error);
545
+ log.error(`โŒ [TOOL] add_notebook failed: ${errorMessage}`);
546
+ return {
547
+ success: false,
548
+ error: errorMessage,
549
+ };
550
+ }
551
+ }
552
+ /**
553
+ * Handle list_notebooks tool
554
+ */
555
+ async handleListNotebooks() {
556
+ log.info(`๐Ÿ”ง [TOOL] list_notebooks called`);
557
+ try {
558
+ const notebooks = this.library.listNotebooks();
559
+ log.success(`โœ… [TOOL] list_notebooks completed (${notebooks.length} notebooks)`);
560
+ return {
561
+ success: true,
562
+ data: { notebooks },
563
+ };
564
+ }
565
+ catch (error) {
566
+ const errorMessage = error instanceof Error ? error.message : String(error);
567
+ log.error(`โŒ [TOOL] list_notebooks failed: ${errorMessage}`);
568
+ return {
569
+ success: false,
570
+ error: errorMessage,
571
+ };
572
+ }
573
+ }
574
+ /**
575
+ * Handle get_notebook tool
576
+ */
577
+ async handleGetNotebook(args) {
578
+ log.info(`๐Ÿ”ง [TOOL] get_notebook called`);
579
+ log.info(` ID: ${args.id}`);
580
+ try {
581
+ const notebook = this.library.getNotebook(args.id);
582
+ if (!notebook) {
583
+ log.warning(`โš ๏ธ [TOOL] Notebook not found: ${args.id}`);
584
+ return {
585
+ success: false,
586
+ error: `Notebook not found: ${args.id}`,
587
+ };
588
+ }
589
+ log.success(`โœ… [TOOL] get_notebook completed: ${notebook.name}`);
590
+ return {
591
+ success: true,
592
+ data: { notebook },
593
+ };
594
+ }
595
+ catch (error) {
596
+ const errorMessage = error instanceof Error ? error.message : String(error);
597
+ log.error(`โŒ [TOOL] get_notebook failed: ${errorMessage}`);
598
+ return {
599
+ success: false,
600
+ error: errorMessage,
601
+ };
602
+ }
603
+ }
604
+ /**
605
+ * Handle select_notebook tool
606
+ */
607
+ async handleSelectNotebook(args) {
608
+ log.info(`๐Ÿ”ง [TOOL] select_notebook called`);
609
+ log.info(` ID: ${args.id}`);
610
+ try {
611
+ const notebook = this.library.selectNotebook(args.id);
612
+ log.success(`โœ… [TOOL] select_notebook completed: ${notebook.name}`);
613
+ return {
614
+ success: true,
615
+ data: { notebook },
616
+ };
617
+ }
618
+ catch (error) {
619
+ const errorMessage = error instanceof Error ? error.message : String(error);
620
+ log.error(`โŒ [TOOL] select_notebook failed: ${errorMessage}`);
621
+ return {
622
+ success: false,
623
+ error: errorMessage,
624
+ };
625
+ }
626
+ }
627
+ /**
628
+ * Handle update_notebook tool
629
+ */
630
+ async handleUpdateNotebook(args) {
631
+ log.info(`๐Ÿ”ง [TOOL] update_notebook called`);
632
+ log.info(` ID: ${args.id}`);
633
+ try {
634
+ const notebook = this.library.updateNotebook(args);
635
+ log.success(`โœ… [TOOL] update_notebook completed: ${notebook.name}`);
636
+ return {
637
+ success: true,
638
+ data: { notebook },
639
+ };
640
+ }
641
+ catch (error) {
642
+ const errorMessage = error instanceof Error ? error.message : String(error);
643
+ log.error(`โŒ [TOOL] update_notebook failed: ${errorMessage}`);
644
+ return {
645
+ success: false,
646
+ error: errorMessage,
647
+ };
648
+ }
649
+ }
650
+ /**
651
+ * Handle remove_notebook tool
652
+ */
653
+ async handleRemoveNotebook(args) {
654
+ log.info(`๐Ÿ”ง [TOOL] remove_notebook called`);
655
+ log.info(` ID: ${args.id}`);
656
+ try {
657
+ const notebook = this.library.getNotebook(args.id);
658
+ if (!notebook) {
659
+ log.warning(`โš ๏ธ [TOOL] Notebook not found: ${args.id}`);
660
+ return {
661
+ success: false,
662
+ error: `Notebook not found: ${args.id}`,
663
+ };
664
+ }
665
+ const removed = this.library.removeNotebook(args.id);
666
+ if (removed) {
667
+ const closedSessions = await this.sessionManager.closeSessionsForNotebook(notebook.url);
668
+ log.success(`โœ… [TOOL] remove_notebook completed`);
669
+ return {
670
+ success: true,
671
+ data: { removed: true, closed_sessions: closedSessions },
672
+ };
673
+ }
674
+ else {
675
+ log.warning(`โš ๏ธ [TOOL] Notebook not found: ${args.id}`);
676
+ return {
677
+ success: false,
678
+ error: `Notebook not found: ${args.id}`,
679
+ };
680
+ }
681
+ }
682
+ catch (error) {
683
+ const errorMessage = error instanceof Error ? error.message : String(error);
684
+ log.error(`โŒ [TOOL] remove_notebook failed: ${errorMessage}`);
685
+ return {
686
+ success: false,
687
+ error: errorMessage,
688
+ };
689
+ }
690
+ }
691
+ /**
692
+ * Handle search_notebooks tool
693
+ */
694
+ async handleSearchNotebooks(args) {
695
+ log.info(`๐Ÿ”ง [TOOL] search_notebooks called`);
696
+ log.info(` Query: "${args.query}"`);
697
+ try {
698
+ const notebooks = this.library.searchNotebooks(args.query);
699
+ log.success(`โœ… [TOOL] search_notebooks completed (${notebooks.length} results)`);
700
+ return {
701
+ success: true,
702
+ data: { notebooks },
703
+ };
704
+ }
705
+ catch (error) {
706
+ const errorMessage = error instanceof Error ? error.message : String(error);
707
+ log.error(`โŒ [TOOL] search_notebooks failed: ${errorMessage}`);
708
+ return {
709
+ success: false,
710
+ error: errorMessage,
711
+ };
712
+ }
713
+ }
714
+ /**
715
+ * Handle get_library_stats tool
716
+ */
717
+ async handleGetLibraryStats() {
718
+ log.info(`๐Ÿ”ง [TOOL] get_library_stats called`);
719
+ try {
720
+ const stats = this.library.getStats();
721
+ log.success(`โœ… [TOOL] get_library_stats completed`);
722
+ return {
723
+ success: true,
724
+ data: stats,
725
+ };
726
+ }
727
+ catch (error) {
728
+ const errorMessage = error instanceof Error ? error.message : String(error);
729
+ log.error(`โŒ [TOOL] get_library_stats failed: ${errorMessage}`);
730
+ return {
731
+ success: false,
732
+ error: errorMessage,
733
+ };
734
+ }
735
+ }
736
+ /**
737
+ * Handle cleanup_data tool
738
+ *
739
+ * ULTRATHINK Deep Cleanup - scans entire system for ALL NotebookLM MCP files
740
+ */
741
+ async handleCleanupData(args) {
742
+ const { confirm, preserve_library = false } = args;
743
+ log.info(`๐Ÿ”ง [TOOL] cleanup_data called`);
744
+ log.info(` Confirm: ${confirm}`);
745
+ log.info(` Preserve Library: ${preserve_library}`);
746
+ const cleanupManager = new CleanupManager();
747
+ try {
748
+ // Always run in deep mode
749
+ const mode = "deep";
750
+ if (!confirm) {
751
+ // Preview mode - show what would be deleted
752
+ log.info(` ๐Ÿ“‹ Generating cleanup preview (mode: ${mode})...`);
753
+ const preview = await cleanupManager.getCleanupPaths(mode, preserve_library);
754
+ const platformInfo = cleanupManager.getPlatformInfo();
755
+ log.info(` Found ${preview.totalPaths.length} items (${cleanupManager.formatBytes(preview.totalSizeBytes)})`);
756
+ log.info(` Platform: ${platformInfo.platform}`);
757
+ return {
758
+ success: true,
759
+ data: {
760
+ status: "preview",
761
+ mode,
762
+ preview: {
763
+ categories: preview.categories,
764
+ totalPaths: preview.totalPaths.length,
765
+ totalSizeBytes: preview.totalSizeBytes,
766
+ },
767
+ },
768
+ };
769
+ }
770
+ else {
771
+ // Cleanup mode - actually delete files
772
+ log.info(` ๐Ÿ—‘๏ธ Performing cleanup (mode: ${mode})...`);
773
+ const result = await cleanupManager.performCleanup(mode, preserve_library);
774
+ if (result.success) {
775
+ log.success(`โœ… [TOOL] cleanup_data completed - deleted ${result.deletedPaths.length} items`);
776
+ }
777
+ else {
778
+ log.warning(`โš ๏ธ [TOOL] cleanup_data completed with ${result.failedPaths.length} errors`);
779
+ }
780
+ return {
781
+ success: result.success,
782
+ data: {
783
+ status: result.success ? "completed" : "partial",
784
+ mode,
785
+ result: {
786
+ deletedPaths: result.deletedPaths,
787
+ failedPaths: result.failedPaths,
788
+ totalSizeBytes: result.totalSizeBytes,
789
+ categorySummary: result.categorySummary,
790
+ },
791
+ },
792
+ };
793
+ }
794
+ }
795
+ catch (error) {
796
+ const errorMessage = error instanceof Error ? error.message : String(error);
797
+ log.error(`โŒ [TOOL] cleanup_data failed: ${errorMessage}`);
798
+ return {
799
+ success: false,
800
+ error: errorMessage,
801
+ };
802
+ }
803
+ }
804
+ /**
805
+ * Cleanup all resources (called on server shutdown)
806
+ */
807
+ async cleanup() {
808
+ log.info(`๐Ÿงน Cleaning up tool handlers...`);
809
+ await this.sessionManager.closeAllSessions();
810
+ log.success(`โœ… Tool handlers cleanup complete`);
811
+ }
812
+ }
813
+ //# sourceMappingURL=handlers.js.map