#
tokens: 8092/50000 13/13 files
lines: off (toggle) GitHub
raw markdown copy
# Directory Structure

```
├── .cursorrules
├── .env.example
├── .eslintrc.json
├── .gitignore
├── Dockerfile
├── jest.config.js
├── package-lock.json
├── package.json
├── README.md
├── smithery.yaml
├── src
│   ├── functions
│   │   └── videos.ts
│   ├── index.ts
│   └── types
│       ├── youtube-captions-scraper.d.ts
│       ├── youtube-transcript-api.d.ts
│       └── youtube.ts
├── test-transcript.js
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------

```json
{
  "parser": "@typescript-eslint/parser",
  "plugins": ["@typescript-eslint"],
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended"
  ],
  "parserOptions": {
    "ecmaVersion": 2022,
    "sourceType": "module"
  },
  "rules": {
    "@typescript-eslint/explicit-function-return-type": "off",
    "@typescript-eslint/no-explicit-any": "off",
    "@typescript-eslint/no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }]
  }
} 
```

--------------------------------------------------------------------------------
/.cursorrules:
--------------------------------------------------------------------------------

```
You are a Senior TypeScript Developer and an Expert in Model Context Protocol (MCP) server development. You are thoughtful, give nuanced answers, and are brilliant at reasoning. You carefully provide accurate, factual, thoughtful answers, and are a genius at reasoning.

