@roomi-fields/notebooklm-mcp 1.3.5 â 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +93 -658
- package/dist/accounts/account-manager.d.ts +163 -0
- package/dist/accounts/account-manager.d.ts.map +1 -0
- package/dist/accounts/account-manager.js +614 -0
- package/dist/accounts/account-manager.js.map +1 -0
- package/dist/accounts/auto-login-manager.d.ts +62 -0
- package/dist/accounts/auto-login-manager.d.ts.map +1 -0
- package/dist/accounts/auto-login-manager.js +537 -0
- package/dist/accounts/auto-login-manager.js.map +1 -0
- package/dist/accounts/crypto.d.ts +45 -0
- package/dist/accounts/crypto.d.ts.map +1 -0
- package/dist/accounts/crypto.js +138 -0
- package/dist/accounts/crypto.js.map +1 -0
- package/dist/accounts/index.d.ts +14 -0
- package/dist/accounts/index.d.ts.map +1 -0
- package/dist/accounts/index.js +14 -0
- package/dist/accounts/index.js.map +1 -0
- package/dist/accounts/types.d.ts +103 -0
- package/dist/accounts/types.d.ts.map +1 -0
- package/dist/accounts/types.js +7 -0
- package/dist/accounts/types.js.map +1 -0
- package/dist/auth/auth-manager.d.ts +9 -2
- package/dist/auth/auth-manager.d.ts.map +1 -1
- package/dist/auth/auth-manager.js +60 -6
- package/dist/auth/auth-manager.js.map +1 -1
- package/dist/auto-discovery/auto-discovery.d.ts.map +1 -1
- package/dist/auto-discovery/auto-discovery.js +2 -1
- package/dist/auto-discovery/auto-discovery.js.map +1 -1
- package/dist/cli/accounts.d.ts +13 -0
- package/dist/cli/accounts.d.ts.map +1 -0
- package/dist/cli/accounts.js +195 -0
- package/dist/cli/accounts.js.map +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +9 -0
- package/dist/config.js.map +1 -1
- package/dist/content/content-generator.d.ts +153 -0
- package/dist/content/content-generator.d.ts.map +1 -0
- package/dist/content/content-generator.js +637 -0
- package/dist/content/content-generator.js.map +1 -0
- package/dist/content/content-manager.d.ts +364 -0
- package/dist/content/content-manager.d.ts.map +1 -0
- package/dist/content/content-manager.js +3846 -0
- package/dist/content/content-manager.js.map +1 -0
- package/dist/content/content-templates.d.ts +183 -0
- package/dist/content/content-templates.d.ts.map +1 -0
- package/dist/content/content-templates.js +719 -0
- package/dist/content/content-templates.js.map +1 -0
- package/dist/content/index.d.ts +14 -0
- package/dist/content/index.d.ts.map +1 -0
- package/dist/content/index.js +14 -0
- package/dist/content/index.js.map +1 -0
- package/dist/content/types.d.ts +285 -0
- package/dist/content/types.d.ts.map +1 -0
- package/dist/content/types.js +10 -0
- package/dist/content/types.js.map +1 -0
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/http-wrapper.d.ts +7 -0
- package/dist/http-wrapper.d.ts.map +1 -1
- package/dist/http-wrapper.js +449 -29
- package/dist/http-wrapper.js.map +1 -1
- package/dist/index.js +26 -2
- package/dist/index.js.map +1 -1
- package/dist/library/notebook-library.d.ts +4 -0
- package/dist/library/notebook-library.d.ts.map +1 -1
- package/dist/library/notebook-library.js +20 -3
- package/dist/library/notebook-library.js.map +1 -1
- package/dist/session/browser-session.d.ts +35 -8
- package/dist/session/browser-session.d.ts.map +1 -1
- package/dist/session/browser-session.js +242 -28
- package/dist/session/browser-session.js.map +1 -1
- package/dist/session/session-manager.d.ts +6 -0
- package/dist/session/session-manager.d.ts.map +1 -1
- package/dist/session/session-manager.js +46 -14
- package/dist/session/session-manager.js.map +1 -1
- package/dist/session/shared-context-manager.d.ts +3 -3
- package/dist/session/shared-context-manager.d.ts.map +1 -1
- package/dist/session/shared-context-manager.js +8 -7
- package/dist/session/shared-context-manager.js.map +1 -1
- package/dist/stdio-http-proxy.d.ts +24 -0
- package/dist/stdio-http-proxy.d.ts.map +1 -0
- package/dist/stdio-http-proxy.js +592 -0
- package/dist/stdio-http-proxy.js.map +1 -0
- package/dist/tools/index.d.ts +106 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +1028 -7
- package/dist/tools/index.js.map +1 -1
- package/dist/types.d.ts +81 -17
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/citation-extractor.d.ts +66 -0
- package/dist/utils/citation-extractor.d.ts.map +1 -0
- package/dist/utils/citation-extractor.js +492 -0
- package/dist/utils/citation-extractor.js.map +1 -0
- package/dist/utils/page-utils.d.ts +8 -0
- package/dist/utils/page-utils.d.ts.map +1 -1
- package/dist/utils/page-utils.js +112 -8
- package/dist/utils/page-utils.js.map +1 -1
- package/docs/ARCHITECTURE_MIGRATION_STUDY.md +894 -0
- package/docs/CHROME_PROFILE_LIMITATION.md +15 -1
- package/docs/MULTI_ACCOUNT_SYSTEM.md +304 -0
- package/package.json +10 -10
- package/dist/__tests__/cleanup-manager.test.d.ts +0 -2
- package/dist/__tests__/cleanup-manager.test.d.ts.map +0 -1
- package/dist/__tests__/cleanup-manager.test.js +0 -341
- package/dist/__tests__/cleanup-manager.test.js.map +0 -1
- package/dist/__tests__/config-parsing.test.d.ts +0 -2
- package/dist/__tests__/config-parsing.test.d.ts.map +0 -1
- package/dist/__tests__/config-parsing.test.js +0 -338
- package/dist/__tests__/config-parsing.test.js.map +0 -1
- package/dist/__tests__/config.test.d.ts +0 -2
- package/dist/__tests__/config.test.d.ts.map +0 -1
- package/dist/__tests__/config.test.js +0 -267
- package/dist/__tests__/config.test.js.map +0 -1
- package/dist/__tests__/errors.test.d.ts +0 -2
- package/dist/__tests__/errors.test.d.ts.map +0 -1
- package/dist/__tests__/errors.test.js +0 -166
- package/dist/__tests__/errors.test.js.map +0 -1
- package/dist/__tests__/logger.test.d.ts +0 -2
- package/dist/__tests__/logger.test.d.ts.map +0 -1
- package/dist/__tests__/logger.test.js +0 -324
- package/dist/__tests__/logger.test.js.map +0 -1
- package/dist/__tests__/page-utils.test.d.ts +0 -2
- package/dist/__tests__/page-utils.test.d.ts.map +0 -1
- package/dist/__tests__/page-utils.test.js +0 -349
- package/dist/__tests__/page-utils.test.js.map +0 -1
- package/dist/__tests__/setup-verification.test.d.ts +0 -2
- package/dist/__tests__/setup-verification.test.d.ts.map +0 -1
- package/dist/__tests__/setup-verification.test.js +0 -15
- package/dist/__tests__/setup-verification.test.js.map +0 -1
- package/dist/__tests__/stealth-utils.test.d.ts +0 -2
- package/dist/__tests__/stealth-utils.test.d.ts.map +0 -1
- package/dist/__tests__/stealth-utils.test.js +0 -413
- package/dist/__tests__/stealth-utils.test.js.map +0 -1
- package/dist/__tests__/types.test.d.ts +0 -2
- package/dist/__tests__/types.test.d.ts.map +0 -1
- package/dist/__tests__/types.test.js +0 -461
- package/dist/__tests__/types.test.js.map +0 -1
package/dist/http-wrapper.js
CHANGED
|
@@ -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
|
-
//
|
|
167
|
-
app.get('/notebooks
|
|
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
|
|
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
|
-
//
|
|
180
|
-
app.
|
|
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.
|
|
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
|
-
//
|
|
196
|
-
app.
|
|
213
|
+
// Get notebook
|
|
214
|
+
app.get('/notebooks/:id', async (req, res) => {
|
|
197
215
|
try {
|
|
198
|
-
const result = await toolHandlers.
|
|
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
|
-
//
|
|
209
|
-
app.
|
|
226
|
+
// Update notebook
|
|
227
|
+
app.put('/notebooks/:id', async (req, res) => {
|
|
210
228
|
try {
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
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
|
-
//
|
|
225
|
-
app.
|
|
242
|
+
// Delete notebook
|
|
243
|
+
app.delete('/notebooks/:id', async (req, res) => {
|
|
226
244
|
try {
|
|
227
|
-
const result = await toolHandlers.
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|