YouTube MCP Server
by icraft2170
Verified
- youtube-data-mcp-server
- src
- functions
import { google, youtube_v3 } from 'googleapis';
import { getSubtitles } from 'youtube-captions-scraper';
export interface VideoOptions {
videoId: string;
parts?: string[];
}
export interface SearchOptions {
query: string;
maxResults?: number;
}
export interface ChannelOptions {
channelId: string;
maxResults?: number;
}
export interface TrendingOptions {
regionCode?: string;
categoryId?: string;
maxResults?: number;
}
export interface CompareVideosOptions {
videoIds: string[];
}
export class VideoManagement {
private youtube: youtube_v3.Youtube;
private readonly MAX_RESULTS_PER_PAGE = 50;
private readonly ABSOLUTE_MAX_RESULTS = 500;
constructor() {
this.youtube = google.youtube({
version: 'v3',
auth: process.env.YOUTUBE_API_KEY
});
}
async getVideo({ videoId, parts = ['snippet'] }: VideoOptions) {
try {
const response = await this.youtube.videos.list({
part: parts,
id: [videoId]
});
if (!response.data.items?.length) {
throw new Error('Video not found.');
}
return response.data.items[0];
} catch (error: any) {
throw new Error(`Failed to retrieve video information: ${error.message}`);
}
}
async searchVideos({ query, maxResults = 10 }: SearchOptions) {
try {
const results: youtube_v3.Schema$SearchResult[] = [];
let nextPageToken: string | undefined = undefined;
const targetResults = Math.min(maxResults, this.ABSOLUTE_MAX_RESULTS);
while (results.length < targetResults) {
const response: youtube_v3.Schema$SearchListResponse = (await this.youtube.search.list({
part: ['snippet'],
q: query,
maxResults: Math.min(this.MAX_RESULTS_PER_PAGE, targetResults - results.length),
type: ['video'],
pageToken: nextPageToken
})).data;
if (!response.items?.length) {
break;
}
results.push(...response.items);
nextPageToken = response.nextPageToken || undefined;
if (!nextPageToken) {
break;
}
}
return results.slice(0, targetResults);
} catch (error: any) {
throw new Error(`Failed to search videos: ${error.message}`);
}
}
async getTranscript(videoId: string, lang?: string) {
try {
const transcript = await getSubtitles({
videoID: videoId,
lang: lang || process.env.YOUTUBE_TRANSCRIPT_LANG || 'en'
});
return transcript;
} catch (error: any) {
throw new Error(`Failed to retrieve transcript: ${error.message}`);
}
}
async getRelatedVideos(videoId: string, maxResults: number = 10) {
try {
const response = await this.youtube.search.list({
part: ['snippet'],
type: ['video'],
maxResults,
relatedToVideoId: videoId
} as youtube_v3.Params$Resource$Search$List);
return response.data.items || [];
} catch (error: any) {
throw new Error(`Failed to retrieve related videos: ${error.message}`);
}
}
async getChannelStatistics(channelId: string) {
try {
const response = await this.youtube.channels.list({
part: ['snippet', 'statistics'],
id: [channelId]
});
if (!response.data.items?.length) {
throw new Error('Channel not found.');
}
const channel = response.data.items[0];
return {
title: channel.snippet?.title,
subscriberCount: channel.statistics?.subscriberCount,
viewCount: channel.statistics?.viewCount,
videoCount: channel.statistics?.videoCount
};
} catch (error: any) {
throw new Error(`Failed to retrieve channel statistics: ${error.message}`);
}
}
async getChannelTopVideos({ channelId, maxResults = 10 }: ChannelOptions) {
try {
const searchResults: youtube_v3.Schema$SearchResult[] = [];
let nextPageToken: string | undefined = undefined;
const targetResults = Math.min(maxResults, this.ABSOLUTE_MAX_RESULTS);
while (searchResults.length < targetResults) {
const searchResponse: youtube_v3.Schema$SearchListResponse = (await this.youtube.search.list({
part: ['id'],
channelId: channelId,
maxResults: Math.min(this.MAX_RESULTS_PER_PAGE, targetResults - searchResults.length),
order: 'viewCount',
type: ['video'],
pageToken: nextPageToken
})).data;
if (!searchResponse.items?.length) {
break;
}
searchResults.push(...searchResponse.items);
nextPageToken = searchResponse.nextPageToken || undefined;
if (!nextPageToken) {
break;
}
}
if (!searchResults.length) {
throw new Error('No videos found.');
}
const videoIds = searchResults
.map(item => item.id?.videoId)
.filter((id): id is string => id !== undefined);
// Retrieve video details in batches of 50
const videoDetails: youtube_v3.Schema$Video[] = [];
for (let i = 0; i < videoIds.length; i += this.MAX_RESULTS_PER_PAGE) {
const batch = videoIds.slice(i, i + this.MAX_RESULTS_PER_PAGE);
const videosResponse = await this.youtube.videos.list({
part: ['snippet', 'statistics'],
id: batch
});
if (videosResponse.data.items) {
videoDetails.push(...videosResponse.data.items);
}
}
return videoDetails.slice(0, targetResults).map(video => ({
id: video.id,
title: video.snippet?.title,
publishedAt: video.snippet?.publishedAt,
viewCount: video.statistics?.viewCount,
likeCount: video.statistics?.likeCount,
commentCount: video.statistics?.commentCount
}));
} catch (error: any) {
throw new Error(`Failed to retrieve channel's top videos: ${error.message}`);
}
}
async getVideoEngagementRatio(videoId: string) {
try {
const response = await this.youtube.videos.list({
part: ['statistics'],
id: [videoId]
});
if (!response.data.items?.length) {
throw new Error('Video not found.');
}
const stats = response.data.items[0].statistics;
const viewCount = parseInt(stats?.viewCount || '0');
const likeCount = parseInt(stats?.likeCount || '0');
const commentCount = parseInt(stats?.commentCount || '0');
const engagementRatio = viewCount > 0
? ((likeCount + commentCount) / viewCount * 100).toFixed(2)
: '0';
return {
viewCount,
likeCount,
commentCount,
engagementRatio: `${engagementRatio}%`
};
} catch (error: any) {
throw new Error(`Failed to calculate video engagement ratio: ${error.message}`);
}
}
async getTrendingVideos({ regionCode = 'US', categoryId, maxResults = 10 }: TrendingOptions) {
try {
const params: youtube_v3.Params$Resource$Videos$List = {
part: ['snippet', 'statistics'],
chart: 'mostPopular',
regionCode: regionCode,
maxResults: maxResults
};
if (categoryId) {
params.videoCategoryId = categoryId;
}
const response = await this.youtube.videos.list(params);
return response.data.items?.map(video => ({
id: video.id,
title: video.snippet?.title,
channelTitle: video.snippet?.channelTitle,
publishedAt: video.snippet?.publishedAt,
viewCount: video.statistics?.viewCount,
likeCount: video.statistics?.likeCount
})) || [];
} catch (error: any) {
throw new Error(`Failed to retrieve trending videos: ${error.message}`);
}
}
async compareVideos({ videoIds }: CompareVideosOptions) {
try {
const response = await this.youtube.videos.list({
part: ['snippet', 'statistics'],
id: videoIds
});
if (!response.data.items?.length) {
throw new Error('No videos found.');
}
return response.data.items.map(video => ({
id: video.id,
title: video.snippet?.title,
viewCount: video.statistics?.viewCount,
likeCount: video.statistics?.likeCount,
commentCount: video.statistics?.commentCount,
publishedAt: video.snippet?.publishedAt
}));
} catch (error: any) {
throw new Error(`Failed to compare videos: ${error.message}`);
}
}
}