@pan-sec/notebooklm-mcp 2026.2.11 → 2026.3.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 (305) hide show
  1. package/README.md +62 -19
  2. package/SECURITY.md +31 -61
  3. package/dist/auth/auth-manager.d.ts +2 -1
  4. package/dist/auth/auth-manager.d.ts.map +1 -1
  5. package/dist/auth/auth-manager.js +117 -44
  6. package/dist/auth/auth-manager.js.map +1 -1
  7. package/dist/auth/mcp-auth.d.ts +24 -4
  8. package/dist/auth/mcp-auth.d.ts.map +1 -1
  9. package/dist/auth/mcp-auth.js +149 -19
  10. package/dist/auth/mcp-auth.js.map +1 -1
  11. package/dist/compliance/alert-manager.d.ts.map +1 -1
  12. package/dist/compliance/alert-manager.js +7 -4
  13. package/dist/compliance/alert-manager.js.map +1 -1
  14. package/dist/compliance/breach-detection.d.ts.map +1 -1
  15. package/dist/compliance/breach-detection.js +14 -7
  16. package/dist/compliance/breach-detection.js.map +1 -1
  17. package/dist/compliance/change-log.d.ts.map +1 -1
  18. package/dist/compliance/change-log.js +7 -4
  19. package/dist/compliance/change-log.js.map +1 -1
  20. package/dist/compliance/compliance-logger.d.ts.map +1 -1
  21. package/dist/compliance/compliance-logger.js +11 -6
  22. package/dist/compliance/compliance-logger.js.map +1 -1
  23. package/dist/compliance/consent-manager.d.ts.map +1 -1
  24. package/dist/compliance/consent-manager.js +5 -3
  25. package/dist/compliance/consent-manager.js.map +1 -1
  26. package/dist/compliance/data-erasure.d.ts +1 -1
  27. package/dist/compliance/data-erasure.d.ts.map +1 -1
  28. package/dist/compliance/data-erasure.js +142 -83
  29. package/dist/compliance/data-erasure.js.map +1 -1
  30. package/dist/compliance/data-export.d.ts.map +1 -1
  31. package/dist/compliance/data-export.js +23 -12
  32. package/dist/compliance/data-export.js.map +1 -1
  33. package/dist/compliance/data-inventory.d.ts.map +1 -1
  34. package/dist/compliance/data-inventory.js +7 -6
  35. package/dist/compliance/data-inventory.js.map +1 -1
  36. package/dist/compliance/dsar-handler.d.ts +7 -1
  37. package/dist/compliance/dsar-handler.d.ts.map +1 -1
  38. package/dist/compliance/dsar-handler.js +74 -61
  39. package/dist/compliance/dsar-handler.js.map +1 -1
  40. package/dist/compliance/evidence-collector.d.ts.map +1 -1
  41. package/dist/compliance/evidence-collector.js +10 -6
  42. package/dist/compliance/evidence-collector.js.map +1 -1
  43. package/dist/compliance/health-monitor.d.ts.map +1 -1
  44. package/dist/compliance/health-monitor.js +15 -9
  45. package/dist/compliance/health-monitor.js.map +1 -1
  46. package/dist/compliance/incident-manager.d.ts.map +1 -1
  47. package/dist/compliance/incident-manager.js +5 -3
  48. package/dist/compliance/incident-manager.js.map +1 -1
  49. package/dist/compliance/policy-docs.d.ts.map +1 -1
  50. package/dist/compliance/policy-docs.js +14 -11
  51. package/dist/compliance/policy-docs.js.map +1 -1
  52. package/dist/compliance/privacy-notice-text.d.ts.map +1 -1
  53. package/dist/compliance/privacy-notice-text.js +3 -4
  54. package/dist/compliance/privacy-notice-text.js.map +1 -1
  55. package/dist/compliance/privacy-notice.d.ts.map +1 -1
  56. package/dist/compliance/privacy-notice.js +5 -3
  57. package/dist/compliance/privacy-notice.js.map +1 -1
  58. package/dist/compliance/report-generator.d.ts.map +1 -1
  59. package/dist/compliance/report-generator.js +5 -3
  60. package/dist/compliance/report-generator.js.map +1 -1
  61. package/dist/compliance/retention-engine.d.ts.map +1 -1
  62. package/dist/compliance/retention-engine.js +24 -10
  63. package/dist/compliance/retention-engine.js.map +1 -1
  64. package/dist/compliance/siem-exporter.d.ts.map +1 -1
  65. package/dist/compliance/siem-exporter.js +40 -16
  66. package/dist/compliance/siem-exporter.js.map +1 -1
  67. package/dist/config.d.ts +8 -31
  68. package/dist/config.d.ts.map +1 -1
  69. package/dist/config.js +26 -64
  70. package/dist/config.js.map +1 -1
  71. package/dist/errors.d.ts +22 -2
  72. package/dist/errors.d.ts.map +1 -1
  73. package/dist/errors.js +55 -4
  74. package/dist/errors.js.map +1 -1
  75. package/dist/gemini/gemini-client.d.ts +1 -0
  76. package/dist/gemini/gemini-client.d.ts.map +1 -1
  77. package/dist/gemini/gemini-client.js +50 -49
  78. package/dist/gemini/gemini-client.js.map +1 -1
  79. package/dist/gemini/types.d.ts +3 -1
  80. package/dist/gemini/types.d.ts.map +1 -1
  81. package/dist/gemini/types.js.map +1 -1
  82. package/dist/index.d.ts +52 -1
  83. package/dist/index.d.ts.map +1 -1
  84. package/dist/index.js +412 -89
  85. package/dist/index.js.map +1 -1
  86. package/dist/library/notebook-library.d.ts.map +1 -1
  87. package/dist/library/notebook-library.js +2 -1
  88. package/dist/library/notebook-library.js.map +1 -1
  89. package/dist/logging/query-logger.d.ts +13 -1
  90. package/dist/logging/query-logger.d.ts.map +1 -1
  91. package/dist/logging/query-logger.js +62 -10
  92. package/dist/logging/query-logger.js.map +1 -1
  93. package/dist/notebook-creation/audio-manager.d.ts.map +1 -1
  94. package/dist/notebook-creation/audio-manager.js +19 -24
  95. package/dist/notebook-creation/audio-manager.js.map +1 -1
  96. package/dist/notebook-creation/browser-options.d.ts +28 -0
  97. package/dist/notebook-creation/browser-options.d.ts.map +1 -0
  98. package/dist/notebook-creation/browser-options.js +75 -0
  99. package/dist/notebook-creation/browser-options.js.map +1 -0
  100. package/dist/notebook-creation/data-table-manager.d.ts.map +1 -1
  101. package/dist/notebook-creation/data-table-manager.js +20 -21
  102. package/dist/notebook-creation/data-table-manager.js.map +1 -1
  103. package/dist/notebook-creation/discover-creation-flow.d.ts +0 -6
  104. package/dist/notebook-creation/discover-creation-flow.d.ts.map +1 -1
  105. package/dist/notebook-creation/discover-creation-flow.js +10 -10
  106. package/dist/notebook-creation/discover-creation-flow.js.map +1 -1
  107. package/dist/notebook-creation/discover-quota.d.ts +0 -6
  108. package/dist/notebook-creation/discover-quota.d.ts.map +1 -1
  109. package/dist/notebook-creation/discover-quota.js +12 -13
  110. package/dist/notebook-creation/discover-quota.js.map +1 -1
  111. package/dist/notebook-creation/discover-sources.js +15 -16
  112. package/dist/notebook-creation/discover-sources.js.map +1 -1
  113. package/dist/notebook-creation/dom-scripts.d.ts +10 -0
  114. package/dist/notebook-creation/dom-scripts.d.ts.map +1 -0
  115. package/dist/notebook-creation/dom-scripts.js +58 -0
  116. package/dist/notebook-creation/dom-scripts.js.map +1 -0
  117. package/dist/notebook-creation/errors.d.ts +18 -0
  118. package/dist/notebook-creation/errors.d.ts.map +1 -0
  119. package/dist/notebook-creation/errors.js +20 -0
  120. package/dist/notebook-creation/errors.js.map +1 -0
  121. package/dist/notebook-creation/index.d.ts +2 -1
  122. package/dist/notebook-creation/index.d.ts.map +1 -1
  123. package/dist/notebook-creation/index.js +2 -1
  124. package/dist/notebook-creation/index.js.map +1 -1
  125. package/dist/notebook-creation/notebook-creator.d.ts +6 -82
  126. package/dist/notebook-creation/notebook-creator.d.ts.map +1 -1
  127. package/dist/notebook-creation/notebook-creator.js +49 -835
  128. package/dist/notebook-creation/notebook-creator.js.map +1 -1
  129. package/dist/notebook-creation/notebook-nav.d.ts +19 -0
  130. package/dist/notebook-creation/notebook-nav.d.ts.map +1 -0
  131. package/dist/notebook-creation/notebook-nav.js +240 -0
  132. package/dist/notebook-creation/notebook-nav.js.map +1 -0
  133. package/dist/notebook-creation/notebook-sync.d.ts.map +1 -1
  134. package/dist/notebook-creation/notebook-sync.js +36 -38
  135. package/dist/notebook-creation/notebook-sync.js.map +1 -1
  136. package/dist/notebook-creation/selector-discovery.d.ts.map +1 -1
  137. package/dist/notebook-creation/selector-discovery.js +17 -24
  138. package/dist/notebook-creation/selector-discovery.js.map +1 -1
  139. package/dist/notebook-creation/selectors.d.ts +23 -37
  140. package/dist/notebook-creation/selectors.d.ts.map +1 -1
  141. package/dist/notebook-creation/selectors.js +56 -60
  142. package/dist/notebook-creation/selectors.js.map +1 -1
  143. package/dist/notebook-creation/source-manager.d.ts +25 -0
  144. package/dist/notebook-creation/source-manager.d.ts.map +1 -1
  145. package/dist/notebook-creation/source-manager.js +689 -50
  146. package/dist/notebook-creation/source-manager.js.map +1 -1
  147. package/dist/notebook-creation/types.d.ts +4 -0
  148. package/dist/notebook-creation/types.d.ts.map +1 -1
  149. package/dist/notebook-creation/video-manager.d.ts.map +1 -1
  150. package/dist/notebook-creation/video-manager.js +33 -35
  151. package/dist/notebook-creation/video-manager.js.map +1 -1
  152. package/dist/observability/metrics.d.ts +19 -0
  153. package/dist/observability/metrics.d.ts.map +1 -0
  154. package/dist/observability/metrics.js +35 -0
  155. package/dist/observability/metrics.js.map +1 -0
  156. package/dist/quota/quota-manager.d.ts +11 -3
  157. package/dist/quota/quota-manager.d.ts.map +1 -1
  158. package/dist/quota/quota-manager.js +139 -47
  159. package/dist/quota/quota-manager.js.map +1 -1
  160. package/dist/resources/resource-handlers.d.ts.map +1 -1
  161. package/dist/resources/resource-handlers.js +39 -17
  162. package/dist/resources/resource-handlers.js.map +1 -1
  163. package/dist/session/browser-session.d.ts.map +1 -1
  164. package/dist/session/browser-session.js +22 -22
  165. package/dist/session/browser-session.js.map +1 -1
  166. package/dist/session/session-timeout.d.ts.map +1 -1
  167. package/dist/session/session-timeout.js +4 -2
  168. package/dist/session/session-timeout.js.map +1 -1
  169. package/dist/session/shared-context-manager.d.ts.map +1 -1
  170. package/dist/session/shared-context-manager.js +31 -30
  171. package/dist/session/shared-context-manager.js.map +1 -1
  172. package/dist/tools/annotations.d.ts.map +1 -1
  173. package/dist/tools/annotations.js +9 -56
  174. package/dist/tools/annotations.js.map +1 -1
  175. package/dist/tools/definitions/ask-question.d.ts.map +1 -1
  176. package/dist/tools/definitions/ask-question.js +35 -100
  177. package/dist/tools/definitions/ask-question.js.map +1 -1
  178. package/dist/tools/definitions/chat-history.d.ts +47 -1
  179. package/dist/tools/definitions/chat-history.d.ts.map +1 -1
  180. package/dist/tools/definitions/chat-history.js +10 -1
  181. package/dist/tools/definitions/chat-history.js.map +1 -1
  182. package/dist/tools/definitions/data-tables.d.ts.map +1 -1
  183. package/dist/tools/definitions/data-tables.js +2 -0
  184. package/dist/tools/definitions/data-tables.js.map +1 -1
  185. package/dist/tools/definitions/gemini.d.ts.map +1 -1
  186. package/dist/tools/definitions/gemini.js +54 -11
  187. package/dist/tools/definitions/gemini.js.map +1 -1
  188. package/dist/tools/definitions/notebook-management.d.ts.map +1 -1
  189. package/dist/tools/definitions/notebook-management.js +100 -70
  190. package/dist/tools/definitions/notebook-management.js.map +1 -1
  191. package/dist/tools/definitions/query-history.d.ts +47 -1
  192. package/dist/tools/definitions/query-history.d.ts.map +1 -1
  193. package/dist/tools/definitions/query-history.js +7 -0
  194. package/dist/tools/definitions/query-history.js.map +1 -1
  195. package/dist/tools/definitions/session-management.d.ts.map +1 -1
  196. package/dist/tools/definitions/session-management.js +5 -0
  197. package/dist/tools/definitions/session-management.js.map +1 -1
  198. package/dist/tools/definitions/system.d.ts.map +1 -1
  199. package/dist/tools/definitions/system.js +71 -100
  200. package/dist/tools/definitions/system.js.map +1 -1
  201. package/dist/tools/definitions/video.d.ts.map +1 -1
  202. package/dist/tools/definitions/video.js +4 -1
  203. package/dist/tools/definitions/video.js.map +1 -1
  204. package/dist/tools/definitions.d.ts.map +1 -1
  205. package/dist/tools/definitions.js +4 -0
  206. package/dist/tools/definitions.js.map +1 -1
  207. package/dist/tools/handlers/ask-question.d.ts +1 -1
  208. package/dist/tools/handlers/ask-question.d.ts.map +1 -1
  209. package/dist/tools/handlers/ask-question.js +57 -13
  210. package/dist/tools/handlers/ask-question.js.map +1 -1
  211. package/dist/tools/handlers/audio-video.d.ts.map +1 -1
  212. package/dist/tools/handlers/audio-video.js +22 -161
  213. package/dist/tools/handlers/audio-video.js.map +1 -1
  214. package/dist/tools/handlers/auth.d.ts +14 -19
  215. package/dist/tools/handlers/auth.d.ts.map +1 -1
  216. package/dist/tools/handlers/auth.js +77 -121
  217. package/dist/tools/handlers/auth.js.map +1 -1
  218. package/dist/tools/handlers/error-utils.d.ts +16 -0
  219. package/dist/tools/handlers/error-utils.d.ts.map +1 -0
  220. package/dist/tools/handlers/error-utils.js +39 -0
  221. package/dist/tools/handlers/error-utils.js.map +1 -0
  222. package/dist/tools/handlers/gemini.d.ts +2 -0
  223. package/dist/tools/handlers/gemini.d.ts.map +1 -1
  224. package/dist/tools/handlers/gemini.js +88 -51
  225. package/dist/tools/handlers/gemini.js.map +1 -1
  226. package/dist/tools/handlers/index.d.ts +39 -47
  227. package/dist/tools/handlers/index.d.ts.map +1 -1
  228. package/dist/tools/handlers/index.js +15 -4
  229. package/dist/tools/handlers/index.js.map +1 -1
  230. package/dist/tools/handlers/notebook-creation.d.ts.map +1 -1
  231. package/dist/tools/handlers/notebook-creation.js +102 -86
  232. package/dist/tools/handlers/notebook-creation.js.map +1 -1
  233. package/dist/tools/handlers/notebook-management.d.ts +8 -8
  234. package/dist/tools/handlers/notebook-management.d.ts.map +1 -1
  235. package/dist/tools/handlers/notebook-management.js +34 -80
  236. package/dist/tools/handlers/notebook-management.js.map +1 -1
  237. package/dist/tools/handlers/session-management.d.ts +8 -10
  238. package/dist/tools/handlers/session-management.d.ts.map +1 -1
  239. package/dist/tools/handlers/session-management.js +34 -63
  240. package/dist/tools/handlers/session-management.js.map +1 -1
  241. package/dist/tools/handlers/system.d.ts.map +1 -1
  242. package/dist/tools/handlers/system.js +45 -10
  243. package/dist/tools/handlers/system.js.map +1 -1
  244. package/dist/tools/handlers/types.d.ts +1 -1
  245. package/dist/tools/handlers/types.d.ts.map +1 -1
  246. package/dist/tools/handlers/webhooks.d.ts.map +1 -1
  247. package/dist/tools/handlers/webhooks.js +15 -13
  248. package/dist/tools/handlers/webhooks.js.map +1 -1
  249. package/dist/types.d.ts +7 -17
  250. package/dist/types.d.ts.map +1 -1
  251. package/dist/utils/audit-logger.d.ts +19 -1
  252. package/dist/utils/audit-logger.d.ts.map +1 -1
  253. package/dist/utils/audit-logger.js +198 -30
  254. package/dist/utils/audit-logger.js.map +1 -1
  255. package/dist/utils/cleanup-manager.d.ts.map +1 -1
  256. package/dist/utils/cleanup-manager.js +6 -3
  257. package/dist/utils/cleanup-manager.js.map +1 -1
  258. package/dist/utils/crypto.d.ts +4 -1
  259. package/dist/utils/crypto.d.ts.map +1 -1
  260. package/dist/utils/crypto.js +32 -21
  261. package/dist/utils/crypto.js.map +1 -1
  262. package/dist/utils/file-lock.d.ts.map +1 -1
  263. package/dist/utils/file-lock.js +87 -16
  264. package/dist/utils/file-lock.js.map +1 -1
  265. package/dist/utils/file-permissions.d.ts +2 -0
  266. package/dist/utils/file-permissions.d.ts.map +1 -1
  267. package/dist/utils/file-permissions.js +2 -1
  268. package/dist/utils/file-permissions.js.map +1 -1
  269. package/dist/utils/logger.d.ts +4 -0
  270. package/dist/utils/logger.d.ts.map +1 -1
  271. package/dist/utils/logger.js +16 -0
  272. package/dist/utils/logger.js.map +1 -1
  273. package/dist/utils/page-utils.d.ts +13 -0
  274. package/dist/utils/page-utils.d.ts.map +1 -1
  275. package/dist/utils/page-utils.js +61 -39
  276. package/dist/utils/page-utils.js.map +1 -1
  277. package/dist/utils/response-validator.d.ts.map +1 -1
  278. package/dist/utils/response-validator.js +27 -22
  279. package/dist/utils/response-validator.js.map +1 -1
  280. package/dist/utils/secrets-scanner.d.ts +11 -0
  281. package/dist/utils/secrets-scanner.d.ts.map +1 -1
  282. package/dist/utils/secrets-scanner.js +65 -17
  283. package/dist/utils/secrets-scanner.js.map +1 -1
  284. package/dist/utils/secure-memory.d.ts +9 -31
  285. package/dist/utils/secure-memory.d.ts.map +1 -1
  286. package/dist/utils/secure-memory.js +17 -102
  287. package/dist/utils/secure-memory.js.map +1 -1
  288. package/dist/utils/security.d.ts +4 -3
  289. package/dist/utils/security.d.ts.map +1 -1
  290. package/dist/utils/security.js +43 -13
  291. package/dist/utils/security.js.map +1 -1
  292. package/dist/utils/stealth-utils.d.ts.map +1 -1
  293. package/dist/utils/stealth-utils.js +4 -4
  294. package/dist/utils/stealth-utils.js.map +1 -1
  295. package/dist/webhooks/types.d.ts +4 -0
  296. package/dist/webhooks/types.d.ts.map +1 -1
  297. package/dist/webhooks/webhook-dispatcher.d.ts +80 -12
  298. package/dist/webhooks/webhook-dispatcher.d.ts.map +1 -1
  299. package/dist/webhooks/webhook-dispatcher.js +497 -74
  300. package/dist/webhooks/webhook-dispatcher.js.map +1 -1
  301. package/docs/archive/ISSUES-legacy-2026-04-24.md +644 -0
  302. package/docs/dependency-risk.md +25 -0
  303. package/docs/testing-runbook.md +166 -0
  304. package/docs/usage-guide.md +2 -1
  305. package/package.json +34 -16
