@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
JavaScript
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();