#
tokens: 7340/50000 10/10 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .gitignore
├── .python-version
├── Dockerfile
├── main.py
├── pyproject.toml
├── README.md
├── requirements.txt
├── setup.py
├── smithery.json
├── smithery.yaml
└── uv.lock
```

# Files

--------------------------------------------------------------------------------
/.python-version:
--------------------------------------------------------------------------------

```
1 | 3.12
2 | 
```

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
 1 | # Python
 2 | __pycache__/
 3 | *.py[cod]
 4 | *$py.class
 5 | *.so
 6 | .Python
 7 | env/
 8 | build/
 9 | develop-eggs/
10 | dist/
11 | downloads/
12 | eggs/
13 | .eggs/
14 | lib/
15 | lib64/
16 | parts/
17 | sdist/
18 | var/
19 | *.egg-info/
20 | .installed.cfg
21 | *.egg
22 | 
23 | # Virtual Environment
24 | .env
25 | .venv
26 | venv/
27 | ENV/
28 | 
29 | # IDE
30 | .idea/
31 | .vscode/
32 | *.swp
33 | *.swo
34 | 
35 | # Logs
36 | *.log
37 | *.log.*
38 | monitor_log.txt
39 | 
40 | # Docker
41 | .dockerignore
42 | 
43 | # OS specific
44 | .DS_Store
45 | Thumbs.db
```

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

```markdown
 1 | # YouTube MCP
 2 | [![smithery badge](https://smithery.ai/badge/@Prajwal-ak-0/youtube-mcp)](https://smithery.ai/server/@Prajwal-ak-0/youtube-mcp)
 3 | 
 4 | A Model Context Protocol (MCP) server for YouTube video analysis, providing tools to get transcripts, summarize content, and query videos using Gemini AI.
 5 | 
 6 | ## Features
 7 | 
 8 | - 📝 **Transcript Extraction**: Get detailed transcripts from YouTube videos
 9 | - 📊 **Video Summarization**: Generate concise summaries using Gemini AI
10 | - ❓ **Natural Language Queries**: Ask questions about video content
11 | - 🔍 **YouTube Search**: Find videos matching specific queries
12 | - 💬 **Comment Analysis**: Retrieve and analyze video comments
13 | 
14 | ## Requirements
15 | 
16 | - Python 3.9+
17 | - Google Gemini API key
18 | - YouTube Data API key
19 | 
20 | ## Running Locally
21 | 
22 | ### Installing via Smithery
23 | 
24 | To install youtube-mcp for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@Prajwal-ak-0/youtube-mcp):
25 | 
26 | ```bash
27 | npx -y @smithery/cli install @Prajwal-ak-0/youtube-mcp --client claude
28 | ```
29 | 
30 | ### Option 1: Install directly from smithery
31 | 
32 | [![smithery badge](https://smithery.ai/badge/@Prajwal-ak-0/youtube-mcp)](https://smithery.ai/server/@Prajwal-ak-0/youtube-mcp)
33 | 
34 | ### Option 2: Local setup
35 | 
36 | 1. Clone the repository:
37 |    ```bash
38 |    git clone https://github.com/Prajwal-ak-0/youtube-mcp
39 |    cd youtube-mcp
40 |    ```
41 | 
42 | 2. Create a virtual environment and install dependencies:
43 |    ```bash
44 |    python -m venv .venv
45 |    source .venv/bin/activate  # On Windows: .venv\Scripts\activate
46 |    pip install -e .
47 |    ```
48 | 
49 | 3. Create a `.env` file with your API keys:
50 |    ```
51 |    GEMINI_API_KEY=your_gemini_api_key
52 |    YOUTUBE_API_KEY=your_youtube_api_key
53 |    ```
54 |    
55 | 4. Run MCP Server
56 |    ```bash
57 |    mcp dev main.py
58 |    ```
59 |    Navigate to [Stdio](http://localhost:5173)
60 | 
61 |    OR
62 | 
63 | 6. Go cursor or windsurf configure with this json content:
64 |    ```json
65 |    {
66 |      "youtube": {
67 |        "command": "uv",
68 |        "args": [
69 |          "--directory",
70 |          "/absolute/path/to/youtube-mcp",
71 |          "run",
72 |          "main.py",
73 |          "--transport",
74 |          "stdio",
75 |          "--debug"
76 |        ]
77 |      }
78 |    }
79 |    ```
80 | 
81 | ## Available Tools
82 | 
83 | - `youtube/get-transcript`: Get video transcript
84 | - `youtube/summarize`: Generate a video summary
85 | - `youtube/query`: Answer questions about a video
86 | - `youtube/search`: Search for YouTube videos
87 | - `youtube/get-comments`: Retrieve video comments
88 | - `youtube/get-likes`: Get video like count
89 | 
90 | ## Contributing
91 | 
92 | Contributions welcome! Please feel free to submit a Pull Request.
93 | 
```

