@yilin-jing/youtube-mcp-server 1.2.0 → 1.3.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.
@@ -8,4 +8,6 @@ export declare const DataCleaners: {
8
8
  cleanVideoList: (videos: any[]) => any[];
9
9
  cleanSearchResults: (items: any[]) => any[];
10
10
  cleanPlaylistItems: (items: any[]) => any[];
11
+ cleanComment: (item: any) => any;
12
+ cleanComments: (result: any) => any;
11
13
  };
package/dist/cleaners.js CHANGED
@@ -108,4 +108,38 @@ export const DataCleaners = {
108
108
  return [];
109
109
  return items.map(DataCleaners.cleanPlaylistItem).filter(Boolean);
110
110
  },
111
+ cleanComment: (item) => {
112
+ if (!item)
113
+ return null;
114
+ const snippet = item.snippet || {};
115
+ const topComment = snippet.topLevelComment?.snippet || {};
116
+ return {
117
+ id: item.id,
118
+ author: topComment.authorDisplayName,
119
+ authorChannelId: topComment.authorChannelId?.value,
120
+ text: topComment.textDisplay,
121
+ likes: topComment.likeCount ? parseInt(topComment.likeCount) : 0,
122
+ published: topComment.publishedAt,
123
+ updated: topComment.updatedAt,
124
+ replyCount: snippet.totalReplyCount || 0,
125
+ replies: item.replies?.comments?.map((reply) => ({
126
+ id: reply.id,
127
+ author: reply.snippet?.authorDisplayName,
128
+ authorChannelId: reply.snippet?.authorChannelId?.value,
129
+ text: reply.snippet?.textDisplay,
130
+ likes: reply.snippet?.likeCount ? parseInt(reply.snippet.likeCount) : 0,
131
+ published: reply.snippet?.publishedAt,
132
+ })) || [],
133
+ };
134
+ },
135
+ cleanComments: (result) => {
136
+ if (!result)
137
+ return { comments: [], nextPageToken: null };
138
+ const items = result.items || [];
139
+ return {
140
+ comments: items.map(DataCleaners.cleanComment).filter(Boolean),
141
+ nextPageToken: result.nextPageToken || null,
142
+ totalResults: result.pageInfo?.totalResults,
143
+ };
144
+ },
111
145
  };
package/dist/server.js CHANGED
@@ -8,6 +8,7 @@ import { VideoService } from './services/video.js';
8
8
  import { TranscriptService } from './services/transcript.js';
9
9
  import { PlaylistService } from './services/playlist.js';
10
10
  import { ChannelService } from './services/channel.js';
11
+ import { CommentService } from './services/comment.js';
11
12
  import { DataCleaners } from './cleaners.js';
12
13
  function saveData(data, dir, toolName) {
13
14
  try {
@@ -36,12 +37,13 @@ function formatResponse(cleanedData, options) {
36
37
  export async function startMcpServer() {
37
38
  const server = new McpServer({
38
39
  name: 'youtube-mcp-server',
39
- version: '1.2.0',
40
+ version: '1.3.0',
40
41
  });
41
42
  const videoService = new VideoService();
42
43
  const transcriptService = new TranscriptService();
43
44
  const playlistService = new PlaylistService();
44
45
  const channelService = new ChannelService();
46
+ const commentService = new CommentService();
45
47
  server.tool('videos_getVideo', 'Get video info. Returns cleaned data in TOON format.', {
46
48
  videoId: z.string().describe('The YouTube video ID'),
47
49
  parts: z.array(z.string()).optional().describe('Parts to retrieve'),
@@ -103,6 +105,18 @@ export async function startMcpServer() {
103
105
  const cleaned = DataCleaners.cleanPlaylistItems(result);
104
106
  return formatResponse(cleaned, { ...(save_dir && { saveDir: save_dir }), toolName: 'playlists_getPlaylistItems' });
105
107
  });
108
+ server.tool('comments_getComments', 'Get video comments. Returns cleaned data in TOON format.', {
109
+ videoId: z.string().describe('The YouTube video ID'),
110
+ maxResults: z.number().optional().describe('Max results (1-100, default: 100)'),
111
+ order: z.enum(['time', 'relevance']).optional().describe('Sort order (default: relevance)'),
112
+ pageToken: z.string().optional().describe('Page token for pagination'),
113
+ textFormat: z.enum(['html', 'plainText']).optional().describe('Text format (default: plainText)'),
114
+ save_dir: z.string().optional().describe('Directory to save cleaned JSON'),
115
+ }, async ({ videoId, maxResults, order, pageToken, textFormat, save_dir }) => {
116
+ const result = await commentService.getComments({ videoId, maxResults, order, pageToken, textFormat });
117
+ const cleaned = DataCleaners.cleanComments(result);
118
+ return formatResponse(cleaned, { ...(save_dir && { saveDir: save_dir }), toolName: 'comments_getComments' });
119
+ });
106
120
  const transport = new StdioServerTransport();
107
121
  await server.connect(transport);
108
122
  console.error(`YouTube MCP Server v1.2.0 started successfully`);
@@ -0,0 +1,17 @@
1
+ import { CommentParams } from '../types.js';
2
+ /**
3
+ * Service for interacting with YouTube comments
4
+ */
5
+ export declare class CommentService {
6
+ private youtube;
7
+ private initialized;
8
+ constructor();
9
+ /**
10
+ * Initialize the YouTube client only when needed
11
+ */
12
+ private initialize;
13
+ /**
14
+ * Get comments for a YouTube video
15
+ */
16
+ getComments({ videoId, maxResults, order, pageToken, textFormat }: CommentParams): Promise<any>;
17
+ }
@@ -0,0 +1,47 @@
1
+ import { google } from 'googleapis';
2
+ /**
3
+ * Service for interacting with YouTube comments
4
+ */
5
+ export class CommentService {
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 comments for a YouTube video
29
+ */
30
+ async getComments({ videoId, maxResults = 100, order = 'relevance', pageToken, textFormat = 'plainText' }) {
31
+ try {
32
+ this.initialize();
33
+ const response = await this.youtube.commentThreads.list({
34
+ part: ['snippet', 'replies'],
35
+ videoId,
36
+ maxResults,
37
+ order,
38
+ pageToken,
39
+ textFormat
40
+ });
41
+ return response.data;
42
+ }
43
+ catch (error) {
44
+ throw new Error(`Failed to get comments: ${error instanceof Error ? error.message : String(error)}`);
45
+ }
46
+ }
47
+ }
package/dist/types.d.ts CHANGED
@@ -68,3 +68,13 @@ export interface PlaylistItemsParams {
68
68
  playlistId: string;
69
69
  maxResults?: number;
70
70
  }
71
+ /**
72
+ * Comment parameters
73
+ */
74
+ export interface CommentParams {
75
+ videoId: string;
76
+ maxResults?: number;
77
+ order?: 'time' | 'relevance';
78
+ pageToken?: string;
79
+ textFormat?: 'html' | 'plainText';
80
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yilin-jing/youtube-mcp-server",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "YouTube MCP Server with TOON format for 90%+ token savings",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",