@roomi-fields/notebooklm-mcp 1.3.6 → 1.5.1

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 (284) hide show
  1. package/LICENSE +22 -22
  2. package/README.md +71 -34
  3. package/deployment/INDEX.md +292 -0
  4. package/deployment/PACKAGE-FILES.txt +180 -0
  5. package/deployment/QUICK-START.md +100 -0
  6. package/deployment/docs/01-INSTALL.md +611 -0
  7. package/deployment/docs/02-CONFIGURATION.md +404 -0
  8. package/deployment/docs/03-API.md +1691 -0
  9. package/deployment/docs/04-N8N-INTEGRATION.md +373 -0
  10. package/deployment/docs/05-TROUBLESHOOTING.md +429 -0
  11. package/deployment/docs/06-NOTEBOOK-LIBRARY.md +692 -0
  12. package/deployment/docs/07-AUTO-DISCOVERY.md +236 -0
  13. package/deployment/docs/08-WSL-USAGE.md +363 -0
  14. package/deployment/docs/09-MULTI-INTERFACE.md +293 -0
  15. package/deployment/docs/10-CONTENT-MANAGEMENT.md +421 -0
  16. package/deployment/docs/11-MULTI-ACCOUNT.md +295 -0
  17. package/deployment/docs/README.md +207 -0
  18. package/deployment/scripts/README.md +564 -0
  19. package/deployment/scripts/install.ps1 +114 -0
  20. package/deployment/scripts/setup-auth.ps1 +217 -0
  21. package/deployment/scripts/start-server.ps1 +72 -0
  22. package/deployment/scripts/stop-server.ps1 +51 -0
  23. package/deployment/scripts/test-api.ps1 +651 -0
  24. package/deployment/scripts/test-auth.ps1 +323 -0
  25. package/deployment/scripts/test-auto-discovery.ps1 +295 -0
  26. package/deployment/scripts/test-cors.ps1 +398 -0
  27. package/deployment/scripts/test-errors.ps1 +581 -0
  28. package/deployment/scripts/test-server.ps1 +140 -0
  29. package/deployment/scripts/test-sessions.ps1 +426 -0
  30. package/deployment/scripts/test-validation.ps1 +299 -0
  31. package/dist/accounts/account-manager.d.ts +163 -0
  32. package/dist/accounts/account-manager.d.ts.map +1 -0
  33. package/dist/accounts/account-manager.js +614 -0
  34. package/dist/accounts/account-manager.js.map +1 -0
  35. package/dist/accounts/auto-login-manager.d.ts +62 -0
  36. package/dist/accounts/auto-login-manager.d.ts.map +1 -0
  37. package/dist/accounts/auto-login-manager.js +537 -0
  38. package/dist/accounts/auto-login-manager.js.map +1 -0
  39. package/dist/accounts/crypto.d.ts +45 -0
  40. package/dist/accounts/crypto.d.ts.map +1 -0
  41. package/dist/accounts/crypto.js +138 -0
  42. package/dist/accounts/crypto.js.map +1 -0
  43. package/dist/accounts/index.d.ts +14 -0
  44. package/dist/accounts/index.d.ts.map +1 -0
  45. package/dist/accounts/index.js +14 -0
  46. package/dist/accounts/index.js.map +1 -0
  47. package/dist/accounts/types.d.ts +103 -0
  48. package/dist/accounts/types.d.ts.map +1 -0
  49. package/dist/accounts/types.js +7 -0
  50. package/dist/accounts/types.js.map +1 -0
  51. package/dist/auth/auth-manager.d.ts +9 -2
  52. package/dist/auth/auth-manager.d.ts.map +1 -1
  53. package/dist/auth/auth-manager.js +60 -6
  54. package/dist/auth/auth-manager.js.map +1 -1
  55. package/dist/auto-discovery/auto-discovery.d.ts.map +1 -1
  56. package/dist/auto-discovery/auto-discovery.js +2 -1
  57. package/dist/auto-discovery/auto-discovery.js.map +1 -1
  58. package/dist/cli/accounts.d.ts +13 -0
  59. package/dist/cli/accounts.d.ts.map +1 -0
  60. package/dist/cli/accounts.js +195 -0
  61. package/dist/cli/accounts.js.map +1 -0
  62. package/dist/config.d.ts +1 -0
  63. package/dist/config.d.ts.map +1 -1
  64. package/dist/config.js +24 -0
  65. package/dist/config.js.map +1 -1
  66. package/dist/content/content-generator.d.ts +153 -0
  67. package/dist/content/content-generator.d.ts.map +1 -0
  68. package/dist/content/content-generator.js +637 -0
  69. package/dist/content/content-generator.js.map +1 -0
  70. package/dist/content/content-manager.d.ts +364 -0
  71. package/dist/content/content-manager.d.ts.map +1 -0
  72. package/dist/content/content-manager.js +3841 -0
  73. package/dist/content/content-manager.js.map +1 -0
  74. package/dist/content/content-templates.d.ts +183 -0
  75. package/dist/content/content-templates.d.ts.map +1 -0
  76. package/dist/content/content-templates.js +719 -0
  77. package/dist/content/content-templates.js.map +1 -0
  78. package/dist/content/index.d.ts +14 -0
  79. package/dist/content/index.d.ts.map +1 -0
  80. package/dist/content/index.js +14 -0
  81. package/dist/content/index.js.map +1 -0
  82. package/dist/content/types.d.ts +285 -0
  83. package/dist/content/types.d.ts.map +1 -0
  84. package/dist/content/types.js +10 -0
  85. package/dist/content/types.js.map +1 -0
  86. package/dist/errors.d.ts +1 -1
  87. package/dist/errors.d.ts.map +1 -1
  88. package/dist/errors.js.map +1 -1
  89. package/dist/http-wrapper.d.ts +7 -0
  90. package/dist/http-wrapper.d.ts.map +1 -1
  91. package/dist/http-wrapper.js +449 -29
  92. package/dist/http-wrapper.js.map +1 -1
  93. package/dist/i18n/en.json +120 -0
  94. package/dist/i18n/fr.json +120 -0
  95. package/dist/i18n/index.d.ts +168 -0
  96. package/dist/i18n/index.d.ts.map +1 -0
  97. package/dist/i18n/index.js +213 -0
  98. package/dist/i18n/index.js.map +1 -0
  99. package/dist/index.js +26 -2
  100. package/dist/index.js.map +1 -1
  101. package/dist/library/notebook-library.d.ts +4 -0
  102. package/dist/library/notebook-library.d.ts.map +1 -1
  103. package/dist/library/notebook-library.js +20 -3
  104. package/dist/library/notebook-library.js.map +1 -1
  105. package/dist/session/browser-session.d.ts +35 -8
  106. package/dist/session/browser-session.d.ts.map +1 -1
  107. package/dist/session/browser-session.js +243 -28
  108. package/dist/session/browser-session.js.map +1 -1
  109. package/dist/session/session-manager.d.ts +6 -0
  110. package/dist/session/session-manager.d.ts.map +1 -1
  111. package/dist/session/session-manager.js +46 -14
  112. package/dist/session/session-manager.js.map +1 -1
  113. package/dist/session/shared-context-manager.d.ts +3 -3
  114. package/dist/session/shared-context-manager.d.ts.map +1 -1
  115. package/dist/session/shared-context-manager.js +10 -7
  116. package/dist/session/shared-context-manager.js.map +1 -1
  117. package/dist/stdio-http-proxy.d.ts +24 -0
  118. package/dist/stdio-http-proxy.d.ts.map +1 -0
  119. package/dist/stdio-http-proxy.js +592 -0
  120. package/dist/stdio-http-proxy.js.map +1 -0
  121. package/dist/tools/index.d.ts +106 -1
  122. package/dist/tools/index.d.ts.map +1 -1
  123. package/dist/tools/index.js +1028 -7
  124. package/dist/tools/index.js.map +1 -1
  125. package/dist/types.d.ts +81 -17
  126. package/dist/types.d.ts.map +1 -1
  127. package/dist/utils/citation-extractor.d.ts +66 -0
  128. package/dist/utils/citation-extractor.d.ts.map +1 -0
  129. package/dist/utils/citation-extractor.js +492 -0
  130. package/dist/utils/citation-extractor.js.map +1 -0
  131. package/dist/utils/page-utils.d.ts +8 -0
  132. package/dist/utils/page-utils.d.ts.map +1 -1
  133. package/dist/utils/page-utils.js +112 -8
  134. package/dist/utils/page-utils.js.map +1 -1
  135. package/docs/ADDING_A_LANGUAGE.md +209 -0
  136. package/docs/ARCHITECTURE_MIGRATION_STUDY.md +894 -0
  137. package/docs/CHROME_PROFILE_LIMITATION.md +15 -1
  138. package/docs/MULTI_ACCOUNT_SYSTEM.md +304 -0
  139. package/package.json +15 -12
  140. package/scripts/archive/add-and-activate-notebook.ps1 +31 -0
  141. package/scripts/archive/add-new-notebook.ps1 +25 -0
  142. package/scripts/archive/add-rom1pey.ps1 +2 -0
  143. package/scripts/archive/add-rpmonster.ps1 +2 -0
  144. package/scripts/archive/add-source-debug.ps1 +11 -0
  145. package/scripts/archive/add-source-e2e.ps1 +28 -0
  146. package/scripts/archive/add-source-visible.ps1 +11 -0
  147. package/scripts/archive/add-test-notebook.ps1 +13 -0
  148. package/scripts/archive/add-test-source.ps1 +50 -0
  149. package/scripts/archive/capture-screen.ps1 +11 -0
  150. package/scripts/archive/change-language.mjs +45 -0
  151. package/scripts/archive/change-language.ts +44 -0
  152. package/scripts/archive/check-account.ps1 +19 -0
  153. package/scripts/archive/check-notebook-2.ps1 +8 -0
  154. package/scripts/archive/check-test-notebook.ps1 +11 -0
  155. package/scripts/archive/create-notebook-auto.ps1 +31 -0
  156. package/scripts/archive/create-notebook.ps1 +8 -0
  157. package/scripts/archive/create-rom1pey-notebook.ps1 +19 -0
  158. package/scripts/archive/create-rom1pey.ps1 +8 -0
  159. package/scripts/archive/create-test-notebook-fresh.ps1 +21 -0
  160. package/scripts/archive/create-test-notebook.ps1 +16 -0
  161. package/scripts/archive/debug-add-source-auto.ps1 +29 -0
  162. package/scripts/archive/debug-add-source.ps1 +19 -0
  163. package/scripts/archive/debug-add-text-source.ps1 +47 -0
  164. package/scripts/archive/debug-home.ps1 +10 -0
  165. package/scripts/archive/debug-selectors.ps1 +55 -0
  166. package/scripts/archive/debug-sources-panel.ps1 +22 -0
  167. package/scripts/archive/debug-ui.ps1 +17 -0
  168. package/scripts/archive/discover-home.ps1 +26 -0
  169. package/scripts/archive/kill-automation-chrome.ps1 +37 -0
  170. package/scripts/archive/list-my-notebooks.ps1 +27 -0
  171. package/scripts/archive/navigate-home-visible.ps1 +23 -0
  172. package/scripts/archive/navigate-home.ps1 +15 -0
  173. package/scripts/archive/run-e2e-english.ps1 +111 -0
  174. package/scripts/archive/run-e2e-rom1pey-v2.ps1 +122 -0
  175. package/scripts/archive/run-e2e-rom1pey.ps1 +117 -0
  176. package/scripts/archive/setup-english-test.ps1 +36 -0
  177. package/scripts/archive/setup-test-notebook.ps1 +71 -0
  178. package/scripts/archive/simple-add-source.ps1 +14 -0
  179. package/scripts/archive/t10.ps1 +2 -0
  180. package/scripts/archive/t20.ps1 +4 -0
  181. package/scripts/archive/t30.ps1 +9 -0
  182. package/scripts/archive/t31.ps1 +11 -0
  183. package/scripts/archive/t32.ps1 +6 -0
  184. package/scripts/archive/t39.ps1 +5 -0
  185. package/scripts/archive/t40.ps1 +5 -0
  186. package/scripts/archive/t53.ps1 +12 -0
  187. package/scripts/archive/t54.ps1 +12 -0
  188. package/scripts/archive/t55.ps1 +11 -0
  189. package/scripts/archive/t9.ps1 +1 -0
  190. package/scripts/archive/test-access.ps1 +28 -0
  191. package/scripts/archive/test-add-delete-source.ps1 +64 -0
  192. package/scripts/archive/test-add-source-visible.ps1 +16 -0
  193. package/scripts/archive/test-add-source.ps1 +19 -0
  194. package/scripts/archive/test-add-text-debug.ps1 +28 -0
  195. package/scripts/archive/test-add-text-source.ps1 +8 -0
  196. package/scripts/archive/test-add-url-source.ps1 +7 -0
  197. package/scripts/archive/test-ask-ascii.ps1 +20 -0
  198. package/scripts/archive/test-ask-cnv.ps1 +20 -0
  199. package/scripts/archive/test-ask-headed.ps1 +51 -0
  200. package/scripts/archive/test-ask-ifs.ps1 +16 -0
  201. package/scripts/archive/test-ask-now.ps1 +24 -0
  202. package/scripts/archive/test-ask-real.ps1 +19 -0
  203. package/scripts/archive/test-ask-visible.ps1 +20 -0
  204. package/scripts/archive/test-create-notebook.ps1 +8 -0
  205. package/scripts/archive/test-create-then-add.ps1 +17 -0
  206. package/scripts/archive/test-delete-source.ps1 +41 -0
  207. package/scripts/archive/test-e2e-notebook.ps1 +21 -0
  208. package/scripts/archive/test-english-notebook.ps1 +20 -0
  209. package/scripts/archive/test-english.ps1 +7 -0
  210. package/scripts/archive/test-full-custom-instructions.ps1 +40 -0
  211. package/scripts/archive/test-full-infographic.ps1 +34 -0
  212. package/scripts/archive/test-full-language.ps1 +21 -0
  213. package/scripts/archive/test-full-presentation.ps1 +85 -0
  214. package/scripts/archive/test-full-report.ps1 +34 -0
  215. package/scripts/archive/test-full-source-selection.ps1 +35 -0
  216. package/scripts/archive/test-full-video-brief.ps1 +22 -0
  217. package/scripts/archive/test-full-video-explainer.ps1 +22 -0
  218. package/scripts/archive/test-full-video-styles.ps1 +37 -0
  219. package/scripts/archive/test-generate-report.ps1 +15 -0
  220. package/scripts/archive/test-generate-study-guide.ps1 +11 -0
  221. package/scripts/archive/test-headed-ask.ps1 +13 -0
  222. package/scripts/archive/test-headed-now.ps1 +9 -0
  223. package/scripts/archive/test-headed.ps1 +9 -0
  224. package/scripts/archive/test-hello.ps1 +7 -0
  225. package/scripts/archive/test-i18n-studio.ps1 +8 -0
  226. package/scripts/archive/test-i18n.ps1 +7 -0
  227. package/scripts/archive/test-manual-headed.ps1 +26 -0
  228. package/scripts/archive/test-mathieu-quota.ps1 +8 -0
  229. package/scripts/archive/test-notebook-1.ps1 +10 -0
  230. package/scripts/archive/test-notebook-2-sources.ps1 +12 -0
  231. package/scripts/archive/test-notebook1.ps1 +14 -0
  232. package/scripts/archive/test-personal-notebook.ps1 +14 -0
  233. package/scripts/archive/test-rate-limit.ps1 +19 -0
  234. package/scripts/archive/test-real-ask.ps1 +50 -0
  235. package/scripts/archive/test-real-ask2.ps1 +30 -0
  236. package/scripts/archive/test-rom1pey.ps1 +7 -0
  237. package/scripts/archive/test-rotation-complete.ps1 +14 -0
  238. package/scripts/archive/test-rotation.ps1 +8 -0
  239. package/scripts/archive/test-show-browser.ps1 +39 -0
  240. package/scripts/archive/test-update-notebook.ps1 +4 -0
  241. package/scripts/archive/verify-language-slow.ps1 +21 -0
  242. package/scripts/archive/verify-language.ps1 +15 -0
  243. package/scripts/check-server.ps1 +46 -0
  244. package/scripts/mcp-wsl-helper.sh +146 -0
  245. package/scripts/start-server.ps1 +94 -0
  246. package/scripts/stop-server.ps1 +30 -0
  247. package/scripts/switch-account-language.sh +191 -0
  248. package/scripts/test-account.ps1 +58 -0
  249. package/dist/__tests__/cleanup-manager.test.d.ts +0 -2
  250. package/dist/__tests__/cleanup-manager.test.d.ts.map +0 -1
  251. package/dist/__tests__/cleanup-manager.test.js +0 -341
  252. package/dist/__tests__/cleanup-manager.test.js.map +0 -1
  253. package/dist/__tests__/config-parsing.test.d.ts +0 -2
  254. package/dist/__tests__/config-parsing.test.d.ts.map +0 -1
  255. package/dist/__tests__/config-parsing.test.js +0 -338
  256. package/dist/__tests__/config-parsing.test.js.map +0 -1
  257. package/dist/__tests__/config.test.d.ts +0 -2
  258. package/dist/__tests__/config.test.d.ts.map +0 -1
  259. package/dist/__tests__/config.test.js +0 -267
  260. package/dist/__tests__/config.test.js.map +0 -1
  261. package/dist/__tests__/errors.test.d.ts +0 -2
  262. package/dist/__tests__/errors.test.d.ts.map +0 -1
  263. package/dist/__tests__/errors.test.js +0 -166
  264. package/dist/__tests__/errors.test.js.map +0 -1
  265. package/dist/__tests__/logger.test.d.ts +0 -2
  266. package/dist/__tests__/logger.test.d.ts.map +0 -1
  267. package/dist/__tests__/logger.test.js +0 -324
  268. package/dist/__tests__/logger.test.js.map +0 -1
  269. package/dist/__tests__/page-utils.test.d.ts +0 -2
  270. package/dist/__tests__/page-utils.test.d.ts.map +0 -1
  271. package/dist/__tests__/page-utils.test.js +0 -349
  272. package/dist/__tests__/page-utils.test.js.map +0 -1
  273. package/dist/__tests__/setup-verification.test.d.ts +0 -2
  274. package/dist/__tests__/setup-verification.test.d.ts.map +0 -1
  275. package/dist/__tests__/setup-verification.test.js +0 -15
  276. package/dist/__tests__/setup-verification.test.js.map +0 -1
  277. package/dist/__tests__/stealth-utils.test.d.ts +0 -2
  278. package/dist/__tests__/stealth-utils.test.d.ts.map +0 -1
  279. package/dist/__tests__/stealth-utils.test.js +0 -413
  280. package/dist/__tests__/stealth-utils.test.js.map +0 -1
  281. package/dist/__tests__/types.test.d.ts +0 -2
  282. package/dist/__tests__/types.test.d.ts.map +0 -1
  283. package/dist/__tests__/types.test.js +0 -461
  284. package/dist/__tests__/types.test.js.map +0 -1