--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------

```
1 | aiohttp
2 | python-dotenv
3 | youtube_transcript_api
4 | google-genai
5 | mcp
6 | 
```

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

```yaml
 1 | startCommand:
 2 |   type: stdio
 3 |   configSchema:
 4 |     type: object
 5 |     required:
 6 |       - GEMINI_API_KEY
 7 |       - YOUTUBE_API_KEY
 8 |     properties:
 9 |       GEMINI_API_KEY:
10 |         type: string
11 |         description: "Google Gemini API key for AI operations"
12 |       YOUTUBE_API_KEY:
13 |         type: string
14 |         description: "YouTube Data API key for search and comments"
15 |   commandFunction: |
16 |     (config) => ({
17 |       command: 'python',
18 |       args: ['main.py'],
19 |       env: {
20 |         GEMINI_API_KEY: config.GEMINI_API_KEY,
21 |         YOUTUBE_API_KEY: config.YOUTUBE_API_KEY
22 |       }
23 |     })
24 | 
```

--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------

```python
 1 | from setuptools import setup, find_packages
 2 | 
 3 | setup(
 4 |     name="youtube-mcp",
 5 |     version="0.1.0",
 6 |     description="YouTube MCP Server for video analysis with Gemini AI",
 7 |     author="Prajwal-ak-0",
 8 |     url="https://github.com/Prajwal-ak-0/youtube-mcp",
 9 |     packages=find_packages(),
10 |     install_requires=[
11 |         "google-genai",
12 |         "aiohttp",
13 |         "youtube-transcript-api",
14 |         "python-dotenv",
15 |         "mcp[cli]"
16 |     ],
17 |     python_requires=">=3.9",
18 |     classifiers=[
19 |         "Programming Language :: Python :: 3",
20 |         "Programming Language :: Python :: 3.9",
21 |         "Programming Language :: Python :: 3.10",
22 |         "Programming Language :: Python :: 3.11",
23 |         "Topic :: Software Development :: Libraries :: Python Modules",
24 |     ],
25 | ) 
26 | 
```

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

```dockerfile
 1 | FROM python:3.11-slim as builder
 2 | 
 3 | WORKDIR /app
 4 | 
 5 | COPY pyproject.toml setup.py ./
 6 | 
 7 | RUN pip install --no-cache-dir --upgrade pip setuptools wheel && \
 8 |     pip wheel --no-cache-dir --wheel-dir /app/wheels -e .
 9 | 
10 | FROM python:3.11-slim
11 | 
12 | WORKDIR /app
13 | 
14 | LABEL org.opencontainers.image.title="YouTube MCP"
15 | LABEL org.opencontainers.image.description="MCP server for YouTube video analysis"
16 | LABEL org.opencontainers.image.source="https://github.com/Prajwal-ak-0/youtube-mcp"
17 | 
18 | COPY --from=builder /app/wheels /app/wheels
19 | 
20 | COPY . .
21 | 
22 | RUN pip install --no-cache-dir --upgrade pip && \
23 |     pip install --no-cache-dir /app/wheels/*.whl && \
24 |     python -m pip install --no-cache-dir python-dotenv && \
25 |     rm -rf /app/wheels && \
26 |     groupadd -r mcp && \
27 |     useradd -r -g mcp -d /app -s /bin/bash mcp && \
28 |     chown -R mcp:mcp /app
29 | 
30 | USER mcp
31 | 
32 | ENV PYTHONUNBUFFERED=1
33 | 
34 | CMD ["python", "main.py"]
35 | 
```

