docs(readme): expand MCP server setup instructions#170
docs(readme): expand MCP server setup instructions#170s4steve wants to merge 5 commits intoteng-lin:mainfrom
Conversation
Adds a full MCP (Model Context Protocol) SSE server exposing 27 tools covering notebooks, sources, chat, artifacts, and notes APIs. - `src/notebooklm/mcp_server.py`: MCP server implementation - `pyproject.toml`: add `mcp` optional dep group + `notebooklm-mcp` entry point - `Dockerfile`, `docker-compose.yml`, `.dockerignore`: containerization support Install with: `pip install 'notebooklm-py[mcp]'` Run with: `notebooklm-mcp --host 0.0.0.0 --port 8765` Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…s mode - Remove notebooklm-py.png (not needed in repo) - Replace updated_at with is_owner in notebook dict serialization - Add stateless=True to MCP server run() for proper SSE behavior Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Update "Three Ways to Use" to "Four Ways to Use" with MCP Server entry - Add MCP Server quick start section with CLI and Docker usage - Add mcp and all install options to Installation section Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…cker - [Critical] Add optional API key auth middleware (--api-key / NOTEBOOKLM_MCP_API_KEY); returns HTTP 401 without valid Bearer token; warns at startup if unconfigured - [High] Change default --host from 0.0.0.0 to 127.0.0.1; Dockerfile CMD retains explicit 0.0.0.0 for container use - [High] Fix _wait_and_download_media bug: file_path now only set on successful download via dispatch dict, not unconditionally - [Medium] Add _sanitize_text_input() helper; apply to all free-text inputs (instructions, custom_prompt, title, question, content) to prevent prompt injection - [Medium] Add ensure_ascii=False to all json.dumps() calls - [Medium] Refactor _dispatch_tool into per-tool _handle_* functions with a dispatch dict - [Medium] Move import json, ReportFormat, ArtifactType to top-level - [Medium] Dockerfile: replace COPY . . with selective COPY; add non-root appuser before CMD - [Medium] .dockerignore: exclude storage_state.json and .notebooklm/ Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add authentication prerequisite, API key security, host binding guidance, Claude Desktop JSON config, Docker setup steps, and environment variable reference table. Fix tool count (26, not 27). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly expands the integration capabilities of NotebookLM by introducing a Model Context Protocol (MCP) server. This new server allows any MCP-compatible client, such as Claude Desktop or Claude Code, to seamlessly interact with NotebookLM's functionalities. The changes include detailed setup instructions, enhanced security features like API key authentication, and full Docker support, making it easier for developers to deploy and utilize NotebookLM in various environments. Highlights
Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces an MCP server with Docker support, exposing library functionality as tools for MCP-compatible clients. A critical security vulnerability has been identified: the server runs without mandatory authentication by default and binds to all network interfaces in the Docker configuration. This could allow unauthenticated attackers on the same network to gain full access to the user's NotebookLM account and sensitive data. Additionally, there's a critical bug in the generate_mind_map tool handler and two medium-severity bugs in media and report download helpers that could lead to incorrect artifact downloads.
| status = await client.artifacts.generate_mind_map(args["notebook_id"]) | ||
| await client.artifacts.wait_for_completion(args["notebook_id"], status.task_id) | ||
| artifact = await client.artifacts.get(args["notebook_id"], status.task_id) | ||
| return json.dumps( | ||
| _artifact_to_dict(artifact) if artifact else {"task_id": status.task_id}, |
There was a problem hiding this comment.
The generate_mind_map method in the client does not return a GenerationStatus object with a task_id. Instead, it directly returns a dictionary containing the mind map data and the note_id of the created mind map artifact. The current implementation incorrectly tries to access status.task_id, which will cause a runtime error. The logic should be updated to use the note_id from the result to fetch the artifact. The call to wait_for_completion is also unnecessary and incorrect for mind map generation.
| status = await client.artifacts.generate_mind_map(args["notebook_id"]) | |
| await client.artifacts.wait_for_completion(args["notebook_id"], status.task_id) | |
| artifact = await client.artifacts.get(args["notebook_id"], status.task_id) | |
| return json.dumps( | |
| _artifact_to_dict(artifact) if artifact else {"task_id": status.task_id}, | |
| result = await client.artifacts.generate_mind_map(args["notebook_id"]) | |
| note_id = result.get("note_id") | |
| if not note_id: | |
| raise RuntimeError("Failed to generate mind map: no note_id returned.") | |
| artifact = await client.artifacts.get(args["notebook_id"], note_id) | |
| return json.dumps( | |
| _artifact_to_dict(artifact) if artifact else {"task_id": note_id}, |
| if not api_key: | ||
| logger.warning( | ||
| "No API key configured. Anyone with network access can use this server. " | ||
| "Set --api-key or NOTEBOOKLM_MCP_API_KEY to restrict access." | ||
| ) |
There was a problem hiding this comment.
The MCP server does not require an API key by default, only issuing a warning if one is missing. When deployed via the provided Docker configuration (which binds to 0.0.0.0), this exposes the user's Google session cookies (loaded from storage_state.json) to anyone on the network. An unauthenticated attacker could gain full access to the user's NotebookLM account, including reading and deleting notebooks. Authentication should be mandatory when the server is bound to a non-loopback interface.
| if not api_key: | |
| logger.warning( | |
| "No API key configured. Anyone with network access can use this server. " | |
| "Set --api-key or NOTEBOOKLM_MCP_API_KEY to restrict access." | |
| ) | |
| if host != "127.0.0.1" and not api_key: | |
| raise ValueError("API key is required when binding to a non-loopback interface. Set --api-key or NOTEBOOKLM_MCP_API_KEY.") | |
| if not api_key: | |
| logger.warning( | |
| "No API key configured. Only loopback access is safe. " | |
| "Set --api-key or NOTEBOOKLM_MCP_API_KEY to restrict access." | |
| ) |
|
|
||
| EXPOSE 8765 | ||
|
|
||
| CMD ["notebooklm-mcp", "--host", "0.0.0.0", "--port", "8765"] |
There was a problem hiding this comment.
Binding to 0.0.0.0 by default in the Docker container exposes the application to the network. In this application, which handles sensitive Google session cookies and has optional authentication, this default configuration is insecure. It is safer to bind to 127.0.0.1 by default or ensure the application enforces authentication when exposed.
| download_func = download_methods.get(kind) | ||
| if download_func: | ||
| try: | ||
| await download_func(notebook_id, str(output_path)) |
There was a problem hiding this comment.
The download functions for media artifacts (download_audio, download_video, etc.) are called without specifying the artifact_id. When multiple artifacts of the same type exist, this could lead to downloading the wrong file (the latest one by default) instead of the one that was just generated. The artifact_id of the newly completed artifact is available and should be passed to ensure the correct file is downloaded.
| await download_func(notebook_id, str(output_path)) | |
| await download_func(notebook_id, str(output_path), artifact_id=artifact_id) |
| result["file_path"] = str(out_path) | ||
| elif artifact.kind == ArtifactType.REPORT: | ||
| out_path = download_dir / f"report_{artifact_id}.md" | ||
| await client.artifacts.download_report(notebook_id, str(out_path)) |
There was a problem hiding this comment.
The download_report function is called without specifying the artifact_id. When multiple reports exist in a notebook, this could lead to downloading the content of the wrong report (the latest one by default) instead of the one that was just generated. The artifact_id is available and should be passed to ensure the correct report content is retrieved.
| await client.artifacts.download_report(notebook_id, str(out_path)) | |
| await client.artifacts.download_report(notebook_id, str(out_path), artifact_id=artifact_id) |
Summary
notebooklm loginbefore starting the server)--api-keyflag andNOTEBOOKLM_MCP_API_KEYenv var)127.0.0.1vs network-exposed0.0.0.0)settings.jsonJSON config example alongside the CLI command.envfor API key)NOTEBOOKLM_MCP_API_KEY,NOTEBOOKLM_DOWNLOAD_DIR)Test plan
claude mcp addcommand with--headerflag works with an API key🤖 Generated with Claude Code