Skip to main content
Glama

youtube_to_linkedin_post

Convert YouTube video content into LinkedIn post drafts by analyzing transcripts and generating professional content tailored to your audience and tone preferences.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
youtubeUrlYesYouTube video URL
toneNoTone of the LinkedIn postfirst-person
summaryToneNoTone of the summaryprofessional
audienceNoTarget audiencegeneral
hashtagsNoRelevant hashtags (optional)
includeCallToActionNoWhether to include a call to action

Implementation Reference

  • Inline handler for the youtube_to_linkedin_post tool. Orchestrates the full pipeline: extracts transcript, fetches title, summarizes content, generates LinkedIn post.
    async ({ youtubeUrl, tone, summaryTone, audience, hashtags, includeCallToAction }) => { try { // Check if API keys are set if (!apiKeyManager.hasOpenAIKey()) { throw new Error("OpenAI API key not set. Please use the set_api_keys tool first."); } // Step 1: Extract transcript const transcript = await extractTranscript(youtubeUrl, apiKeyManager.getYouTubeKey()); // Step 2: Get video metadata (title, etc.) const videoTitle = await getVideoTitle(youtubeUrl); // Step 3: Summarize transcript const summary = await summarizeTranscript( transcript, summaryTone, audience, 200, apiKeyManager.getOpenAIKey() ); // Step 4: Generate LinkedIn post const post = await generateLinkedInPost( summary, videoTitle, undefined, // speaker name not available without additional API calls hashtags, tone, includeCallToAction, apiKeyManager.getOpenAIKey() ); return { content: [{ type: "text", text: JSON.stringify({ success: true, videoTitle, transcript: transcript.substring(0, 300) + "...", // Preview only summary, post }, null, 2) }] }; } catch (error) { return { content: [{ type: "text", text: JSON.stringify({ success: false, error: error.message }, null, 2) }], isError: true }; } },
  • Zod input schema defining parameters for the youtube_to_linkedin_post tool.
    { youtubeUrl: z.string().url().describe("YouTube video URL"), tone: z.enum(["first-person", "third-person", "thought-leader"]) .default("first-person") .describe("Tone of the LinkedIn post"), summaryTone: z.enum(["educational", "inspirational", "professional", "conversational"]) .default("professional") .describe("Tone of the summary"), audience: z.enum(["general", "technical", "business", "academic"]) .default("general") .describe("Target audience"), hashtags: z.array(z.string()).optional().describe("Relevant hashtags (optional)"), includeCallToAction: z.boolean().default(true) .describe("Whether to include a call to action") },
  • src/server.js:223-299 (registration)
    MCP server tool registration for youtube_to_linkedin_post.
    server.tool( "youtube_to_linkedin_post", { youtubeUrl: z.string().url().describe("YouTube video URL"), tone: z.enum(["first-person", "third-person", "thought-leader"]) .default("first-person") .describe("Tone of the LinkedIn post"), summaryTone: z.enum(["educational", "inspirational", "professional", "conversational"]) .default("professional") .describe("Tone of the summary"), audience: z.enum(["general", "technical", "business", "academic"]) .default("general") .describe("Target audience"), hashtags: z.array(z.string()).optional().describe("Relevant hashtags (optional)"), includeCallToAction: z.boolean().default(true) .describe("Whether to include a call to action") }, async ({ youtubeUrl, tone, summaryTone, audience, hashtags, includeCallToAction }) => { try { // Check if API keys are set if (!apiKeyManager.hasOpenAIKey()) { throw new Error("OpenAI API key not set. Please use the set_api_keys tool first."); } // Step 1: Extract transcript const transcript = await extractTranscript(youtubeUrl, apiKeyManager.getYouTubeKey()); // Step 2: Get video metadata (title, etc.) const videoTitle = await getVideoTitle(youtubeUrl); // Step 3: Summarize transcript const summary = await summarizeTranscript( transcript, summaryTone, audience, 200, apiKeyManager.getOpenAIKey() ); // Step 4: Generate LinkedIn post const post = await generateLinkedInPost( summary, videoTitle, undefined, // speaker name not available without additional API calls hashtags, tone, includeCallToAction, apiKeyManager.getOpenAIKey() ); return { content: [{ type: "text", text: JSON.stringify({ success: true, videoTitle, transcript: transcript.substring(0, 300) + "...", // Preview only summary, post }, null, 2) }] }; } catch (error) { return { content: [{ type: "text", text: JSON.stringify({ success: false, error: error.message }, null, 2) }], isError: true }; } }, { description: "Generate a LinkedIn post draft directly from a YouTube video URL" } );
  • Core helper that generates the LinkedIn post text from summary and parameters using OpenAI GPT.
    export async function generateLinkedInPost( summary, videoTitle, speakerName = null, hashtags = [], tone = "first-person", includeCallToAction = true, apiKey ) { if (!apiKey) { throw new Error("OpenAI API key not provided"); } if (!summary || summary.trim().length === 0) { throw new Error("Empty summary provided"); } console.log(`Generating LinkedIn post with tone: ${tone}`); try { // Initialize OpenAI client with provided API key const openai = new OpenAI({ apiKey: apiKey, }); // Prepare hashtag string const hashtagString = hashtags && hashtags.length > 0 ? hashtags.map(tag => tag.startsWith('#') ? tag : `#${tag}`).join(' ') : ''; // Prepare speaker reference const speakerReference = speakerName ? `by ${speakerName}` : ''; const response = await openai.chat.completions.create({ model: "gpt-3.5-turbo", messages: [ { role: "system", content: `You are a professional LinkedIn content creator. Create a compelling LinkedIn post in a ${tone} tone based on the provided video summary. The post should be between 500-1200 characters (not including hashtags). Structure the post with: 1. An attention-grabbing hook 2. 2-3 key insights from the video 3. A personal reflection or takeaway ${includeCallToAction ? '4. A soft call to action (e.g., asking a question, inviting comments)' : ''} The post should feel authentic, professional, and valuable to LinkedIn readers. Avoid clickbait or overly promotional language.` }, { role: "user", content: `Create a LinkedIn post based on this YouTube video: Title: ${videoTitle} ${speakerReference} Summary: ${summary} ${hashtagString ? `Suggested hashtags: ${hashtagString}` : ''} Please format the post ready to copy and paste to LinkedIn.` } ], temperature: 0.7, max_tokens: 700 }); if (response.choices && response.choices.length > 0) { let post = response.choices[0].message.content.trim(); // Ensure hashtags are at the end if they weren't included if (hashtagString && !post.includes(hashtagString)) { post += `\n\n${hashtagString}`; } return post; } else { throw new Error("No post generated"); } } catch (error) { console.error("Post generation error:", error); throw new Error(`Failed to generate LinkedIn post: ${error.message}`); } }
  • Helper to extract full transcript text from YouTube video URL.
    export async function extractTranscript(youtubeUrl, youtubeApiKey = null) { try { console.log(`Extracting transcript from: ${youtubeUrl}`); // Extract video ID from URL const videoId = extractVideoId(youtubeUrl); if (!videoId) { throw new Error("Invalid YouTube URL. Could not extract video ID."); } // Try to get transcript using youtube-transcript package try { const transcriptItems = await YoutubeTranscript.fetchTranscript(videoId); if (!transcriptItems || transcriptItems.length === 0) { throw new Error("No transcript available"); } // Combine transcript segments into a single text const fullTranscript = transcriptItems .map(item => item.text) .join(' ') .replace(/\s+/g, ' '); // Clean up extra spaces return fullTranscript; } catch (error) { console.error("Error with primary transcript method:", error); // Fallback to YouTube API if available if (youtubeApiKey) { return await fetchTranscriptWithYouTubeAPI(videoId, youtubeApiKey); } else { throw new Error("Failed to extract transcript: " + error.message); } } } catch (error) { console.error("Transcript extraction error:", error); throw new Error(`Failed to extract transcript: ${error.message}`); } }
  • Helper to summarize transcript with specified tone and audience using OpenAI.
    export async function summarizeTranscript(transcript, tone, audience, wordCount, apiKey) { if (!apiKey) { throw new Error("OpenAI API key not provided"); } if (!transcript || transcript.trim().length === 0) { throw new Error("Empty transcript provided"); } console.log(`Summarizing transcript (${transcript.length} chars) with tone: ${tone}, audience: ${audience}`); try { // Initialize OpenAI client with provided API key const openai = new OpenAI({ apiKey: apiKey, }); // Truncate transcript if it's too long (to fit within token limits) const truncatedTranscript = truncateText(transcript, 15000); const response = await openai.chat.completions.create({ model: "gpt-3.5-turbo", messages: [ { role: "system", content: `You are a professional content summarizer. Summarize the provided transcript in a ${tone} tone for a ${audience} audience. The summary should be approximately ${wordCount} words and capture the key points, insights, and valuable information from the transcript. Focus on making the summary concise, informative, and engaging.` }, { role: "user", content: `Please summarize the following video transcript:\n\n${truncatedTranscript}` } ], temperature: 0.7, max_tokens: 500 }); if (response.choices && response.choices.length > 0) { return response.choices[0].message.content.trim(); } else { throw new Error("No summary generated"); } } catch (error) { console.error("Summarization error:", error); throw new Error(`Failed to summarize transcript: ${error.message}`); } }

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/NvkAnirudh/LinkedIn-Post-Generator'

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