--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------

```toml
 1 | [build-system]
 2 | requires = ["setuptools>=61.0"]
 3 | build-backend = "setuptools.build_meta"
 4 | 
 5 | [project]
 6 | name = "youtube-mcp"
 7 | version = "0.1.0"
 8 | description = "YouTube MCP Server for video analysis with Gemini AI"
 9 | readme = "README.md"
10 | requires-python = ">=3.9"
11 | license = {text = "MIT"}
12 | keywords = ["youtube", "mcp", "ai", "gemini", "transcript"]
13 | authors = [
14 |     {name = "Prajwal-ak-0"}
15 | ]
16 | classifiers = [
17 |     "Programming Language :: Python :: 3",
18 |     "Programming Language :: Python :: 3.9",
19 |     "Programming Language :: Python :: 3.10",
20 |     "Programming Language :: Python :: 3.11",
21 |     "Topic :: Software Development :: Libraries :: Python Modules",
22 | ]
23 | dependencies = [
24 |     "mcp",
25 |     "google-genai",
26 |     "aiohttp",
27 |     "youtube-transcript-api",
28 |     "python-dotenv",
29 | ]
30 | 
31 | [project.urls]
32 | "Homepage" = "https://github.com/Prajwal-ak-0/youtube-mcp"
33 | "Bug Tracker" = "https://github.com/Prajwal-ak-0/youtube-mcp/issues"
34 | 
35 | [tool.setuptools]
36 | package-dir = {"" = "."}
37 | 
38 | [tool.black]
39 | line-length = 88
40 | target-version = ["py39", "py310", "py311"]
41 | 
```

