ask_question
Query NotebookLM to get answers based on your uploaded documents, providing synthesized responses with source citations.
Instructions
Conversational Research Partner (NotebookLM • Gemini 2.5 • Session RAG)
No Active Notebook
Visit https://notebooklm.google to create a notebook and get a share link
Use add_notebook to add it to your library (explains how to get the link)
Use list_notebooks to show available sources
Use select_notebook to set one active
Auth tip: If login is required, use the prompt 'notebooklm.auth-setup' and then verify with the 'get_health' tool. If authentication later fails (e.g., expired cookies), use the prompt 'notebooklm.auth-repair'.
Tip: Tell the user you can manage NotebookLM library and ask which notebook to use for the current task.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| question | Yes | The question to ask NotebookLM | |
| session_id | No | Optional session ID for contextual conversations. If omitted, a new session is created. | |
| notebook_id | No | Optional notebook ID from your library. If omitted, uses the active notebook. Use list_notebooks to see available notebooks. | |
| notebook_url | No | Optional notebook URL (overrides notebook_id). Use this for ad-hoc queries to notebooks not in your library. | |
| show_browser | No | Show browser window for debugging (simple version). For advanced control (typing speed, stealth, etc.), use browser_options instead. | |
| browser_options | No | Optional browser behavior settings. Claude can control everything: visibility, typing speed, stealth mode, timeouts. Useful for debugging or fine-tuning. |
Implementation Reference
- src/tools/handlers.ts:41-177 (handler)Main handler function that executes the 'ask_question' tool. Manages browser sessions, resolves notebooks, interacts with NotebookLM via browser automation, handles errors and rate limits, and returns structured results including session information for follow-ups.async handleAskQuestion( args: { question: string; session_id?: string; notebook_id?: string; notebook_url?: string; show_browser?: boolean; browser_options?: BrowserOptions; }, sendProgress?: ProgressCallback ): Promise<ToolResult<AskQuestionResult>> { const { question, session_id, notebook_id, notebook_url, show_browser, browser_options } = args; log.info(`🔧 [TOOL] ask_question called`); log.info(` Question: "${question.substring(0, 100)}"...`); if (session_id) { log.info(` Session ID: ${session_id}`); } if (notebook_id) { log.info(` Notebook ID: ${notebook_id}`); } if (notebook_url) { log.info(` Notebook URL: ${notebook_url}`); } try { // Resolve notebook URL let resolvedNotebookUrl = notebook_url; if (!resolvedNotebookUrl && notebook_id) { const notebook = this.library.incrementUseCount(notebook_id); if (!notebook) { throw new Error(`Notebook not found in library: ${notebook_id}`); } resolvedNotebookUrl = notebook.url; log.info(` Resolved notebook: ${notebook.name}`); } else if (!resolvedNotebookUrl) { const active = this.library.getActiveNotebook(); if (active) { const notebook = this.library.incrementUseCount(active.id); if (!notebook) { throw new Error(`Active notebook not found: ${active.id}`); } resolvedNotebookUrl = notebook.url; log.info(` Using active notebook: ${notebook.name}`); } } // Progress: Getting or creating session await sendProgress?.("Getting or creating browser session...", 1, 5); // Apply browser options temporarily const originalConfig = { ...CONFIG }; const effectiveConfig = applyBrowserOptions(browser_options, show_browser); Object.assign(CONFIG, effectiveConfig); // Calculate overrideHeadless parameter for session manager // show_browser takes precedence over browser_options.headless let overrideHeadless: boolean | undefined = undefined; if (show_browser !== undefined) { overrideHeadless = show_browser; } else if (browser_options?.show !== undefined) { overrideHeadless = browser_options.show; } else if (browser_options?.headless !== undefined) { overrideHeadless = !browser_options.headless; } try { // Get or create session (with headless override to handle mode changes) const session = await this.sessionManager.getOrCreateSession( session_id, resolvedNotebookUrl, overrideHeadless ); // Progress: Asking question await sendProgress?.("Asking question to NotebookLM...", 2, 5); // Ask the question (pass progress callback) const rawAnswer = await session.ask(question, sendProgress); const answer = `${rawAnswer.trimEnd()}${FOLLOW_UP_REMINDER}`; // Get session info const sessionInfo = session.getInfo(); const result: AskQuestionResult = { status: "success", question, answer, session_id: session.sessionId, notebook_url: session.notebookUrl, session_info: { age_seconds: sessionInfo.age_seconds, message_count: sessionInfo.message_count, last_activity: sessionInfo.last_activity, }, }; // Progress: Complete await sendProgress?.("Question answered successfully!", 5, 5); log.success(`✅ [TOOL] ask_question completed successfully`); return { success: true, data: result, }; } finally { // Restore original CONFIG Object.assign(CONFIG, originalConfig); } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); // Special handling for rate limit errors if (error instanceof RateLimitError || errorMessage.toLowerCase().includes("rate limit")) { log.error(`🚫 [TOOL] Rate limit detected`); return { success: false, error: "NotebookLM rate limit reached (50 queries/day for free accounts).\n\n" + "You can:\n" + "1. Use the 're_auth' tool to login with a different Google account\n" + "2. Wait until tomorrow for the quota to reset\n" + "3. Upgrade to Google AI Pro/Ultra for 5x higher limits\n\n" + `Original error: ${errorMessage}`, }; } log.error(`❌ [TOOL] ask_question failed: ${errorMessage}`); return { success: false, error: errorMessage, }; } }
- Tool definition including input schema (JSON Schema) for the 'ask_question' tool, specifying parameters like question, session_id, notebook options, and browser controls.export const askQuestionTool: Tool = { name: "ask_question", // Description will be set dynamically using buildAskQuestionDescription description: "Dynamic description placeholder", inputSchema: { type: "object", properties: { question: { type: "string", description: "The question to ask NotebookLM", }, session_id: { type: "string", description: "Optional session ID for contextual conversations. If omitted, a new session is created.", }, notebook_id: { type: "string", description: "Optional notebook ID from your library. If omitted, uses the active notebook. " + "Use list_notebooks to see available notebooks.", }, notebook_url: { type: "string", description: "Optional notebook URL (overrides notebook_id). Use this for ad-hoc queries to notebooks not in your library.", }, show_browser: { type: "boolean", description: "Show browser window for debugging (simple version). " + "For advanced control (typing speed, stealth, etc.), use browser_options instead.", }, browser_options: { type: "object", description: "Optional browser behavior settings. Claude can control everything: " + "visibility, typing speed, stealth mode, timeouts. Useful for debugging or fine-tuning.", properties: { show: { type: "boolean", description: "Show browser window (default: from ENV or false)", }, headless: { type: "boolean", description: "Run browser in headless mode (default: true)", }, timeout_ms: { type: "number", description: "Browser operation timeout in milliseconds (default: 30000)", }, stealth: { type: "object", description: "Human-like behavior settings to avoid detection", properties: { enabled: { type: "boolean", description: "Master switch for all stealth features (default: true)", }, random_delays: { type: "boolean", description: "Random delays between actions (default: true)", }, human_typing: { type: "boolean", description: "Human-like typing patterns (default: true)", }, mouse_movements: { type: "boolean", description: "Realistic mouse movements (default: true)", }, typing_wpm_min: { type: "number", description: "Minimum typing speed in WPM (default: 160)", }, typing_wpm_max: { type: "number", description: "Maximum typing speed in WPM (default: 240)", }, delay_min_ms: { type: "number", description: "Minimum delay between actions in ms (default: 100)", }, delay_max_ms: { type: "number", description: "Maximum delay between actions in ms (default: 400)", }, }, }, viewport: { type: "object", description: "Browser viewport size", properties: { width: { type: "number", description: "Viewport width in pixels (default: 1920)", }, height: { type: "number", description: "Viewport height in pixels (default: 1080)", }, }, }, }, }, }, required: ["question"], }, };
- src/tools/definitions.ts:20-33 (registration)Function that builds and registers all tool definitions, dynamically setting the description for 'ask_question' based on the current notebook library state.export function buildToolDefinitions(library: NotebookLibrary): Tool[] { // Update the description for ask_question based on the library state const dynamicAskQuestionTool = { ...askQuestionTool, description: buildAskQuestionDescription(library), }; return [ dynamicAskQuestionTool, ...notebookManagementTools, ...sessionManagementTools, ...systemTools, ]; }
- src/index.ts:158-169 (registration)Dispatch/registration in the main MCP server handler: routes 'ask_question' tool calls to the handleAskQuestion method with progress support.case "ask_question": result = await this.toolHandlers.handleAskQuestion( args as { question: string; session_id?: string; notebook_id?: string; notebook_url?: string; show_browser?: boolean; }, sendProgress ); break;
- src/types.ts:21-33 (schema)TypeScript interface defining the structure of the result returned by the 'ask_question' tool.export interface AskQuestionResult { status: "success" | "error"; question: string; answer?: string; error?: string; notebook_url: string; session_id?: string; session_info?: { age_seconds: number; message_count: number; last_activity: number; }; }