#!/usr/bin/env node
/**
* NotebookLM MCP Server
*
* MCP Server for Google NotebookLM - Chat with Gemini 2.5 through NotebookLM
* with session support and human-like behavior!
*
* Features:
* - Session-based contextual conversations
* - Auto re-login on session expiry
* - Human-like typing and mouse movements
* - Persistent browser fingerprint
* - Stealth mode with Patchright
* - Claude Code integration via npx
*
* Usage:
* npx notebooklm-mcp
* node dist/index.js
*
* Environment Variables:
* NOTEBOOK_URL - Default NotebookLM notebook URL
* AUTO_LOGIN_ENABLED - Enable automatic login (true/false)
* LOGIN_EMAIL - Google email for auto-login
* LOGIN_PASSWORD - Google password for auto-login
* HEADLESS - Run browser in headless mode (true/false)
* MAX_SESSIONS - Maximum concurrent sessions (default: 10)
* SESSION_TIMEOUT - Session timeout in seconds (default: 900)
*
* Based on the Python NotebookLM API implementation
*/
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
Tool,
} from "@modelcontextprotocol/sdk/types.js";
import { AuthManager } from "./auth/auth-manager.js";
import { SessionManager } from "./session/session-manager.js";
import { NotebookLibrary } from "./library/notebook-library.js";
import { ToolHandlers, buildToolDefinitions } from "./tools/index.js";
import { ResourceHandlers } from "./resources/resource-handlers.js";
import { SettingsManager } from "./utils/settings-manager.js";
import { CliHandler } from "./utils/cli-handler.js";
import { CONFIG } from "./config.js";
import { log } from "./utils/logger.js";
/**
* Main MCP Server Class
*/
class NotebookLMMCPServer {
private server: Server;
private authManager: AuthManager;
private sessionManager: SessionManager;
private library: NotebookLibrary;
private toolHandlers: ToolHandlers;
private resourceHandlers: ResourceHandlers;
private settingsManager: SettingsManager;
private toolDefinitions: Tool[];
constructor() {
// Initialize MCP Server
this.server = new Server(
{
name: "notebooklm-mcp",
version: "1.1.0",
},
{
capabilities: {
tools: {},
resources: {},
resourceTemplates: {},
prompts: {}, // Required for completion/complete support in some clients
logging: {},
},
}
);
// Initialize managers
this.authManager = new AuthManager();
this.sessionManager = new SessionManager(this.authManager);
this.library = new NotebookLibrary();
this.settingsManager = new SettingsManager();
// Initialize handlers
this.toolHandlers = new ToolHandlers(
this.sessionManager,
this.authManager,
this.library
);
this.resourceHandlers = new ResourceHandlers(this.library);
// Build and Filter tool definitions
const allTools = buildToolDefinitions(this.library) as Tool[];
this.toolDefinitions = this.settingsManager.filterTools(allTools);
// Setup handlers
this.setupHandlers();
this.setupShutdownHandlers();
const activeSettings = this.settingsManager.getEffectiveSettings();
log.info("π NotebookLM MCP Server initialized");
log.info(` Version: 1.1.0`);
log.info(` Node: ${process.version}`);
log.info(` Platform: ${process.platform}`);
log.info(` Profile: ${activeSettings.profile} (${this.toolDefinitions.length} tools active)`);
}
/**
* Setup MCP request handlers
*/
private setupHandlers(): void {
// Register Resource Handlers (Resources, Templates, Completions)
this.resourceHandlers.registerHandlers(this.server);
// List available tools
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
log.info("π [MCP] list_tools request received");
return {
tools: this.toolDefinitions,
};
});
// Handle tool calls
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
const progressToken = (args as any)?._meta?.progressToken;
log.info(`π§ [MCP] Tool call: ${name}`);
if (progressToken) {
log.info(` π Progress token: ${progressToken}`);
}
// Create progress callback function
const sendProgress = async (message: string, progress?: number, total?: number) => {
if (progressToken) {
await this.server.notification({
method: "notifications/progress",
params: {
progressToken,
message,
...(progress !== undefined && { progress }),
...(total !== undefined && { total }),
},
});
log.dim(` π Progress: ${message}`);
}
};
try {
let result;
switch (name) {
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;
case "add_notebook":
result = await this.toolHandlers.handleAddNotebook(
args as {
url: string;
name: string;
description: string;
topics: string[];
content_types?: string[];
use_cases?: string[];
tags?: string[];
}
);
break;
case "list_notebooks":
result = await this.toolHandlers.handleListNotebooks();
break;
case "get_notebook":
result = await this.toolHandlers.handleGetNotebook(
args as { id: string }
);
break;
case "select_notebook":
result = await this.toolHandlers.handleSelectNotebook(
args as { id: string }
);
break;
case "update_notebook":
result = await this.toolHandlers.handleUpdateNotebook(
args as {
id: string;
name?: string;
description?: string;
topics?: string[];
content_types?: string[];
use_cases?: string[];
tags?: string[];
url?: string;
}
);
break;
case "remove_notebook":
result = await this.toolHandlers.handleRemoveNotebook(
args as { id: string }
);
break;
case "search_notebooks":
result = await this.toolHandlers.handleSearchNotebooks(
args as { query: string }
);
break;
case "get_library_stats":
result = await this.toolHandlers.handleGetLibraryStats();
break;
case "list_sessions":
result = await this.toolHandlers.handleListSessions();
break;
case "close_session":
result = await this.toolHandlers.handleCloseSession(
args as { session_id: string }
);
break;
case "reset_session":
result = await this.toolHandlers.handleResetSession(
args as { session_id: string }
);
break;
case "get_health":
result = await this.toolHandlers.handleGetHealth();
break;
case "setup_auth":
result = await this.toolHandlers.handleSetupAuth(
args as { show_browser?: boolean },
sendProgress
);
break;
case "re_auth":
result = await this.toolHandlers.handleReAuth(
args as { show_browser?: boolean },
sendProgress
);
break;
case "cleanup_data":
result = await this.toolHandlers.handleCleanupData(
args as { confirm: boolean }
);
break;
default:
log.error(`β [MCP] Unknown tool: ${name}`);
return {
content: [
{
type: "text",
text: JSON.stringify(
{
success: false,
error: `Unknown tool: ${name}`,
},
null,
2
),
},
],
};
}
// Return result
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : String(error);
log.error(`β [MCP] Tool execution error: ${errorMessage}`);
return {
content: [
{
type: "text",
text: JSON.stringify(
{
success: false,
error: errorMessage,
},
null,
2
),
},
],
};
}
});
}
/**
* Setup graceful shutdown handlers
*/
private setupShutdownHandlers(): void {
let shuttingDown = false;
const shutdown = async (signal: string) => {
if (shuttingDown) {
return;
}
shuttingDown = true;
log.info(`\nπ Received ${signal}, shutting down gracefully...`);
try {
// Cleanup tool handlers (closes all sessions)
await this.toolHandlers.cleanup();
// Close server
await this.server.close();
log.success("β
Shutdown complete");
process.exit(0);
} catch (error) {
log.error(`β Error during shutdown: ${error}`);
process.exit(1);
}
};
const requestShutdown = (signal: string) => {
void shutdown(signal);
};
process.on("SIGINT", () => requestShutdown("SIGINT"));
process.on("SIGTERM", () => requestShutdown("SIGTERM"));
process.on("uncaughtException", (error) => {
log.error(`π₯ Uncaught exception: ${error}`);
log.error(error.stack || "");
requestShutdown("uncaughtException");
});
process.on("unhandledRejection", (reason, promise) => {
log.error(`π₯ Unhandled rejection at: ${promise}`);
log.error(`Reason: ${reason}`);
requestShutdown("unhandledRejection");
});
}
/**
* Start the MCP server
*/
async start(): Promise<void> {
log.info("π― Starting NotebookLM MCP Server...");
log.info("");
log.info("π Configuration:");
log.info(` Config Dir: ${CONFIG.configDir}`);
log.info(` Data Dir: ${CONFIG.dataDir}`);
log.info(` Headless: ${CONFIG.headless}`);
log.info(` Max Sessions: ${CONFIG.maxSessions}`);
log.info(` Session Timeout: ${CONFIG.sessionTimeout}s`);
log.info(` Stealth: ${CONFIG.stealthEnabled}`);
log.info("");
// Create stdio transport
const transport = new StdioServerTransport();
// Connect server to transport
await this.server.connect(transport);
log.success("β
MCP Server connected via stdio");
log.success("π Ready to receive requests from Claude Code!");
log.info("");
log.info("π‘ Available tools:");
for (const tool of this.toolDefinitions) {
const desc = tool.description ? tool.description.split('\n')[0] : 'No description'; // First line only
log.info(` - ${tool.name}: ${desc.substring(0, 80)}...`);
}
log.info("");
log.info("π For documentation, see: README.md");
log.info("π For MCP details, see: MCP_INFOS.md");
log.info("");
}
}
/**
* Main entry point
*/
async function main() {
// Handle CLI commands
const args = process.argv.slice(2);
if (args.length > 0 && args[0] === "config") {
const cli = new CliHandler();
await cli.handleCommand(args);
process.exit(0);
}
// Print banner
console.error("ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ");
console.error("β β");
console.error("β NotebookLM MCP Server v1.0.0 β");
console.error("β β");
console.error("β Chat with Gemini 2.5 through NotebookLM via MCP β");
console.error("β β");
console.error("ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ");
console.error("");
try {
const server = new NotebookLMMCPServer();
await server.start();
} catch (error) {
log.error(`π₯ Fatal error starting server: ${error}`);
if (error instanceof Error) {
log.error(error.stack || "");
}
process.exit(1);
}
}
// Run the server
main();