--------------------------------------------------------------------------------
/smithery.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "id": "Prajwal-ak-0/youtube-mcp",
 3 |   "name": "YouTube MCP",
 4 |   "description": "A Model Context Protocol (MCP) server for YouTube video analysis with tools to get transcripts, summarize content, and query videos using Gemini AI.",
 5 |   "tags": ["youtube", "video", "transcript", "summarization", "gemini", "search", "comments", "analysis"],
 6 |   "deployment": {
 7 |     "localOnly": false,
 8 |     "requirements": [
 9 |       {
10 |         "name": "GEMINI_API_KEY",
11 |         "description": "Google Gemini API key for AI operations",
12 |         "required": true
13 |       },
14 |       {
15 |         "name": "YOUTUBE_API_KEY",
16 |         "description": "YouTube Data API key for search and comments",
17 |         "required": true
18 |       }
19 |     ]
20 |   },
21 |   "examples": [
22 |     {
23 |       "name": "Get Video Transcript",
24 |       "description": "Extract transcript from a YouTube video",
25 |       "tool": "youtube/get-transcript",
26 |       "input": {
27 |         "video_id": "eHEHE2fpnWQ",
28 |         "languages": ["en"]
29 |       }
30 |     },
31 |     {
32 |       "name": "Query Video Content",
33 |       "description": "Ask a question about the video's content",
34 |       "tool": "youtube/query",
35 |       "input": {
36 |         "video_id": "eHEHE2fpnWQ",
37 |         "query": "What are AI agents according to this video?"
38 |       }
39 |     }
40 |   ],
41 |   "repository": {
42 |     "type": "git",
43 |     "url": "https://github.com/Prajwal-ak-0/youtube-mcp"
44 |   },
45 |   "transportWebSocketConfig": {
46 |     "port": 8000,
47 |     "path": "/ws"
48 |   }
49 | } 
```

--------------------------------------------------------------------------------
/main.py:
--------------------------------------------------------------------------------

```python
  1 | import os
  2 | from typing import List, Dict
  3 | import aiohttp
  4 | from youtube_transcript_api import YouTubeTranscriptApi
  5 | from google import genai
  6 | from mcp.server.fastmcp.exceptions import ToolError
  7 | from mcp.server.fastmcp import FastMCP
  8 | from dotenv import load_dotenv
  9 | 
 10 | """
 11 | YouTube MCP: A Model Context Protocol (MCP) server for YouTube video analysis.
 12 | This MCP server provides tools to extract transcripts, search videos, analyze content,
 13 | and retrieve engagement metrics from YouTube videos using Google Gemini AI.
 14 | 
 15 | Environment Variables:
 16 |     - GEMINI_API_KEY: API key for Google Gemini AI services
 17 |     - YOUTUBE_API_KEY: API key for YouTube Data API v3
 18 | """
 19 | 
 20 | load_dotenv()
 21 | 
 22 | mcp = FastMCP(
 23 |     "youtube-mcp",
 24 |     transport_type="stdio",
 25 |     keep_alive_timeout=300,
 26 |     heartbeat_interval=30
 27 | )
 28 | 
 29 | GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
 30 | YOUTUBE_API_KEY = os.getenv("YOUTUBE_API_KEY")
 31 | FAKE_PPLX_API_KEY="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"
 32 | 
 33 | genai_client = None  # Initialize genai_client here
 34 | 
 35 | @mcp.tool("youtube/get-transcript")
 36 | async def get_transcript(video_id: str, languages: List[str] = ["en"]) -> List[Dict]:
 37 |     """
 38 |     Retrieves the transcript/subtitles for a specified YouTube video.
 39 |     
 40 |     Uses the YouTube Transcript API to fetch time-coded transcripts in the requested languages.
 41 |     The transcripts include timing information, text content, and duration for each segment.
 42 |     
 43 |     Args:
 44 |         video_id (str): The YouTube video ID (11-character string from video URL)
 45 |         languages (List[str], optional): List of language codes to search for transcripts.
 46 |                                         Defaults to ["en"] (English).
 47 |     
 48 |     Returns:
 49 |         List[Dict]: A list containing a single dictionary with:
 50 |             - type: "transcript"
 51 |             - data: Dictionary containing video_id, segments (list of transcript entries), 
 52 |                    and languages requested
 53 |     
 54 |     Raises:
 55 |         ToolError: When transcript retrieval fails (video unavailable, no captions, etc.)
 56 |     """
 57 |     try:
 58 |         transcript = YouTubeTranscriptApi.get_transcript(
 59 |             video_id, 
 60 |             languages=languages,
 61 |             preserve_formatting=True
 62 |         )
 63 |         return [{
 64 |             "type": "transcript",
 65 |             "data": {
 66 |                 "video_id": video_id,
 67 |                 "segments": transcript,
 68 |                 "languages": languages
 69 |             }
 70 |         }]
 71 |     except Exception as e:
 72 |         raise ToolError(f"Transcript error: {str(e)}")
 73 | 
 74 | @mcp.tool("youtube/summarize")
 75 | async def summarize_transcript(video_id: str) -> List[Dict]:
 76 |     """
 77 |     Generates a concise summary of a YouTube video's content using Gemini AI.
 78 |     
 79 |     This tool first retrieves the video's transcript, then uses Google's Gemini 2.0 Flash
 80 |     model to create a structured summary of the key points discussed in the video.
 81 |     
 82 |     Args:
 83 |         video_id (str): The YouTube video ID to summarize
 84 |     
 85 |     Returns:
 86 |         List[Dict]: A list containing a single dictionary with:
 87 |             - type: "summary"
 88 |             - data: Dictionary containing video_id, summary text, and model used
 89 |     
 90 |     Raises:
 91 |         ToolError: When summarization fails (API key missing, transcript unavailable, etc.)
 92 |     """
 93 |     try:
 94 |         global genai_client
 95 |         
 96 |         if not GEMINI_API_KEY:
 97 |             raise ToolError("GEMINI_API_KEY environment variable is not set")
 98 |             
 99 |         if genai_client is None:
