notebooklm-mcp-rpc
# notebooklm-mcp-rpc
A pure-TypeScript [Model Context Protocol](https://modelcontextprotocol.io) server for Google NotebookLM. Speaks Google's undocumented `batchexecute` RPC protocol directly — no Python runtime, no `notebooklm` CLI, no shelling out. Deploys cleanly to Vercel Node functions.
> Companion / sibling project to [`notebooklm-mcp`](../notebooklm-mcp), which wraps the [`notebooklm-py`](https://github.com/teng-lin/notebooklm-py) CLI as a subprocess. Pick this one if you want zero Python dependencies and seamless serverless deployment.
## What's in the box
- **Native RPC client** — encoder/decoder for the `batchexecute` wire format ported faithfully from [`notebooklm-py`](https://github.com/teng-lin/notebooklm-py)'s `_core.py` and `rpc/`.
- **Native chat client** — POSTs to the streaming `GenerateFreeFormStreamed` endpoint and parses the chunked response.
- **51 MCP tools** spanning notebooks, sources, chat, artifacts (all 9 generation types + downloads), notes, research, sharing, and language settings.
- **Auth refresh** — concurrent-safe re-scrape of `SNlM0e` / `FdrFJe` on 401/403.
- **Stdio + Streamable-HTTP** transports. Vercel adapter included.
- **Strict TypeScript**: `exactOptionalPropertyTypes`, `noUncheckedIndexedAccess`, `verbatimModuleSyntax`, full Zod input validation.
## Quick start
```bash
npm install
npm run build
# Provide auth (one of the three options below) — see "Authentication".
export NOTEBOOKLM_STORAGE_PATH=/Users/me/.notebooklm/storage_state.json
# Run the server (stdio, default).
node dist/index.js
```
### Hooking up Claude Desktop / Cursor / Windsurf
```json
{
"mcpServers": {
"notebooklm": {
"command": "node",
"args": ["/absolute/path/to/notebooklm-mcp-rpc/dist/index.js"],
"env": {
"NOTEBOOKLM_STORAGE_PATH": "/Users/me/.notebooklm/storage_state.json"
}
}
}
}
```
### Remote / HTTP
```bash
MCP_TRANSPORT=http PORT=3000 node dist/index.js
# POST JSON-RPC to http://localhost:3000/mcp
# GET http://localhost:3000/healthz for a liveness check
```
### Vercel
Push to Vercel — `vercel.json` and `api/mcp.ts` are already wired:
```bash
vercel deploy
# Endpoint: https://<your-project>.vercel.app/mcp
```
Set `NOTEBOOKLM_AUTH_JSON` (the inline contents of `storage_state.json`) as a project env var. The Vercel Node runtime supports everything we need; no custom container.
## Authentication
You need a valid Google session. The simplest path is to bootstrap it once with the Python `notebooklm` CLI, then point this server at the resulting file:
```bash
pip install "notebooklm-py[browser]"
playwright install chromium
notebooklm login # writes ~/.notebooklm/storage_state.json
```
Now choose one of:
| Variable | Use when |
|---|---|
| `NOTEBOOKLM_STORAGE_PATH` | Local dev. Path to `storage_state.json`. |
| `NOTEBOOKLM_AUTH_JSON` | Serverless / CI. Inline JSON contents of the same file. |
| `NOTEBOOKLM_COOKIES` + `NOTEBOOKLM_CSRF_TOKEN` + `NOTEBOOKLM_SESSION_ID` | You already have tokens from another pipeline. |
Cookies do expire. When they do, `auth_status` will return an `isError: true` result with `reason: "auth"` and a reminder to re-run the login step.
## Tool catalog (51)
| Group | Tools |
|---|---|
| Auth / status | `auth_status` |
| Notebooks | `notebook_list`, `notebook_create`, `notebook_rename`, `notebook_delete`, `notebook_raw` |
| Sources | `source_list`, `source_add_url`, `source_add_text`, `source_delete`, `source_rename`, `source_refresh`, `source_fulltext`, `source_guide`, `source_wait` |
| Chat | `chat_ask`, `chat_history`, `chat_last_conversation_id`, `chat_configure` |
| Artifacts | `artifact_list`, `artifact_get`, `artifact_delete`, `artifact_rename`, `artifact_wait` |
| Generation | `generate_audio`, `generate_video`, `generate_cinematic_video`, `generate_report`, `generate_quiz`, `generate_flashcards`, `generate_infographic`, `generate_slide_deck`, `generate_data_table`, `generate_mind_map`, `revise_slide` |
| Downloads | `download_artifact` (one tool, dispatches by artifact type — writes to `outputPath` or returns inline base64/text) |
| Notes | `note_list`, `note_create`, `note_update`, `note_delete` |
| Research | `research_start`, `research_poll`, `research_wait`, `research_import_all` |
| Sharing | `share_status`, `share_set_public`, `share_set_view_level`, `share_add_user`, `share_remove_user` |
| Language | `language_get`, `language_set` |
## Architecture
```
src/
├── auth.ts # storage_state → cookies → CSRF + session-id
├── rpc/
│ ├── types.ts # method IDs + enum codes (source of truth)
│ ├── encoder.ts # encode_rpc_request, build_request_body
│ ├── decoder.ts # strip_anti_xssi, parse_chunked_response, extract_rpc_result
│ └── client.ts # RpcClient — fetch + auth refresh + chat streaming
├── api/
│ ├── _helpers.ts # asString / getPath / sourceIdsTriple etc.
│ ├── notebooks.ts # list/create/rename/delete/raw
│ ├── sources.ts # add_url/add_text/list/delete/rename/wait/fulltext/guide
│ ├── chat.ts # ask (streaming), history, configure
│ ├── artifacts.ts # list/wait + 11 generation paths + download URL extraction
│ ├── notes.ts # CRUD over GET_NOTES_AND_MIND_MAPS
│ ├── research.ts # start/poll/import/wait
│ ├── sharing.ts # public link, view level, per-user
│ ├── settings.ts # language get/set
│ └── client.ts # NotebookLMClient — namespaced facade
├── tools/ # MCP tool registrations (one file per domain)
├── transport/{stdio,http}.ts
├── server.ts # createServer, registerAllTools
├── config.ts # env-driven config (zod-validated)
├── errors.ts # AuthError, RateLimitError, ProtocolError, …
├── logger.ts # stderr-only structured logger
└── index.ts # bin entry
api/mcp.ts # Vercel function adapter
```
## Limitations (v0.1)
- **No file uploads**. Adding PDFs / audio / images via the resumable upload protocol is not implemented yet — use `source_add_url` (web URLs and YouTube) or `source_add_text`. File support tracked under `_core.py`'s `o4cbdc` method.
- **Sharing payload parsing** is shallow — `share_status` returns the raw RPC payload alongside best-effort fields. Public-link toggle and per-user invite/remove all work end-to-end.
- **Slide deck downloads** require `EXPORT_ARTIFACT` (handled automatically by `download_artifact`); other artifact types extract URLs from the `artifact_list` response.
## Why two MCPs?
| | `notebooklm-mcp` | `notebooklm-mcp-rpc` |
|---|---|---|
| Python runtime needed | Yes (CLI subprocess) | No |
| Vercel-deployable | Only with custom container | Plain Node function |
| Tracks upstream protocol fixes | Automatic | Manual (rare; method IDs change a few times a year) |
| Setup steps | install Python + CLI + login | login once, then JS-only |
This project (`-rpc`) is the right pick when you want an autonomous, single-runtime deploy.
## License
MIT — see LICENSE.
TDQS
Scored across 52 tools
Each tool has a clearly distinct purpose, targeting a specific resource and action. Even within the many 'generate' tools, the output type (audio, video, quiz, etc.) uniquely identifies each. No two tools appear to do the same thing.
Tool names predominantly follow the 'resource_action' pattern (e.g., artifact_delete, notebook_list), but a few use 'action_resource' (e.g., download_artifact, generate_audio). The mixing is minor and names remain descriptive and predictable.
With 52 tools, the count far exceeds the 'too many' threshold of 25+. While NotebookLM is a complex product, such a large tool surface can overwhelm an agent and suggests opportunities for consolidation (e.g., parameterizing generation types).
The tool set covers the full lifecycle of notebooks, sources, artifacts, notes, chat, research, sharing, and content generation. Every major operation (CRUD, import, export, configuration) is addressed with no obvious gaps for the domain.