- Follow the user's requirements carefully & to the letter.
- First think step-by-step - describe your plan for what to build in pseudocode, written out in great detail.
- Confirm, then write code!
- Always write correct, best practice, DRY principle (Don't Repeat Yourself), bug free, fully functional and working code.
- Focus on easy and readable code, over being performant.
- Fully implement all requested functionality.
- Leave NO todo's, placeholders or missing pieces.
- Ensure code is complete! Verify thoroughly finalised.
- Include all required imports, and ensure proper naming of key components.
- Be concise. Minimize any other prose.
- If you think there might not be a correct answer, you say so.
- If you do not know the answer, say so, instead of guessing.

### Coding Environment
The user asks questions about the following technologies:
- TypeScript
- Node.js
- Model Context Protocol SDK
- YouTube API
- Google Cloud APIs
- External service integrations

### Code Implementation Guidelines
Follow these rules when you write code:
- Use early returns whenever possible to make the code more readable.
- Define explicit types for all functions, parameters, and return values.
- Prefer functional programming patterns with const arrow functions over traditional function declarations.
- Event handler functions should be named with a "handle" prefix, like "handleVideoProcessing".
- Organize related MCP functions into logical function groups.
- Implement thorough error handling for all asynchronous operations and API calls.
- Use environment variables for sensitive information like API keys.
- Include appropriate logging for debugging and monitoring.
- Use correct import syntax for external libraries.
- Properly initialize API clients, especially for Google services.
- Write modular, testable code with clear separation of concerns.

```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
[![MseeP.ai Security Assessment Badge](https://mseep.net/pr/icraft2170-youtube-data-mcp-server-badge.png)](https://mseep.ai/app/icraft2170-youtube-data-mcp-server)

# YouTube MCP Server
[![smithery badge](https://smithery.ai/badge/@icraft2170/youtube-data-mcp-server)](https://smithery.ai/server/@icraft2170/youtube-data-mcp-server)

A Model Context Protocol (MCP) server implementation utilizing the YouTube Data API. It allows AI language models to interact with YouTube content through a standardized interface.

## Key Features

### Video Information
* Retrieve detailed video information (title, description, duration, statistics)
* Search for videos by keywords
* Get related videos based on a specific video
* Calculate and analyze video engagement ratios

### Transcript/Caption Management
* Retrieve video captions with multi-language support
* Specify language preferences for transcripts
* Access time-stamped captions for precise content reference

### Channel Analysis
* View detailed channel statistics (subscribers, views, video count)
* Get top-performing videos from a channel
* Analyze channel growth and engagement metrics

### Trend Analysis
* View trending videos by region and category
* Compare performance metrics across multiple videos
* Discover popular content in specific categories

## Available Tools

The server provides the following MCP tools:

| Tool Name | Description | Required Parameters |
|-----------|-------------|---------------------|
| `getVideoDetails` | Get detailed information about multiple YouTube videos including metadata, statistics, and content details | `videoIds` (array) |
| `searchVideos` | Search for videos based on a query string | `query`, `maxResults` (optional) |
| `getTranscripts` | Retrieve transcripts for multiple videos | `videoIds` (array), `lang` (optional) |
| `getRelatedVideos` | Get videos related to a specific video based on YouTube's recommendation algorithm | `videoId`, `maxResults` (optional) |
| `getChannelStatistics` | Retrieve detailed metrics for multiple channels including subscriber count, view count, and video count | `channelIds` (array) |
| `getChannelTopVideos` | Get the most viewed videos from a specific channel | `channelId`, `maxResults` (optional) |
| `getVideoEngagementRatio` | Calculate engagement metrics for multiple videos (views, likes, comments, and engagement ratio) | `videoIds` (array) |
| `getTrendingVideos` | Get currently popular videos by region and category | `regionCode` (optional), `categoryId` (optional), `maxResults` (optional) |
| `compareVideos` | Compare statistics across multiple videos | `videoIds` (array) |

## Installation

### Automatic Installation via Smithery

Automatically install YouTube MCP Server for Claude Desktop via [Smithery](https://smithery.ai/server/@icraft2170/youtube-data-mcp-server):

```bash
npx -y @smithery/cli install @icraft2170/youtube-data-mcp-server --client claude
```

### Manual Installation
```bash
# Install from npm
npm install youtube-data-mcp-server

# Or clone repository
git clone https://github.com/icraft2170/youtube-data-mcp-server.git
cd youtube-data-mcp-server
npm install
```

## Environment Configuration
Set the following environment variables:
* `YOUTUBE_API_KEY`: YouTube Data API key (required)
* `YOUTUBE_TRANSCRIPT_LANG`: Default caption language (optional, default: 'ko')

## MCP Client Configuration
Add the following to your Claude Desktop configuration file:

```json
{
  "mcpServers": {
    "youtube": {
      "command": "npx",
      "args": ["-y", "youtube-data-mcp-server"],
      "env": {
        "YOUTUBE_API_KEY": "YOUR_API_KEY_HERE",
        "YOUTUBE_TRANSCRIPT_LANG": "ko"
      }
    }
  }
}
```

## YouTube API Setup
1. Access Google Cloud Console
2. Create a new project or select an existing one
3. Enable YouTube Data API v3
4. Create API credentials (API key)
5. Use the generated API key in your environment configuration

## Development

```bash
# Install dependencies
npm install

# Run in development mode
npm run dev

# Build
npm run build
```

## Network Configuration

The server exposes the following ports for communication:
- HTTP: 3000
- gRPC: 3001

## System Requirements
- Node.js 18.0.0 or higher

## Security Considerations
- Always keep your API key secure and never commit it to version control systems
- Manage your API key through environment variables or configuration files
- Set usage limits for your API key to prevent unauthorized use

## License
This project is licensed under the MIT License. See the LICENSE file for details. 

```

--------------------------------------------------------------------------------
/src/types/youtube-transcript-api.d.ts:
--------------------------------------------------------------------------------

```typescript
declare module 'youtube-transcript-api' {
  interface TranscriptItem {
    text: string;
    start: number;
    duration: number;
  }

  export class YoutubeTranscript {
    getTranscript(videoId: string): Promise<TranscriptItem[]>;
  }
} 
```

--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------

```javascript
export default {
  preset: 'ts-jest',
  testEnvironment: 'node',
  extensionsToTreatAsEsm: ['.ts'],
  moduleNameMapper: {
    '^(\\.{1,2}/.*)\\.js$': '$1',
  },
  transform: {
    '^.+\\.tsx?$': ['ts-jest', {
      useESM: true,
    }],
  },
}; 
```

--------------------------------------------------------------------------------
/src/types/youtube-captions-scraper.d.ts:
--------------------------------------------------------------------------------

```typescript
declare module 'youtube-captions-scraper' {
  interface SubtitleOptions {
    videoID: string;
    lang?: string;
  }

  interface SubtitleItem {
    start: number;
    dur: number;
    text: string;
  }

  export function getSubtitles(options: SubtitleOptions): Promise<SubtitleItem[]>;
} 
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "declaration": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist", "**/*.test.ts"]
} 
```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
# Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile
FROM node:lts-alpine

# Create app directory
WORKDIR /app

# Install app dependencies
COPY package*.json ./

# Use npm ci to install dependencies without running scripts
RUN npm ci --ignore-scripts

# Copy all project files
COPY . .

# Build the project
RUN npm run build

# Ensure the main file is executable (if not already set)
RUN chmod +x dist/index.js

# Set environment variables for Docker (actual values should be provided at runtime)
ENV YOUTUBE_API_KEY=""
ENV YOUTUBE_TRANSCRIPT_LANG="en"

# Command to run the MCP server
CMD ["node", "dist/index.js"]

```

--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------

```yaml
# Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml

startCommand:
  type: stdio
  configSchema:
    # JSON Schema defining the configuration options for the MCP.
    type: object
    required:
      - youtubeApiKey
    properties:
      youtubeApiKey:
        type: string
        description: YouTube Data API key for accessing the YouTube API.
      youtubeTranscriptLang:
        type: string
        default: en
        description: Default transcript language. Defaults to 'en'.
  commandFunction:
    # A JS function that produces the CLI command based on the given config to start the MCP on stdio.
    |-
    (config) => ({
      command: 'node',
      args: ['dist/index.js'],
      env: {
        YOUTUBE_API_KEY: config.youtubeApiKey,
        YOUTUBE_TRANSCRIPT_LANG: config.youtubeTranscriptLang || 'en'
      }
    })
  exampleConfig:
    youtubeApiKey: YOUR_YOUTUBE_API_KEY_HERE
    youtubeTranscriptLang: en

```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "youtube-data-mcp-server",
  "version": "1.0.15",
  "description": "YouTube MCP Server Implementation",
  "main": "dist/index.js",
  "type": "module",
  "bin": {
    "youtube-data-mcp-server": "./dist/index.js"
  },
  "files": [
    "dist",
    "README.md"
  ],
  "scripts": {
    "start": "node dist/index.js",
    "dev": "nodemon --exec ts-node --esm ./src/index.ts",
    "build": "tsc",
    "clean": "rimraf dist",
    "prebuild": "npm run clean",
    "lint": "eslint src/**/*.ts",
    "test": "jest",
    "prepare": "npm run build",
    "postinstall": "chmod +x ./dist/index.js"
  },
  "keywords": [
    "mcp",
    "youtube",
    "claude",
    "modelcontextprotocol"
  ],
  "author": "Hero",
  "license": "MIT",
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.7.0",
    "dotenv": "^16.4.7",
    "googleapis": "^129.0.0",
    "pnpm": "^10.6.4",
    "youtube-captions-scraper": "^2.0.0",
    "zod": "^3.24.2"
  },
  "devDependencies": {
    "@types/jest": "^29.5.12",
    "@types/node": "^18.0.0",
    "@typescript-eslint/eslint-plugin": "^7.1.0",
    "@typescript-eslint/parser": "^7.1.0",
    "eslint": "^8.57.0",
    "jest": "^29.7.0",
    "nodemon": "^3.0.0",
    "rimraf": "^5.0.10",
    "ts-jest": "^29.1.2",
    "ts-node": "^10.9.1",
    "typescript": "^5.0.0"
  },
  "engines": {
    "node": ">=18.0.0"
  },
  "publishConfig": {
    "access": "public"
  }
}

```

--------------------------------------------------------------------------------
/src/types/youtube.ts:
--------------------------------------------------------------------------------

```typescript
export interface VideoInfo {
  id: string;
  snippet: {
    title: string;
    description: string;
    thumbnails: {
      default: { url: string };
      medium: { url: string };
      high: { url: string };
    };
    channelId: string;
    channelTitle: string;
    publishedAt: string;
  };
  statistics: {
    viewCount: string;
    likeCount: string;
    commentCount: string;
  };
}

export interface ChannelInfo {
  id: string;
  snippet: {
    title: string;
    description: string;
    thumbnails: {
      default: { url: string };
      medium: { url: string };
      high: { url: string };
    };
    customUrl: string;
  };
  statistics: {
    viewCount: string;
    subscriberCount: string;
    videoCount: string;
  };
}

export interface SearchResult {
  id: {
    kind: string;
    videoId: string | null;
    channelId: string | null;
    playlistId: string | null;
  };
  snippet: {
    title: string;
    description: string;
    thumbnails: {
      default: { url: string };
      medium: { url: string };
      high: { url: string };
    };
    channelTitle: string;
    publishedAt: string;
  };
}

export interface CommentInfo {
  id: string;
  snippet: {
    topLevelComment: {
      snippet: {
        textDisplay: string;
        authorDisplayName: string;
        authorProfileImageUrl: string;
        likeCount: number;
        publishedAt: string;
      };
    };
    totalReplyCount: number;
  };
} 
```

--------------------------------------------------------------------------------
/src/functions/videos.ts:
--------------------------------------------------------------------------------

```typescript
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}`);
    }
  }
} 
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
#!/usr/bin/env node

