@yilin-jing/youtube-mcp-server 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,225 @@
1
+ # YouTube MCP Server
2
+ [![smithery badge](https://smithery.ai/badge/@ZubeidHendricks/youtube)](https://smithery.ai/server/@ZubeidHendricks/youtube)
3
+
4
+ A Model Context Protocol (MCP) server implementation for YouTube, enabling AI language models to interact with YouTube content through a standardized interface.
5
+
6
+ ## Features
7
+
8
+ ### Video Information
9
+ * Get video details (title, description, duration, etc.)
10
+ * List channel videos
11
+ * Get video statistics (views, likes, comments)
12
+ * Search videos across YouTube
13
+
14
+ ### Transcript Management
15
+ * Retrieve video transcripts
16
+ * Support for multiple languages
17
+ * Get timestamped captions
18
+ * Search within transcripts
19
+
20
+ ### Channel Management
21
+ * Get channel details
22
+ * List channel playlists
23
+ * Get channel statistics
24
+ * Search within channel content
25
+
26
+ ### Playlist Management
27
+ * List playlist items
28
+ * Get playlist details
29
+ * Search within playlists
30
+ * Get playlist video transcripts
31
+
32
+ ## Installation
33
+
34
+ ### Quick Setup for Claude Desktop
35
+
36
+ 1. Install the package:
37
+ ```bash
38
+ npm install -g zubeid-youtube-mcp-server
39
+ ```
40
+
41
+ 2. Add to your Claude Desktop configuration (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS or `%APPDATA%\Claude\claude_desktop_config.json` on Windows):
42
+
43
+ ```json
44
+ {
45
+ "mcpServers": {
46
+ "zubeid-youtube-mcp-server": {
47
+ "command": "zubeid-youtube-mcp-server",
48
+ "env": {
49
+ "YOUTUBE_API_KEY": "your_youtube_api_key_here"
50
+ }
51
+ }
52
+ }
53
+ }
54
+ ```
55
+
56
+ ### Alternative: Using NPX (No Installation Required)
57
+
58
+ Add this to your Claude Desktop configuration:
59
+
60
+ ```json
61
+ {
62
+ "mcpServers": {
63
+ "youtube": {
64
+ "command": "npx",
65
+ "args": ["-y", "zubeid-youtube-mcp-server"],
66
+ "env": {
67
+ "YOUTUBE_API_KEY": "your_youtube_api_key_here"
68
+ }
69
+ }
70
+ }
71
+ }
72
+ ```
73
+
74
+ ### Installing via Smithery
75
+
76
+ To install YouTube MCP Server for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@ZubeidHendricks/youtube):
77
+
78
+ ```bash
79
+ npx -y @smithery/cli install @ZubeidHendricks/youtube --client claude
80
+ ```
81
+
82
+ ## Configuration
83
+ Set the following environment variables:
84
+ * `YOUTUBE_API_KEY`: Your YouTube Data API key (required)
85
+ * `YOUTUBE_TRANSCRIPT_LANG`: Default language for transcripts (optional, defaults to 'en')
86
+ ### Using with VS Code
87
+
88
+ For one-click installation, click one of the install buttons below:
89
+
90
+ [![Install with NPX in VS Code](https://img.shields.io/badge/VS_Code-NPM-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=youtube&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22zubeid-youtube-mcp-server%22%5D%2C%22env%22%3A%7B%22YOUTUBE_API_KEY%22%3A%22%24%7Binput%3AapiKey%7D%22%7D%7D&inputs=%5B%7B%22type%22%3A%22promptString%22%2C%22id%22%3A%22apiKey%22%2C%22description%22%3A%22YouTube+API+Key%22%2C%22password%22%3Atrue%7D%5D) [![Install with NPX in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-NPM-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=youtube&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22zubeid-youtube-mcp-server%22%5D%2C%22env%22%3A%7B%22YOUTUBE_API_KEY%22%3A%22%24%7Binput%3AapiKey%7D%22%7D%7D&inputs=%5B%7B%22type%22%3A%22promptString%22%2C%22id%22%3A%22apiKey%22%2C%22description%22%3A%22YouTube+API+Key%22%2C%22password%22%3Atrue%7D%5D&quality=insiders)
91
+
92
+ ### Manual Installation
93
+
94
+ If you prefer manual installation, first check the install buttons at the top of this section. Otherwise, follow these steps:
95
+
96
+ Add the following JSON block to your User Settings (JSON) file in VS Code. You can do this by pressing `Ctrl + Shift + P` and typing `Preferences: Open User Settings (JSON)`.
97
+
98
+ ```json
99
+ {
100
+ "mcp": {
101
+ "inputs": [
102
+ {
103
+ "type": "promptString",
104
+ "id": "apiKey",
105
+ "description": "YouTube API Key",
106
+ "password": true
107
+ }
108
+ ],
109
+ "servers": {
110
+ "youtube": {
111
+ "command": "npx",
112
+ "args": ["-y", "zubeid-youtube-mcp-server"],
113
+ "env": {
114
+ "YOUTUBE_API_KEY": "${input:apiKey}"
115
+ }
116
+ }
117
+ }
118
+ }
119
+ }
120
+ ```
121
+
122
+ Optionally, you can add it to a file called `.vscode/mcp.json` in your workspace:
123
+
124
+ ```json
125
+ {
126
+ "inputs": [
127
+ {
128
+ "type": "promptString",
129
+ "id": "apiKey",
130
+ "description": "YouTube API Key",
131
+ "password": true
132
+ }
133
+ ],
134
+ "servers": {
135
+ "youtube": {
136
+ "command": "npx",
137
+ "args": ["-y", "zubeid-youtube-mcp-server"],
138
+ "env": {
139
+ "YOUTUBE_API_KEY": "${input:apiKey}"
140
+ }
141
+ }
142
+ }
143
+ }
144
+ ```
145
+ ## YouTube API Setup
146
+ 1. Go to Google Cloud Console
147
+ 2. Create a new project or select an existing one
148
+ 3. Enable the YouTube Data API v3
149
+ 4. Create API credentials (API key)
150
+ 5. Copy the API key for configuration
151
+
152
+ ## Examples
153
+
154
+ ### Managing Videos
155
+
156
+ ```javascript
157
+ // Get video details
158
+ const video = await youtube.videos.getVideo({
159
+ videoId: "video-id"
160
+ });
161
+
162
+ // Get video transcript
163
+ const transcript = await youtube.transcripts.getTranscript({
164
+ videoId: "video-id",
165
+ language: "en"
166
+ });
167
+
168
+ // Search videos
169
+ const searchResults = await youtube.videos.searchVideos({
170
+ query: "search term",
171
+ maxResults: 10
172
+ });
173
+ ```
174
+
175
+ ### Managing Channels
176
+
177
+ ```javascript
178
+ // Get channel details
179
+ const channel = await youtube.channels.getChannel({
180
+ channelId: "channel-id"
181
+ });
182
+
183
+ // List channel videos
184
+ const videos = await youtube.channels.listVideos({
185
+ channelId: "channel-id",
186
+ maxResults: 50
187
+ });
188
+ ```
189
+
190
+ ### Managing Playlists
191
+
192
+ ```javascript
193
+ // Get playlist items
194
+ const playlistItems = await youtube.playlists.getPlaylistItems({
195
+ playlistId: "playlist-id",
196
+ maxResults: 50
197
+ });
198
+
199
+ // Get playlist details
200
+ const playlist = await youtube.playlists.getPlaylist({
201
+ playlistId: "playlist-id"
202
+ });
203
+ ```
204
+
205
+ ## Development
206
+
207
+ ```bash
208
+ # Install dependencies
209
+ npm install
210
+
211
+ # Run tests
212
+ npm test
213
+
214
+ # Build
215
+ npm run build
216
+
217
+ # Lint
218
+ npm run lint
219
+ ```
220
+
221
+ ## Contributing
222
+ See CONTRIBUTING.md for information about contributing to this repository.
223
+
224
+ ## License
225
+ This project is licensed under the MIT License - see the LICENSE file for details.
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env node
2
+ import { startMcpServer } from './server.js';
3
+ // Check for required environment variables
4
+ if (!process.env.YOUTUBE_API_KEY) {
5
+ console.error('Error: YOUTUBE_API_KEY environment variable is required.');
6
+ console.error('Please set it before running this server.');
7
+ process.exit(1);
8
+ }
9
+ // Start the MCP server
10
+ startMcpServer()
11
+ .then(() => {
12
+ console.log('YouTube MCP Server started successfully');
13
+ })
14
+ .catch(error => {
15
+ console.error('Failed to start YouTube MCP Server:', error);
16
+ process.exit(1);
17
+ });
@@ -0,0 +1 @@
1
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,16 @@
1
+ import { startMcpServer } from './server.js';
2
+ // Check for required environment variables
3
+ if (!process.env.YOUTUBE_API_KEY) {
4
+ console.error('Error: YOUTUBE_API_KEY environment variable is required.');
5
+ console.error('Please set it before running this server.');
6
+ process.exit(1);
7
+ }
8
+ // Start the MCP server
9
+ startMcpServer()
10
+ .then(() => {
11
+ console.log('YouTube MCP Server started successfully');
12
+ })
13
+ .catch(error => {
14
+ console.error('Failed to start YouTube MCP Server:', error);
15
+ process.exit(1);
16
+ });
@@ -0,0 +1,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function startMcpServer(): Promise<McpServer>;
package/dist/server.js ADDED
@@ -0,0 +1,106 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
+ import { z } from 'zod';
4
+ import { VideoService } from './services/video.js';
5
+ import { TranscriptService } from './services/transcript.js';
6
+ import { PlaylistService } from './services/playlist.js';
7
+ import { ChannelService } from './services/channel.js';
8
+ export async function startMcpServer() {
9
+ const server = new McpServer({
10
+ name: 'zubeid-youtube-mcp-server',
11
+ version: '1.0.0',
12
+ });
13
+ const videoService = new VideoService();
14
+ const transcriptService = new TranscriptService();
15
+ const playlistService = new PlaylistService();
16
+ const channelService = new ChannelService();
17
+ // Register tools using the new McpServer API
18
+ server.tool('videos_getVideo', 'Get detailed information about a YouTube video', {
19
+ videoId: z.string().describe('The YouTube video ID'),
20
+ parts: z.array(z.string()).optional().describe('Parts of the video to retrieve'),
21
+ }, async ({ videoId, parts }) => {
22
+ const result = await videoService.getVideo({ videoId, parts });
23
+ return {
24
+ content: [{
25
+ type: 'text',
26
+ text: JSON.stringify(result, null, 2)
27
+ }]
28
+ };
29
+ });
30
+ server.tool('videos_searchVideos', 'Search for videos on YouTube', {
31
+ query: z.string().describe('Search query'),
32
+ maxResults: z.number().optional().describe('Maximum number of results to return'),
33
+ }, async ({ query, maxResults }) => {
34
+ const result = await videoService.searchVideos({ query, maxResults });
35
+ return {
36
+ content: [{
37
+ type: 'text',
38
+ text: JSON.stringify(result, null, 2)
39
+ }]
40
+ };
41
+ });
42
+ server.tool('transcripts_getTranscript', 'Get the transcript of a YouTube video', {
43
+ videoId: z.string().describe('The YouTube video ID'),
44
+ language: z.string().optional().describe('Language code for the transcript'),
45
+ }, async ({ videoId, language }) => {
46
+ const result = await transcriptService.getTranscript({ videoId, language });
47
+ return {
48
+ content: [{
49
+ type: 'text',
50
+ text: JSON.stringify(result, null, 2)
51
+ }]
52
+ };
53
+ });
54
+ server.tool('channels_getChannel', 'Get information about a YouTube channel', {
55
+ channelId: z.string().describe('The YouTube channel ID'),
56
+ }, async ({ channelId }) => {
57
+ const result = await channelService.getChannel({ channelId });
58
+ return {
59
+ content: [{
60
+ type: 'text',
61
+ text: JSON.stringify(result, null, 2)
62
+ }]
63
+ };
64
+ });
65
+ server.tool('channels_listVideos', 'Get videos from a specific channel', {
66
+ channelId: z.string().describe('The YouTube channel ID'),
67
+ maxResults: z.number().optional().describe('Maximum number of results to return'),
68
+ }, async ({ channelId, maxResults }) => {
69
+ const result = await channelService.listVideos({ channelId, maxResults });
70
+ return {
71
+ content: [{
72
+ type: 'text',
73
+ text: JSON.stringify(result, null, 2)
74
+ }]
75
+ };
76
+ });
77
+ server.tool('playlists_getPlaylist', 'Get information about a YouTube playlist', {
78
+ playlistId: z.string().describe('The YouTube playlist ID'),
79
+ }, async ({ playlistId }) => {
80
+ const result = await playlistService.getPlaylist({ playlistId });
81
+ return {
82
+ content: [{
83
+ type: 'text',
84
+ text: JSON.stringify(result, null, 2)
85
+ }]
86
+ };
87
+ });
88
+ server.tool('playlists_getPlaylistItems', 'Get videos in a YouTube playlist', {
89
+ playlistId: z.string().describe('The YouTube playlist ID'),
90
+ maxResults: z.number().optional().describe('Maximum number of results to return'),
91
+ }, async ({ playlistId, maxResults }) => {
92
+ const result = await playlistService.getPlaylistItems({ playlistId, maxResults });
93
+ return {
94
+ content: [{
95
+ type: 'text',
96
+ text: JSON.stringify(result, null, 2)
97
+ }]
98
+ };
99
+ });
100
+ // Create transport and connect
101
+ const transport = new StdioServerTransport();
102
+ await server.connect(transport);
103
+ console.error(`YouTube MCP Server v1.0.0 started successfully`);
104
+ console.error(`Server will validate YouTube API key when tools are called`);
105
+ return server;
106
+ }
@@ -0,0 +1,29 @@
1
+ import { ChannelParams, ChannelVideosParams } from '../types.js';
2
+ /**
3
+ * Service for interacting with YouTube channels
4
+ */
5
+ export declare class ChannelService {
6
+ private youtube;
7
+ private initialized;
8
+ constructor();
9
+ /**
10
+ * Initialize the YouTube client only when needed
11
+ */
12
+ private initialize;
13
+ /**
14
+ * Get channel details
15
+ */
16
+ getChannel({ channelId }: ChannelParams): Promise<any>;
17
+ /**
18
+ * Get channel playlists
19
+ */
20
+ getPlaylists({ channelId, maxResults }: ChannelVideosParams): Promise<any[]>;
21
+ /**
22
+ * Get channel videos
23
+ */
24
+ listVideos({ channelId, maxResults }: ChannelVideosParams): Promise<any[]>;
25
+ /**
26
+ * Get channel statistics
27
+ */
28
+ getStatistics({ channelId }: ChannelParams): Promise<any>;
29
+ }
@@ -0,0 +1,95 @@
1
+ import { google } from 'googleapis';
2
+ /**
3
+ * Service for interacting with YouTube channels
4
+ */
5
+ export class ChannelService {
6
+ youtube;
7
+ initialized = false;
8
+ constructor() {
9
+ // Don't initialize in constructor
10
+ }
11
+ /**
12
+ * Initialize the YouTube client only when needed
13
+ */
14
+ initialize() {
15
+ if (this.initialized)
16
+ return;
17
+ const apiKey = process.env.YOUTUBE_API_KEY;
18
+ if (!apiKey) {
19
+ throw new Error('YOUTUBE_API_KEY environment variable is not set.');
20
+ }
21
+ this.youtube = google.youtube({
22
+ version: 'v3',
23
+ auth: apiKey
24
+ });
25
+ this.initialized = true;
26
+ }
27
+ /**
28
+ * Get channel details
29
+ */
30
+ async getChannel({ channelId }) {
31
+ try {
32
+ this.initialize();
33
+ const response = await this.youtube.channels.list({
34
+ part: ['snippet', 'statistics', 'contentDetails'],
35
+ id: [channelId]
36
+ });
37
+ return response.data.items?.[0] || null;
38
+ }
39
+ catch (error) {
40
+ throw new Error(`Failed to get channel: ${error instanceof Error ? error.message : String(error)}`);
41
+ }
42
+ }
43
+ /**
44
+ * Get channel playlists
45
+ */
46
+ async getPlaylists({ channelId, maxResults = 50 }) {
47
+ try {
48
+ this.initialize();
49
+ const response = await this.youtube.playlists.list({
50
+ part: ['snippet', 'contentDetails'],
51
+ channelId,
52
+ maxResults
53
+ });
54
+ return response.data.items || [];
55
+ }
56
+ catch (error) {
57
+ throw new Error(`Failed to get channel playlists: ${error instanceof Error ? error.message : String(error)}`);
58
+ }
59
+ }
60
+ /**
61
+ * Get channel videos
62
+ */
63
+ async listVideos({ channelId, maxResults = 50 }) {
64
+ try {
65
+ this.initialize();
66
+ const response = await this.youtube.search.list({
67
+ part: ['snippet'],
68
+ channelId,
69
+ maxResults,
70
+ order: 'date',
71
+ type: ['video']
72
+ });
73
+ return response.data.items || [];
74
+ }
75
+ catch (error) {
76
+ throw new Error(`Failed to list channel videos: ${error instanceof Error ? error.message : String(error)}`);
77
+ }
78
+ }
79
+ /**
80
+ * Get channel statistics
81
+ */
82
+ async getStatistics({ channelId }) {
83
+ try {
84
+ this.initialize();
85
+ const response = await this.youtube.channels.list({
86
+ part: ['statistics'],
87
+ id: [channelId]
88
+ });
89
+ return response.data.items?.[0]?.statistics || null;
90
+ }
91
+ catch (error) {
92
+ throw new Error(`Failed to get channel statistics: ${error instanceof Error ? error.message : String(error)}`);
93
+ }
94
+ }
95
+ }
@@ -0,0 +1,25 @@
1
+ import { PlaylistParams, PlaylistItemsParams, SearchParams } from '../types.js';
2
+ /**
3
+ * Service for interacting with YouTube playlists
4
+ */
5
+ export declare class PlaylistService {
6
+ private youtube;
7
+ private initialized;
8
+ constructor();
9
+ /**
10
+ * Initialize the YouTube client only when needed
11
+ */
12
+ private initialize;
13
+ /**
14
+ * Get information about a YouTube playlist
15
+ */
16
+ getPlaylist({ playlistId }: PlaylistParams): Promise<any>;
17
+ /**
18
+ * Get videos in a YouTube playlist
19
+ */
20
+ getPlaylistItems({ playlistId, maxResults }: PlaylistItemsParams): Promise<any[]>;
21
+ /**
22
+ * Search for playlists on YouTube
23
+ */
24
+ searchPlaylists({ query, maxResults }: SearchParams): Promise<any[]>;
25
+ }
@@ -0,0 +1,78 @@
1
+ import { google } from 'googleapis';
2
+ /**
3
+ * Service for interacting with YouTube playlists
4
+ */
5
+ export class PlaylistService {
6
+ youtube;
7
+ initialized = false;
8
+ constructor() {
9
+ // Don't initialize in constructor
10
+ }
11
+ /**
12
+ * Initialize the YouTube client only when needed
13
+ */
14
+ initialize() {
15
+ if (this.initialized)
16
+ return;
17
+ const apiKey = process.env.YOUTUBE_API_KEY;
18
+ if (!apiKey) {
19
+ throw new Error('YOUTUBE_API_KEY environment variable is not set.');
20
+ }
21
+ this.youtube = google.youtube({
22
+ version: "v3",
23
+ auth: apiKey
24
+ });
25
+ this.initialized = true;
26
+ }
27
+ /**
28
+ * Get information about a YouTube playlist
29
+ */
30
+ async getPlaylist({ playlistId }) {
31
+ try {
32
+ this.initialize();
33
+ const response = await this.youtube.playlists.list({
34
+ part: ['snippet', 'contentDetails'],
35
+ id: [playlistId]
36
+ });
37
+ return response.data.items?.[0] || null;
38
+ }
39
+ catch (error) {
40
+ throw new Error(`Failed to get playlist: ${error instanceof Error ? error.message : String(error)}`);
41
+ }
42
+ }
43
+ /**
44
+ * Get videos in a YouTube playlist
45
+ */
46
+ async getPlaylistItems({ playlistId, maxResults = 50 }) {
47
+ try {
48
+ this.initialize();
49
+ const response = await this.youtube.playlistItems.list({
50
+ part: ['snippet', 'contentDetails'],
51
+ playlistId,
52
+ maxResults
53
+ });
54
+ return response.data.items || [];
55
+ }
56
+ catch (error) {
57
+ throw new Error(`Failed to get playlist items: ${error instanceof Error ? error.message : String(error)}`);
58
+ }
59
+ }
60
+ /**
61
+ * Search for playlists on YouTube
62
+ */
63
+ async searchPlaylists({ query, maxResults = 10 }) {
64
+ try {
65
+ this.initialize();
66
+ const response = await this.youtube.search.list({
67
+ part: ['snippet'],
68
+ q: query,
69
+ maxResults,
70
+ type: ['playlist']
71
+ });
72
+ return response.data.items || [];
73
+ }
74
+ catch (error) {
75
+ throw new Error(`Failed to search playlists: ${error instanceof Error ? error.message : String(error)}`);
76
+ }
77
+ }
78
+ }
@@ -0,0 +1,21 @@
1
+ import { TranscriptParams, SearchTranscriptParams } from '../types.js';
2
+ /**
3
+ * Service for interacting with YouTube video transcripts
4
+ */
5
+ export declare class TranscriptService {
6
+ private initialized;
7
+ constructor();
8
+ private initialize;
9
+ /**
10
+ * Get the transcript of a YouTube video
11
+ */
12
+ getTranscript({ videoId, language }: TranscriptParams): Promise<any>;
13
+ /**
14
+ * Search within a transcript
15
+ */
16
+ searchTranscript({ videoId, query, language }: SearchTranscriptParams): Promise<any>;
17
+ /**
18
+ * Get transcript with timestamps
19
+ */
20
+ getTimestampedTranscript({ videoId, language }: TranscriptParams): Promise<any>;
21
+ }
@@ -0,0 +1,85 @@
1
+ import { YoutubeTranscript } from "youtube-transcript";
2
+ /**
3
+ * Service for interacting with YouTube video transcripts
4
+ */
5
+ export class TranscriptService {
6
+ // No YouTube API key needed for transcripts, but we'll implement the same pattern
7
+ initialized = false;
8
+ constructor() {
9
+ // No initialization needed
10
+ }
11
+ initialize() {
12
+ if (this.initialized)
13
+ return;
14
+ // No API key needed for transcripts, but we'll check if language is set
15
+ this.initialized = true;
16
+ }
17
+ /**
18
+ * Get the transcript of a YouTube video
19
+ */
20
+ async getTranscript({ videoId, language = process.env.YOUTUBE_TRANSCRIPT_LANG || 'en' }) {
21
+ try {
22
+ this.initialize();
23
+ // YoutubeTranscript.fetchTranscript only accepts videoId
24
+ const transcript = await YoutubeTranscript.fetchTranscript(videoId);
25
+ return {
26
+ videoId,
27
+ language,
28
+ transcript
29
+ };
30
+ }
31
+ catch (error) {
32
+ throw new Error(`Failed to get transcript: ${error instanceof Error ? error.message : String(error)}`);
33
+ }
34
+ }
35
+ /**
36
+ * Search within a transcript
37
+ */
38
+ async searchTranscript({ videoId, query, language = process.env.YOUTUBE_TRANSCRIPT_LANG || 'en' }) {
39
+ try {
40
+ this.initialize();
41
+ const transcript = await YoutubeTranscript.fetchTranscript(videoId);
42
+ // Search through transcript for the query
43
+ const matches = transcript.filter(item => item.text.toLowerCase().includes(query.toLowerCase()));
44
+ return {
45
+ videoId,
46
+ query,
47
+ matches,
48
+ totalMatches: matches.length
49
+ };
50
+ }
51
+ catch (error) {
52
+ throw new Error(`Failed to search transcript: ${error instanceof Error ? error.message : String(error)}`);
53
+ }
54
+ }
55
+ /**
56
+ * Get transcript with timestamps
57
+ */
58
+ async getTimestampedTranscript({ videoId, language = process.env.YOUTUBE_TRANSCRIPT_LANG || 'en' }) {
59
+ try {
60
+ this.initialize();
61
+ const transcript = await YoutubeTranscript.fetchTranscript(videoId);
62
+ // Format timestamps in human-readable format
63
+ const timestampedTranscript = transcript.map(item => {
64
+ const seconds = item.offset / 1000;
65
+ const minutes = Math.floor(seconds / 60);
66
+ const remainingSeconds = Math.floor(seconds % 60);
67
+ const formattedTime = `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`;
68
+ return {
69
+ timestamp: formattedTime,
70
+ text: item.text,
71
+ startTimeMs: item.offset,
72
+ durationMs: item.duration
73
+ };
74
+ });
75
+ return {
76
+ videoId,
77
+ language,
78
+ timestampedTranscript
79
+ };
80
+ }
81
+ catch (error) {
82
+ throw new Error(`Failed to get timestamped transcript: ${error instanceof Error ? error.message : String(error)}`);
83
+ }
84
+ }
85
+ }
@@ -0,0 +1,35 @@
1
+ import { VideoParams, SearchParams, TrendingParams, RelatedVideosParams } from '../types.js';
2
+ /**
3
+ * Service for interacting with YouTube videos
4
+ */
5
+ export declare class VideoService {
6
+ private youtube;
7
+ private initialized;
8
+ constructor();
9
+ /**
10
+ * Initialize the YouTube client only when needed
11
+ */
12
+ private initialize;
13
+ /**
14
+ * Get detailed information about a YouTube video
15
+ */
16
+ getVideo({ videoId, parts }: VideoParams): Promise<any>;
17
+ /**
18
+ * Search for videos on YouTube
19
+ */
20
+ searchVideos({ query, maxResults }: SearchParams): Promise<any[]>;
21
+ /**
22
+ * Get video statistics like views, likes, and comments
23
+ */
24
+ getVideoStats({ videoId }: {
25
+ videoId: string;
26
+ }): Promise<any>;
27
+ /**
28
+ * Get trending videos
29
+ */
30
+ getTrendingVideos({ regionCode, maxResults, videoCategoryId }: TrendingParams): Promise<any[]>;
31
+ /**
32
+ * Get related videos for a specific video
33
+ */
34
+ getRelatedVideos({ videoId, maxResults }: RelatedVideosParams): Promise<any[]>;
35
+ }
@@ -0,0 +1,117 @@
1
+ import { google } from 'googleapis';
2
+ /**
3
+ * Service for interacting with YouTube videos
4
+ */
5
+ export class VideoService {
6
+ youtube;
7
+ initialized = false;
8
+ constructor() {
9
+ // Don't initialize in constructor
10
+ }
11
+ /**
12
+ * Initialize the YouTube client only when needed
13
+ */
14
+ initialize() {
15
+ if (this.initialized)
16
+ return;
17
+ const apiKey = process.env.YOUTUBE_API_KEY;
18
+ if (!apiKey) {
19
+ throw new Error('YOUTUBE_API_KEY environment variable is not set.');
20
+ }
21
+ this.youtube = google.youtube({
22
+ version: 'v3',
23
+ auth: apiKey
24
+ });
25
+ this.initialized = true;
26
+ }
27
+ /**
28
+ * Get detailed information about a YouTube video
29
+ */
30
+ async getVideo({ videoId, parts = ['snippet', 'contentDetails', 'statistics'] }) {
31
+ try {
32
+ this.initialize();
33
+ const response = await this.youtube.videos.list({
34
+ part: parts,
35
+ id: [videoId]
36
+ });
37
+ return response.data.items?.[0] || null;
38
+ }
39
+ catch (error) {
40
+ throw new Error(`Failed to get video: ${error instanceof Error ? error.message : String(error)}`);
41
+ }
42
+ }
43
+ /**
44
+ * Search for videos on YouTube
45
+ */
46
+ async searchVideos({ query, maxResults = 10 }) {
47
+ try {
48
+ this.initialize();
49
+ const response = await this.youtube.search.list({
50
+ part: ['snippet'],
51
+ q: query,
52
+ maxResults,
53
+ type: ['video']
54
+ });
55
+ return response.data.items || [];
56
+ }
57
+ catch (error) {
58
+ throw new Error(`Failed to search videos: ${error instanceof Error ? error.message : String(error)}`);
59
+ }
60
+ }
61
+ /**
62
+ * Get video statistics like views, likes, and comments
63
+ */
64
+ async getVideoStats({ videoId }) {
65
+ try {
66
+ this.initialize();
67
+ const response = await this.youtube.videos.list({
68
+ part: ['statistics'],
69
+ id: [videoId]
70
+ });
71
+ return response.data.items?.[0]?.statistics || null;
72
+ }
73
+ catch (error) {
74
+ throw new Error(`Failed to get video stats: ${error instanceof Error ? error.message : String(error)}`);
75
+ }
76
+ }
77
+ /**
78
+ * Get trending videos
79
+ */
80
+ async getTrendingVideos({ regionCode = 'US', maxResults = 10, videoCategoryId = '' }) {
81
+ try {
82
+ this.initialize();
83
+ const params = {
84
+ part: ['snippet', 'contentDetails', 'statistics'],
85
+ chart: 'mostPopular',
86
+ regionCode,
87
+ maxResults
88
+ };
89
+ if (videoCategoryId) {
90
+ params.videoCategoryId = videoCategoryId;
91
+ }
92
+ const response = await this.youtube.videos.list(params);
93
+ return response.data.items || [];
94
+ }
95
+ catch (error) {
96
+ throw new Error(`Failed to get trending videos: ${error instanceof Error ? error.message : String(error)}`);
97
+ }
98
+ }
99
+ /**
100
+ * Get related videos for a specific video
101
+ */
102
+ async getRelatedVideos({ videoId, maxResults = 10 }) {
103
+ try {
104
+ this.initialize();
105
+ const response = await this.youtube.search.list({
106
+ part: ['snippet'],
107
+ relatedToVideoId: videoId,
108
+ maxResults,
109
+ type: ['video']
110
+ });
111
+ return response.data.items || [];
112
+ }
113
+ catch (error) {
114
+ throw new Error(`Failed to get related videos: ${error instanceof Error ? error.message : String(error)}`);
115
+ }
116
+ }
117
+ }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Video details parameters
3
+ */
4
+ export interface VideoParams {
5
+ videoId: string;
6
+ parts?: string[];
7
+ }
8
+ /**
9
+ * Search videos parameters
10
+ */
11
+ export interface SearchParams {
12
+ query: string;
13
+ maxResults?: number;
14
+ }
15
+ /**
16
+ * Trending videos parameters
17
+ */
18
+ export interface TrendingParams {
19
+ regionCode?: string;
20
+ maxResults?: number;
21
+ videoCategoryId?: string;
22
+ }
23
+ /**
24
+ * Related videos parameters
25
+ */
26
+ export interface RelatedVideosParams {
27
+ videoId: string;
28
+ maxResults?: number;
29
+ }
30
+ /**
31
+ * Transcript parameters
32
+ */
33
+ export interface TranscriptParams {
34
+ videoId: string;
35
+ language?: string;
36
+ }
37
+ /**
38
+ * Search transcript parameters
39
+ */
40
+ export interface SearchTranscriptParams {
41
+ videoId: string;
42
+ query: string;
43
+ language?: string;
44
+ }
45
+ /**
46
+ * Channel parameters
47
+ */
48
+ export interface ChannelParams {
49
+ channelId: string;
50
+ }
51
+ /**
52
+ * Channel videos parameters
53
+ */
54
+ export interface ChannelVideosParams {
55
+ channelId: string;
56
+ maxResults?: number;
57
+ }
58
+ /**
59
+ * Playlist parameters
60
+ */
61
+ export interface PlaylistParams {
62
+ playlistId: string;
63
+ }
64
+ /**
65
+ * Playlist items parameters
66
+ */
67
+ export interface PlaylistItemsParams {
68
+ playlistId: string;
69
+ maxResults?: number;
70
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@yilin-jing/youtube-mcp-server",
3
+ "version": "1.0.0",
4
+ "description": "YouTube MCP Server Implementation with SDK 1.23 support",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "youtube-mcp-server": "dist/cli.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "scripts": {
16
+ "start": "node ./dist/index.js",
17
+ "build": "tsc",
18
+ "dev": "nodemon --exec \"npm run build && npm start\" --ext ts",
19
+ "test": "bun test",
20
+ "prepublishOnly": "npm run build"
21
+ },
22
+ "dependencies": {
23
+ "@modelcontextprotocol/sdk": "^1.23.0",
24
+ "googleapis": "^129.0.0",
25
+ "ytdl-core": "^4.11.5",
26
+ "youtube-transcript": "^1.0.6",
27
+ "zod": "^3.23.0"
28
+ },
29
+ "devDependencies": {
30
+ "@types/node": "^18.0.0",
31
+ "bun-types": "^1.3.3",
32
+ "nodemon": "^3.0.0",
33
+ "ts-node": "^10.9.1",
34
+ "typescript": "^5.0.0"
35
+ },
36
+ "keywords": [
37
+ "youtube",
38
+ "mcp",
39
+ "model-context-protocol",
40
+ "ai",
41
+ "claude",
42
+ "anthropic"
43
+ ],
44
+ "author": "Yilin Jing",
45
+ "license": "MIT",
46
+ "repository": {
47
+ "type": "git",
48
+ "url": "git+https://github.com/Jing-yilin/youtube-mcp-server.git"
49
+ },
50
+ "bugs": {
51
+ "url": "https://github.com/Jing-yilin/youtube-mcp-server/issues"
52
+ },
53
+ "homepage": "https://github.com/Jing-yilin/youtube-mcp-server#readme"
54
+ }