Notebooklm-Unofficial-API-MCP
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Notebooklm-Unofficial-API-MCP show me my recent notebooks"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Notebooklm-Unofficial-API-MCP
A thin wrapper around notebooklm-py
(MIT licensed, by Teng Lin) — the unofficial Python client/MCP server for Google
NotebookLM. This project depends on notebooklm-py rather than vendoring it, and
adds project-specific MCP tools on top of its stock tool set (notebooks, sources,
chat, studio artifact generation, deep research, sharing).
Why a wrapper
notebooklm-py already ships a complete MCP server (notebooklm-mcp). This
wrapper exists so custom tools can be layered on without forking upstream —
notebooklm.mcp.server.create_server() is called and its returned FastMCP
instance is extended in src/notebooklm_wrapper/server.py.
Related MCP server: NotebookLM MCP Server
Getting started
Each person who runs this needs their own authenticated session — there is
no shared credential in this repo, and none is possible: notebooklm-py is
unofficial and works by driving your own Google account, not a documented API.
1. Prerequisites
Python 3.10+
uv(recommended) orpip
2. Clone and install
git clone https://github.com/DevstackK/Notebooklm-Unoffical-API-MCP.git
cd Notebooklm-Unoffical-API-MCP
uv sync
# or: pip install -e .3. Authenticate with your own Google account
Pick one:
A — Browser login (Playwright), simplest first run:
uv run playwright install chromium # one-time browser download
uv run notebooklm loginThis opens a Chromium window; sign in to the Google account whose NotebookLM
notebooks you want the server to access, then close it. The session is saved
to a local profile (storage_state.json) — nothing is uploaded anywhere.
B — Import cookies from a browser you're already logged into:
uv pip install "notebooklm-py[cookies]"
uv run notebooklm login --browser-cookies # reads from Chrome by default
uv run notebooklm login --browser-cookies=firefox # or another supported browserSkips the Playwright/Chromium download entirely — useful if you're already signed into NotebookLM in your regular browser.
Verify it worked:
uv run notebooklm statusIf you ever need to switch Google accounts: uv run notebooklm login --fresh.
4. Run the MCP server
uv run notebooklm-unofficial-mcp5. Point Claude Code (or another MCP client) at it
claude mcp add notebooklm-unofficial -- uv run --directory /path/to/Notebooklm-Unoffical-API-MCP notebooklm-unofficial-mcpSwap /path/to/Notebooklm-Unoffical-API-MCP for wherever you cloned it. Any
MCP-compatible client (Claude Desktop, etc.) can point at the same
notebooklm-unofficial-mcp command over stdio.
Adding a tool
Add @mcp.tool()-decorated functions inside _register_custom_tools in
src/notebooklm_wrapper/server.py.
License
MIT — see LICENSE. Not affiliated with Google or the notebooklm-py
project; "unofficial" in the name reflects that both this wrapper and its
upstream dependency use NotebookLM's undocumented web API.
Available Tools
39 toolsawait_uploadARead-only
Wait for a file uploaded via a source_add(source_type="file") link to land.
Pass the human_upload.url (or the bare token) that source_add returned.
Polls the server in-process until the browser/agent upload commits the source:
{"status":"received","source_id",...,"file":{...}}— the upload landed.{"status":"pending",...}— nothing yet after ~timeouts; re-invoke with the same link (the wait resumes; a transport reset does not lose it).{"status":"unconfirmed",...}— the upload may have landed, but its source could not be confirmed. Checksource_listbefore requesting a new link to avoid adding the same file twice.{"status":"expired_or_invalid",...}— the link failed; mint a fresh one viasource_add(source_type="file").
Inputs: upload_link (required), timeout. Returns: object
chat_askA
Ask a notebook's sources a question, and/or recall prior turns. Accepts a notebook name or ID.
Pass conversation_id to continue a specific conversation; omit it to
continue the notebook's most-recent conversation (or start a new one).
source_ids (optional) scopes the question to specific sources by
id/prefix/title; omit it to query every source. It accepts a real list, a
JSON-array string, or a comma-separated string (the comma form cannot
carry a source title that itself contains a comma — use a JSON array or a
real list for those).
history (optional, default 0): the max number of prior Q&A pairs
(each a {question, answer}) to also return (oldest-first), from the
conversation as it stood before this question. There is no unbounded
"all" value — pass a generously large number (e.g. 100) for the whole
conversation. Omit question (leave it empty) with history > 0 to
recall prior pairs without asking anything new; a recall-only call also
echoes the conversation_id it read. Pass neither and the call is
rejected.
Returns the answer, turn_number, and citation references
(when a question is asked).
references controls citation detail: lite (default) returns
source_id / citation_number / cited_text; full adds
chunk-level char offsets and scores.
suggest_followups (optional, default False): when True the
result also carries a suggested_prompts list of AI-suggested
follow-up questions (each a {title, prompt}), scoped to the same
source_ids and steered by question when one is given. It works on
its own too — pass it with no question (and history 0) to get
suggested questions without asking anything. When omitted/False the
result never contains a suggested_prompts key.
Inputs: notebook (required), history, question, references, +3 more. Returns: object
chat_cancelA
Stop a notebook's active chat generation.
conversation_id defaults to the notebook's most-recent session.
Pass the task_id returned by chat_start when cancelling a
detached ask: the MCP server abandons its local response stream first,
then cancels the associated Google session when one exists.
An unknown task ID or one belonging to another notebook is rejected.
Inputs: notebook (required), task_id, conversation_id. Returns: object
chat_configureA
Configure a notebook's chat behavior. Accepts a notebook name or ID.
Two mutually-exclusive ways to configure:
chat_modeis a preset — one ofdefault/learning-guide/concise/detailed. It replaces the whole block, so it can't be combined withgoal/response_length(that's rejected).goal(custom persona; selects the CUSTOM goal) andresponse_length(default/longer/shorter) set a custom config. A partial call (just one) merges with the current settings — the omitted field is preserved. Only a bare call (no preset, neither field) is rejected, as it would reset every setting to its default.
Inputs: notebook (required), goal, chat_mode, response_length. Returns: object
chat_startA
Start a chat ask as a detached background task and return immediately.
The watchdog-safe way to ask when generation may run long (large/shared
notebooks routinely take 1–3 minutes, and remote MCP transports cut a
call at ~60s of silence — which kills a blocking chat_ask
mid-generation). The ask runs server-side regardless of what happens to
this call; chat_status collects the answer. For questions expected
to answer quickly, chat_ask stays the one-call path.
Parameters mirror chat_ask's ask path (history /
suggest_followups are chat_ask-only). Returns one of:
{"status": "started", "task_id": ...}— callchat_statuswith thistask_idin ~20–30s; re-invoke whilepending.{"status": "already_running", "task_id": ...}— an identical ask is still in flight; poll thattask_id(no double generation).
A finished ask is never replayed: starting the same question again
re-asks and appends a new conversation turn, exactly like chat_ask
(so an answer can't go stale after source_add / chat_configure).
A finished payload stays pollable by its task_id for ~30 minutes,
then chat_status reports unknown.
Inputs: notebook (required), question (required), references, source_ids, +1 more. Returns: object
chat_statusARead-only
Poll detached asks, or inspect a notebook's live generation session.
Detached-task mode: task_id accepts one id, a list, or a
comma-separated string — poll
a whole batch in ONE call and re-invoke every ~20–30s while anything
is pending. Per-task statuses:
pending(state:queuedwaiting for a generation slot, orgenerating) — re-invoke; polling never restarts a generation.completed— the fullchat_ask-shaped payload, plusqueued_s/generation_stimings.failed—error: {code, message, retriable, ...}; a retriable code is worth onechat_startretry (which re-runs the generation).unknown— no such task, or its result expired;chat_startagain.
A single string id returns that task's status dict; a list (even of
one) returns {"tasks": [...]} in input order. At most 64 ids per
call.
Live-session mode: pass notebook instead of task_id to inspect
Google's current chat session. conversation_id is optional and
defaults to that notebook's most-recent conversation. The result is
idle or generating and includes the generation token when Google
supplies one. The two modes are mutually exclusive.
Inputs: task_id, notebook, conversation_id. Returns: object
notebook_createB
Create a new notebook with the given title.
Inputs: title (required). Returns: object
notebook_deleteADestructive
Delete a notebook (irreversible). Accepts a notebook name or ID.
Two-step confirmation: called with confirm=False (the default) it does
NOT delete — it returns a needs_confirmation preview of the resolved
notebook. Re-submit its canonical notebook_id with confirm=True.
Inputs: notebook (required), confirm. Returns: object
notebook_describeARead-only
Fetch a notebook's AI-generated description. Accepts a notebook name or ID.
Returns the resolved notebook_id plus the AI description. Pass
include_metadata=True to additionally fetch the notebook's metadata
(details + source list) and surface it under a metadata key; the
default output (include_metadata omitted) is unchanged.
Inputs: notebook (required), include_metadata. Returns: object
notebook_listARead-only
List all notebooks (id + title + metadata).
Returns a bounded page: limit (default 50) items from offset (default
0), plus total / offset / has_more. Page forward by re-calling
with offset += limit while has_more is true.
Inputs: limit, offset. Returns: object
notebook_renameB
Rename a notebook. Accepts a notebook name or ID.
Inputs: notebook (required), new_title (required). Returns: object
note_saveA
Create a note, or update an existing one (upsert). Accepts a notebook name or ID.
Mode is chosen SOLELY by note:
noteomitted → create a new note;titleANDcontentare both required. Returnsstatus="created".notegiven (a note name or id) → update that note; supplytitleand/orcontent(at least one — title-only renames, content-only replaces the body). A ref that doesn't resolve is a not-found error, NEVER a stray create. Returnsstatus="updated".
Inputs: notebook (required), note, title, content. Returns: object
pingA
Health check for this wrapper server.
Inputs: no named parameters. Returns: result
research_cancelA
Cancel an in-flight research run in a notebook.
Accepts a notebook name or ID and the poll_task_id to cancel — the
value from research_start / research_status. run_id is a
deprecated alias (removed in v0.9.0).
Sends the cancel unless the run is already TERMINAL (completed /
failed), which returns cancel_requested: false with the observed
status and no RPC. Otherwise returns cancel_requested: true with
run_status_before; a just-started run reading not_found /
no_research (replication lag) is cancelled too. Fire-and-forget; poll
research_status afterward to confirm.
Inputs: notebook (required), run_id, poll_task_id. Returns: object
research_importA
Import a completed research task's sources into the notebook.
Accepts a notebook name or ID and the poll_task_id to import — the
value from research_start / research_status. task_id is a
deprecated alias (removed in v0.9.0).
The id pins the task. Timeout-tolerant: a timed-out import reconciles
what committed. Idempotent: sources already present (by URL) are skipped
as already_present; allow_duplicate re-adds them.
cited_only imports only report-cited sources (all, if none resolve).
max_sources caps the count.
Inputs: notebook (required), task_id, cited_only, max_sources, +2 more. Returns: object
research_startA
Start a research session in a notebook. Accepts a notebook name or ID.
Non-blocking. Carry the returned poll_task_id into
research_status / research_import / research_cancel — the
single id that drives polling (it resolves deep vs fast for you). Poll
research_status until completed, then research_import to add
the sources.
source is web (default) or drive. mode is fast
(default) or deep (deep is web-only).
Inputs: query (required), notebook (required), mode, source. Returns: object
research_statusARead-only
Check a notebook's research status. Accepts a notebook name or ID.
Returns status (no_research|in_progress|completed|failed|not_found),
poll_task_id, sources, and report metadata. Poll until
completed, then pass poll_task_id to research_import.
termination_reason splits failed into no_results|cancelled|
unknown (else completed|in_progress); see hint.
report and per-source report_markdown are omitted unless
include_report=True (truncated to report_max_chars).
report_char_count is the full size; report_truncated flags an
omitted/truncated one. source_limit/source_offset page sources.
poll_task_id (optional) pins one of several in-flight tasks; omit
for a single task (ambiguous with two+). An unmatched pin reports
not_found. task_id is a deprecated alias (removed in v0.9.0).
Inputs: notebook (required), task_id, poll_task_id, source_limit, +3 more. Returns: object
server_infoARead-only
Report the server version and local authentication health.
Returns the package version, live chat_tasks gauges
({generating, queued, concurrency, cached_results} — check load
before submitting a chat_start batch), and an auth block (authenticated /
storage_exists / json_valid / cookies_present / sid_cookie /
profile). Use it to confirm the server is logged in before driving
notebook tools; if authenticated is false, run notebooklm login on
the server host.
Set include_account=True to also fetch an account block: the
signed-in identity {email, authuser} (persisted first, then a live
WIZ_global_data probe when authenticated; None only when it can't be
discovered) plus quota fields {available, notebook_limit, source_limit, tier, output_language, output_language_is_default}
(output_language is the global account code, e.g. "en", or None
with output_language_is_default: true when using NotebookLM's default).
The quota fields need a live session, so the block is off by default — the
default call is a fast, network-free probe. When the session is missing or
stale the fields degrade to {available: False, reason: ...} (identity
still included) rather than failing the whole call.
profile names the resolved storage profile the probe ran against
(e.g. "default"); the booleans are the actual health signals.
The absolute on-disk storage path is deliberately not returned: it leaks the server-host OS username / filesystem layout to any (possibly remote) caller, while telling the agent nothing it can act on.
Inputs: include_account. Returns: object
source_addA
Add sources by notebook name or ID: single, batch, or in-channel bytes.
Choose ONE mode:
Single mode — pass source_type; it selects the required input:
url/youtube— requireurl(youtube→ a YouTube link).text— requirestext;titleoptional.file— over stdio, requirespath(a local path on the server host insideNOTEBOOKLM_MCP_ALLOWED_ROOTS; host-path file-add is off until that env is set — not$HOMEand not~/.notebooklm). Over the remote (http) connector the host filesystem is unreachable, so it returnsupload_requiredwith two actor paths:human_upload(open the signed URL in a browser) andagent_upload(an agent POSTs the bytes as the raw body);agent_instructionsgives the rule (tryagent_upload, elsehuman_upload.url). Alternatively passbytes_base64to add a SMALL file in-channel (any transport, no signed URL): standard base64 (not URL-safe) ≤ 10,000 chars (≈ 7 KB);filenameseeds the title/extension. A bigger file must take the signed URL (≤ 200 MiB).drive— requiresdocument_id+mime_type(one of google-doc|google-slides|google-sheets|pdf; required, no default — a wrong default fails non-Doc imports, #1827).
The single-mode content inputs are mutually exclusive — supply only the one
your source_type requires (bytes_base64 is the file alternative to
path). An explicit title for url/youtube/drive is honored via a post-add
rename; a miss returns title_override_applied: false (#1960).
Pass wait=true to block until the ONE added source finishes processing and
return the source_wait aggregate (buckets + per-bucket *_count +
total_count) with a top-level source_id (present even on timeout/failure);
timeout/interval tune the poll. wait is single-mode only and NOT for a
remote file signed-URL upload (add it, then source_wait). Without wait
the added source is echoed under source with string kind /
status_label labels; imports are ASYNCHRONOUS so the echo is usually still
processing/preparing — confirm with source_wait or
source_list(status="error"). A failed import is flagged inline
(status_label="error" + a warning); source_wait also flags a READY web
page with suspiciously thin text (dead link/soft-404/paywall).
Single web URLs support fallback_fetch=True: eligible connection failures
become static text with fallback provenance (impersonate + markdown extras).
cleanup_on_failure=True additionally deletes the attributable failed stub
once its replacement is ready. Private fetch destinations are blocked.
Batch mode — pass urls (http/https, including YouTube). Returns
notebook_id, added/failed counts and per-input results with
input, status (added/error), source fields or an error object.
results[i] corresponds to urls[i] and includes commit_state
(confirmed/rejected/unknown/not_sent). Batch is URL-only; invalid entries are
not_sent errors. Escaping failures retain settled members as batch_outcome.
Single-mode inputs and wait are invalid with urls; allow_internal
applies to every entry.
Inputs: notebook (required), url, path, text, +13 more.
source_add_drive_fileA
Add a Google Drive file by downloading it server-side and uploading it (supports epub/docx/pptx/txt/md/rtf/odt/csv/tsv/pdf).
Probes the Drive file and routes: a downloadable type is fetched server-side and
uploaded; a Google-native Doc/Slides/Sheet isn't downloadable and returns a pointer
error → use source_add(source_type='drive', mime_type=…) for those (or for a
Drive PDF you'd rather add by reference). Use source_add(source_type='file') when
you hold the bytes locally.
Accepts a notebook name or ID and a Drive file id or share URL (/d/<id>,
/file/d/<id>/…, ?id=<id>). The fetch runs server-side with the profile's
session, so it works on the remote (http) connector too — no upload_required
step. Processed ASYNCHRONOUSLY; pass wait=true to block until READY (else
confirm via source_wait / source_list(status="error")).
Inputs: notebook (required), document_id (required), wait, title. Returns: object
source_add_play_bookA
Add a Google Play Book as a source by its content id (Expert Intelligence).
content_id is a volume id from source_list_play_books. The title must
be exportable — a publisher-blocked one is refused up front. Accepts a notebook
name or ID. Reads back as an expert_intelligence source. Processed
asynchronously; pass wait=true to block until READY. Web backend only.
Inputs: notebook (required), content_id (required), wait. Returns: object
source_deleteADestructive
Delete sources. source XOR sources. Preview: omit both.
Confirm with the preview's canonical notebook_id and source id(s).
Inputs: notebook (required), source, confirm, sources. Returns: object
source_listARead-only
List a notebook's sources. Accepts a notebook name or ID.
detail (default full): full gives each source's metadata plus string
kind / status_label / drive_status_label labels; compact returns
only id / title / kind / status_label / drive_status_label /
created_at — a low-token roster.
drive_status_label is a separate axis, null on non-Drive sources:
a Drive file that was deleted or unshared keeps reading
status_label="ready" (ingestion did finish) while this turns
deleted / inaccessible / syncing, so answers may be stale.
Pass status to list only sources whose status_label matches (error =
a broken import's ghost row). Pass label (name or ID) to restrict to that
label's members; composes with status.
Inputs: notebook (required), label, limit, detail, +2 more. Returns: object
source_list_play_booksARead-only
List Google Play Books eligible to be added as sources (Expert Intelligence).
Returns the account's purchased ebooks NotebookLM can ingest (US only, 18+).
Each row has content_id (pass it to source_add_play_book), title,
authors, export_disabled and reason (a non-exportable title cannot
be added), plus cover_url / store_url. Empty for an account with no
Play Books library. Web backend only.
Inputs: limit, offset. Returns: object
source_readARead-only
Read a source at one of two detail levels. Accepts a notebook/source name or ID.
detail selects what you get back (two distinct shapes):
summary— a tiny AI digest for low-token triage:{notebook_id, source_id, summary, keywords}. Cheap to fan out across many sources before deciding which to pull in full.full(DEFAULT) — the source metadata (incl.kind/status_label/drive_status_label) plus the extractedcontent, the fullchar_count, and atruncatedflag.contentis ALWAYS bounded: omittingmax_charscaps it at the first 10,000 chars; raisemax_charsand/or page withoffset(slice[offset : offset+max_chars]).char_countstays the FULL length.contentisnull(char_count0) when the source isn't ready yet or has no extractable text.
output_format (text default / markdown, needs the server's
markdownify extra) and max_chars / offset apply only to
detail="full" (ignored for summary). Prefer chat_ask for
querying large sources rather than pulling the whole body.
Inputs: source (required), notebook (required), detail, offset, +2 more.
source_renameC
Rename a source. Accepts a notebook/source name or ID.
Inputs: source (required), notebook (required), new_title (required). Returns: object
source_waitARead-only
Wait for sources to finish processing. Accepts a notebook name or ID.
Waits for a subset when sources (list or comma/JSON string) is given, a
single source when source (name or ID) is given, else every source. All
three modes return the SAME structured aggregate, so an agent never has to
branch on the shape:
{"notebook_id", "ok", "ready", "timed_out", "failed", "not_found"}plus per-bucket *_count + total_count. ready holds sources that
reached READY (with kind / status_label labels); timed_out /
failed / not_found hold {"source_id", "error"} entries. ok is
true iff all error buckets empty. Subset
and all-sources modes report partial progress (a slow or failed source no
longer discards the ones that did become ready).
A READY web-page entry may carry a non-blocking warning when its indexed
text is thin (likely dead link / soft-404 / paywall); advisory only
(still READY, still ok — verify with source_read (detail="full")).
An unresolved ref in sources / source raises NOT_FOUND before the wait —
an input error, distinct from a resolved source the backend reports missing /
failed / slow (which lands in a bucket).
Inputs: notebook (required), source, sources, timeout, +1 more.
studio_deleteADestructive
Delete notes/artifacts irreversibly: item XOR items (1–100 refs).
Names/IDs resolve over Studio. Preview first; confirm with canonical
notebook_id and item ID(s). Batch confirmation requires full IDs.
Text notes batch into one write; artifacts (including mind maps) route
individually by kind. Batch returns deleted / not_found arrays.
An absent single full ID is an idempotent success.
Inputs: notebook (required), item, items, confirm. Returns: object
studio_downloadA
Download a generated artifact. Accepts a notebook name or ID.
Target the artifact in ONE of two ways (exactly one):
artifact— a name-or-id ref (title / id / unique-id-prefix), the form the otherartifact_*tools take; resolves to its type + id.artifact_type— a registry-advertised type, optionally withartifact_id(full or unique-prefix) for a specific one; omitartifact_idto get the latest artifact of that type.
output_format overrides the default where the selected artifact type
supports it. The tool schema advertises the current type and format enums.
Over stdio the artifact is written to path (required). Over the
remote (http) connector the server filesystem is unreachable, so the tool
returns a clickable resource_link plus {"status": "download_ready", "url": …} — a short-lived signed URL; path is ignored. A text kind
(report/data-table) also returns the body inline (bounded content +
char_count + truncated) for link-incapable hosts. On the remote
connector an explicit artifact_id (and output_format) is validated up
front — an unknown/ambiguous id fails immediately, not as a 400 when opened.
Inputs: notebook (required), path, artifact, artifact_id, +2 more.
studio_generateA
Start generating a studio artifact. Accepts a notebook name or ID.
Non-blocking: returns immediately with a task_id; poll
studio_status(notebook, task_id) until is_complete is true.
Exception: mind-map renders synchronously (no task_id) — its node
tree is under mind_map (or null), the map's id under mind_map_id.
artifact_type selects the artifact kind (each routes to its own
generator):
audio— podcast-style overview (audio_format: deep-dive|brief|critique|debate,audio_length: short|default|long).video— video overview (video_format: explainer|brief|cinematic|short,style: auto|custom|classic|whiteboard| kawaii|anime|watercolor|retro-print|heritage|paper-craft,style_prompt: free-text custom-style prompt — requiresstyle=custom).cinematic-video— AI-generated documentary video.slide-deck— slide deck (deck_format: detailed|presenter,deck_length: default|short).quiz/flashcards— study aids (quantity: fewer|standard|more,difficulty: easy|medium|hard).infographic— single-image infographic (orientation: landscape|portrait|square,detail: concise|standard|detailed,style: auto|sketch-note|professional|bento-grid|editorial| instructional|bricks|clay|anime|kawaii|scientific).data-table— extracted data table.mind-map— mind map (map_kind: interactive|note-backed).report— text report (report_format: briefing-doc|study-guide|blog-post|custom).
Each per-kind option is valid ONLY for the kind(s) listed above; passing one
to a different artifact_type is a validation error, not a silent no-op.
Options default to the standard choice when omitted.
source_ids (optional) scopes generation to specific sources; omit it
to use every source. It accepts a real list, a JSON-array string, or a
comma-separated string (a source title containing a comma needs the
JSON-array or list form).
instructions is free-text guidance for kinds that accept it
(including mind-map). language (optional) is a language code,
e.g. en/ja/zh_Hans.
Inputs: notebook (required), artifact_type (required), style, detail, +14 more. Returns: object
studio_listARead-only
List a notebook's Studio panel — text notes AND generated artifacts.
Accepts a notebook name or ID. Returns a merged items list; each item has
id / title / type (note or a hyphenated artifact kind); artifacts
also carry status_label / url. Bounded page of limit (default 50)
from offset, with total / offset / has_more.
detailladder (NOTE bodies only; read a report/data-table body viastudio_download):summary(default) gives each note a boundedcontent_preview+char_count. Artifacts addcreated_at,generation_prompt,duration_seconds,slide_count,source_count(unknown metadata/counts arenull).full= wholecontent;compact= aid/title/type/status_label/created_atroster.kindfilters to onetype.item(name or id) fetches just that item as a 1-element list with the note's FULLcontentor the artifact's summary metadata; no match is NOT_FOUND.limit/offset/detailare ignored withitem;kindscopes resolution.
Inputs: notebook (required), item, kind, limit, +2 more. Returns: object
studio_renameA
Rename a Studio item (title only) — a text note OR an artifact.
Accepts a notebook name or ID plus an item name-or-id ref resolved over
the merged notes+artifacts list (mirroring studio_delete). Routing is by
resolved type: a note is renamed through the note system, preserving its
content via a get-then-update; every artifact type — audio, video,
slide-deck, quiz, flashcards, infographic, data-table, report, and BOTH
mind-map kinds — through the artifact rename RPC (note-backed mind maps route
back through the note system inside the shared core). Callers need not know
which backing an item has.
Returns item_id / type plus the applied new_title and
is_mind_map.
Inputs: item (required), notebook (required), new_title (required). Returns: object
studio_retryA
Retry a failed Studio artifact in place (the UI "Retry" action).
Accepts a notebook/artifact name or ID. Non-blocking: on acceptance it
returns the kicked-off task_id (equal to the artifact id) and the new
status; poll studio_status(notebook, task_id) until complete. A
synchronous refusal (rate limit / quota / not-retryable) surfaces as an error.
Inputs: artifact (required), notebook (required). Returns: object
studio_statusARead-only
Poll a generation task's status. Accepts a notebook name or ID.
Stateless: pass the task_id from studio_generate. Returns status /
url / error / is_complete / media_ready; poll until done. A
pending url is provisional — trust it only if media_ready.
Inputs: task_id (required), notebook (required). Returns: object
suggest_promptsARead-only
Get AI-suggested, ready-to-send prompts for a studio surface. Accepts a notebook name or ID.
surface selects what the prompts are written for (default ask):
ask— chat questions to ask the notebook's content.audio-deep-dive/audio-brief/audio-critique/audio-debate— prompts to steer an Audio Overview in that format.video-explainer/video-short— prompts to steer a Video Overview.quiz/flashcards— prompts to steer quiz / flashcard generation.
Each result is a ready-to-send instruction you can pass to the matching
generator (chat_ask for ask; studio_generate's instructions for
the studio formats). source_ids (optional) scopes the suggestions to
specific sources; omit for all. query optionally steers the suggestions.
Related: chat_ask(suggest_followups=true) returns ask-surface
suggestions inline with a question (ask + follow-ups in one call); this tool
is the standalone selector across every surface.
Inputs: notebook (required), query, surface, source_ids. Returns: object
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 39 tools
Several tools overlap in purpose: source_add, source_add_drive_file, and source_add_play_book all add sources, while source_wait and await_upload both wait for source readiness, and chat_ask/chat_start/chat_status form a related set. The detailed descriptions clarify routing, but an agent can still face misselection across these families.
Most names follow a clear domain_action convention (notebook_*, source_*, chat_*, share_*, studio_*, research_*). A few outliers like ping, await_upload, server_info, and suggest_prompts break the pattern, but the set is largely predictable.
At 39 tools, the surface is well above the typical 15-tool sweet spot and includes several specialized variants that could be consolidated. While NotebookLM is a broad domain, the count feels over-granular rather than tightly scoped.
The set covers notebook, source, chat, sharing, studio, research, and note lifecycles, including add/read/rename/delete and async polling. Minor gaps remain, such as direct notebook metadata updates or source reprocessing/resync, but most core workflows are supported.
Related MCP Connectors
MCP-native notes and memory for ChatGPT, Claude, and other AI tools.
Shared memory for your AI tools: tasks, notes and context every tool can read and write over MCP.
System-of-record notebook for AI coding agents: pages, datastores, tasks, skills over MCP.
Agent-native notes, tasks, dev-docs, vaults, sync & handoffs. MCP + OpenAPI dual surface.
Related MCP Servers
- AlicenseAqualityBmaintenanceProvides CLI and MCP server to programmatically access Google NotebookLM, enabling AI assistants to create notebooks, add sources, generate podcasts, and more, with support for both personal and enterprise accounts.4114 PyPIMIT
- AlicenseNot gradedqualityCmaintenanceExposes Google NotebookLM as MCP tools, enabling management of notebooks, sources, chat, artifacts, and research via natural language.2MIT
- AlicenseNot gradedqualityDmaintenanceMCP server that connects AI agents to Google NotebookLM, enabling natural language interaction with notebooks, including Q&A, source ingestion, and audio overview generation.4,977 npmMIT
- AlicenseBqualityCmaintenanceMCP server that provides ~39 tools to interact with Google NotebookLM's internal API, enabling notebook management, source addition, chat queries, and Studio artifact generation through stdio, HTTP, and SSE transports.39MIT