YouTube MCP Server

import { MCPFunction, MCPFunctionGroup } from "@modelcontextprotocol/sdk"; import { YoutubeTranscript } from "youtube-transcript"; import * as ytdl from "ytdl-core"; import * as fs from "fs/promises"; // Utility functions function safeGet<T>(obj: any, path: string, defaultValue?: T): T | undefined { return path.split('.').reduce((acc, part) => acc && acc[part] !== undefined ? acc[part] : defaultValue, obj); } function safeParse(value: string | number | null | undefined, defaultValue = 0): number { if (value === null || value === undefined) return defaultValue; const parsed = Number(value); return isNaN(parsed) ? defaultValue : parsed; } function safelyExecute<T>(fn: () => T): T | null { try { return fn(); } catch (error: unknown) { console.error('Execution error:', error instanceof Error ? error.message : 'Unknown error'); return null; } } export class ShortsHooksGenerator implements MCPFunctionGroup { private youtube: any; constructor() { this.youtube = google.youtube({ version: 'v3', auth: process.env.YOUTUBE_API_KEY }); } @MCPFunction({ description: 'Generate hooks for Shorts based on trending patterns', parameters: { type: 'object', properties: { topic: { type: 'string' }, style: { type: 'string', enum: ['question', 'statement', 'revelation', 'challenge'] } }, required: ['topic'] } }) async generateHooks({ topic, style = 'question' }: { topic: string, style?: string }): Promise<any> { try { const trendingShorts = await this.getTrendingShortsInTopic(topic); const patterns = await this.analyzeHookPatterns(trendingShorts); return { hooks: this.createHooks(topic, style, patterns), analysis: patterns.insights, suggestedDurations: patterns.timings }; } catch (error) { throw new Error(`Failed to generate hooks: ${error instanceof Error ? error.message : String(error)}`); } } @MCPFunction({ description: 'Analyze hook performance from existing Shorts', parameters: { type: 'object', properties: { videoIds: { type: 'array', items: { type: 'string' } } }, required: ['videoIds'] } }) async analyzeHookPerformance({ videoIds }: { videoIds: string[] }): Promise<any> { try { const performances = await Promise.all( videoIds.map(id => this.analyzeShortHook(id)) ); return { patterns: this.findSuccessPatterns(performances), recommendations: this.generateHookRecommendations(performances) }; } catch (error) { throw new Error(`Failed to analyze hook performance: ${error instanceof Error ? error.message : String(error)}`); } } // Private helper methods private async getTrendingShortsInTopic(topic: string): Promise<any[]> { const response = await this.youtube.search.list({ part: ['snippet', 'statistics'], q: topic, type: ['video'], videoDuration: 'short', order: 'viewCount', maxResults: 50 }); return response.data.items || []; } private async analyzeHookPatterns(shorts: any[]): Promise<any> { const patterns = { openingPhrases: new Map(), avgDuration: 0, commonFormats: new Map(), insights: [], timings: null }; for (const short of shorts) { const title = short.snippet.title; const firstPhrase = this.extractOpeningPhrase(title); const format = this.identifyFormat(title); patterns.openingPhrases.set( firstPhrase, (patterns.openingPhrases.get(firstPhrase) || 0) + 1 ); patterns.commonFormats.set( format, (patterns.commonFormats.get(format) || 0) + 1 ); } patterns.insights = this.generatePatternInsights(patterns); patterns.timings = this.analyzeTimings(shorts); return patterns; } private createHooks(topic: string, style: string, patterns: any): string[] { const hooks: string[] = []; const topFormats = [...patterns.commonFormats.entries()] .sort((a, b) => b[1] - a[1]) .slice(0, 3) .map(([format]) => format); switch (style) { case 'question': hooks.push( `Want to know the truth about ${topic}?`, `Did you know this about ${topic}?`, `The real reason ${topic} is trending...` ); break; case 'statement': hooks.push( `This ${topic} hack will change your life`, `Nobody tells you this about ${topic}`, `The ${topic} secret they don't want you to know` ); break; case 'revelation': hooks.push( `I discovered something shocking about ${topic}`, `This ${topic} truth will surprise you`, `Everything we know about ${topic} is wrong` ); break; case 'challenge': hooks.push( `Can you guess what happens with ${topic}?`, `Try this ${topic} challenge`, `90% of people fail this ${topic} test` ); break; } topFormats.forEach(format => { hooks.push(this.formatToHook(format, topic)); }); return hooks; } private async analyzeShortHook(videoId: string): Promise<any> { const video = await this.youtube.videos.list({ part: ['snippet', 'statistics'], id: [videoId] }); const details = video.data.items?.[0]; const title = details.snippet.title; const stats = details.statistics; return { hook: this.extractOpeningPhrase(title), format: this.identifyFormat(title), performance: { views: parseInt(stats.viewCount), likes: parseInt(stats.likeCount), retention: parseInt(stats.likeCount) / parseInt(stats.viewCount) } }; } private extractOpeningPhrase(title: string): string { const words = title.split(' '); return words.slice(0, Math.min(5, words.length)).join(' '); } private identifyFormat(title: string): string { if (title.includes('?')) return 'question'; if (title.includes('!')) return 'exclamation'; if (title.match(/\d+/)) return 'number'; if (title.includes('How') || title.includes('Why')) return 'explanation'; return 'statement'; } private generatePatternInsights(patterns: any): string[] { const insights: string[] = []; const topPhrases = [...patterns.openingPhrases.entries()] .sort((a, b) => b[1] - a[1]) .slice(0, 3); insights.push(`Most effective opening phrase: "${topPhrases[0][0]}"`); insights.push(`Top performing format: ${[...patterns.commonFormats.entries()] .sort((a, b) => b[1] - a[1])[0][0]}`); return insights; } private analyzeTimings(shorts: any[]): any { return { optimalHookLength: '3-5 seconds', transitionPoints: [3, 7, 15], peakEngagementWindow: '8-12 seconds' }; } private findSuccessPatterns(performances: any[]): any { const patterns = { highPerforming: [], commonElements: new Set(), avoidElements: new Set() }; performances.sort((a, b) => b.performance.retention - a.performance.retention); const topPerformers = performances.slice(0, Math.ceil(performances.length * 0.2)); const lowPerformers = performances.slice(-Math.ceil(performances.length * 0.2)); topPerformers.forEach(perf => { patterns.highPerforming.push({ hook: perf.hook, format: perf.format, metrics: perf.performance }); const elements = this.extractHookElements(perf.hook); elements.forEach(el => patterns.commonElements.add(el)); }); lowPerformers.forEach(perf => { const elements = this.extractHookElements(perf.hook); elements.forEach(el => { if (!patterns.commonElements.has(el)) { patterns.avoidElements.add(el); } }); }); return patterns; } private generateHookRecommendations(performances: any[]): string[] { const recommendations: string[] = []; const patterns = this.findSuccessPatterns(performances); recommendations.push( `Use these elements: ${[...patterns.commonElements].join(', ')}`, `Avoid these elements: ${[...patterns.avoidElements].join(', ')}`, `Best performing format: ${patterns.highPerforming[0].format}` ); return recommendations; } private extractHookElements(hook: string): string[] { const elements: string[] = []; if (hook.includes('?')) elements.push('question'); if (hook.includes('!')) elements.push('exclamation'); if (hook.match(/\d+/)) elements.push('number'); if (hook.match(/you|your/i)) elements.push('direct-address'); if (hook.match(/never|always|every/i)) elements.push('absolute'); if (hook.match(/secret|hidden|shocking/i)) elements.push('intrigue'); if (hook.match(/how|why|what/i)) elements.push('educational'); if (hook.toLowerCase().includes('watch')) elements.push('call-to-action'); return elements; } private formatToHook(format: string, topic: string): string { switch (format) { case 'question': return `Why is ${topic} breaking the internet?`; case 'exclamation': return `This ${topic} changed everything!`; case 'number': return `3 ${topic} secrets you need to know`; case 'explanation': return `How ${topic} really works`; default: return `The truth about ${topic}`; } } }