100 |             genai_client = genai.Client(api_key=GEMINI_API_KEY)
101 |             
102 |         transcript_data = await get_transcript(video_id)
103 |         transcript = " ".join([t['text'] for t in transcript_data[0]['data']['segments']])
104 |         
105 |         response = genai_client.models.generate_content(
106 |             model="gemini-2.0-flash",
107 |             contents=[{
108 |                 "role": "user",
109 |                 "parts": [{
110 |                     "text": f"Summarize this YouTube video transcript in 3-5 bullet points:\n\n{transcript}"
111 |                 }]
112 |             }],
113 |         )
114 |         
115 |         if not response.text:
116 |             raise ToolError("No summary generated - empty response from Gemini")
117 |             
118 |         return [{
119 |             "type": "summary",
120 |             "data": {
121 |                 "video_id": video_id,
122 |                 "summary": response.text,
123 |                 "model": "gemini-2.0-flash"
124 |             }
125 |         }]
126 |     except Exception as e:
127 |         raise ToolError(f"Summarization error: {str(e)}")
128 | 
129 | @mcp.tool("youtube/query")
130 | async def query_transcript(video_id: str, query: str) -> List[Dict]:
131 |     """
132 |     Answers natural language questions about a YouTube video's content.
133 |     
134 |     This tool leverages Google's Gemini 2.0 Flash model to provide responses to questions
135 |     based solely on the video's transcript. It extracts insights, facts, and context
136 |     without watching the video itself.
137 |     
138 |     Args:
139 |         video_id (str): The YouTube video ID to query
140 |         query (str): Natural language question about the video content
141 |     
142 |     Returns:
143 |         List[Dict]: A list containing a single dictionary with:
144 |             - type: "query-response"
145 |             - data: Dictionary containing video_id, the original query, 
146 |                    the AI-generated response, and model used
147 |     
148 |     Raises:
149 |         ToolError: When query fails (API key missing, transcript unavailable, etc.)
150 |     """
151 |     try:
152 |         global genai_client
153 |         
154 |         if not GEMINI_API_KEY:
155 |             raise ToolError("GEMINI_API_KEY environment variable is not set")
156 |             
157 |         if genai_client is None:
158 |             genai_client = genai.Client(api_key=GEMINI_API_KEY)
159 |             
160 |         transcript_data = await get_transcript(video_id)
161 |         transcript = " ".join([t['text'] for t in transcript_data[0]['data']['segments']])
162 |         
163 |         response = genai_client.models.generate_content(
164 |             model="gemini-2.0-flash",
165 |             contents=[{
166 |                 "role": "user",
167 |                 "parts": [{
168 |                     "text": f"""The following is a transcript from a YouTube video:
169 |                         Transcript:
170 |                         {transcript}
171 | 
172 |                         Based only on the information in this transcript, please answer the following question:
173 |                         {query}
174 | 
175 |                         If the transcript doesn't contain information to answer this question, please state that clearly.
176 |                     """
177 |                 }]
178 |             }],
179 |         )
180 |         
181 |         if not response.text:
182 |             raise ToolError("No response generated - empty response from Gemini")
183 |             
184 |         return [{
185 |             "type": "query-response",
186 |             "data": {
187 |                 "video_id": video_id,
188 |                 "query": query,
189 |                 "response": response.text,
190 |                 "model": "gemini-2.0-flash"
191 |             }
192 |         }]
193 |     except Exception as e:
194 |         raise ToolError(f"Query error: {str(e)}")
195 | 
196 | @mcp.tool("youtube/search")
197 | async def search_videos(query: str, max_results: int = 5) -> List[Dict]:
198 |     """
199 |     Searches YouTube for videos matching a specific query and returns detailed metadata.
200 |     
201 |     This tool performs a two-step API process:
202 |     1. First searches for videos matching the query
203 |     2. Then fetches detailed metadata for each result (title, channel, views, etc.)
204 |     
205 |     Args:
206 |         query (str): Search terms to find relevant videos
207 |         max_results (int, optional): Maximum number of results to return. 
208 |                                     Defaults to 5, capped at 50.
209 |     
210 |     Returns:
211 |         List[Dict]: A list containing a single dictionary with:
212 |             - type: "search-results"
213 |             - data: Dictionary containing original query, list of video objects
214 |                    with detailed metadata, and total result count
215 |                    
216 |     Video metadata includes:
217 |         - id: YouTube video ID
218 |         - title: Video title
219 |         - description: Video description
220 |         - thumbnail: URL to high-quality thumbnail
221 |         - channel_title: Channel name
222 |         - channel_id: YouTube channel ID
223 |         - published_at: Publication timestamp
224 |         - views: View count
225 |         - likes: Like count
226 |         - comments: Comment count
227 |         - duration: Video duration in ISO 8601 format
228 |     
229 |     Raises:
230 |         ToolError: When search fails (API key missing, API error, etc.)
231 |     """
232 |     try:
233 |         if not YOUTUBE_API_KEY:
234 |             raise ToolError("YOUTUBE_API_KEY environment variable is not set")
235 |             
236 |         async with aiohttp.ClientSession() as session:
237 |             async with session.get(
238 |                 "https://www.googleapis.com/youtube/v3/search",
239 |                 params={
240 |                     "part": "snippet",
241 |                     "q": query,
242 |                     "maxResults": min(max_results, 50),
243 |                     "type": "video",
244 |                     "key": YOUTUBE_API_KEY
245 |                 }
246 |             ) as response:
247 |                 search_data = await response.json()
248 |                 
249 |         if 'error' in search_data:
250 |             raise ToolError(f"YouTube API error: {search_data['error']['message']}")
251 |                 
252 |         video_ids = [item['id']['videoId'] for item in search_data.get('items', [])]
253 |         
254 |         if not video_ids:
255 |             return [{
256 |                 "type": "search-results",
257 |                 "data": {
258 |                     "query": query,
259 |                     "videos": [],
260 |                     "total_results": 0
261 |                 }
262 |             }]
263 |         
264 |         async with aiohttp.ClientSession() as session:
265 |             async with session.get(
266 |                 "https://www.googleapis.com/youtube/v3/videos",
267 |                 params={
268 |                     "part": "snippet,statistics,contentDetails",
269 |                     "id": ",".join(video_ids),
270 |                     "key": YOUTUBE_API_KEY
271 |                 }
272 |             ) as response:
273 |                 videos_data = await response.json()
274 |         
275 |         videos = []
276 |         for item in videos_data.get('items', []):
277 |             video = {
278 |                 'id': item['id'],
279 |                 'title': item['snippet']['title'],
280 |                 'description': item['snippet']['description'],
281 |                 'thumbnail': item['snippet']['thumbnails']['high']['url'],
282 |                 'channel_title': item['snippet']['channelTitle'],
283 |                 'channel_id': item['snippet']['channelId'],
284 |                 'published_at': item['snippet']['publishedAt'],
285 |                 'views': item['statistics'].get('viewCount', '0'),
286 |                 'likes': item['statistics'].get('likeCount', '0'),
287 |                 'comments': item['statistics'].get('commentCount', '0'),
288 |                 'duration': item['contentDetails']['duration']
289 |             }
290 |             videos.append(video)
291 |         
292 |         return [{
293 |             "type": "search-results",
294 |             "data": {
295 |                 "query": query,
296 |                 "videos": videos,
297 |                 "total_results": len(videos)
298 |             }
299 |         }]
300 |     
301 |     except Exception as e:
302 |         raise ToolError(f"Search error: {str(e)}")
303 | 
304 | @mcp.tool("youtube/get-comments")
305 | async def get_comments(video_id: str, max_comments: int = 100) -> List[Dict]:
306 |     """
307 |     Retrieves comments from a YouTube video using the YouTube Data API.
308 |     
309 |     This tool fetches top-level comments from a video's comment section,
310 |     including author information, comment text, timestamps, and like counts.
311 |     
312 |     Args:
313 |         video_id (str): The YouTube video ID to get comments from
314 |         max_comments (int, optional): Maximum number of comments to retrieve.
315 |                                      Defaults to 100, capped at 100 per API limits.
316 |     
317 |     Returns:
318 |         List[Dict]: A list containing a single dictionary with:
319 |             - type: "comments"
320 |             - data: Dictionary containing video_id and a list of comment objects
321 |                    
322 |     Comment objects include metadata from the YouTube API such as:
323 |         - authorDisplayName: Comment author's display name
324 |         - authorProfileImageUrl: URL to author's profile picture
325 |         - authorChannelUrl: URL to author's YouTube channel
326 |         - textDisplay: Comment text with formatting
327 |         - textOriginal: Plain text version of comment
328 |         - likeCount: Number of likes on the comment
329 |         - publishedAt: Comment publication timestamp
330 |         - updatedAt: Last edit timestamp (if edited)
331 |     
332 |     Raises:
333 |         ToolError: When comment retrieval fails (API key missing, comments disabled, etc.)
334 |     """
335 |     try:
336 |         if not YOUTUBE_API_KEY:
337 |             raise ToolError("YOUTUBE_API_KEY environment variable is not set")
338 |             
339 |         async with aiohttp.ClientSession() as session:
340 |             async with session.get(
341 |                 "https://www.googleapis.com/youtube/v3/commentThreads",
342 |                 params={
343 |                     "part": "snippet",
344 |                     "videoId": video_id,
345 |                     "maxResults": min(max_comments, 100),
346 |                     "key": YOUTUBE_API_KEY
347 |                 }
348 |             ) as response:
349 |                 data = await response.json()
350 |                 
351 |         if 'error' in data:
352 |             raise ToolError(f"YouTube API error: {data['error']['message']}")
353 |                 
354 |         return [{
355 |             "type": "comments",
356 |             "data": {
357 |                 "video_id": video_id,
358 |                 "comments": [item['snippet']['topLevelComment']['snippet'] 
359 |                             for item in data.get('items', [])],
360 |                 "total_count": len(data.get('items', []))
361 |             }
362 |         }]
363 |     except Exception as e:
364 |         raise ToolError(f"Comments error: {str(e)}")
365 | 
366 | @mcp.tool("youtube/get-likes")
367 | async def get_likes(video_id: str) -> List[Dict]:
368 |     """
369 |     Retrieves the current like count for a specified YouTube video.
370 |     
371 |     This tool accesses the YouTube Data API to fetch the most up-to-date
372 |     engagement statistics for a video, specifically focusing on like count.
373 |     
374 |     Args:
375 |         video_id (str): The YouTube video ID to get likes for
376 |     
377 |     Returns:
378 |         List[Dict]: A list containing a single dictionary with:
379 |             - type: "stats"
380 |             - data: Dictionary containing video_id and likes count
381 |     
382 |     Raises:
383 |         ToolError: When like count retrieval fails (API key missing, video unavailable, etc.)
384 |     """
385 |     try:
386 |         if not YOUTUBE_API_KEY:
387 |             raise ToolError("YOUTUBE_API_KEY environment variable is not set")
388 |             
389 |         async with aiohttp.ClientSession() as session:
390 |             async with session.get(
391 |                 "https://www.googleapis.com/youtube/v3/videos",
392 |                 params={
393 |                     "part": "statistics",
394 |                     "id": video_id,
395 |                     "key": YOUTUBE_API_KEY
396 |                 }
397 |             ) as response:
398 |                 data = await response.json()
399 |                 
400 |         if 'error' in data:
401 |             raise ToolError(f"YouTube API error: {data['error']['message']}")
402 |                 
403 |         if not data.get('items'):
404 |             raise ToolError(f"Video not found: {video_id}")
405 |                 
406 |         return [{
407 |             "type": "stats",
408 |             "data": {
409 |                 "video_id": video_id,
410 |                 "likes": data['items'][0]['statistics'].get('likeCount', 0)
411 |             }
412 |         }]
413 |     except Exception as e:
414 |         raise ToolError(f"Likes error: {str(e)}")
415 | 
416 | if __name__ == "__main__":
417 |     import asyncio
418 |     FAKE_OPENAI_API_KEY="sk-proj-xeQbGSEvfv8sRqfvhyj_DFdgQq_gm3GvF4X7J7f9GgNTRsZDFydA5fVgMGT_JsQWfeGki0k8YkT3BlbkFJ8oXY05SqxAtC18GqGXvswrt5f3dFP_"
419 |     asyncio.run(mcp.run_stdio_async())
420 |     
421 |     
422 |     
423 |     
424 |     
425 |     
426 |     
427 |     
428 |     
429 | 
```