UNPKG

@snahrup/notion-hero-image-mcp

Version:

AI-powered animated hero banner generator for Notion pages. Integrates DALL-E 3, Google Veo 3, Cloudinary CDN, and Notion API.

486 lines (394 loc) 19.6 kB
#!/usr/bin/env node import { createInterface } from 'readline'; import { promises as fs } from 'fs'; import { exec } from 'child_process'; import { promisify } from 'util'; import path from 'path'; import os from 'os'; const execAsync = promisify(exec); // Color codes for beautiful CLI const colors = { reset: '\x1b[0m', bright: '\x1b[1m', dim: '\x1b[2m', red: '\x1b[31m', green: '\x1b[32m', yellow: '\x1b[33m', blue: '\x1b[34m', magenta: '\x1b[35m', cyan: '\x1b[36m', white: '\x1b[37m', bgBlue: '\x1b[44m', bgMagenta: '\x1b[45m', bgCyan: '\x1b[46m' }; const rl = createInterface({ input: process.stdin, output: process.stdout }); const question = (query) => new Promise(resolve => rl.question(query, resolve)); async function clearScreen() { console.clear(); } async function displayBanner() { await clearScreen(); console.log(` ${colors.bright}${colors.cyan} ╔══════════════════════════════════════════════════════════════════════════════╗ ║ ║ ║ ${colors.magenta}███╗ ██╗ ██████╗ ████████╗██╗ ██████╗ ███╗ ██╗ ██╗ ██╗███████╗██████╗ ██████╗ ${colors.cyan}║ ║ ${colors.magenta}████╗ ██║██╔═══██╗╚══██╔══╝██║██╔═══██╗████╗ ██║ ██║ ██║██╔════╝██╔══██╗██╔═══██╗${colors.cyan}║ ║ ${colors.magenta}██╔██╗ ██║██║ ██║ ██║ ██║██║ ██║██╔██╗ ██║ ███████║█████╗ ██████╔╝██║ ██║${colors.cyan}║ ║ ${colors.magenta}██║╚██╗██║██║ ██║ ██║ ██║██║ ██║██║╚██╗██║ ██╔══██║██╔══╝ ██╔══██╗██║ ██║${colors.cyan}║ ║ ${colors.magenta}██║ ╚████║╚██████╔╝ ██║ ██║╚██████╔╝██║ ╚████║ ██║ ██║███████╗██║ ██║╚██████╔╝${colors.cyan}║ ║ ${colors.magenta}╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ${colors.cyan}║ ║ ║ ║ ${colors.yellow}✨ AI-Powered Animated Hero Banners for Your Notion Pages ✨${colors.cyan} ║ ║ ║ ╚══════════════════════════════════════════════════════════════════════════════╝ ${colors.reset} ${colors.bright}Welcome to the Notion Hero Image MCP Setup Wizard!${colors.reset} This tool will help you create stunning animated banners for your Notion pages using: ${colors.green}${colors.reset} DALL-E 3 for AI image generation ${colors.green}${colors.reset} Google Veo 3 for smooth animations ${colors.green}${colors.reset} Cloudinary for fast CDN hosting ${colors.green}${colors.reset} Notion API for seamless integration ${colors.dim}Let's get you set up in just a few minutes...${colors.reset} `); } async function pause() { await question(`\n${colors.dim}Press Enter to continue...${colors.reset}`); } async function getOpenAIKey() { await clearScreen(); console.log(` ${colors.bright}${colors.blue}Step 1 of 4: OpenAI API Key${colors.reset} ${colors.bright}═══════════════════════════════════════${colors.reset} We'll use DALL-E 3 to generate amazing hero images from your prompts. ${colors.yellow}How to get your OpenAI API key:${colors.reset} 1. Visit: ${colors.cyan}https://platform.openai.com/${colors.reset} 2. Sign in or create an account 3. Click on your profile (top right) → "View API keys" 4. Click "${colors.green}Create new secret key${colors.reset}" 5. Name it something like "Notion Hero MCP" 6. ${colors.bright}${colors.red}IMPORTANT:${colors.reset} Copy the key immediately (you won't see it again!) ${colors.dim}The key should look like: sk-proj-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX${colors.reset} `); const apiKey = await question(`\n${colors.bright}Enter your OpenAI API key:${colors.reset} `); if (!apiKey.startsWith('sk-')) { console.log(`\n${colors.red}⚠️ That doesn't look like a valid OpenAI key. Keys start with 'sk-'${colors.reset}`); const retry = await question(`\nWould you like to try again? (y/n): `); if (retry.toLowerCase() === 'y') { return await getOpenAIKey(); } } return apiKey; } async function getCloudinaryInfo() { await clearScreen(); console.log(` ${colors.bright}${colors.blue}Step 2 of 4: Cloudinary Configuration${colors.reset} ${colors.bright}═══════════════════════════════════════${colors.reset} Cloudinary will host your animated banners on a global CDN for fast loading. ${colors.yellow}How to get your Cloudinary credentials:${colors.reset} 1. Visit: ${colors.cyan}https://cloudinary.com/users/register_free${colors.reset} 2. Create a free account (very generous free tier!) 3. After login, look at your Dashboard 4. Find the "${colors.green}API Environment variable${colors.reset}" section 5. You'll see: ${colors.dim}cloudinary://API_KEY:API_SECRET@CLOUD_NAME${colors.reset} ${colors.bright}We need three pieces of information:${colors.reset} `); const cloudName = await question(`\n${colors.bright}1. Cloud Name:${colors.reset} `); const apiKey = await question(`${colors.bright}2. API Key:${colors.reset} `); const apiSecret = await question(`${colors.bright}3. API Secret:${colors.reset} `); return { url: `cloudinary://${apiKey}:${apiSecret}@${cloudName}`, cloudName, apiKey, apiSecret }; } async function setupGoogleCloud() { await clearScreen(); console.log(` ${colors.bright}${colors.blue}Step 3 of 4: Google Cloud (for Veo 3 Animations)${colors.reset} ${colors.bright}═══════════════════════════════════════════════════════════${colors.reset} Google Veo 3 will animate your still images with cinematic quality! ${colors.yellow}This is the most complex step, but I'll guide you through it:${colors.reset} 1. Visit: ${colors.cyan}https://console.cloud.google.com/${colors.reset} 2. Sign in with your Google account 3. You'll get ${colors.green}$300 free credits${colors.reset} if you're new! ${colors.bright}Would you like me to:${colors.reset} ${colors.green}A)${colors.reset} Guide you through manual setup (recommended for first time) ${colors.green}B)${colors.reset} Use existing service account key file ${colors.green}C)${colors.reset} Skip for now (you can add it later) `); const choice = await question(`\nYour choice (A/B/C): `); if (choice.toLowerCase() === 'a') { return await manualGoogleSetup(); } else if (choice.toLowerCase() === 'b') { return await existingGoogleKey(); } else { console.log(`\n${colors.yellow}Skipping Google Cloud setup. You can add it later by running this wizard again.${colors.reset}`); return null; } } async function manualGoogleSetup() { console.log(` ${colors.bright}${colors.cyan}Let's create your Google Cloud project:${colors.reset} ${colors.yellow}Step 1: Create a Project${colors.reset} 1. In the Google Cloud Console, click the project dropdown (top left) 2. Click "${colors.green}New Project${colors.reset}" 3. Name it: ${colors.bright}notion-hero-mcp${colors.reset} (or your preference) 4. Click "Create" and wait ~30 seconds `); await pause(); const projectId = await question(`\n${colors.bright}Enter your Project ID:${colors.reset} `); console.log(` ${colors.yellow}Step 2: Enable Vertex AI${colors.reset} 1. Go to "${colors.green}APIs & Services${colors.reset}" → "${colors.green}Library${colors.reset}" 2. Search for "${colors.bright}Vertex AI API${colors.reset}" 3. Click on it and press "${colors.green}Enable${colors.reset}" 4. Wait for it to activate (~1 minute) `); await pause(); console.log(` ${colors.yellow}Step 3: Create Service Account${colors.reset} 1. Go to "${colors.green}IAM & Admin${colors.reset}" → "${colors.green}Service Accounts${colors.reset}" 2. Click "${colors.green}Create Service Account${colors.reset}" 3. Name: ${colors.bright}notion-hero-mcp-sa${colors.reset} 4. Description: ${colors.dim}Service account for Notion Hero Image MCP${colors.reset} 5. Click "${colors.green}Create and Continue${colors.reset}" 6. For Role, search and select: "${colors.bright}Vertex AI User${colors.reset}" 7. Click "${colors.green}Done${colors.reset}" `); await pause(); console.log(` ${colors.yellow}Step 4: Download Key File${colors.reset} 1. Click on the service account you just created 2. Go to the "${colors.green}Keys${colors.reset}" tab 3. Click "${colors.green}Add Key${colors.reset}" → "${colors.green}Create new key${colors.reset}" 4. Choose ${colors.bright}JSON${colors.reset} format 5. Save it somewhere safe (e.g., ${colors.cyan}C:\\notion-hero-mcp-key.json${colors.reset}) ${colors.red}⚠️ Keep this file secure and NEVER share it!${colors.reset} `); const keyPath = await question(`\n${colors.bright}Enter the full path to your key file:${colors.reset} `); // Verify file exists try { await fs.access(keyPath); console.log(`\n${colors.green}✓ Key file found!${colors.reset}`); } catch { console.log(`\n${colors.red}⚠️ Could not find key file at that location.${colors.reset}`); const retry = await question(`\nWould you like to enter a different path? (y/n): `); if (retry.toLowerCase() === 'y') { return await manualGoogleSetup(); } } return { projectId, keyPath, region: 'us-central1' }; } async function existingGoogleKey() { const keyPath = await question(`\n${colors.bright}Enter the full path to your service account key JSON file:${colors.reset} `); try { const keyContent = await fs.readFile(keyPath, 'utf8'); const keyData = JSON.parse(keyContent); const projectId = keyData.project_id; console.log(`\n${colors.green}✓ Found project: ${projectId}${colors.reset}`); return { projectId, keyPath, region: 'us-central1' }; } catch (error) { console.log(`\n${colors.red}⚠️ Could not read key file. Make sure it's a valid JSON file.${colors.reset}`); return await setupGoogleCloud(); } } async function getNotionToken() { await clearScreen(); console.log(` ${colors.bright}${colors.blue}Step 4 of 4: Notion Integration${colors.reset} ${colors.bright}═══════════════════════════════════════${colors.reset} Almost done! Let's connect to your Notion workspace. ${colors.yellow}How to create a Notion integration:${colors.reset} 1. Visit: ${colors.cyan}https://www.notion.so/my-integrations${colors.reset} 2. Click "${colors.green}+ New integration${colors.reset}" 3. Give it a name: ${colors.bright}Notion Hero Image MCP${colors.reset} 4. Select your workspace 5. Under capabilities, enable: ${colors.green}${colors.reset} Read content ${colors.green}${colors.reset} Update content 6. Click "${colors.green}Submit${colors.reset}" 7. Copy the "${colors.bright}Internal Integration Token${colors.reset}" ${colors.dim}The token starts with: ntn_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX${colors.reset} ${colors.red}⚠️ IMPORTANT:${colors.reset} After setup, you must share each Notion page with this integration! `); const token = await question(`\n${colors.bright}Enter your Notion Integration Token:${colors.reset} `); if (!token.startsWith('ntn_')) { console.log(`\n${colors.red}⚠️ That doesn't look like a valid Notion token. Tokens start with 'ntn_'${colors.reset}`); const retry = await question(`\nWould you like to try again? (y/n): `); if (retry.toLowerCase() === 'y') { return await getNotionToken(); } } return token; } async function checkFFmpeg() { console.log(`\n${colors.bright}Checking for FFmpeg...${colors.reset}`); try { await execAsync('ffmpeg -version'); console.log(`${colors.green}✓ FFmpeg is installed!${colors.reset}`); return true; } catch { console.log(` ${colors.yellow}⚠️ FFmpeg not found!${colors.reset} FFmpeg is required for converting videos to GIFs. ${colors.bright}To install FFmpeg:${colors.reset} ${colors.cyan}Windows:${colors.reset} 1. Download from: https://www.gyan.dev/ffmpeg/builds/ 2. Extract to C:\\ffmpeg 3. Add C:\\ffmpeg\\bin to your PATH ${colors.cyan}Mac:${colors.reset} brew install ffmpeg ${colors.cyan}Linux:${colors.reset} sudo apt install ffmpeg `); const skip = await question(`\n${colors.dim}Press Enter to continue without FFmpeg (you can install it later)...${colors.reset}`); return false; } } async function createEnvFile(config) { const envContent = `# Cloudinary Configuration CLOUDINARY_URL=${config.cloudinary.url} CLOUDINARY_CLOUD_NAME=${config.cloudinary.cloudName} CLOUDINARY_API_KEY=${config.cloudinary.apiKey} CLOUDINARY_API_SECRET=${config.cloudinary.apiSecret} # OpenAI Configuration OPENAI_API_KEY=${config.openai} # Google Cloud Configuration GOOGLE_PROJECT=${config.google?.projectId || 'your-project-id'} GOOGLE_REGION=${config.google?.region || 'us-central1'} GOOGLE_APPLICATION_CREDENTIALS=${config.google?.keyPath || 'path/to/key.json'} # Notion Configuration NOTION_API_TOKEN=${config.notion} `; await fs.writeFile('.env', envContent); console.log(`\n${colors.green}✓ Created .env file${colors.reset}`); } async function configureMCPClient(config) { console.log(`\n${colors.bright}${colors.cyan}Configuring MCP Client...${colors.reset}`); const platform = os.platform(); let configPath; if (platform === 'win32') { configPath = path.join(os.homedir(), 'AppData', 'Roaming', 'Claude', 'claude_desktop_config.json'); } else if (platform === 'darwin') { configPath = path.join(os.homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json'); } else { console.log(`\n${colors.yellow}⚠️ Auto-configuration not available for your platform.${colors.reset}`); return false; } try { // Read existing config or create new one let existingConfig = {}; try { const configContent = await fs.readFile(configPath, 'utf8'); existingConfig = JSON.parse(configContent); } catch { // Config doesn't exist yet } // Add our MCP server if (!existingConfig.mcpServers) { existingConfig.mcpServers = {}; } const installPath = process.cwd(); existingConfig.mcpServers['notion-hero-image'] = { command: 'node', args: [path.join(installPath, 'dist', 'index.js')], env: { CLOUDINARY_URL: config.cloudinary.url, CLOUDINARY_CLOUD_NAME: config.cloudinary.cloudName, CLOUDINARY_API_KEY: config.cloudinary.apiKey, CLOUDINARY_API_SECRET: config.cloudinary.apiSecret, OPENAI_API_KEY: config.openai, GOOGLE_PROJECT: config.google?.projectId || '', GOOGLE_REGION: config.google?.region || 'us-central1', GOOGLE_APPLICATION_CREDENTIALS: config.google?.keyPath || '', NOTION_API_TOKEN: config.notion } }; // Create directory if it doesn't exist await fs.mkdir(path.dirname(configPath), { recursive: true }); // Write updated config await fs.writeFile(configPath, JSON.stringify(existingConfig, null, 2)); console.log(`${colors.green}✓ Updated Claude Desktop configuration${colors.reset}`); return true; } catch (error) { console.log(`\n${colors.red}⚠️ Could not update Claude Desktop config automatically.${colors.reset}`); console.log(`${colors.dim}Error: ${error.message}${colors.reset}`); return false; } } async function showFinalInstructions(autoConfigured) { await clearScreen(); console.log(` ${colors.bright}${colors.green}🎉 Setup Complete! 🎉${colors.reset} ${colors.bright}Your Notion Hero Image MCP is now configured!${colors.reset} ${autoConfigured ? `${colors.green}✓ Claude Desktop has been automatically configured${colors.reset}` : `${colors.yellow}⚠️ Please manually configure your MCP client${colors.reset}`} ${colors.bright}${colors.cyan}Next Steps:${colors.reset} 1. ${colors.bright}Restart Claude Desktop${colors.reset} (or your MCP client) 2. ${colors.bright}Share Notion pages with your integration:${colors.reset} • Open any Notion page you want to use • Click "..." menu (top right) → "Add connections" • Search for "${colors.green}Notion Hero Image MCP${colors.reset}" • Click to add it 3. ${colors.bright}Test it out in Claude:${colors.reset} • "Create an animated banner for my Notion page called 'Project Ideas'" • "Make a cyberpunk-themed hero image for 'AI Research'" • "Generate a serene nature animation for 'Daily Journal'" ${colors.bright}${colors.yellow}Troubleshooting:${colors.reset} • If tools don't appear: Make sure you restarted Claude Desktop • If Notion pages aren't found: Ensure you shared them with the integration • For help: Visit ${colors.cyan}https://github.com/snahrup/notion-hero-image-mcp${colors.reset} ${colors.dim}Happy creating! ✨${colors.reset} `); } async function main() { try { await displayBanner(); await pause(); console.log(`\n${colors.bright}Starting setup process...${colors.reset}\n`); // Collect all configuration const config = { openai: await getOpenAIKey(), cloudinary: await getCloudinaryInfo(), google: await setupGoogleCloud(), notion: await getNotionToken() }; // Check for FFmpeg await checkFFmpeg(); // Create .env file console.log(`\n${colors.bright}${colors.cyan}Creating configuration files...${colors.reset}`); await createEnvFile(config); // Try to auto-configure Claude Desktop const autoConfigured = await configureMCPClient(config); // Build the project console.log(`\n${colors.bright}Building the project...${colors.reset}`); try { await execAsync('npm run build'); console.log(`${colors.green}✓ Build successful!${colors.reset}`); } catch (error) { console.log(`${colors.yellow}⚠️ Build failed. You may need to run 'npm install' first.${colors.reset}`); } // Show final instructions await showFinalInstructions(autoConfigured); } catch (error) { console.error(`\n${colors.red}Setup failed: ${error.message}${colors.reset}`); } finally { rl.close(); } } // Run the wizard main();