package/dist/index.js CHANGED
@@ -32,6 +32,8 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
32
32
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
33
33
  import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
34
34
  import { createRequire } from "module";
35
+ import crypto from "node:crypto";
36
+ import { pathToFileURL } from "node:url";
35
37
  import { AuthManager } from "./auth/auth-manager.js";
36
38
  import { SessionManager } from "./session/session-manager.js";
37
39
  // Read version from package.json
@@ -43,15 +45,126 @@ import { ToolHandlers, buildToolDefinitions } from "./tools/index.js";
43
45
  import { ResourceHandlers } from "./resources/resource-handlers.js";
44
46
  import { SettingsManager } from "./utils/settings-manager.js";
45
47
  import { CliHandler } from "./utils/cli-handler.js";
46
- import { CONFIG } from "./config.js";
48
+ import { CONFIG, ensureDirectories } from "./config.js";
47
49
  import { log } from "./utils/logger.js";
48
50
  import { audit, getAuditLogger } from "./utils/audit-logger.js";
49
51
  import { checkSecurityContext } from "./utils/security.js";
50
52
  import { getMCPAuthenticator, authenticateMCPRequest } from "./auth/mcp-auth.js";
53
+ import { getComplianceTools, handleComplianceToolCall, } from "./compliance/compliance-tools.js";
54
+ import { getPrivacyNoticeManager, getPrivacyNoticeCLIText } from "./compliance/privacy-notice.js";
55
+ import { runRetentionPolicies } from "./compliance/retention-engine.js";
56
+ import { getBreachDetector } from "./compliance/breach-detection.js";
57
+ import { exportToSIEM } from "./compliance/siem-exporter.js";
58
+ const LIST_TOOLS_PAGE_SIZE = 25;
59
+ const TOOL_NAMES = [
60
+ "ask_question", "add_notebook", "list_notebooks", "get_notebook", "select_notebook",
61
+ "update_notebook", "remove_notebook", "search_notebooks", "get_library_stats",
62
+ "export_library", "get_quota", "set_quota_tier", "get_project_info", "create_notebook",
63
+ "batch_create_notebooks", "sync_library", "list_sessions", "close_session", "reset_session",
64
+ "get_health", "setup_auth", "re_auth", "cleanup_data", "list_sources", "add_source",
65
+ "add_folder", "remove_source", "generate_audio_overview", "get_audio_status", "download_audio",
66
+ "generate_video_overview", "get_video_status", "generate_data_table", "get_data_table",
67
+ "configure_webhook", "list_webhooks", "test_webhook", "remove_webhook", "deep_research",
68
+ "gemini_query", "get_research_status", "upload_document", "query_document", "list_documents",
69
+ "delete_document", "query_chunked_document", "get_query_history", "get_notebook_chat_history",
70
+ "submit_dsar", "export_user_data", "request_data_erasure",
71
+ "grant_consent", "revoke_consent", "report_security_incident", "collect_audit_evidence",
72
+ "generate_compliance_report",
73
+ ];
74
+ function isToolName(name) {
75
+ return TOOL_NAMES.includes(name);
76
+ }
77
+ function toToolArgs(value) {
78
+ if (value && typeof value === "object" && !Array.isArray(value)) {
79
+ return Object.fromEntries(Object.entries(value));
80
+ }
81
+ return {};
82
+ }
83
+ function asToolInput(args) {
84
+ return args;
85
+ }
86
+ function parseListToolsCursor(cursor, totalTools) {
87
+ if (!cursor)
88
+ return 0;
89
+ const offset = Number.parseInt(cursor, 10);
90
+ if (!Number.isInteger(offset) || offset < 0 || offset >= totalTools)
91
+ return 0;
92
+ return offset;
93
+ }
94
+ function classifyToolError(error) {
95
+ const message = error instanceof Error ? error.message : String(error);
96
+ return /\b(transport|protocol|json-?rpc|connection|stdio|notification)\b/i.test(message)
97
+ ? "transport"
98
+ : "domain";
99
+ }
100
+ const TOOLS_EXEMPT_FROM_AUTH = new Set([
101
+ "ask_question", "add_notebook", "list_notebooks", "get_notebook", "select_notebook",
102
+ "update_notebook", "remove_notebook", "search_notebooks", "get_library_stats",
103
+ "get_quota", "set_quota_tier", "get_project_info", "create_notebook",
104
+ "batch_create_notebooks", "sync_library", "list_sessions", "close_session", "reset_session",
105
+ "get_health", "list_sources", "add_source", "remove_source",
106
+ "generate_audio_overview", "get_audio_status", "generate_video_overview", "get_video_status",
107
+ "generate_data_table", "get_data_table", "list_webhooks",
108
+ "deep_research", "gemini_query", "get_research_status",
109
+ "query_document", "list_documents", "query_chunked_document",
110
+ "get_query_history", "get_notebook_chat_history",
111
+ ]);
112
+ const TOOLS_REQUIRING_AUTH = new Set([
113
+ "add_folder",
114
+ "cleanup_data",
115
+ "export_library",
116
+ "setup_auth",
117
+ "re_auth",
118
+ "configure_webhook",
119
+ "remove_webhook",
120
+ "test_webhook",
121
+ "delete_document",
122
+ "upload_document",
123
+ "download_audio",
124
+ // Compliance — destructive or privileged operations.
125
+ "submit_dsar",
126
+ "export_user_data",
127
+ "request_data_erasure",
128
+ "grant_consent",
129
+ "revoke_consent",
130
+ "report_security_incident",
131
+ "collect_audit_evidence",
132
+ "generate_compliance_report",
133
+ ]);
134
+ const ADVANCED_TOOLS = new Set([
135
+ "export_library",
136
+ "list_sessions",
137
+ "close_session",
138
+ "reset_session",
139
+ "cleanup_data",
140
+ "configure_webhook",
141
+ "list_webhooks",
142
+ "test_webhook",
143
+ "remove_webhook",
144
+ "deep_research",
145
+ "gemini_query",
146
+ "get_research_status",
147
+ "upload_document",
148
+ "query_document",
149
+ "list_documents",
150
+ "delete_document",
151
+ "query_chunked_document",
152
+ "get_query_history",
153
+ "get_notebook_chat_history",
154
+ "submit_dsar",
155
+ "export_user_data",
156
+ "request_data_erasure",
157
+ "grant_consent",
158
+ "revoke_consent",
159
+ "report_security_incident",
160
+ "collect_audit_evidence",
161
+ "generate_compliance_report",
162
+ ]);
51
163
  /**
52
164
  * Main MCP Server Class
53
165
  */
