# 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 | [](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 | [](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 |
```