import 'dotenv/config';
import { VideoManagement } from './functions/videos.js';
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

// Environment variable validation
if (!process.env.YOUTUBE_API_KEY) {
  console.error('Error: YOUTUBE_API_KEY environment variable is not set.');
  process.exit(1);
}

// Default subtitle language setting
const defaultTranscriptLang = process.env.YOUTUBE_TRANSCRIPT_LANG || 'ko';

interface VideoDetailsParams {
    videoId: string;
}

interface VideoDetailsListParams {
    videoIds: string[];
}

interface TranscriptParams {
    videoId: string;
    lang?: string;
}

interface TranscriptsParams {
    videoIds: string[];
    lang?: string;
}

interface SearchParams {
    query: string;
    maxResults?: number;
}

interface RelatedVideosParams {
    videoId: string;
    maxResults?: number;
}

interface ChannelParams {
    channelId: string;
    maxResults?: number;
}

interface TrendingParams {
    regionCode?: string;
    categoryId?: string;
    maxResults?: number;
}

interface CompareVideosParams {
    videoIds: string[];
}

interface VideoEngagementRatiosParams {
    videoIds: string[];
}

interface ChannelStatisticsParams {
    channelIds: string[];
}

async function main() {
    const videoManager = new VideoManagement();
    
    // Create MCP server
    const server = new McpServer({
        name: "YouTube",
        version: "1.0.0"
    });

    // Video details retrieval tool
    server.tool("getVideoDetails",
        "Get detailed information about multiple YouTube videos. Returns comprehensive data including video metadata, statistics, and content details. Use this when you need complete information about specific videos.",
        { videoIds: z.array(z.string()) },
        async ({ videoIds }: VideoDetailsListParams) => {
            try {
                const videoPromises = videoIds.map(videoId => 
                    videoManager.getVideo({
                        videoId,
                        parts: ["snippet", "statistics", "contentDetails"]
                    })
                );
                const videoDetailsList = await Promise.all(videoPromises);
                
                // Create a map of videoId to details
                const result = videoIds.reduce((acc, videoId, index) => {
                    acc[videoId] = videoDetailsList[index];
                    return acc;
                }, {} as Record<string, any>);
                
                return {
                    content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
                };
            } catch (error: any) {
                return {
                    content: [{ 
                        type: "text", 
                        text: JSON.stringify({
                            error: error.message,
                            details: error.response?.data
                        }, null, 2) 
                    }]
                };
            }
        }
    );

    // Video search tool
    server.tool("searchVideos",
        "Searches for videos based on a query string. Returns a list of videos matching the search criteria, including titles, descriptions, and metadata. Use this when you need to find videos related to specific topics or keywords.",
        { 
            query: z.string(),
            maxResults: z.number().optional()
        },
        async ({ query, maxResults }: SearchParams) => {
            try {
                const searchResults = await videoManager.searchVideos({
                    query,
                    maxResults
                });
                return {
                    content: [{ type: "text", text: JSON.stringify(searchResults, null, 2) }]
                };
            } catch (error: any) {
                return {
                    content: [{ 
                        type: "text", 
                        text: JSON.stringify({
                            error: error.message,
                            details: error.response?.data
                        }, null, 2) 
                    }]
                };
            }
        }
    );

    // Video transcript retrieval tool
    server.tool("getTranscripts",
        "Retrieves transcripts for multiple videos. Returns the text content of videos' captions, useful for accessibility and content analysis. Use this when you need the spoken content of multiple videos.",
        { 
            videoIds: z.array(z.string()),
            lang: z.string().optional()
        },
        async ({ videoIds, lang }: TranscriptsParams) => {
            try {
                const transcriptPromises = videoIds.map(videoId => 
                    videoManager.getTranscript(videoId, lang)
                );
                const transcripts = await Promise.all(transcriptPromises);
                
                // Create a map of videoId to transcript
                const result = videoIds.reduce((acc, videoId, index) => {
                    acc[videoId] = transcripts[index];
                    return acc;
                }, {} as Record<string, any>);
                
                return {
                    content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
                };
            } catch (error: any) {
                return {
                    content: [{ 
                        type: "text", 
                        text: JSON.stringify({
                            error: error.message
                        }, null, 2) 
                    }]
                };
            }
        }
    );

    // Related videos retrieval tool
    server.tool("getRelatedVideos",
        "Retrieves related videos for a specific video. Returns a list of videos that are similar or related to the specified video, based on YouTube's recommendation algorithm. Use this when you want to discover content similar to a particular video.",
        { 
            videoId: z.string(),
            maxResults: z.number().optional()
        },
        async ({ videoId, maxResults }: RelatedVideosParams) => {
            try {
                const relatedVideos = await videoManager.getRelatedVideos(videoId, maxResults);
                return {
                    content: [{ type: "text", text: JSON.stringify(relatedVideos, null, 2) }]
                };
            } catch (error: any) {
                return {
                    content: [{ 
                        type: "text", 
                        text: JSON.stringify({
                            error: error.message,
                            details: error.response?.data
                        }, null, 2) 
                    }]
                };
            }
        }
    );

    // Channel statistics retrieval tool
    server.tool("getChannelStatistics",
        "Retrieves statistics for multiple channels. Returns detailed metrics including subscriber count, view count, and video count for each channel. Use this when you need to analyze the performance and reach of multiple YouTube channels.",
        { channelIds: z.array(z.string()) },
        async ({ channelIds }: ChannelStatisticsParams) => {
            try {
                const statisticsPromises = channelIds.map(channelId => 
                    videoManager.getChannelStatistics(channelId)
                );
                const statisticsResults = await Promise.all(statisticsPromises);
                
                // Create a map of channelId to statistics
                const result = channelIds.reduce((acc, channelId, index) => {
                    acc[channelId] = statisticsResults[index];
                    return acc;
                }, {} as Record<string, any>);
                
                return {
                    content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
                };
            } catch (error: any) {
                return {
                    content: [{ 
                        type: "text", 
                        text: JSON.stringify({
                            error: error.message,
                            details: error.response?.data
                        }, null, 2) 
                    }]
                };
            }
        }
    );

    // Channel top videos retrieval tool
    server.tool("getChannelTopVideos",
        "Retrieves the top videos from a specific channel. Returns a list of the most viewed or popular videos from the channel, based on view count. Use this when you want to identify the most successful content from a channel.",
        { 
            channelId: z.string(),
            maxResults: z.number().optional()
        },
        async ({ channelId, maxResults }: ChannelParams) => {
            try {
                const topVideos = await videoManager.getChannelTopVideos({ channelId, maxResults });
                return {
                    content: [{ type: "text", text: JSON.stringify(topVideos, null, 2) }]
                };
            } catch (error: any) {
                return {
                    content: [{ 
                        type: "text", 
                        text: JSON.stringify({
                            error: error.message,
                            details: error.response?.data
                        }, null, 2) 
                    }]
                };
            }
        }
    );

    // Video engagement ratio calculation tool
    server.tool("getVideoEngagementRatio",
        "Calculates the engagement ratio for multiple videos. Returns metrics such as view count, like count, comment count, and the calculated engagement ratio for each video. Use this when you want to measure the audience interaction with videos.",
        { videoIds: z.array(z.string()) },
        async ({ videoIds }: VideoEngagementRatiosParams) => {
            try {
                const engagementPromises = videoIds.map(videoId => 
                    videoManager.getVideoEngagementRatio(videoId)
                );
                const engagementResults = await Promise.all(engagementPromises);
                
                // Create a map of videoId to engagement data
                const result = videoIds.reduce((acc, videoId, index) => {
                    acc[videoId] = engagementResults[index];
                    return acc;
                }, {} as Record<string, any>);
                
                return {
                    content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
                };
            } catch (error: any) {
                return {
                    content: [{ 
                        type: "text", 
                        text: JSON.stringify({
                            error: error.message,
                            details: error.response?.data
                        }, null, 2) 
                    }]
                };
            }
        }
    );

    // Trending videos retrieval tool
    server.tool("getTrendingVideos",
        "Retrieves trending videos based on region and category. Returns a list of videos that are currently popular in the specified region and category. Use this when you want to discover what's trending in specific areas or categories. Available category IDs: 1 (Film & Animation), 2 (Autos & Vehicles), 10 (Music), 15 (Pets & Animals), 17 (Sports), 18 (Short Movies), 19 (Travel & Events), 20 (Gaming), 21 (Videoblogging), 22 (People & Blogs), 23 (Comedy), 24 (Entertainment), 25 (News & Politics), 26 (Howto & Style), 27 (Education), 28 (Science & Technology), 29 (Nonprofits & Activism), 30 (Movies), 31 (Anime/Animation), 32 (Action/Adventure), 33 (Classics), 34 (Comedy), 35 (Documentary), 36 (Drama), 37 (Family), 38 (Foreign), 39 (Horror), 40 (Sci-Fi/Fantasy), 41 (Thriller), 42 (Shorts), 43 (Shows), 44 (Trailers).",
        { 
            regionCode: z.string().optional(),
            categoryId: z.string().optional(),
            maxResults: z.number().optional()
        },
        async ({ regionCode, categoryId, maxResults }: TrendingParams) => {
            try {
                const trendingVideos = await videoManager.getTrendingVideos({ regionCode, categoryId, maxResults });
                return {
                    content: [{ type: "text", text: JSON.stringify(trendingVideos, null, 2) }]
                };
            } catch (error: any) {
                return {
                    content: [{ 
                        type: "text", 
                        text: JSON.stringify({
                            error: error.message,
                            details: error.response?.data
                        }, null, 2) 
                    }]
                };
            }
        }
    );

    // Video comparison tool
    server.tool("compareVideos",
        "Compares multiple videos based on their statistics. Returns a comparison of view counts, like counts, comment counts, and other metrics for the specified videos. Use this when you want to analyze the performance of multiple videos side by side.",
        { videoIds: z.array(z.string()) },
        async ({ videoIds }: CompareVideosParams) => {
            try {
                const comparison = await videoManager.compareVideos({ videoIds });
                return {
                    content: [{ type: "text", text: JSON.stringify(comparison, null, 2) }]
                };
            } catch (error: any) {
                return {
                    content: [{ 
                        type: "text", 
                        text: JSON.stringify({
                            error: error.message,
                            details: error.response?.data
                        }, null, 2) 
                    }]
                };
            }
        }
    );

    // Start sending and receiving messages via stdin/stdout
    const transport = new StdioServerTransport();
    await server.connect(transport);

    console.error('YouTube MCP server has started.');
}

main().catch((err) => {
    console.error('Error occurred during server execution:', err);
    process.exit(1);
}); 
```