UNPKG

youtube-data-mcp

Version:

MCP server for YouTube data extraction - transcripts, comments, and search

150 lines 5.25 kB
import { config } from "../config/index.js"; async function fetchFromSerpApi(params) { const url = new URL(config.serpApiBaseUrl); Object.entries({ api_key: config.serpApiKey, ...params }).forEach(([key, value]) => { url.searchParams.append(key, value); }); const response = await fetch(url.toString(), { headers: { "Content-Type": "application/json" }, }); if (!response.ok) { const errorText = await response.text(); throw new Error(`SerpAPI error: ${response.status} - ${errorText}`); } return response.json(); } export async function getVideoInfo(videoId) { if (!config.serpApiKey) { throw new Error("SERPAPI_KEY is not set in environment variables"); } const data = await fetchFromSerpApi({ engine: "youtube_video", v: videoId, }); const sortingTokens = data.comments_sorting_token; return { videoId, title: data.title, viewCount: data.views, publishDate: data.published_date, channelName: data.channel?.name, commentCount: data.extracted_comment_count, commentsNextPageToken: data.comments_next_page_token, commentsSortingTokens: sortingTokens, }; } export async function getComments(videoId, limit, sort, pageToken) { if (!config.serpApiKey) { throw new Error("SERPAPI_KEY is not set in environment variables"); } let tokenToUse = pageToken; let extractedVideoId = videoId || ""; if (!pageToken && videoId) { const videoInfo = await getVideoInfo(videoId); extractedVideoId = videoInfo.videoId; tokenToUse = videoInfo.commentsNextPageToken; if (sort === "time" && videoInfo.commentsSortingTokens) { const newestFirstToken = videoInfo.commentsSortingTokens.find((token) => token.title.toLowerCase().includes("newest")); if (newestFirstToken) { tokenToUse = newestFirstToken.token; } } } if (!tokenToUse) { throw new Error("Could not find valid comments token"); } const data = await fetchFromSerpApi({ engine: "youtube_video", next_page_token: tokenToUse, }); const rawComments = data.comments; if (!rawComments || !Array.isArray(rawComments)) { return { videoId: extractedVideoId, videoTitle: data.title, comments: [], commentCount: 0, nextPageToken: data.comments_next_page_token, }; } const comments = rawComments.slice(0, limit).map((comment) => ({ commentId: comment.comment_id || "", author: comment.channel?.name || "Anonymous", text: comment.content || "", time: comment.published_date || "", likes: comment.extracted_vote_count || 0, replies: comment.replies_count || 0, repliesToken: comment.replies_next_page_token || null, })); return { videoId: data.video_id || extractedVideoId, videoTitle: data.title, comments, commentCount: comments.length, nextPageToken: data.comments_next_page_token, }; } export async function getReplies(pageToken) { if (!config.serpApiKey) { throw new Error("SERPAPI_KEY is not set in environment variables"); } const data = await fetchFromSerpApi({ engine: "youtube_video", next_page_token: pageToken, }); const rawReplies = data.replies; if (!rawReplies || !Array.isArray(rawReplies)) { return { parentCommentId: data.comment_parent_id || "", replies: [], replyCount: 0, nextPageToken: data.replies_next_page_token, }; } const replies = rawReplies.map((reply) => ({ commentId: reply.comment_id || "", author: reply.channel?.name || "Anonymous", text: reply.content || "", time: reply.published_date || "", likes: reply.extracted_vote_count || 0, })); return { parentCommentId: data.comment_parent_id || "", replies, replyCount: replies.length, nextPageToken: data.replies_next_page_token, }; } export async function searchYouTube(query, limit, gl, hl, sp, pageToken) { if (!config.serpApiKey) { throw new Error("SERPAPI_KEY is not set in environment variables"); } const params = { engine: "youtube", search_query: query, }; if (gl) params.gl = gl; if (hl) params.hl = hl; if (sp) params.sp = sp; if (pageToken) params.sp = pageToken; const data = await fetchFromSerpApi(params); const videoResults = data.video_results; const pagination = data.pagination; return { searchMetadata: data.search_metadata, searchParameters: data.search_parameters, videoResults: videoResults?.slice(0, limit) || [], channelResults: data.channel_results, playlistResults: data.playlist_results, pagination: pagination ? { current: pagination.current, next: pagination.next, nextPageToken: pagination.next_page_token, } : undefined, }; } //# sourceMappingURL=serpapi.js.map