Skip to main content
Glama
http-wrapper.ts13 kB
/** * HTTP Wrapper for NotebookLM MCP Server * * Exposes the MCP server via HTTP REST API * Allows n8n and other tools to call the server without stdio */ import express, { Request, Response } from 'express'; import { AuthManager } from './auth/auth-manager.js'; import { SessionManager } from './session/session-manager.js'; import { NotebookLibrary } from './library/notebook-library.js'; import { ToolHandlers } from './tools/index.js'; import { AutoDiscovery } from './auto-discovery/auto-discovery.js'; import { log } from './utils/logger.js'; const app = express(); app.use(express.json()); // CORS for n8n app.use((_req, res, next) => { res.header('Access-Control-Allow-Origin', '*'); res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization'); if (_req.method === 'OPTIONS') { return res.sendStatus(200); } next(); }); // Initialize managers const authManager = new AuthManager(); const sessionManager = new SessionManager(authManager); const library = new NotebookLibrary(sessionManager); const toolHandlers = new ToolHandlers(sessionManager, authManager, library); // Health check app.get('/health', async (_req: Request, res: Response) => { try { const result = await toolHandlers.handleGetHealth(); res.json(result); } catch (error) { res.status(500).json({ success: false, error: error instanceof Error ? error.message : String(error), }); } }); // Ask question app.post('/ask', async (req: Request, res: Response) => { try { const { question, session_id, notebook_id, notebook_url, show_browser } = req.body; if (!question) { return res.status(400).json({ success: false, error: 'Missing required field: question', }); } const result = await toolHandlers.handleAskQuestion( { question, session_id, notebook_id, notebook_url, show_browser }, async (message, progress, total) => { log.info(`Progress: ${message} (${progress}/${total})`); } ); res.json(result); } catch (error) { res.status(500).json({ success: false, error: error instanceof Error ? error.message : String(error), }); } }); // Setup auth app.post('/setup-auth', async (req: Request, res: Response) => { try { const { show_browser } = req.body; const result = await toolHandlers.handleSetupAuth( { show_browser }, async (message, progress, total) => { log.info(`Progress: ${message} (${progress}/${total})`); } ); res.json(result); } catch (error) { res.status(500).json({ success: false, error: error instanceof Error ? error.message : String(error), }); } }); // De-authenticate (logout) app.post('/de-auth', async (_req: Request, res: Response) => { try { const result = await toolHandlers.handleDeAuth(); res.json(result); } catch (error) { res.status(500).json({ success: false, error: error instanceof Error ? error.message : String(error), }); } }); // Re-authenticate app.post('/re-auth', async (req: Request, res: Response) => { try { const { show_browser } = req.body; const result = await toolHandlers.handleReAuth( { show_browser }, async (message, progress, total) => { log.info(`Progress: ${message} (${progress}/${total})`); } ); res.json(result); } catch (error) { res.status(500).json({ success: false, error: error instanceof Error ? error.message : String(error), }); } }); // Cleanup data app.post('/cleanup-data', async (req: Request, res: Response) => { try { const { confirm, preserve_library } = req.body; const result = await toolHandlers.handleCleanupData({ confirm, preserve_library }); res.json(result); } catch (error) { res.status(500).json({ success: false, error: error instanceof Error ? error.message : String(error), }); } }); // List notebooks app.get('/notebooks', async (_req: Request, res: Response) => { try { const result = await toolHandlers.handleListNotebooks(); res.json(result); } catch (error) { res.status(500).json({ success: false, error: error instanceof Error ? error.message : String(error), }); } }); // Add notebook app.post('/notebooks', async (req: Request, res: Response) => { try { const { url, name, description, topics, content_types, use_cases, tags } = req.body; if (!url || !name || !description || !topics) { return res.status(400).json({ success: false, error: 'Missing required fields: url, name, description, topics', }); } const result = await toolHandlers.handleAddNotebook({ url, name, description, topics, content_types, use_cases, tags, }); res.json(result); } catch (error) { res.status(500).json({ success: false, error: error instanceof Error ? error.message : String(error), }); } }); // Get notebook app.get('/notebooks/:id', async (req: Request, res: Response) => { try { const result = await toolHandlers.handleGetNotebook({ id: req.params.id }); res.json(result); } catch (error) { res.status(500).json({ success: false, error: error instanceof Error ? error.message : String(error), }); } }); // Update notebook app.put('/notebooks/:id', async (req: Request, res: Response) => { try { const result = await toolHandlers.handleUpdateNotebook({ id: req.params.id, ...req.body, }); res.json(result); } catch (error) { res.status(500).json({ success: false, error: error instanceof Error ? error.message : String(error), }); } }); // Delete notebook app.delete('/notebooks/:id', async (req: Request, res: Response) => { try { const result = await toolHandlers.handleRemoveNotebook({ id: req.params.id }); res.json(result); } catch (error) { res.status(500).json({ success: false, error: error instanceof Error ? error.message : String(error), }); } }); // Search notebooks app.get('/notebooks/search', async (req: Request, res: Response) => { try { const { query } = req.query; const result = await toolHandlers.handleSearchNotebooks({ query: query as string, }); res.json(result); } catch (error) { res.status(500).json({ success: false, error: error instanceof Error ? error.message : String(error), }); } }); // Get library stats app.get('/notebooks/stats', async (_req: Request, res: Response) => { try { const result = await toolHandlers.handleGetLibraryStats(); res.json(result); } catch (error) { res.status(500).json({ success: false, error: error instanceof Error ? error.message : String(error), }); } }); // Auto-discover notebook metadata app.post('/notebooks/auto-discover', async (req: Request, res: Response) => { try { const { url } = req.body; // Validate URL is provided if (!url) { return res.status(400).json({ success: false, error: 'Missing required field: url', }); } // Validate it's a NotebookLM URL if (!url.includes('notebooklm.google.com')) { return res.status(400).json({ success: false, error: 'Invalid URL: must be a NotebookLM URL (notebooklm.google.com)', }); } // Create AutoDiscovery instance and discover metadata const autoDiscovery = new AutoDiscovery(sessionManager); let metadata; try { metadata = await autoDiscovery.discoverMetadata(url); } catch (error) { return res.status(500).json({ success: false, error: `Failed to discover metadata: ${error instanceof Error ? error.message : String(error)}`, }); } // Transform metadata to NotebookLibrary format // - tags → topics (rename field) // - Add default content_types // - Add default use_cases based on first few tags const notebookInput = { url, name: metadata.name, description: metadata.description, topics: metadata.tags, // tags → topics content_types: ['documentation'], use_cases: metadata.tags.slice(0, 3), // Use first 3 tags as use cases auto_generated: true, }; // Add notebook to library let notebook; try { notebook = await library.addNotebook(notebookInput); } catch (error) { return res.status(500).json({ success: false, error: `Failed to add notebook to library: ${error instanceof Error ? error.message : String(error)}`, }); } // Return success with created notebook res.json({ success: true, notebook, }); } catch (error) { res.status(500).json({ success: false, error: error instanceof Error ? error.message : String(error), }); } }); // Activate notebook (set as active) app.put('/notebooks/:id/activate', async (req: Request, res: Response) => { try { const result = await toolHandlers.handleSelectNotebook({ id: req.params.id }); res.json(result); } catch (error) { res.status(500).json({ success: false, error: error instanceof Error ? error.message : String(error), }); } }); // List sessions app.get('/sessions', async (_req: Request, res: Response) => { try { const result = await toolHandlers.handleListSessions(); res.json(result); } catch (error) { res.status(500).json({ success: false, error: error instanceof Error ? error.message : String(error), }); } }); // Close session app.delete('/sessions/:id', async (req: Request, res: Response) => { try { const result = await toolHandlers.handleCloseSession({ session_id: req.params.id }); res.json(result); } catch (error) { res.status(500).json({ success: false, error: error instanceof Error ? error.message : String(error), }); } }); // Reset session app.post('/sessions/:id/reset', async (req: Request, res: Response) => { try { const result = await toolHandlers.handleResetSession({ session_id: req.params.id }); res.json(result); } catch (error) { res.status(500).json({ success: false, error: error instanceof Error ? error.message : String(error), }); } }); // Start server const PORT = Number(process.env.HTTP_PORT) || 3000; const HOST = process.env.HTTP_HOST || '0.0.0.0'; app.listen(PORT, HOST, () => { log.success(`🌐 NotebookLM MCP HTTP Server v1.3.5`); log.success(` Listening on ${HOST}:${PORT}`); log.info(''); log.info('📊 Quick Links:'); log.info(` Health check: http://localhost:${PORT}/health`); log.info(` API endpoint: http://localhost:${PORT}/ask`); log.info(''); log.info('📖 Available Endpoints:'); log.info(' Authentication:'); log.info(' POST /setup-auth First-time authentication'); log.info(' POST /de-auth Logout (clear credentials)'); log.info(' POST /re-auth Re-authenticate / switch account'); log.info(' POST /cleanup-data Clean all data (requires confirm)'); log.info(''); log.info(' Queries:'); log.info(' POST /ask Ask a question to NotebookLM'); log.info(' GET /health Server health check'); log.info(''); log.info(' Notebooks:'); log.info(' GET /notebooks List all notebooks'); log.info(' POST /notebooks Add a new notebook'); log.info(' POST /notebooks/auto-discover Auto-discover notebook metadata'); log.info(' GET /notebooks/search Search notebooks by query'); log.info(' GET /notebooks/stats Get library statistics'); log.info(' GET /notebooks/:id Get notebook details'); log.info(' PUT /notebooks/:id Update notebook metadata'); log.info(' DELETE /notebooks/:id Delete a notebook'); log.info(' PUT /notebooks/:id/activate Activate a notebook (set as default)'); log.info(''); log.info(' Sessions:'); log.info(' GET /sessions List active sessions'); log.info(' POST /sessions/:id/reset Reset session history'); log.info(' DELETE /sessions/:id Close a session'); log.info(''); log.info('💡 Configuration:'); log.info( ` Host: ${HOST} ${HOST === '0.0.0.0' ? '(accessible from network)' : '(localhost only)'}` ); log.info(` Port: ${PORT}`); log.info(''); log.dim('📖 Documentation: ./deployment/docs/'); log.dim('⏹️ Press Ctrl+C to stop'); }); // Graceful shutdown process.on('SIGTERM', async () => { log.info('SIGTERM received, shutting down gracefully...'); await toolHandlers.cleanup(); process.exit(0); }); process.on('SIGINT', async () => { log.info('SIGINT received, shutting down gracefully...'); await toolHandlers.cleanup(); process.exit(0); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/roomi-fields/notebooklm-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server