notebooklm-skill 1.2.0__py3-none-any.whl

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.
mcp_server/__init__.py ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env python3
2
+ """NotebookLM MCP Server package.
3
+
4
+ Exposes NotebookLM operations as MCP tools usable by
5
+ Claude Code, Cursor, Gemini CLI, and other MCP-compatible clients.
6
+ """
mcp_server/server.py ADDED
@@ -0,0 +1,511 @@
1
+ #!/usr/bin/env python3
2
+ """NotebookLM MCP Server.
3
+
4
+ A FastMCP server that exposes 13 NotebookLM tools, usable by Claude Code,
5
+ Cursor, Gemini CLI, and any other MCP-compatible client.
6
+
7
+ Uses notebooklm-py v0.3.4 async API directly — no subprocess calls.
8
+
9
+ Usage:
10
+ # stdio mode (default — for Claude Code / Cursor)
11
+ notebooklm-mcp # after pip install .
12
+ python3 mcp_server/server.py # direct invocation
13
+
14
+ # HTTP mode (for remote / multi-client access)
15
+ notebooklm-mcp --http
16
+ notebooklm-mcp --http --port 8765
17
+
18
+ MCP client configuration examples:
19
+
20
+ # Claude Code — add to .mcp.json
21
+ {
22
+ "mcpServers": {
23
+ "notebooklm": {
24
+ "command": "python3",
25
+ "args": ["/path/to/mcp_server/server.py"]
26
+ }
27
+ }
28
+ }
29
+
30
+ # Cursor — add to .cursor/mcp.json
31
+ {
32
+ "mcpServers": {
33
+ "notebooklm": {
34
+ "command": "python3",
35
+ "args": ["/path/to/mcp_server/server.py"]
36
+ }
37
+ }
38
+ }
39
+
40
+ # Gemini CLI — add to ~/.gemini/settings.json
41
+ {
42
+ "mcpServers": {
43
+ "notebooklm": {
44
+ "command": "python3",
45
+ "args": ["/path/to/mcp_server/server.py"]
46
+ }
47
+ }
48
+ }
49
+
50
+ # HTTP mode (any client that supports SSE/streamable HTTP)
51
+ {
52
+ "mcpServers": {
53
+ "notebooklm": {
54
+ "url": "http://localhost:8765/mcp"
55
+ }
56
+ }
57
+ }
58
+ """
59
+
60
+ import argparse
61
+ import sys
62
+ from pathlib import Path
63
+
64
+ # Allow direct invocation: python3 mcp_server/server.py (without pip install).
65
+ # Has no effect when called via pip-installed `notebooklm-mcp` entry point.
66
+ sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
67
+
68
+ from fastmcp import FastMCP
69
+
70
+ from mcp_server.tools import (
71
+ add_source,
72
+ ask,
73
+ create_notebook,
74
+ delete_notebook,
75
+ download_artifact,
76
+ generate_artifact,
77
+ list_notebooks,
78
+ list_sources,
79
+ research,
80
+ research_pipeline,
81
+ summarize,
82
+ trend_research,
83
+ )
84
+
85
+ # ---------------------------------------------------------------------------
86
+ # Server instance
87
+ # ---------------------------------------------------------------------------
88
+
89
+ mcp = FastMCP(
90
+ "notebooklm",
91
+ instructions=(
92
+ "NotebookLM research engine — create notebooks, ask questions, "
93
+ "generate and download 10 artifact types (audio, video, slides, "
94
+ "report, study-guide, quiz, flashcards, mind-map, infographic, "
95
+ "data-table), and run full research-to-content pipelines."
96
+ ),
97
+ )
98
+
99
+
100
+ # ---------------------------------------------------------------------------
101
+ # Core Tools (7)
102
+ # ---------------------------------------------------------------------------
103
+
104
+
105
+ @mcp.tool()
106
+ async def nlm_create_notebook(
107
+ title: str,
108
+ sources: list[str] | None = None,
109
+ text_sources: list[str] | None = None,
110
+ ) -> dict:
111
+ """Create a NotebookLM notebook, optionally with URL and text sources.
112
+
113
+ Args:
114
+ title: Human-readable notebook title.
115
+ sources: Optional list of URLs to ingest (web pages, PDFs, YouTube).
116
+ text_sources: Optional list of raw text strings to add as sources.
117
+
118
+ Returns:
119
+ Notebook metadata including notebook id and sources added.
120
+
121
+ Example:
122
+ nlm_create_notebook(
123
+ title="AI Safety Research",
124
+ sources=["https://arxiv.org/abs/2401.00001"],
125
+ text_sources=["Additional context about AI alignment..."]
126
+ )
127
+ """
128
+ try:
129
+ return await create_notebook(title, sources, text_sources)
130
+ except Exception as exc:
131
+ return {"error": str(exc), "status": "failed"}
132
+
133
+
134
+ @mcp.tool()
135
+ async def nlm_list() -> dict:
136
+ """List all notebooks in the NotebookLM account.
137
+
138
+ Returns:
139
+ Dict with notebooks list, each containing id and title.
140
+
141
+ Example:
142
+ nlm_list()
143
+ """
144
+ try:
145
+ return await list_notebooks()
146
+ except Exception as exc:
147
+ return {"error": str(exc), "status": "failed"}
148
+
149
+
150
+ @mcp.tool()
151
+ async def nlm_delete(notebook: str) -> dict:
152
+ """Delete a notebook and all its sources. Irreversible.
153
+
154
+ Args:
155
+ notebook: Notebook ID or title to delete.
156
+
157
+ Returns:
158
+ Deletion status and deleted notebook ID.
159
+
160
+ Example:
161
+ nlm_delete(notebook="Old Research")
162
+ """
163
+ try:
164
+ return await delete_notebook(notebook)
165
+ except Exception as exc:
166
+ return {"error": str(exc), "status": "failed"}
167
+
168
+
169
+ @mcp.tool()
170
+ async def nlm_add_source(
171
+ notebook: str,
172
+ url: str | None = None,
173
+ text: str | None = None,
174
+ text_title: str | None = None,
175
+ file_path: str | None = None,
176
+ ) -> dict:
177
+ """Add a source to an existing notebook.
178
+
179
+ Provide exactly one of: url, text, or file_path.
180
+
181
+ Args:
182
+ notebook: Notebook ID or title.
183
+ url: URL to add (web page, PDF, YouTube, etc.).
184
+ text: Raw text content to add as a source.
185
+ text_title: Title for the text source (default "Text Source").
186
+ file_path: Local file path to upload (PDF, TXT, etc.).
187
+
188
+ Returns:
189
+ Source metadata including source id and type.
190
+
191
+ Example:
192
+ nlm_add_source(notebook="My Research", url="https://example.com/paper.pdf")
193
+ nlm_add_source(notebook="My Research", text="Key finding: ...", text_title="Notes")
194
+ """
195
+ try:
196
+ return await add_source(notebook, url=url, text=text, text_title=text_title, file_path=file_path)
197
+ except Exception as exc:
198
+ return {"error": str(exc), "status": "failed"}
199
+
200
+
201
+ @mcp.tool()
202
+ async def nlm_ask(notebook: str, query: str) -> dict:
203
+ """Ask a question against a notebook's ingested sources.
204
+
205
+ The answer is grounded in the notebook's content and includes citations.
206
+
207
+ Args:
208
+ notebook: Notebook ID or title to query.
209
+ query: Natural-language question.
210
+
211
+ Returns:
212
+ Answer text with citations pointing to source passages.
213
+
214
+ Example:
215
+ nlm_ask(notebook="AI Safety Research", query="What are the main risks?")
216
+ """
217
+ try:
218
+ return await ask(notebook, query)
219
+ except Exception as exc:
220
+ return {"error": str(exc), "status": "failed"}
221
+
222
+
223
+ @mcp.tool()
224
+ async def nlm_summarize(notebook: str) -> dict:
225
+ """Generate a summary of a notebook's content.
226
+
227
+ Args:
228
+ notebook: Notebook ID or title.
229
+
230
+ Returns:
231
+ Summary text extracted from all ingested sources.
232
+
233
+ Example:
234
+ nlm_summarize(notebook="AI Safety Research")
235
+ """
236
+ try:
237
+ return await summarize(notebook)
238
+ except Exception as exc:
239
+ return {"error": str(exc), "status": "failed"}
240
+
241
+
242
+ @mcp.tool()
243
+ async def nlm_list_sources(notebook: str) -> dict:
244
+ """List all sources in a notebook.
245
+
246
+ Args:
247
+ notebook: Notebook ID or title.
248
+
249
+ Returns:
250
+ List of sources with id, title, type, and status.
251
+
252
+ Example:
253
+ nlm_list_sources(notebook="AI Safety Research")
254
+ """
255
+ try:
256
+ return await list_sources(notebook)
257
+ except Exception as exc:
258
+ return {"error": str(exc), "status": "failed"}
259
+
260
+
261
+ # ---------------------------------------------------------------------------
262
+ # Artifact Tools (3)
263
+ # ---------------------------------------------------------------------------
264
+
265
+
266
+ @mcp.tool()
267
+ async def nlm_generate(
268
+ notebook: str,
269
+ type: str,
270
+ lang: str = "en",
271
+ instructions: str | None = None,
272
+ ) -> dict:
273
+ """Generate an artifact from a notebook.
274
+
275
+ Supports 10 artifact types. Waits for generation to complete.
276
+
277
+ Args:
278
+ notebook: Notebook ID or title.
279
+ type: Artifact type — one of: audio, video, slides, report, quiz,
280
+ flashcards, mind-map, infographic, data-table, study-guide.
281
+ lang: Language code (default "en"). Used for audio, video, slides,
282
+ report, infographic, data-table, study-guide.
283
+ instructions: Optional generation instructions. Passed as
284
+ "instructions" for audio/video/slides/quiz/flashcards/
285
+ infographic/data-table, or as "extra_instructions" for
286
+ report/study-guide. Not used for mind-map.
287
+
288
+ Returns:
289
+ Generation status with task_id and completion details.
290
+
291
+ Example:
292
+ nlm_generate(notebook="AI Safety", type="audio", lang="en")
293
+ nlm_generate(notebook="AI Safety", type="report", instructions="Add executive summary")
294
+ nlm_generate(notebook="AI Safety", type="quiz", instructions="Focus on chapter 3")
295
+ """
296
+ try:
297
+ return await generate_artifact(notebook, type, lang=lang, instructions=instructions)
298
+ except Exception as exc:
299
+ return {"error": str(exc), "status": "failed"}
300
+
301
+
302
+ @mcp.tool()
303
+ async def nlm_download(
304
+ notebook: str,
305
+ type: str,
306
+ output_path: str,
307
+ output_format: str | None = None,
308
+ ) -> dict:
309
+ """Download a generated artifact to a local file.
310
+
311
+ Supports all 10 artifact types:
312
+ - audio → .m4a
313
+ - video → .mp4
314
+ - slides → PDF (default) or PPTX (output_format="pptx")
315
+ - report → Markdown
316
+ - study-guide → Markdown
317
+ - quiz → JSON (default), Markdown ("markdown"), or HTML ("html")
318
+ - flashcards → JSON (default), Markdown ("markdown"), or HTML ("html")
319
+ - mind-map → JSON
320
+ - infographic → PNG
321
+ - data-table → CSV
322
+
323
+ Args:
324
+ notebook: Notebook ID or title.
325
+ type: Artifact type — audio, video, slides, report, study-guide,
326
+ quiz, flashcards, mind-map, infographic, or data-table.
327
+ output_path: Local file path to save the artifact.
328
+ output_format: Optional output format for types that support it.
329
+ slides: "pdf" (default) or "pptx".
330
+ quiz/flashcards: "json" (default), "markdown", or "html".
331
+
332
+ Returns:
333
+ Download status and output file path.
334
+
335
+ Example:
336
+ nlm_download(notebook="AI Safety", type="audio", output_path="podcast.m4a")
337
+ nlm_download(notebook="AI Safety", type="slides", output_path="deck.pptx", output_format="pptx")
338
+ nlm_download(notebook="AI Safety", type="quiz", output_path="quiz.md", output_format="markdown")
339
+ """
340
+ try:
341
+ return await download_artifact(notebook, type, output_path, output_format=output_format)
342
+ except Exception as exc:
343
+ return {"error": str(exc), "status": "failed"}
344
+
345
+
346
+ @mcp.tool()
347
+ async def nlm_list_artifacts(
348
+ notebook: str,
349
+ type: str | None = None,
350
+ ) -> dict:
351
+ """List sources/artifacts in a notebook.
352
+
353
+ Optionally filter by type.
354
+
355
+ Args:
356
+ notebook: Notebook ID or title.
357
+ type: Optional filter — not yet supported by upstream API,
358
+ returns all sources for now.
359
+
360
+ Returns:
361
+ List of sources in the notebook.
362
+
363
+ Example:
364
+ nlm_list_artifacts(notebook="AI Safety")
365
+ """
366
+ try:
367
+ result = await list_sources(notebook)
368
+ # Future: filter by type when upstream API supports it
369
+ return result
370
+ except Exception as exc:
371
+ return {"error": str(exc), "status": "failed"}
372
+
373
+
374
+ # ---------------------------------------------------------------------------
375
+ # Research Tool (1)
376
+ # ---------------------------------------------------------------------------
377
+
378
+
379
+ @mcp.tool()
380
+ async def nlm_research(
381
+ notebook: str,
382
+ query: str,
383
+ mode: str = "fast",
384
+ ) -> dict:
385
+ """Run a web research query within a notebook.
386
+
387
+ Uses NotebookLM's built-in research capability to find and ingest
388
+ web sources relevant to the query.
389
+
390
+ Args:
391
+ notebook: Notebook ID or title.
392
+ query: Research topic or question.
393
+ mode: Research mode — "fast" or "deep" (default "fast").
394
+
395
+ Returns:
396
+ Research results and status.
397
+
398
+ Example:
399
+ nlm_research(notebook="AI Safety", query="latest alignment techniques", mode="fast")
400
+ """
401
+ try:
402
+ return await research(notebook, query, mode=mode)
403
+ except Exception as exc:
404
+ return {"error": str(exc), "status": "failed"}
405
+
406
+
407
+ # ---------------------------------------------------------------------------
408
+ # Pipeline Tools (2)
409
+ # ---------------------------------------------------------------------------
410
+
411
+
412
+ @mcp.tool()
413
+ async def nlm_research_pipeline(
414
+ sources: list[str],
415
+ questions: list[str],
416
+ output_format: str = "article",
417
+ ) -> dict:
418
+ """Full research-to-content pipeline.
419
+
420
+ End-to-end workflow: creates a notebook from source URLs, asks all
421
+ research questions, and assembles answers into formatted content.
422
+
423
+ Args:
424
+ sources: URLs to ingest as research sources.
425
+ questions: Research questions to investigate.
426
+ output_format: Output format — "article", "thread", or "report"
427
+ (default "article").
428
+
429
+ Returns:
430
+ Notebook ID, individual answers, and assembled content.
431
+
432
+ Example:
433
+ nlm_research_pipeline(
434
+ sources=["https://example.com/paper1", "https://example.com/paper2"],
435
+ questions=["What is the main finding?", "What methodology was used?"],
436
+ output_format="article"
437
+ )
438
+ """
439
+ try:
440
+ return await research_pipeline(sources, questions, output_format)
441
+ except Exception as exc:
442
+ return {"error": str(exc), "status": "failed"}
443
+
444
+
445
+ @mcp.tool()
446
+ async def nlm_trend_research(
447
+ geo: str = "TW",
448
+ count: int = 5,
449
+ platform: str = "threads",
450
+ ) -> dict:
451
+ """Trending topics to researched content pipeline.
452
+
453
+ Fetches current trending topics (via trend-pulse), then for each topic:
454
+ creates a notebook, researches it, and generates platform-ready content.
455
+
456
+ Args:
457
+ geo: Geographic region code (default "TW"). Examples: "US", "JP".
458
+ count: Number of trending topics to process (default 5).
459
+ platform: Target content platform — "threads", "instagram", or
460
+ "article" (default "threads").
461
+
462
+ Returns:
463
+ Trends processed with generated content for each.
464
+
465
+ Example:
466
+ nlm_trend_research(geo="TW", count=3, platform="threads")
467
+ """
468
+ try:
469
+ return await trend_research(geo, count, platform)
470
+ except Exception as exc:
471
+ return {"error": str(exc), "status": "failed"}
472
+
473
+
474
+ # ---------------------------------------------------------------------------
475
+ # Entry point
476
+ # ---------------------------------------------------------------------------
477
+
478
+
479
+ def main() -> None:
480
+ """Parse arguments and start the MCP server."""
481
+ parser = argparse.ArgumentParser(
482
+ description="NotebookLM MCP Server",
483
+ formatter_class=argparse.RawDescriptionHelpFormatter,
484
+ epilog=(
485
+ "Examples:\n"
486
+ " notebooklm-mcp # stdio mode (Claude Code, Cursor)\n"
487
+ " notebooklm-mcp --http # HTTP mode on port 8765\n"
488
+ " notebooklm-mcp --http --port 9000\n"
489
+ ),
490
+ )
491
+ parser.add_argument(
492
+ "--http",
493
+ action="store_true",
494
+ help="Run in HTTP mode instead of stdio (default port 8765)",
495
+ )
496
+ parser.add_argument(
497
+ "--port",
498
+ type=int,
499
+ default=8765,
500
+ help="Port for HTTP mode (default: 8765)",
501
+ )
502
+ args = parser.parse_args()
503
+
504
+ if args.http:
505
+ mcp.run(transport="streamable-http", host="0.0.0.0", port=args.port)
506
+ else:
507
+ mcp.run(transport="stdio")
508
+
509
+
510
+ if __name__ == "__main__":
511
+ main()