54
- class NotebookLMMCPServer {
166
+ export class NotebookLMMCPServer {
167
+ options;
55
168
  server;
56
169
  authManager;
57
170
  sessionManager;
@@ -61,8 +174,12 @@ class NotebookLMMCPServer {
61
174
  settingsManager;
62
175
  toolDefinitions;
63
176
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
64
- toolRegistry;
65
- constructor() {
177
+ toolRegistry = new Map();
178
+ complianceToolNames;
179
+ retentionTimer;
180
+ advancedToolsEnabled;
181
+ constructor(options = {}) {
182
+ this.options = options;
66
183
  // Initialize MCP Server
67
184
  this.server = new Server({
68
185
  name: "notebooklm-mcp",
@@ -72,8 +189,7 @@ class NotebookLMMCPServer {
72
189
  tools: {},
73
190
  resources: {},
74
191
  prompts: {},
75
- completions: {}, // Required for completion/complete handler
76
- logging: {},
192
+ completions: {},
77
193
  },
78
194
  });
79
195
  // Initialize managers
@@ -81,21 +197,37 @@ class NotebookLMMCPServer {
81
197
  this.sessionManager = new SessionManager(this.authManager);
82
198
  this.library = new NotebookLibrary();
83
199
  this.settingsManager = new SettingsManager();
200
+ this.advancedToolsEnabled = process.env.NLMCP_ADVANCED_TOOLS === "1";
84
201
  // Initialize handlers
85
202
  this.toolHandlers = new ToolHandlers(this.sessionManager, this.authManager, this.library);
86
203
  this.resourceHandlers = new ResourceHandlers(this.library);
87
204
  // Build and Filter tool definitions
88
205
  const allTools = buildToolDefinitions(this.library);
89
- this.toolDefinitions = this.settingsManager.filterTools(allTools);
206
+ this.toolDefinitions = this.filterAdvancedTools(this.settingsManager.filterTools(allTools));
207
+ // Track compliance tool names for the short-circuit dispatch path.
208
+ this.complianceToolNames = new Set(this.filterAdvancedTools(getComplianceTools()).map((t) => t.name));
90
209
  // Setup handlers
91
210
  this.setupHandlers();
92
- this.setupShutdownHandlers();
211
+ if (this.options.registerShutdownHandlers !== false) {
212
+ this.setupShutdownHandlers();
213
+ }
93
214
  const activeSettings = this.settingsManager.getEffectiveSettings();
94
215
  log.info("🚀 NotebookLM MCP Server initialized");
95
216
  log.info(` Version: ${VERSION}`);
96
217
  log.info(` Node: ${process.version}`);
97
218
  log.info(` Platform: ${process.platform}`);
98
219
  log.info(` Profile: ${activeSettings.profile} (${this.toolDefinitions.length} tools active)`);
220
+ log.info(` Advanced tools: ${this.advancedToolsEnabled ? "enabled" : "disabled"}`);
221
+ }
222
+ filterAdvancedTools(tools) {
223
+ if (this.advancedToolsEnabled)
224
+ return tools;
225
+ return tools.filter((tool) => !isToolName(tool.name) || !ADVANCED_TOOLS.has(tool.name));
226
+ }
227
+ filterAdvancedToolRegistry(registry) {
228
+ if (this.advancedToolsEnabled)
229
+ return registry;
230
+ return new Map(Array.from(registry.entries()).filter(([name]) => !isToolName(name) || !ADVANCED_TOOLS.has(name)));
99
231
  }
100
232
  /**
101
233
  * Setup MCP request handlers
@@ -104,99 +236,124 @@ class NotebookLMMCPServer {
104
236
  // Register Resource Handlers (Resources, Templates, Completions)
105
237
  this.resourceHandlers.registerHandlers(this.server);
106
238
  // Build tool registry once (not per-request)
107
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
108
- this.toolRegistry = new Map([
239
+ this.toolRegistry = this.filterAdvancedToolRegistry(new Map([
109
240
  // Ask Question
110
- ["ask_question", (a, p) => this.toolHandlers.handleAskQuestion(a, p)],
241
+ ["ask_question", (args, progress) => this.toolHandlers.handleAskQuestion(asToolInput(args), progress)],
111
242
  // Notebook Management
112
- ["add_notebook", (a) => this.toolHandlers.handleAddNotebook(a)],
243
+ ["add_notebook", (args) => this.toolHandlers.handleAddNotebook(asToolInput(args))],
113
244
  ["list_notebooks", () => this.toolHandlers.handleListNotebooks()],
114
- ["get_notebook", (a) => this.toolHandlers.handleGetNotebook(a)],
115
- ["select_notebook", (a) => this.toolHandlers.handleSelectNotebook(a)],
116
- ["update_notebook", (a) => this.toolHandlers.handleUpdateNotebook(a)],
117
- ["remove_notebook", (a) => this.toolHandlers.handleRemoveNotebook(a)],
118
- ["search_notebooks", (a) => this.toolHandlers.handleSearchNotebooks(a)],
245
+ ["get_notebook", (args) => this.toolHandlers.handleGetNotebook(asToolInput(args))],
246
+ ["select_notebook", (args) => this.toolHandlers.handleSelectNotebook(asToolInput(args))],
247
+ ["update_notebook", (args) => this.toolHandlers.handleUpdateNotebook(asToolInput(args))],
248
+ ["remove_notebook", (args) => this.toolHandlers.handleRemoveNotebook(asToolInput(args))],
249
+ ["search_notebooks", (args) => this.toolHandlers.handleSearchNotebooks(asToolInput(args))],
119
250
  ["get_library_stats", () => this.toolHandlers.handleGetLibraryStats()],
120
- ["export_library", (a) => this.toolHandlers.handleExportLibrary(a)],
251
+ ["export_library", (args) => this.toolHandlers.handleExportLibrary(asToolInput(args))],
121
252
  // Quota & System
122
- ["get_quota", (a) => this.toolHandlers.handleGetQuota(a)],
123
- ["set_quota_tier", (a) => this.toolHandlers.handleSetQuotaTier(a)],
253
+ ["get_quota", (args) => this.toolHandlers.handleGetQuota(asToolInput(args))],
254
+ ["set_quota_tier", (args) => this.toolHandlers.handleSetQuotaTier(asToolInput(args))],
124
255
  ["get_project_info", () => this.toolHandlers.handleGetProjectInfo()],
125
256
  // Notebook Creation
126
- ["create_notebook", (a, p) => this.toolHandlers.handleCreateNotebook(a, p)],
127
- ["batch_create_notebooks", (a, p) => this.toolHandlers.handleBatchCreateNotebooks(a, p)],
128
- ["sync_library", (a) => this.toolHandlers.handleSyncLibrary(a)],
257
+ ["create_notebook", (args, progress) => this.toolHandlers.handleCreateNotebook(asToolInput(args), progress)],
258
+ ["batch_create_notebooks", (args, progress) => this.toolHandlers.handleBatchCreateNotebooks(asToolInput(args), progress)],
259
+ ["sync_library", (args) => this.toolHandlers.handleSyncLibrary(asToolInput(args))],
129
260
  // Session Management
130
261
  ["list_sessions", () => this.toolHandlers.handleListSessions()],
131
- ["close_session", (a) => this.toolHandlers.handleCloseSession(a)],
132
- ["reset_session", (a) => this.toolHandlers.handleResetSession(a)],
133
- ["get_health", (a) => this.toolHandlers.handleGetHealth(a)],
262
+ ["close_session", (args) => this.toolHandlers.handleCloseSession(asToolInput(args))],
263
+ ["reset_session", (args) => this.toolHandlers.handleResetSession(asToolInput(args))],
264
+ ["get_health", (args) => this.toolHandlers.handleGetHealth(asToolInput(args))],
134
265
  // Auth
135
- ["setup_auth", (a, p) => this.toolHandlers.handleSetupAuth(a, p)],
136
- ["re_auth", (a, p) => this.toolHandlers.handleReAuth(a, p)],
137
- ["cleanup_data", (a) => this.toolHandlers.handleCleanupData(a)],
266
+ ["setup_auth", (args, progress) => this.toolHandlers.handleSetupAuth(asToolInput(args), progress)],
267
+ ["re_auth", (args, progress) => this.toolHandlers.handleReAuth(asToolInput(args), progress)],
268
+ ["cleanup_data", (args) => this.toolHandlers.handleCleanupData(asToolInput(args))],
138
269
  // Sources
139
- ["list_sources", (a) => this.toolHandlers.handleListSources(a)],
140
- ["add_source", (a) => this.toolHandlers.handleAddSource(a)],
141
- ["add_folder", (a, p) => this.toolHandlers.handleAddFolder(a, p)],
142
- ["remove_source", (a) => this.toolHandlers.handleRemoveSource(a)],
270
+ ["list_sources", (args) => this.toolHandlers.handleListSources(asToolInput(args))],
271
+ ["add_source", (args) => this.toolHandlers.handleAddSource(asToolInput(args))],
272
+ ["add_folder", (args, progress) => this.toolHandlers.handleAddFolder(asToolInput(args), progress)],
273
+ ["remove_source", (args) => this.toolHandlers.handleRemoveSource(asToolInput(args))],
143
274
  // Audio / Video / Data Table
144
- ["generate_audio_overview", (a) => this.toolHandlers.handleGenerateAudioOverview(a)],
145
- ["get_audio_status", (a) => this.toolHandlers.handleGetAudioStatus(a)],
146
- ["download_audio", (a) => this.toolHandlers.handleDownloadAudio(a)],
147
- ["generate_video_overview", (a) => this.toolHandlers.handleGenerateVideoOverview(a)],
148
- ["get_video_status", (a) => this.toolHandlers.handleGetVideoStatus(a)],
149
- ["generate_data_table", (a) => this.toolHandlers.handleGenerateDataTable(a)],
150
- ["get_data_table", (a) => this.toolHandlers.handleGetDataTable(a)],
275
+ ["generate_audio_overview", (args) => this.toolHandlers.handleGenerateAudioOverview(asToolInput(args))],
276
+ ["get_audio_status", (args) => this.toolHandlers.handleGetAudioStatus(asToolInput(args))],
277
+ ["download_audio", (args) => this.toolHandlers.handleDownloadAudio(asToolInput(args))],
278
+ ["generate_video_overview", (args) => this.toolHandlers.handleGenerateVideoOverview(asToolInput(args))],
279
+ ["get_video_status", (args) => this.toolHandlers.handleGetVideoStatus(asToolInput(args))],
280
+ ["generate_data_table", (args) => this.toolHandlers.handleGenerateDataTable(asToolInput(args))],
281
+ ["get_data_table", (args) => this.toolHandlers.handleGetDataTable(asToolInput(args))],
151
282
  // Webhooks
152
- ["configure_webhook", (a) => this.toolHandlers.handleConfigureWebhook(a)],
283
+ ["configure_webhook", (args) => this.toolHandlers.handleConfigureWebhook(asToolInput(args))],
153
284
  ["list_webhooks", () => this.toolHandlers.handleListWebhooks()],
154
- ["test_webhook", (a) => this.toolHandlers.handleTestWebhook(a)],
155
- ["remove_webhook", (a) => this.toolHandlers.handleRemoveWebhook(a)],
285
+ ["test_webhook", (args) => this.toolHandlers.handleTestWebhook(asToolInput(args))],
286
+ ["remove_webhook", (args) => this.toolHandlers.handleRemoveWebhook(asToolInput(args))],
156
287
  // Gemini API
157
- ["deep_research", (a, p) => this.toolHandlers.handleDeepResearch(a, p)],
158
- ["gemini_query", (a) => this.toolHandlers.handleGeminiQuery(a)],
159
- ["get_research_status", (a) => this.toolHandlers.handleGetResearchStatus(a)],
160
- ["upload_document", (a) => this.toolHandlers.handleUploadDocument(a)],
161
- ["query_document", (a) => this.toolHandlers.handleQueryDocument(a)],
162
- ["list_documents", (a) => this.toolHandlers.handleListDocuments(a)],
163
- ["delete_document", (a) => this.toolHandlers.handleDeleteDocument(a)],
164
- ["query_chunked_document", (a) => this.toolHandlers.handleQueryChunkedDocument(a)],
165
- ["get_query_history", (a) => this.toolHandlers.handleGetQueryHistory(a)],
166
- ["get_notebook_chat_history", (a) => this.toolHandlers.handleGetNotebookChatHistory(a)],
167
- ]);
168
- // List available tools
169
- this.server.setRequestHandler(ListToolsRequestSchema, async () => {
288
+ ["deep_research", (args, progress) => this.toolHandlers.handleDeepResearch(asToolInput(args), progress)],
289
+ ["gemini_query", (args) => this.toolHandlers.handleGeminiQuery(asToolInput(args))],
290
+ ["get_research_status", (args) => this.toolHandlers.handleGetResearchStatus(asToolInput(args))],
291
+ ["upload_document", (args) => this.toolHandlers.handleUploadDocument(asToolInput(args))],
292
+ ["query_document", (args) => this.toolHandlers.handleQueryDocument(asToolInput(args))],
293
+ ["list_documents", (args) => this.toolHandlers.handleListDocuments(asToolInput(args))],
294
+ ["delete_document", (args) => this.toolHandlers.handleDeleteDocument(asToolInput(args))],
295
+ ["query_chunked_document", (args) => this.toolHandlers.handleQueryChunkedDocument(asToolInput(args))],
296
+ ["get_query_history", (args) => this.toolHandlers.handleGetQueryHistory(asToolInput(args))],
297
+ ["get_notebook_chat_history", (args) => this.toolHandlers.handleGetNotebookChatHistory(asToolInput(args))],
298
+ ]));
299
+ // Startup assertion: every dispatched tool must be explicitly auth-classified (I313)
300
+ for (const toolName of this.toolRegistry.keys()) {
301
+ if (isToolName(toolName) && !TOOLS_REQUIRING_AUTH.has(toolName) && !TOOLS_EXEMPT_FROM_AUTH.has(toolName)) {
302
+ log.warning(`⚠️ Tool '${toolName}' not in auth lists — defaulting to unauthenticated`);
303
+ }
304
+ }
305
+ for (const toolName of this.complianceToolNames) {
306
+ if (!isToolName(toolName)) {
307
+ // Compliance tools not in TOOL_NAMES use read-auth by default; log for audit visibility
308
+ log.info(` [auth] compliance:'${toolName}' → read-auth (not in TOOLS_REQUIRING_AUTH)`);
309
+ }
310
+ }
311
+ // List available tools — rebuild each call so ask_question description reflects current notebook (I022)
312
+ this.server.setRequestHandler(ListToolsRequestSchema, async (request) => {
170
313
  log.info("📋 [MCP] list_tools request received");
314
+ const allTools = buildToolDefinitions(this.library);
315
+ const tools = this.filterAdvancedTools(this.settingsManager.filterTools(allTools));
316
+ const offset = parseListToolsCursor(request.params?.cursor, tools.length);
317
+ const page = tools.slice(offset, offset + LIST_TOOLS_PAGE_SIZE);
318
+ const nextOffset = offset + page.length;
171
319
  return {
172
- tools: this.toolDefinitions,
320
+ tools: page,
321
+ ...(nextOffset < tools.length && { nextCursor: String(nextOffset) }),
173
322
  };
174
323
  });
175
324
  // Handle tool calls
176
- this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
325
+ this.server.setRequestHandler(CallToolRequestSchema, async (request) => log.withContext({
326
+ correlation_id: crypto.randomUUID(),
327
+ tool: request.params.name,
328
+ }, async () => {
177
329
  const { name, arguments: args } = request.params;
178
- const progressToken = args?._meta?.progressToken;
179
- const authToken = args?._meta?.authToken || process.env.NLMCP_AUTH_TOKEN;
330
+ // Per MCP spec, _meta lives on request.params, not inside arguments
331
+ const meta = request.params._meta;
332
+ const progressToken = meta?.progressToken;
333
+ const authToken = meta?.authToken || process.env.NLMCP_AUTH_TOKEN;
180
334
  log.info(`🔧 [MCP] Tool call: ${name}`);
181
335
  if (progressToken) {
182
336
  log.info(` 📊 Progress token: ${progressToken}`);
183
337
  }
184
338
  // === SECURITY: MCP Authentication ===
185
- // Tools that access the filesystem always require auth, even if globally disabled
186
- const TOOLS_REQUIRING_AUTH = ["add_folder", "cleanup_data", "export_library"];
187
- const requiresAuth = TOOLS_REQUIRING_AUTH.includes(name);
339
+ // Tools that touch the filesystem, wipe credentials, dispatch outbound
340
+ // HTTP, delete remote resources, or exercise GDPR data-subject rights
341
+ // always require auth, even if globally disabled via NLMCP_AUTH_DISABLED.
342
+ const requiresAuth = isToolName(name) && TOOLS_REQUIRING_AUTH.has(name);
188
343
  const authResult = requiresAuth
189
- ? await authenticateMCPRequest(authToken, name, true)
190
- : await authenticateMCPRequest(authToken, name);
344
+ ? await authenticateMCPRequest(authToken, name, true, "admin")
345
+ : await authenticateMCPRequest(authToken, name, false, "read");
191
346
  if (!authResult.authenticated) {
192
347
  log.warning(`🔒 [MCP] Authentication failed for tool: ${name}`);
193
348
  return {
349
+ isError: true,
194
350
  content: [
195
351
  {
196
352
  type: "text",
197
353
  text: JSON.stringify({
198
354
  success: false,
199
355
  error: authResult.error || "Authentication required",
356
+ _errorType: "domain",
200
357
  }),
201
358
  },
202
359
  ],
@@ -218,19 +375,33 @@ class NotebookLMMCPServer {
218
375
  }
219
376
  };
220
377
  try {
378
+ // Compliance tools have their own dispatcher that returns MCP-shaped
379
+ // TextContent[] directly. Short-circuit before the generic wrapper
380
+ // so dashboard/report text isn't double-encoded as JSON.
381
+ if (this.complianceToolNames.has(name)) {
382
+ const content = await handleComplianceToolCall(name, toToolArgs(args));
383
+ return { content };
384
+ }
221
385
  const handler = this.toolRegistry.get(name);
222
386
  if (!handler) {
223
387
  log.error(`❌ [MCP] Unknown tool: ${name}`);
388
+ const errorBody = {
389
+ success: false,
390
+ error: `Unknown tool: ${name}`,
391
+ _errorType: "domain",
392
+ };
224
393
  return {
394
+ isError: true,
225
395
  content: [
226
396
  {
227
397
  type: "text",
228
- text: JSON.stringify({ success: false, error: `Unknown tool: ${name}` }, null, 2),
398
+ text: JSON.stringify(errorBody, null, 2),
229
399
  },
230
400
  ],
401
+ structuredContent: errorBody,
231
402
  };
232
403
  }
233
- const result = await handler(args, sendProgress);
404
+ const result = await handler(toToolArgs(args), sendProgress);
234
405
  // Return result
235
406
  return {
236
407
  content: [
@@ -239,37 +410,77 @@ class NotebookLMMCPServer {
239
410
  text: JSON.stringify(result, null, 2),
240
411
  },
241
412
  ],
413
+ structuredContent: result,
242
414
  };
243
415
  }
244
416
  catch (error) {
245
- const errorMessage = error instanceof Error ? error.message : String(error);
246
- log.error(`❌ [MCP] Tool execution error: ${errorMessage}`);
417
+ const rawMessage = error instanceof Error ? error.message : String(error);
418
+ const errorType = classifyToolError(error);
419
+ log.error(`❌ [MCP] Tool execution error for '${name}': ${rawMessage}`);
420
+ // Sanitize before returning to client: strip absolute paths and stack fragments (I328)
421
+ const sanitized = rawMessage
422
+ .replace(/(?:\/[^\s/:,'"]+)+/g, "[path]")
423
+ .replace(/\bat\s+\S+\s+\(\S+:\d+:\d+\)/g, "")
424
+ .trim();
425
+ const errorBody = {
426
+ success: false,
427
+ data: null,
428
+ error: sanitized,
429
+ _errorType: errorType,
430
+ };
247
431
  return {
432
+ isError: true,
248
433
  content: [
249
434
  {
250
435
  type: "text",
251
- text: JSON.stringify({
252
- success: false,
253
- error: errorMessage,
254
- }, null, 2),
436
+ text: JSON.stringify(errorBody, null, 2),
255
437
  },
256
438
  ],
439
+ structuredContent: errorBody,
257
440
  };
258
441
  }
259
- });
442
+ }));
260
443
  }
261
444
  /**
262
445
  * Setup graceful shutdown handlers
263
446
  */
264
447
  setupShutdownHandlers() {
265
448
  let shuttingDown = false;
266
- const shutdown = async (signal) => {
449
+ const flushFatalError = async (signal, error) => {
450
+ if (signal !== "uncaughtException" && signal !== "unhandledRejection")
451
+ return;
452
+ const message = error instanceof Error ? error.message : String(error ?? signal);
453
+ try {
454
+ await this.server.notification({
455
+ method: "notifications/message",
456
+ params: {
457
+ level: "error",
458
+ logger: "notebooklm-mcp",
459
+ data: {
460
+ success: false,
461
+ error: message,
462
+ _errorType: "transport",
463
+ signal,
464
+ },
465
+ },
466
+ });
467
+ }
468
+ catch (notifyError) {
469
+ log.warning(`⚠️ Failed to flush fatal MCP error notification: ${notifyError instanceof Error ? notifyError.message : String(notifyError)}`);
470
+ }
471
+ };
472
+ const shutdown = async (signal, error) => {
267
473
  if (shuttingDown) {
268
474
  return;
269
475
  }
270
476
  shuttingDown = true;
271
477
  log.info(`\n🛑 Received ${signal}, shutting down gracefully...`);
272
478
  try {
479
+ await flushFatalError(signal, error);
480
+ if (this.retentionTimer) {
481
+ clearInterval(this.retentionTimer);
482
+ this.retentionTimer = undefined;
483
+ }
273
484
  // Cleanup tool handlers (closes all sessions)
274
485
  await this.toolHandlers.cleanup();
275
486
  // Close server
@@ -282,28 +493,108 @@ class NotebookLMMCPServer {
282
493
  process.exit(1);
283
494
  }
284
495
  };
285
- const requestShutdown = (signal) => {
286
- void shutdown(signal);
496
+ const requestShutdown = (signal, error) => {
497
+ void shutdown(signal, error);
287
498
  };
288
499
  process.on("SIGINT", () => requestShutdown("SIGINT"));
289
500
  process.on("SIGTERM", () => requestShutdown("SIGTERM"));
290
501
  process.on("uncaughtException", (error) => {
291
502
  log.error(`💥 Uncaught exception: ${error}`);
292
503
  log.error(error.stack || "");
293
- requestShutdown("uncaughtException");
504
+ requestShutdown("uncaughtException", error);
294
505
  });
295
506
  process.on("unhandledRejection", (reason, promise) => {
296
507
  log.error(`💥 Unhandled rejection at: ${promise}`);
297
508
  log.error(`Reason: ${reason}`);
298
- requestShutdown("unhandledRejection");
509
+ requestShutdown("unhandledRejection", reason);
299
510
  });
300
511
  }
512
+ /**
513
+ * Bootstrap the compliance module at startup:
514
+ * 1. Display the privacy notice to stderr on first run and auto-record
515
+ * acknowledgment (stdio MCP cannot prompt interactively, so the
516
+ * notice is informational — operators wishing for explicit consent
517
+ * should call the `grant_consent` MCP tool).
518
+ * 2. Run the retention engine immediately and schedule it every 6 hours
519
+ * so archive/delete policies actually fire. Timer is unref()'d so it
520
+ * does not keep the process alive on shutdown.
521
+ */
522
+ async bootstrapCompliance() {
523
+ try {
524
+ const privacy = getPrivacyNoticeManager();
525
+ if (await privacy.needsDisplay()) {
526
+ log.info("");
527
+ log.info("━━━ Privacy notice (first run) ━━━");
528
+ // Multi-line notice — write via stderr directly so formatting survives.
529
+ process.stderr.write(getPrivacyNoticeCLIText() + "\n");
530
+ log.info("━━━ End privacy notice ━━━");
531
+ log.info("");
532
+ await privacy.acknowledge("auto");
533
+ log.info("📜 Privacy notice recorded (method=auto). Use grant_consent to record explicit consent.");
534
+ }
535
+ }
536
+ catch (err) {
537
+ log.warning(`⚠️ Privacy notice bootstrap failed: ${err instanceof Error ? err.message : String(err)}`);
538
+ }
539
+ try {
540
+ const runOnce = async () => {
541
+ const results = await runRetentionPolicies();
542
+ if (results.length > 0) {
543
+ log.info(`🗂️ Retention engine ran ${results.length} policy(ies)`);
544
+ }
545
+ };
546
+ await runOnce();
547
+ const SIX_HOURS_MS = 6 * 60 * 60 * 1000;
548
+ this.retentionTimer = setInterval(() => {
549
+ void runOnce().catch((err) => log.warning(`⚠️ retention policy run failed: ${err instanceof Error ? err.message : String(err)}`));
550
+ }, SIX_HOURS_MS);
551
+ this.retentionTimer.unref();
552
+ }
553
+ catch (err) {
554
+ log.warning(`⚠️ Retention engine bootstrap failed: ${err instanceof Error ? err.message : String(err)}`);
555
+ }
556
+ // Subscribe breach detector to audit event stream (I244)
557
+ try {
558
+ const breachDetector = getBreachDetector();
559
+ getAuditLogger().onEvent(async (event) => {
560
+ if (event.eventType !== "auth" && event.eventType !== "security")
561
+ return;
562
+ try {
563
+ await breachDetector.checkEvent(event.eventName, event.details);
564
+ }
565
+ catch (err) {
566
+ log.warning(`breach detector checkEvent failed: ${err instanceof Error ? err.message : String(err)}`);
567
+ }
568
+ });
569
+ log.info("🛡️ Breach detector subscribed to audit events");
570
+ }
571
+ catch (err) {
572
+ log.warning(`⚠️ Breach detector bootstrap failed: ${err instanceof Error ? err.message : String(err)}`);
573
+ }
574
+ // Pipe audit events to SIEM when enabled (I247). exportToSIEM is a no-op
575
+ // when NLMCP_SIEM_ENABLED is not true.
576
+ try {
577
+ getAuditLogger().onEvent(async (event) => {
578
+ const severity = event.eventType === "security" && typeof event.details.severity === "string"
579
+ ? event.details.severity
580
+ : event.success ? "info" : "error";
581
+ await exportToSIEM(event.eventType, event.eventName, severity, `${event.eventType}:${event.eventName}`, "audit-logger", event.details);
582
+ });
583
+ log.info("📡 SIEM audit export bridge registered");
584
+ }
585
+ catch (err) {
586
+ log.warning(`⚠️ SIEM bootstrap failed: ${err instanceof Error ? err.message : String(err)}`);
587
+ }
588
+ }
301
589
  /**
302
590
  * Start the MCP server
303
591
  */
304
- async start() {
592
+ async start(transport = new StdioServerTransport()) {
305
593
  log.info("🎯 Starting NotebookLM MCP Server (Security Hardened)...");
306
594
  log.info("");
595
+ if (process.env.NODE_ENV !== "test") {
596
+ ensureDirectories();
597
+ }
307
598
  // Security: Check security context and warn about issues
308
599
  const securityCheck = checkSecurityContext();
309
600
  if (!securityCheck.secure) {
@@ -317,6 +608,21 @@ class NotebookLMMCPServer {
317
608
  const mcpAuth = getMCPAuthenticator();
318
609
  await mcpAuth.initialize();
319
610
  const authStatus = mcpAuth.getStatus();
611
+ // Audit: verify hash-chain integrity from previous run (I218)
612
+ try {
613
+ const integrity = await getAuditLogger().verifyIntegrity();
614
+ if (!integrity.valid) {
615
+ log.warning(`⚠️ Audit log integrity check failed: ${integrity.errors.join(", ")}`);
616
+ }
617
+ else {
618
+ log.info("🔒 Audit log integrity verified");
619
+ }
620
+ }
621
+ catch (err) {
622
+ log.warning(`⚠️ Audit integrity check error: ${err instanceof Error ? err.message : String(err)}`);
623
+ }
624
+ // Compliance: surface privacy notice on first run and schedule retention.
625
+ await this.bootstrapCompliance();
320
626
  // Audit: Log server startup
321
627
  await audit.system("server_start", {
322
628
  version: VERSION,
@@ -337,10 +643,15 @@ class NotebookLMMCPServer {
337
643
  log.info(` Session Timeout: ${CONFIG.sessionTimeout}s`);
338
644
  log.info(` Stealth: ${CONFIG.stealthEnabled}`);
339
645
  log.info(` Audit Logging: ${getAuditLogger().getStats().totalEvents >= 0 ? 'enabled' : 'disabled'}`);
340
- log.info(` MCP Authentication: ${authStatus.enabled ? 'enabled' : 'disabled'}`);
646
+ // I314: verbose effective auth state
647
+ log.info(` MCP Authentication: ${authStatus.enabled ? 'ENABLED' : 'DISABLED'}`);
648
+ if (authStatus.enabled) {
649
+ const tokenSrc = process.env.NLMCP_AUTH_TOKEN ? "env var" : "hash file";
650
+ log.info(` Admin token: ${authStatus.hasToken ? `configured (${tokenSrc})` : 'NOT SET — all admin tools will reject'}`);
651
+ log.info(` Read-only token: ${authStatus.hasReadOnlyToken ? 'configured' : 'not set (admin token used for read)'}`);
652
+ log.info(` Admin tools (${TOOLS_REQUIRING_AUTH.size}): require auth even if globally disabled`);
653
+ }
341
654
  log.info("");
342
- // Create stdio transport
343
- const transport = new StdioServerTransport();
344
655
  // Connect server to transport
345
656
  await this.server.connect(transport);
346
657
  log.success("✅ MCP Server connected via stdio");
@@ -348,19 +659,27 @@ class NotebookLMMCPServer {
348
659
  log.info("");
349
660
  log.info("💡 Available tools:");
350
661
  for (const tool of this.toolDefinitions) {
351
- const desc = tool.description ? tool.description.split('\n')[0] : 'No description'; // First line only
352
- log.info(` - ${tool.name}: ${desc.substring(0, 80)}...`);
662
+ const raw = tool.description ? tool.description.split('\n')[0] : 'No description';
663
+ const desc = raw.replace(/\p{Extended_Pictographic}/gu, '').trim();
664
+ log.info(` - ${tool.name}: ${desc.substring(0, 80)}${desc.length > 80 ? '...' : ''}`);
353
665
  }
354
666
  log.info("");
355
667
  log.info("📖 For documentation, see: README.md");
356
- log.info("📖 For MCP details, see: MCP_INFOS.md");
357
668
  log.info("");
358
669
  }
670
+ async stop() {
671
+ if (this.retentionTimer) {
672
+ clearInterval(this.retentionTimer);
673
+ this.retentionTimer = undefined;
674
+ }
675
+ await this.toolHandlers.cleanup();
676
+ await this.server.close();
677
+ }
359
678
  }
360
679
  /**
361
680
  * Main entry point
362
681
  */
363
- async function main() {
682
+ export async function main() {
364
683
  // Handle CLI commands
365
684
  const args = process.argv.slice(2);
366
685
  if (args.length > 0 && args[0] === "config") {
@@ -394,6 +713,10 @@ async function main() {
394
713
  process.exit(1);
395
714
  }
396
715
  }
397
- // Run the server
398
- main();
716
+ const isDirectRun = process.argv[1]
717
+ ? import.meta.url === pathToFileURL(process.argv[1]).href
718
+ : false;
719
+ if (isDirectRun) {
720
+ void main();
721
+ }
399
722
  //# sourceMappingURL=index.js.map