@@ -5,6 +5,7 @@
5
5
  * Allows n8n and other tools to call the server without stdio
6
6
  */
7
7
  import express from 'express';
8
+ import { randomUUID } from 'crypto';
8
9
  import { AuthManager } from './auth/auth-manager.js';
9
10
  import { SessionManager } from './session/session-manager.js';
10
11
  import { NotebookLibrary } from './library/notebook-library.js';
@@ -12,7 +13,15 @@ import { ToolHandlers } from './tools/index.js';
12
13
  import { AutoDiscovery } from './auto-discovery/auto-discovery.js';
13
14
  import { log } from './utils/logger.js';
14
15
  const app = express();
15
- app.use(express.json());
16
+ app.use(express.json({ limit: '10mb' }));
17
+ // Request ID middleware for debugging and log correlation
18
+ app.use((req, res, next) => {
19
+ // Use existing X-Request-ID header or generate a new one
20
+ const requestId = req.headers['x-request-id'] || randomUUID();
21
+ req.requestId = requestId;
22
+ res.setHeader('X-Request-ID', requestId);
23
+ next();
24
+ });
16
25
  // CORS for n8n
17
26
  app.use((_req, res, next) => {
18
27
  res.header('Access-Control-Allow-Origin', '*');
@@ -43,20 +52,25 @@ app.get('/health', async (_req, res) => {
43
52
  });
44
53
  // Ask question
45
54
  app.post('/ask', async (req, res) => {
55
+ const reqId = req.requestId.substring(0, 8); // Short ID for logs
46
56
  try {
47
57
  const { question, session_id, notebook_id, notebook_url, show_browser } = req.body;
48
58
  if (!question) {
59
+ log.warning(`[${reqId}] /ask - Missing question`);
49
60
  return res.status(400).json({
50
61
  success: false,
51
62
  error: 'Missing required field: question',
52
63
  });
53
64
  }
65
+ log.info(`[${reqId}] /ask - "${question.substring(0, 50)}..."`);
54
66
  const result = await toolHandlers.handleAskQuestion({ question, session_id, notebook_id, notebook_url, show_browser }, async (message, progress, total) => {
55
- log.info(`Progress: ${message} (${progress}/${total})`);
67
+ log.info(`[${reqId}] Progress: ${message} (${progress}/${total})`);
56
68
  });
69
+ log.success(`[${reqId}] /ask - Completed`);
57
70
  res.json(result);
58
71
  }
59
72
  catch (error) {
73
+ log.error(`[${reqId}] /ask - Error: ${error instanceof Error ? error.message : String(error)}`);
60
74
  res.status(500).json({
61
75
  success: false,
62
76
  error: error instanceof Error ? error.message : String(error),
@@ -163,10 +177,17 @@ app.post('/notebooks', async (req, res) => {
163
177
  });
164
178
  }
165
179
  });
166
- // Get notebook
167
- app.get('/notebooks/:id', async (req, res) => {
180
+ // Search notebooks (MUST be before /notebooks/:id to avoid being shadowed)
181
+ app.get('/notebooks/search', async (req, res) => {
168
182
  try {
169
- const result = await toolHandlers.handleGetNotebook({ id: req.params.id });
183
+ const { query } = req.query;
184
+ if (typeof query !== 'string' || !query.trim()) {
185
+ return res.status(400).json({
186
+ success: false,
187
+ error: 'Missing or invalid query parameter',
188
+ });
189
+ }
190
+ const result = await toolHandlers.handleSearchNotebooks({ query });
170
191
  res.json(result);
171
192
  }
172
193
  catch (error) {
@@ -176,13 +197,10 @@ app.get('/notebooks/:id', async (req, res) => {
176
197
  });
177
198
  }
178
199
  });
179
- // Update notebook
180
- app.put('/notebooks/:id', async (req, res) => {
200
+ // Get library stats (MUST be before /notebooks/:id to avoid being shadowed)
201
+ app.get('/notebooks/stats', async (_req, res) => {
181
202
  try {
182
- const result = await toolHandlers.handleUpdateNotebook({
183
- id: req.params.id,
184
- ...req.body,
185
- });
203
+ const result = await toolHandlers.handleGetLibraryStats();
186
204
  res.json(result);
187
205
  }
188
206
  catch (error) {
@@ -192,10 +210,10 @@ app.put('/notebooks/:id', async (req, res) => {
192
210
  });
193
211
  }
194
212
  });
195
- // Delete notebook
196
- app.delete('/notebooks/:id', async (req, res) => {
213
+ // Get notebook
214
+ app.get('/notebooks/:id', async (req, res) => {
197
215
  try {
198
- const result = await toolHandlers.handleRemoveNotebook({ id: req.params.id });
216
+ const result = await toolHandlers.handleGetNotebook({ id: req.params.id });
199
217
  res.json(result);
200
218
  }
201
219
  catch (error) {
@@ -205,12 +223,12 @@ app.delete('/notebooks/:id', async (req, res) => {
205
223
  });
206
224
  }
207
225
  });
208
- // Search notebooks
209
- app.get('/notebooks/search', async (req, res) => {
226
+ // Update notebook
227
+ app.put('/notebooks/:id', async (req, res) => {
210
228
  try {
211
- const { query } = req.query;
212
- const result = await toolHandlers.handleSearchNotebooks({
213
- query: query,
229
+ const result = await toolHandlers.handleUpdateNotebook({
230
+ id: req.params.id,
231
+ ...req.body,
214
232
  });
215
233
  res.json(result);
216
234
  }
@@ -221,10 +239,10 @@ app.get('/notebooks/search', async (req, res) => {
221
239
  });
222
240
  }
223
241
  });
224
- // Get library stats
225
- app.get('/notebooks/stats', async (_req, res) => {
242
+ // Delete notebook
243
+ app.delete('/notebooks/:id', async (req, res) => {
226
244
  try {
227
- const result = await toolHandlers.handleGetLibraryStats();
245
+ const result = await toolHandlers.handleRemoveNotebook({ id: req.params.id });
228
246
  res.json(result);
229
247
  }
230
248
  catch (error) {
@@ -245,11 +263,20 @@ app.post('/notebooks/auto-discover', async (req, res) => {
245
263
  error: 'Missing required field: url',
246
264
  });
247
265
  }
248
- // Validate it's a NotebookLM URL
249
- if (!url.includes('notebooklm.google.com')) {
266
+ // Validate it's a NotebookLM URL (proper URL parsing to prevent bypass)
267
+ try {
268
+ const parsedUrl = new URL(url);
269
+ if (parsedUrl.hostname !== 'notebooklm.google.com') {
270
+ return res.status(400).json({
271
+ success: false,
272
+ error: 'Invalid URL: must be a NotebookLM URL (notebooklm.google.com)',
273
+ });
274
+ }
275
+ }
276
+ catch {
250
277
  return res.status(400).json({
251
278
  success: false,
252
- error: 'Invalid URL: must be a NotebookLM URL (notebooklm.google.com)',
279
+ error: 'Invalid URL format',
253
280
  });
254
281
  }
255
282
  // Create AutoDiscovery instance and discover metadata
@@ -301,6 +328,22 @@ app.post('/notebooks/auto-discover', async (req, res) => {
301
328
  });
302
329
  }
303
330
  });
331
+ // Create a new notebook in NotebookLM (via browser automation)
332
+ app.post('/notebooks/create', async (req, res) => {
333
+ try {
334
+ const { name, show_browser } = req.body;
335
+ const result = await toolHandlers.handleCreateNotebook({ name, show_browser }, async (message, progress, total) => {
336
+ log.info(`Progress: ${message} (${progress}/${total})`);
337
+ });
338
+ res.json(result);
339
+ }
340
+ catch (error) {
341
+ res.status(500).json({
342
+ success: false,
343
+ error: error instanceof Error ? error.message : String(error),
344
+ });
345
+ }
346
+ });
304
347
  // Activate notebook (set as active)
305
348
  app.put('/notebooks/:id/activate', async (req, res) => {
306
349
  try {
@@ -353,11 +396,367 @@ app.post('/sessions/:id/reset', async (req, res) => {
353
396
  });
354
397
  }
355
398
  });
399
+ // ========================================
400
+ // Content Management Routes
401
+ // ========================================
402
+ // Add source to notebook
403
+ app.post('/content/sources', async (req, res) => {
404
+ try {
405
+ const { source_type, file_path, url, text, title, notebook_url, session_id, show_browser } = req.body;
406
+ if (!source_type) {
407
+ return res.status(400).json({
408
+ success: false,
409
+ error: 'Missing required field: source_type',
410
+ });
411
+ }
412
+ const result = await toolHandlers.handleAddSource({
413
+ source_type,
414
+ file_path,
415
+ url,
416
+ text,
417
+ title,
418
+ notebook_url,
419
+ session_id,
420
+ show_browser,
421
+ });
422
+ res.json(result);
423
+ }
424
+ catch (error) {
425
+ res.status(500).json({
426
+ success: false,
427
+ error: error instanceof Error ? error.message : String(error),
428
+ });
429
+ }
430
+ });
431
+ // Delete source from notebook
432
+ app.delete('/content/sources/:id', async (req, res) => {
433
+ try {
434
+ const { notebook_url, session_id } = req.query;
435
+ const sourceId = req.params.id;
436
+ if (!sourceId) {
437
+ return res.status(400).json({
438
+ success: false,
439
+ error: 'Missing source ID in URL path',
440
+ });
441
+ }
442
+ const result = await toolHandlers.handleDeleteSource({
443
+ source_id: sourceId,
444
+ notebook_url: typeof notebook_url === 'string' ? notebook_url : undefined,
445
+ session_id: typeof session_id === 'string' ? session_id : undefined,
446
+ });
447
+ if (!result.success) {
448
+ // Return 404 if source not found
449
+ if (result.error?.includes('not found')) {
450
+ return res.status(404).json(result);
451
+ }
452
+ return res.status(500).json(result);
453
+ }
454
+ res.json(result);
455
+ }
456
+ catch (error) {
457
+ res.status(500).json({
458
+ success: false,
459
+ error: error instanceof Error ? error.message : String(error),
460
+ });
461
+ }
462
+ });
463
+ // Delete source by name (alternative endpoint)
464
+ app.delete('/content/sources', async (req, res) => {
465
+ try {
466
+ const { source_name, source_id, notebook_url, session_id } = req.query;
467
+ if (!source_name && !source_id) {
468
+ return res.status(400).json({
469
+ success: false,
470
+ error: 'Missing required query parameter: source_name or source_id',
471
+ });
472
+ }
473
+ const result = await toolHandlers.handleDeleteSource({
474
+ source_id: typeof source_id === 'string' ? source_id : undefined,
475
+ source_name: typeof source_name === 'string' ? source_name : undefined,
476
+ notebook_url: typeof notebook_url === 'string' ? notebook_url : undefined,
477
+ session_id: typeof session_id === 'string' ? session_id : undefined,
478
+ });
479
+ if (!result.success) {
480
+ // Return 404 if source not found
481
+ if (result.error?.includes('not found')) {
482
+ return res.status(404).json(result);
483
+ }
484
+ return res.status(500).json(result);
485
+ }
486
+ res.json(result);
487
+ }
488
+ catch (error) {
489
+ res.status(500).json({
490
+ success: false,
491
+ error: error instanceof Error ? error.message : String(error),
492
+ });
493
+ }
494
+ });
495
+ // Generate content (audio_overview, presentation, report, data_table, infographic, and video are supported)
496
+ app.post('/content/generate', async (req, res) => {
497
+ try {
498
+ const { content_type, custom_instructions, notebook_url, session_id, language, video_style, video_format, infographic_format, report_format, presentation_style, presentation_length, } = req.body;
499
+ if (!content_type) {
500
+ return res.status(400).json({
501
+ success: false,
502
+ error: 'Missing required field: content_type',
503
+ });
504
+ }
505
+ // Validate content_type is supported
506
+ const supportedTypes = [
507
+ 'audio_overview',
508
+ 'presentation',
509
+ 'report',
510
+ 'infographic',
511
+ 'data_table',
512
+ 'video',
513
+ ];
514
+ if (!supportedTypes.includes(content_type)) {
515
+ return res.status(400).json({
516
+ success: false,
517
+ error: `Content type '${content_type}' is not supported. Supported types: ${supportedTypes.join(', ')}. ` +
518
+ 'These use real NotebookLM Studio UI buttons or the generic ContentGenerator.',
519
+ });
520
+ }
521
+ // Warn if custom_instructions provided for content types that don't support it
522
+ const noCustomInstructionsTypes = ['report']; // report and mindmap (when implemented) don't support prompts
523
+ if (custom_instructions && noCustomInstructionsTypes.includes(content_type)) {
524
+ return res.status(400).json({
525
+ success: false,
526
+ error: `Content type '${content_type}' does not support custom_instructions. ` +
527
+ `Only format/language options are available for this type.`,
528
+ });
529
+ }
530
+ // Validate video_style if provided (only valid for video content type)
531
+ const validVideoStyles = [
532
+ 'classroom',
533
+ 'documentary',
534
+ 'animated',
535
+ 'corporate',
536
+ 'cinematic',
537
+ 'minimalist',
538
+ ];
539
+ if (video_style && !validVideoStyles.includes(video_style)) {
540
+ return res.status(400).json({
541
+ success: false,
542
+ error: `Video style '${video_style}' is not supported. Supported styles: ${validVideoStyles.join(', ')}.`,
543
+ });
544
+ }
545
+ if (video_style && content_type !== 'video') {
546
+ return res.status(400).json({
547
+ success: false,
548
+ error: `video_style is only valid for content_type 'video', not '${content_type}'.`,
549
+ });
550
+ }
551
+ // Validate video_format if provided
552
+ if (video_format && !['brief', 'explainer'].includes(video_format)) {
553
+ return res.status(400).json({
554
+ success: false,
555
+ error: `Video format '${video_format}' is not supported. Supported formats: brief, explainer.`,
556
+ });
557
+ }
558
+ // Validate infographic_format if provided
559
+ if (infographic_format && !['horizontal', 'vertical'].includes(infographic_format)) {
560
+ return res.status(400).json({
561
+ success: false,
562
+ error: `Infographic format '${infographic_format}' is not supported. Supported formats: horizontal, vertical.`,
563
+ });
564
+ }
565
+ // Validate report_format if provided
566
+ if (report_format && !['summary', 'detailed'].includes(report_format)) {
567
+ return res.status(400).json({
568
+ success: false,
569
+ error: `Report format '${report_format}' is not supported. Supported formats: summary, detailed.`,
570
+ });
571
+ }
572
+ // Validate presentation_style if provided
573
+ if (presentation_style &&
574
+ !['detailed_slideshow', 'presenter_notes'].includes(presentation_style)) {
575
+ return res.status(400).json({
576
+ success: false,
577
+ error: `Presentation style '${presentation_style}' is not supported. Supported styles: detailed_slideshow, presenter_notes.`,
578
+ });
579
+ }
580
+ // Validate presentation_length if provided
581
+ if (presentation_length && !['short', 'default'].includes(presentation_length)) {
582
+ return res.status(400).json({
583
+ success: false,
584
+ error: `Presentation length '${presentation_length}' is not supported. Supported lengths: short, default.`,
585
+ });
586
+ }
587
+ // Note: data_table has no format options - it exports to Google Sheets
588
+ const result = await toolHandlers.handleGenerateContent({
589
+ content_type,
590
+ custom_instructions,
591
+ notebook_url,
592
+ session_id,
593
+ language,
594
+ video_style,
595
+ video_format,
596
+ infographic_format,
597
+ report_format,
598
+ presentation_style,
599
+ presentation_length,
600
+ });
601
+ res.json(result);
602
+ }
603
+ catch (error) {
604
+ res.status(500).json({
605
+ success: false,
606
+ error: error instanceof Error ? error.message : String(error),
607
+ });
608
+ }
609
+ });
610
+ // List sources and generated content
611
+ app.get('/content', async (req, res) => {
612
+ try {
613
+ const { notebook_url, session_id } = req.query;
614
+ const result = await toolHandlers.handleListContent({
615
+ notebook_url: typeof notebook_url === 'string' ? notebook_url : undefined,
616
+ session_id: typeof session_id === 'string' ? session_id : undefined,
617
+ });
618
+ res.json(result);
619
+ }
620
+ catch (error) {
621
+ res.status(500).json({
622
+ success: false,
623
+ error: error instanceof Error ? error.message : String(error),
624
+ });
625
+ }
626
+ });
627
+ // Download/export content (audio, video, infographic, presentation, data_table)
628
+ app.get('/content/download', async (req, res) => {
629
+ try {
630
+ const { content_type, output_path, notebook_url, session_id } = req.query;
631
+ if (!content_type) {
632
+ return res.status(400).json({
633
+ success: false,
634
+ error: 'Missing required field: content_type',
635
+ });
636
+ }
637
+ // Validate content_type is downloadable/exportable
638
+ // - audio_overview, video, infographic: downloadable as files
639
+ // - presentation: exports to Google Slides
640
+ // - data_table: exports to Google Sheets
641
+ // - report: text-based only (no export)
642
+ const exportableTypes = [
643
+ 'audio_overview',
644
+ 'video',
645
+ 'infographic',
646
+ 'presentation',
647
+ 'data_table',
648
+ ];
649
+ if (!exportableTypes.includes(content_type)) {
650
+ return res.status(400).json({
651
+ success: false,
652
+ error: `Content type '${content_type}' is not exportable. Exportable types: ${exportableTypes.join(', ')}. ` +
653
+ 'Report content is text-based and returned in the generation response.',
654
+ });
655
+ }
656
+ const result = await toolHandlers.handleDownloadContent({
657
+ content_type: content_type,
658
+ output_path: typeof output_path === 'string' ? output_path : undefined,
659
+ notebook_url: typeof notebook_url === 'string' ? notebook_url : undefined,
660
+ session_id: typeof session_id === 'string' ? session_id : undefined,
661
+ });
662
+ res.json(result);
663
+ }
664
+ catch (error) {
665
+ res.status(500).json({
666
+ success: false,
667
+ error: error instanceof Error ? error.message : String(error),
668
+ });
669
+ }
670
+ });
671
+ // Create a note in the notebook
672
+ app.post('/content/notes', async (req, res) => {
673
+ try {
674
+ const { title, content, notebook_url, session_id } = req.body;
675
+ if (!title) {
676
+ return res.status(400).json({
677
+ success: false,
678
+ error: 'Missing required field: title',
679
+ });
680
+ }
681
+ if (!content) {
682
+ return res.status(400).json({
683
+ success: false,
684
+ error: 'Missing required field: content',
685
+ });
686
+ }
687
+ const result = await toolHandlers.handleCreateNote({
688
+ title,
689
+ content,
690
+ notebook_url,
691
+ session_id,
692
+ });
693
+ res.json(result);
694
+ }
695
+ catch (error) {
696
+ res.status(500).json({
697
+ success: false,
698
+ error: error instanceof Error ? error.message : String(error),
699
+ });
700
+ }
701
+ });
702
+ // Save chat/discussion to a note
703
+ app.post('/content/chat-to-note', async (req, res) => {
704
+ try {
705
+ const { title, notebook_url, session_id } = req.body;
706
+ const result = await toolHandlers.handleSaveChatToNote({
707
+ title,
708
+ notebook_url,
709
+ session_id,
710
+ });
711
+ res.json(result);
712
+ }
713
+ catch (error) {
714
+ res.status(500).json({
715
+ success: false,
716
+ error: error instanceof Error ? error.message : String(error),
717
+ });
718
+ }
719
+ });
720
+ // Convert a note to a source
721
+ app.post('/content/notes/:noteTitle/to-source', async (req, res) => {
722
+ try {
723
+ const { noteTitle } = req.params;
724
+ const { notebook_url, session_id } = req.body;
725
+ if (!noteTitle) {
726
+ return res.status(400).json({
727
+ success: false,
728
+ error: 'Missing required parameter: noteTitle',
729
+ });
730
+ }
731
+ const result = await toolHandlers.handleConvertNoteToSource({
732
+ note_title: decodeURIComponent(noteTitle),
733
+ notebook_url,
734
+ session_id,
735
+ });
736
+ res.json(result);
737
+ }
738
+ catch (error) {
739
+ res.status(500).json({
740
+ success: false,
741
+ error: error instanceof Error ? error.message : String(error),
742
+ });
743
+ }
744
+ });
745
+ // Global error handler - catches any unhandled errors in async routes
746
+ app.use((err, req, res, _next) => {
747
+ const reqId = req.requestId?.substring(0, 8) || 'unknown';
748
+ log.error(`[${reqId}] Unhandled error: ${err.message}`);
749
+ res.status(500).json({
750
+ success: false,
751
+ error: 'Internal server error',
752
+ requestId: req.requestId,
753
+ });
754
+ });
356
755
  // Start server
357
756
  const PORT = Number(process.env.HTTP_PORT) || 3000;
358
757
  const HOST = process.env.HTTP_HOST || '0.0.0.0';
359
758
  app.listen(PORT, HOST, () => {
360
- log.success(`🌐 NotebookLM MCP HTTP Server v1.3.5`);
759
+ log.success(`🌐 NotebookLM MCP HTTP Server v1.4.2`);
361
760
  log.success(` Listening on ${HOST}:${PORT}`);
362
761
  log.info('');
363
762
  log.info('📊 Quick Links:');
@@ -391,6 +790,17 @@ app.listen(PORT, HOST, () => {
391
790
  log.info(' POST /sessions/:id/reset Reset session history');
392
791
  log.info(' DELETE /sessions/:id Close a session');
393
792
  log.info('');
793
+ log.info(' Content Management:');
794
+ log.info(' POST /content/sources Add source to notebook');
795
+ log.info(' DELETE /content/sources/:id Delete source by ID');
796
+ log.info(' DELETE /content/sources Delete source by name (query param)');
797
+ log.info(' POST /content/generate Generate content (audio, video, etc.)');
798
+ log.info(' GET /content/download Download/export generated content');
799
+ log.info(' POST /content/notes Create a note in the notebook');
800
+ log.info(' POST /content/chat-to-note Save chat/discussion to a note');
801
+ log.info(' POST /content/notes/:title/to-source Convert note to source');
802
+ log.info(' GET /content List sources and content');
803
+ log.info('');
394
804
  log.info('💡 Configuration:');
395
805
  log.info(` Host: ${HOST} ${HOST === '0.0.0.0' ? '(accessible from network)' : '(localhost only)'}`);
396
806
  log.info(` Port: ${PORT}`);
@@ -398,15 +808,25 @@ app.listen(PORT, HOST, () => {
398
808
  log.dim('📖 Documentation: ./deployment/docs/');
399
809
  log.dim('âšī¸ Press Ctrl+C to stop');
400
810
  });
401
- // Graceful shutdown
811
+ // Graceful shutdown with error handling
402
812
  process.on('SIGTERM', async () => {
403
813
  log.info('SIGTERM received, shutting down gracefully...');
404
- await toolHandlers.cleanup();
814
+ try {
815
+ await toolHandlers.cleanup();
816
+ }
817
+ catch (error) {
818
+ log.error(`Cleanup failed: ${error}`);
819
+ }
405
820
  process.exit(0);
406
821
  });
407
822
  process.on('SIGINT', async () => {
408
823
  log.info('SIGINT received, shutting down gracefully...');
409
- await toolHandlers.cleanup();
824
+ try {
825
+ await toolHandlers.cleanup();
826
+ }
827
+ catch (error) {
828
+ log.error(`Cleanup failed: ${error}`);
829
+ }
410
830
  process.exit(0);
411
831
  });
412
832
  //# sourceMappingURL=http-wrapper.js.map