Thanks for your interest in contributing. This project gives you programmatic access to NotebookLM through an MCP server and CLI, built on top of Google's undocumented internal batchexecute API. The API can change without notice, so contributions need to be grounded in captured, verified payloads, not assumptions.
For small features, bug fixes, and improvements, just open a PR with a clear description.
For large architectural changes (new auth providers, enterprise support, new client abstractions), open an issue first to discuss the approach. This saves everyone's time. We may already have plans or constraints you should know about.
Requirements: Python >= 3.11, uv
# Clone and install
git clone https://github.com/jacob-bd/notebooklm-mcp-cli.git
cd notebooklm-mcp-cli
uv tool install .
# Authenticate (opens Chrome for Google login)
nlm login
# Verify
nlm login --check
The MCP server keeps code in memory. After making changes, you must run:
uv cache clean && uv tool install --force .
Then restart any running MCP clients (Claude Code, Claude Desktop, etc.) before testing. If you skip this, you're testing old code.
cli/ and mcp/ → services/ → core/
(thin wrappers) (business logic) (low-level API)
This layering is non-negotiable:
cli/ and mcp/ are thin wrappers. They handle UX concerns (prompts, spinners, JSON responses) and delegate everything to services/services/ contains all business logic, validation, and error handling. Returns typed dicts.cli/ and mcp/ must never import from core/ directly. Always go through services/services/ raises ServiceError / ValidationError. Never raw exceptions.See CLAUDE.md for the full module map and storage structure.
This project wraps an undocumented API. The only reliable way to implement new features is to capture the actual network requests Google's web UI makes. Here's the workflow:
batchexecute requestsf.req form parameter (request payload) and the response bodyThink in full circles: create, status, download. A half-implemented feature (for example, creation without status parsing) creates gaps that are easy to miss.
| Step | File | What |
|---|---|---|
| 1 | docs/API_REFERENCE.md |
Document the RPC ID and param structure |
| 2 | core/client.py |
Add the low-level API method |
| 3 | services/*.py |
Add business logic, validation, error handling |
| 4 | mcp/tools/*.py |
Add MCP tool (thin wrapper around service) |
| 5 | cli/commands/*.py |
Add CLI command (thin wrapper around service) |
| 6 | Status/read-back | Update status parsing if your feature produces artifacts |
| 7 | tests/services/ |
Write unit tests for the service function |
| 8 | docs/MCP_CLI_TEST_PLAN.md |
Add test cases for manual validation |
CLI verb wrappers: When adding a new CLI option to a command function in cli/commands/, you must also add it to the corresponding verb wrapper in cli/commands/verbs.py. The tests/cli/test_verbs_parity.py test enforces this — CI will fail if a verb wrapper is missing a parameter from its target function.
Always validate the core API call works before wiring up the MCP tool or CLI command. Write a quick Python test script, run it, confirm it works. Then add the wrappers. This prevents shipping broken tools.
Both checks must pass. CI runs them as separate steps:
# Linting
uv run ruff check .
# Formatting
uv run ruff format --check .
# Auto-fix formatting
uv run ruff format .
Use Conventional Commits:
feat: New featurefix: Bug fixstyle: Formatting, lint fixes (no logic change)docs: Documentation onlyrefactor: Code change that neither fixes a bug nor adds a featuretest: Adding or updating tests# Full suite
uv run pytest
# Single test
uv run pytest tests/test_file.py::test_function -v
Automated tests alone are not enough. You must manually verify your changes work via both the CLI and MCP tools:
# CLI
nlm <your-command> ...
# MCP (restart your MCP client first after reinstalling)
# Use the MCP tool in Claude Code, Claude Desktop, or your client of choice
Include evidence of both CLI and MCP testing in your PR description.
ServiceError or ValidationError from services/errors.py. Never raw Python exceptions.raise ServiceError(...) from err (ruff rule B904)confirm=True. Always show the user what will happen before they confirm.Operations that currently require confirmation: - Deleting notebooks, sources, notes, or studio artifacts (irreversible) - Syncing Drive sources - All studio content creation - Slide revision
If your feature is destructive or produces billable/quota-consuming results, add the confirm parameter.
Google rolls out API changes gradually, sometimes by region, sometimes by account. If users report failures you can't reproduce:
f.req) from their browserSee the add_url_source() dual-RPC fallback in core/sources.py for an example of this pattern.
This is a security-conscious codebase. Keep these in mind:
.ssh, .aws, .gnupg, etc.)0o600 / 0o700)To report a security vulnerability, please email the maintainer directly. Don't open a public issue.
Co-authored-by trailers. Commits are attributed to the PR author.This project keeps dependencies minimal. If your change requires a new dependency, justify it in the PR description. Prefer using what's already available (httpx, typer, websocket-client) over adding new packages.
By contributing, you agree that your contributions will be licensed under the MIT License.