reddit-mcp-server 1.0.3 → 1.0.5

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/dist/bin.js ADDED
@@ -0,0 +1,1680 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
+ // If the importer is in node compatibility mode or this is not an ESM
19
+ // file that has been converted to a CommonJS file using a Babel-
20
+ // compatible transform (i.e. "__esModule" has not been set), then set
21
+ // "default" to the CommonJS "module.exports" for node compatibility.
22
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
+ mod
24
+ ));
25
+
26
+ // src/index.ts
27
+ var import_server = require("@modelcontextprotocol/sdk/server/index.js");
28
+ var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
29
+ var import_types6 = require("@modelcontextprotocol/sdk/types.js");
30
+
31
+ // src/client/reddit-client.ts
32
+ var RedditClient = class {
33
+ clientId;
34
+ clientSecret;
35
+ userAgent;
36
+ username;
37
+ password;
38
+ accessToken;
39
+ tokenExpiry = 0;
40
+ baseUrl = "https://oauth.reddit.com";
41
+ authenticated = false;
42
+ constructor(config) {
43
+ this.clientId = config.clientId;
44
+ this.clientSecret = config.clientSecret;
45
+ this.userAgent = config.userAgent;
46
+ this.username = config.username;
47
+ this.password = config.password;
48
+ }
49
+ async makeRequest(path, options = {}) {
50
+ if (Date.now() >= this.tokenExpiry || !this.authenticated) {
51
+ await this.authenticate();
52
+ }
53
+ const url = `${this.baseUrl}${path}`;
54
+ const headers = {
55
+ "User-Agent": this.userAgent,
56
+ Authorization: `Bearer ${this.accessToken}`,
57
+ ...options.headers
58
+ };
59
+ const response = await fetch(url, {
60
+ ...options,
61
+ headers
62
+ });
63
+ if (response.status === 401 && this.authenticated) {
64
+ await this.authenticate();
65
+ const retryHeaders = {
66
+ ...headers,
67
+ Authorization: `Bearer ${this.accessToken}`
68
+ };
69
+ return fetch(url, {
70
+ ...options,
71
+ headers: retryHeaders
72
+ });
73
+ }
74
+ return response;
75
+ }
76
+ async authenticate() {
77
+ try {
78
+ const now = Date.now();
79
+ if (this.accessToken && now < this.tokenExpiry) {
80
+ return;
81
+ }
82
+ const authUrl = "https://www.reddit.com/api/v1/access_token";
83
+ const authData = new URLSearchParams();
84
+ if (this.username && this.password) {
85
+ authData.append("grant_type", "password");
86
+ authData.append("username", this.username);
87
+ authData.append("password", this.password);
88
+ } else {
89
+ authData.append("grant_type", "client_credentials");
90
+ }
91
+ const credentials = Buffer.from(`${this.clientId}:${this.clientSecret}`).toString("base64");
92
+ const response = await fetch(authUrl, {
93
+ method: "POST",
94
+ headers: {
95
+ "User-Agent": this.userAgent,
96
+ "Content-Type": "application/x-www-form-urlencoded",
97
+ Authorization: `Basic ${credentials}`
98
+ },
99
+ body: authData.toString()
100
+ });
101
+ if (!response.ok) {
102
+ throw new Error(`Authentication failed: ${response.status}`);
103
+ }
104
+ const data = await response.json();
105
+ this.accessToken = data.access_token;
106
+ this.tokenExpiry = now + data.expires_in * 1e3;
107
+ this.authenticated = true;
108
+ } catch {
109
+ throw new Error("Failed to authenticate with Reddit API");
110
+ }
111
+ }
112
+ async checkAuthentication() {
113
+ if (!this.authenticated) {
114
+ try {
115
+ await this.authenticate();
116
+ return true;
117
+ } catch {
118
+ return false;
119
+ }
120
+ }
121
+ return true;
122
+ }
123
+ async getUser(username) {
124
+ await this.authenticate();
125
+ try {
126
+ const response = await this.makeRequest(`/user/${username}/about.json`);
127
+ if (!response.ok) {
128
+ throw new Error(`HTTP ${response.status}`);
129
+ }
130
+ const json = await response.json();
131
+ const data = json.data;
132
+ return {
133
+ name: data.name,
134
+ id: data.id,
135
+ commentKarma: data.comment_karma,
136
+ linkKarma: data.link_karma,
137
+ totalKarma: data.total_karma || data.comment_karma + data.link_karma,
138
+ isMod: data.is_mod,
139
+ isGold: data.is_gold,
140
+ isEmployee: data.is_employee,
141
+ createdUtc: data.created_utc,
142
+ profileUrl: `https://reddit.com/user/${data.name}`
143
+ };
144
+ } catch {
145
+ throw new Error(`Failed to get user info for ${username}`);
146
+ }
147
+ }
148
+ async getSubredditInfo(subredditName) {
149
+ await this.authenticate();
150
+ try {
151
+ const response = await this.makeRequest(`/r/${subredditName}/about.json`);
152
+ if (!response.ok) {
153
+ throw new Error(`HTTP ${response.status}`);
154
+ }
155
+ const json = await response.json();
156
+ const data = json.data;
157
+ return {
158
+ displayName: data.display_name,
159
+ title: data.title,
160
+ description: data.description || "",
161
+ publicDescription: data.public_description || "",
162
+ subscribers: data.subscribers,
163
+ activeUserCount: data.active_user_count,
164
+ createdUtc: data.created_utc,
165
+ over18: data.over18,
166
+ subredditType: data.subreddit_type,
167
+ url: data.url
168
+ };
169
+ } catch {
170
+ throw new Error(`Failed to get subreddit info for ${subredditName}`);
171
+ }
172
+ }
173
+ async getTopPosts(subreddit, timeFilter = "week", limit = 10) {
174
+ await this.authenticate();
175
+ try {
176
+ const endpoint = subreddit ? `/r/${subreddit}/top.json` : "/top.json";
177
+ const params = new URLSearchParams({
178
+ t: timeFilter,
179
+ limit: limit.toString()
180
+ });
181
+ const response = await this.makeRequest(`${endpoint}?${params}`);
182
+ if (!response.ok) {
183
+ throw new Error(`HTTP ${response.status}`);
184
+ }
185
+ const json = await response.json();
186
+ return json.data.children.map((child) => {
187
+ const post = child.data;
188
+ return {
189
+ id: post.id,
190
+ title: post.title,
191
+ author: post.author,
192
+ subreddit: post.subreddit,
193
+ selftext: post.selftext,
194
+ url: post.url,
195
+ score: post.score,
196
+ upvoteRatio: post.upvote_ratio,
197
+ numComments: post.num_comments,
198
+ createdUtc: post.created_utc,
199
+ over18: post.over_18,
200
+ spoiler: post.spoiler,
201
+ edited: !!post.edited,
202
+ isSelf: post.is_self,
203
+ linkFlairText: post.link_flair_text,
204
+ permalink: post.permalink
205
+ };
206
+ });
207
+ } catch {
208
+ throw new Error(`Failed to get top posts for ${subreddit || "home"}`);
209
+ }
210
+ }
211
+ async getPost(postId, subreddit) {
212
+ await this.authenticate();
213
+ try {
214
+ const endpoint = subreddit ? `/r/${subreddit}/comments/${postId}.json` : `/api/info.json?id=t3_${postId}`;
215
+ const response = await this.makeRequest(endpoint);
216
+ if (!response.ok) {
217
+ throw new Error(`HTTP ${response.status}`);
218
+ }
219
+ const json = await response.json();
220
+ let post;
221
+ if (subreddit) {
222
+ post = json[0].data.children[0].data;
223
+ } else {
224
+ if (!json.data.children.length) {
225
+ throw new Error(`Post with ID ${postId} not found`);
226
+ }
227
+ post = json.data.children[0].data;
228
+ }
229
+ return {
230
+ id: post.id,
231
+ title: post.title,
232
+ author: post.author,
233
+ subreddit: post.subreddit,
234
+ selftext: post.selftext,
235
+ url: post.url,
236
+ score: post.score,
237
+ upvoteRatio: post.upvote_ratio,
238
+ numComments: post.num_comments,
239
+ createdUtc: post.created_utc,
240
+ over18: post.over_18,
241
+ spoiler: post.spoiler,
242
+ edited: !!post.edited,
243
+ isSelf: post.is_self,
244
+ linkFlairText: post.link_flair_text,
245
+ permalink: post.permalink
246
+ };
247
+ } catch {
248
+ throw new Error(`Failed to get post with ID ${postId}`);
249
+ }
250
+ }
251
+ async getTrendingSubreddits(limit = 5) {
252
+ await this.authenticate();
253
+ try {
254
+ const params = new URLSearchParams({ limit: limit.toString() });
255
+ const response = await this.makeRequest(`/subreddits/popular.json?${params}`);
256
+ if (!response.ok) {
257
+ throw new Error(`HTTP ${response.status}`);
258
+ }
259
+ const json = await response.json();
260
+ return json.data.children.map((child) => child.data.display_name);
261
+ } catch {
262
+ throw new Error("Failed to get trending subreddits");
263
+ }
264
+ }
265
+ async createPost(subreddit, title, content, isSelf = true) {
266
+ await this.authenticate();
267
+ if (!this.username || !this.password) {
268
+ throw new Error("User authentication required for posting");
269
+ }
270
+ try {
271
+ const kind = isSelf ? "self" : "link";
272
+ const params = new URLSearchParams();
273
+ params.append("sr", subreddit);
274
+ params.append("kind", kind);
275
+ params.append("title", title);
276
+ params.append(isSelf ? "text" : "url", content);
277
+ const response = await this.makeRequest("/api/submit", {
278
+ method: "POST",
279
+ headers: {
280
+ "Content-Type": "application/x-www-form-urlencoded"
281
+ },
282
+ body: params.toString()
283
+ });
284
+ if (!response.ok) {
285
+ throw new Error(`HTTP ${response.status}`);
286
+ }
287
+ const json = await response.json();
288
+ if (json.success) {
289
+ const postId = json.data.id;
290
+ return await this.getPost(postId);
291
+ } else {
292
+ throw new Error("Failed to create post");
293
+ }
294
+ } catch {
295
+ throw new Error(`Failed to create post in ${subreddit}`);
296
+ }
297
+ }
298
+ async checkPostExists(postId) {
299
+ await this.authenticate();
300
+ try {
301
+ const response = await this.makeRequest(`/api/info.json?id=t3_${postId}`);
302
+ if (!response.ok) {
303
+ return false;
304
+ }
305
+ const json = await response.json();
306
+ return json.data.children.length > 0;
307
+ } catch {
308
+ return false;
309
+ }
310
+ }
311
+ async replyToPost(postId, content) {
312
+ await this.authenticate();
313
+ if (!this.username || !this.password) {
314
+ throw new Error("User authentication required for posting replies");
315
+ }
316
+ try {
317
+ if (!await this.checkPostExists(postId)) {
318
+ throw new Error(`Post with ID ${postId} does not exist or is not accessible`);
319
+ }
320
+ const params = new URLSearchParams();
321
+ params.append("thing_id", `t3_${postId}`);
322
+ params.append("text", content);
323
+ const response = await this.makeRequest("/api/comment", {
324
+ method: "POST",
325
+ headers: {
326
+ "Content-Type": "application/x-www-form-urlencoded"
327
+ },
328
+ body: params.toString()
329
+ });
330
+ if (!response.ok) {
331
+ throw new Error(`HTTP ${response.status}`);
332
+ }
333
+ const commentData = await response.json();
334
+ return {
335
+ id: commentData.id,
336
+ author: this.username,
337
+ body: content,
338
+ score: 1,
339
+ controversiality: 0,
340
+ subreddit: commentData.subreddit,
341
+ submissionTitle: commentData.link_title,
342
+ createdUtc: Date.now() / 1e3,
343
+ edited: false,
344
+ isSubmitter: false,
345
+ permalink: commentData.permalink
346
+ };
347
+ } catch {
348
+ throw new Error(`Failed to reply to post ${postId}`);
349
+ }
350
+ }
351
+ async searchReddit(query, options = {}) {
352
+ await this.authenticate();
353
+ try {
354
+ const { subreddit, sort = "relevance", timeFilter = "all", limit = 25, type = "link" } = options;
355
+ const endpoint = subreddit ? `/r/${subreddit}/search.json` : "/search.json";
356
+ const params = new URLSearchParams({
357
+ q: query,
358
+ sort,
359
+ t: timeFilter,
360
+ limit: limit.toString(),
361
+ type,
362
+ ...subreddit && { restrict_sr: "true" }
363
+ });
364
+ const response = await this.makeRequest(`${endpoint}?${params}`);
365
+ if (!response.ok) {
366
+ throw new Error(`HTTP ${response.status}`);
367
+ }
368
+ const json = await response.json();
369
+ return json.data.children.filter((child) => child.kind === "t3").map((child) => {
370
+ const post = child.data;
371
+ return {
372
+ id: post.id,
373
+ title: post.title,
374
+ author: post.author,
375
+ subreddit: post.subreddit,
376
+ selftext: post.selftext || "",
377
+ url: post.url,
378
+ score: post.score,
379
+ upvoteRatio: post.upvote_ratio,
380
+ numComments: post.num_comments,
381
+ createdUtc: post.created_utc,
382
+ over18: post.over_18,
383
+ spoiler: post.spoiler,
384
+ edited: !!post.edited,
385
+ isSelf: post.is_self,
386
+ linkFlairText: post.link_flair_text,
387
+ permalink: post.permalink
388
+ };
389
+ });
390
+ } catch {
391
+ throw new Error(`Failed to search Reddit for: ${query}`);
392
+ }
393
+ }
394
+ async getPostComments(postId, subreddit, options = {}) {
395
+ await this.authenticate();
396
+ try {
397
+ const { sort = "best", limit = 100 } = options;
398
+ const params = new URLSearchParams({
399
+ sort,
400
+ limit: limit.toString()
401
+ });
402
+ const response = await this.makeRequest(`/r/${subreddit}/comments/${postId}.json?${params}`);
403
+ if (!response.ok) {
404
+ throw new Error(`HTTP ${response.status}`);
405
+ }
406
+ const json = await response.json();
407
+ const postData = json[0].data.children[0].data;
408
+ const post = {
409
+ id: postData.id,
410
+ title: postData.title,
411
+ author: postData.author,
412
+ subreddit: postData.subreddit,
413
+ selftext: postData.selftext || "",
414
+ url: postData.url,
415
+ score: postData.score,
416
+ upvoteRatio: postData.upvote_ratio,
417
+ numComments: postData.num_comments,
418
+ createdUtc: postData.created_utc,
419
+ over18: postData.over_18,
420
+ spoiler: postData.spoiler,
421
+ edited: !!postData.edited,
422
+ isSelf: postData.is_self,
423
+ linkFlairText: postData.link_flair_text,
424
+ permalink: postData.permalink
425
+ };
426
+ const comments = [];
427
+ const parseComments = (commentData, depth = 0) => {
428
+ for (const item of commentData) {
429
+ if (item.kind === "t1" && item.data.body) {
430
+ comments.push({
431
+ id: item.data.id,
432
+ author: item.data.author,
433
+ body: item.data.body,
434
+ score: item.data.score,
435
+ controversiality: item.data.controversiality,
436
+ subreddit: item.data.subreddit,
437
+ submissionTitle: post.title,
438
+ createdUtc: item.data.created_utc,
439
+ edited: !!item.data.edited,
440
+ isSubmitter: item.data.is_submitter,
441
+ permalink: item.data.permalink,
442
+ depth,
443
+ parentId: item.data.parent_id
444
+ });
445
+ if (item.data.replies && item.data.replies.data && item.data.replies.data.children) {
446
+ parseComments(item.data.replies.data.children, depth + 1);
447
+ }
448
+ }
449
+ }
450
+ };
451
+ if (json[1] && json[1].data && json[1].data.children) {
452
+ parseComments(json[1].data.children);
453
+ }
454
+ return { post, comments };
455
+ } catch {
456
+ throw new Error(`Failed to get comments for post ${postId}`);
457
+ }
458
+ }
459
+ async getUserPosts(username, options = {}) {
460
+ await this.authenticate();
461
+ try {
462
+ const { sort = "new", timeFilter = "all", limit = 25 } = options;
463
+ const params = new URLSearchParams({
464
+ sort,
465
+ t: timeFilter,
466
+ limit: limit.toString()
467
+ });
468
+ const response = await this.makeRequest(`/user/${username}/submitted.json?${params}`);
469
+ if (!response.ok) {
470
+ throw new Error(`HTTP ${response.status}`);
471
+ }
472
+ const json = await response.json();
473
+ return json.data.children.filter((child) => child.kind === "t3").map((child) => {
474
+ const post = child.data;
475
+ return {
476
+ id: post.id,
477
+ title: post.title,
478
+ author: post.author,
479
+ subreddit: post.subreddit,
480
+ selftext: post.selftext || "",
481
+ url: post.url,
482
+ score: post.score,
483
+ upvoteRatio: post.upvote_ratio,
484
+ numComments: post.num_comments,
485
+ createdUtc: post.created_utc,
486
+ over18: post.over_18,
487
+ spoiler: post.spoiler,
488
+ edited: !!post.edited,
489
+ isSelf: post.is_self,
490
+ linkFlairText: post.link_flair_text,
491
+ permalink: post.permalink
492
+ };
493
+ });
494
+ } catch {
495
+ throw new Error(`Failed to get posts for user ${username}`);
496
+ }
497
+ }
498
+ async getUserComments(username, options = {}) {
499
+ await this.authenticate();
500
+ try {
501
+ const { sort = "new", timeFilter = "all", limit = 25 } = options;
502
+ const params = new URLSearchParams({
503
+ sort,
504
+ t: timeFilter,
505
+ limit: limit.toString()
506
+ });
507
+ const response = await this.makeRequest(`/user/${username}/comments.json?${params}`);
508
+ if (!response.ok) {
509
+ throw new Error(`HTTP ${response.status}`);
510
+ }
511
+ const json = await response.json();
512
+ return json.data.children.filter((child) => child.kind === "t1").map((child) => {
513
+ const comment = child.data;
514
+ return {
515
+ id: comment.id,
516
+ author: comment.author,
517
+ body: comment.body,
518
+ score: comment.score,
519
+ controversiality: comment.controversiality,
520
+ subreddit: comment.subreddit,
521
+ submissionTitle: comment.link_title || "",
522
+ createdUtc: comment.created_utc,
523
+ edited: !!comment.edited,
524
+ isSubmitter: comment.is_submitter,
525
+ permalink: comment.permalink
526
+ };
527
+ });
528
+ } catch {
529
+ throw new Error(`Failed to get comments for user ${username}`);
530
+ }
531
+ }
532
+ };
533
+ var redditClient = null;
534
+ function initializeRedditClient(config) {
535
+ redditClient = new RedditClient(config);
536
+ return redditClient;
537
+ }
538
+ function getRedditClient() {
539
+ return redditClient;
540
+ }
541
+
542
+ // src/utils/formatters.ts
543
+ function formatTimestamp(timestamp) {
544
+ try {
545
+ const date = new Date(timestamp * 1e3);
546
+ return date.toISOString().replace("T", " ").replace(/\.\d+Z$/, " UTC");
547
+ } catch {
548
+ return String(timestamp);
549
+ }
550
+ }
551
+ function analyzeUserActivity(karmaRatio, isMod, accountAgeDays) {
552
+ const insights = [];
553
+ if (karmaRatio > 5) {
554
+ insights.push("Primarily a commenter, highly engaged in discussions");
555
+ } else if (karmaRatio < 0.2) {
556
+ insights.push("Content creator, focuses on sharing posts");
557
+ } else {
558
+ insights.push("Balanced participation in both posting and commenting");
559
+ }
560
+ if (accountAgeDays < 30) {
561
+ insights.push("New user, still exploring Reddit");
562
+ } else if (accountAgeDays > 365 * 5) {
563
+ insights.push("Long-time Redditor with extensive platform experience");
564
+ }
565
+ if (isMod) {
566
+ insights.push("Community leader who helps maintain subreddit quality");
567
+ }
568
+ return insights.join("\n - ");
569
+ }
570
+ function analyzePostEngagement(score, ratio, numComments) {
571
+ const insights = [];
572
+ if (score > 1e3 && ratio > 0.95) {
573
+ insights.push("Highly successful post with strong community approval");
574
+ } else if (score > 100 && ratio > 0.8) {
575
+ insights.push("Well-received post with good engagement");
576
+ } else if (ratio < 0.5) {
577
+ insights.push("Controversial post that sparked debate");
578
+ }
579
+ if (numComments > 100) {
580
+ insights.push("Generated significant discussion");
581
+ } else if (numComments > score * 0.5) {
582
+ insights.push("Highly discussable content with active comment section");
583
+ } else if (numComments === 0) {
584
+ insights.push("Yet to receive community interaction");
585
+ }
586
+ return insights.join("\n - ");
587
+ }
588
+ function analyzeSubredditHealth(subscribers, activeUsers, ageDays) {
589
+ const insights = [];
590
+ if (subscribers > 1e6) {
591
+ insights.push("Major subreddit with massive following");
592
+ } else if (subscribers > 1e5) {
593
+ insights.push("Well-established community");
594
+ } else if (subscribers < 1e3) {
595
+ insights.push("Niche community, potential for growth");
596
+ }
597
+ if (activeUsers) {
598
+ const activityRatio = activeUsers / subscribers;
599
+ if (activityRatio > 0.1) {
600
+ insights.push("Highly active community with strong engagement");
601
+ } else if (activityRatio < 0.01) {
602
+ insights.push("Could benefit from more community engagement initiatives");
603
+ }
604
+ }
605
+ if (ageDays > 365 * 5) {
606
+ insights.push("Mature subreddit with established culture");
607
+ } else if (ageDays < 90) {
608
+ insights.push("New subreddit still forming its community");
609
+ }
610
+ return insights.join("\n - ");
611
+ }
612
+ function getUserRecommendations(karmaRatio, isMod, accountAgeDays) {
613
+ const recommendations = [];
614
+ if (karmaRatio > 5) {
615
+ recommendations.push("Consider creating more posts to share your expertise");
616
+ } else if (karmaRatio < 0.2) {
617
+ recommendations.push("Engage more in discussions to build community connections");
618
+ }
619
+ if (accountAgeDays < 30) {
620
+ recommendations.push("Explore popular subreddits in your areas of interest");
621
+ recommendations.push("Read community guidelines before posting");
622
+ }
623
+ if (isMod) {
624
+ recommendations.push("Share moderation insights with other community leaders");
625
+ }
626
+ if (!recommendations.length) {
627
+ recommendations.push("Maintain your balanced engagement across Reddit");
628
+ }
629
+ return recommendations.join("\n - ");
630
+ }
631
+ function getBestEngagementTime(createdUtc) {
632
+ const postHour = new Date(createdUtc * 1e3).getHours();
633
+ if (14 <= postHour && postHour <= 18) {
634
+ return "Posted during peak engagement hours (2 PM - 6 PM), good timing!";
635
+ } else if (23 <= postHour || postHour <= 5) {
636
+ return "Consider posting during more active hours (morning to evening)";
637
+ } else {
638
+ return "Posted during moderate activity hours, timing could be optimized";
639
+ }
640
+ }
641
+ function getSubredditEngagementTips(subreddit) {
642
+ const tips = [];
643
+ if (subreddit.subscribers > 1e6) {
644
+ tips.push("Post during peak hours for maximum visibility");
645
+ tips.push("Ensure content is highly polished due to high competition");
646
+ } else if (subreddit.subscribers < 1e3) {
647
+ tips.push("Engage actively to help grow the community");
648
+ tips.push("Consider cross-posting to related larger subreddits");
649
+ }
650
+ if (subreddit.activeUserCount) {
651
+ const activityRatio = subreddit.activeUserCount / subreddit.subscribers;
652
+ if (activityRatio > 0.1) {
653
+ tips.push("Quick responses recommended due to high activity");
654
+ }
655
+ }
656
+ return tips.length ? tips.join("\n - ") : "Regular engagement recommended to maintain community presence";
657
+ }
658
+ function analyzeCommentImpact(score, isEdited, isOp) {
659
+ const insights = [];
660
+ if (score > 100) {
661
+ insights.push("Highly upvoted comment with significant community agreement");
662
+ } else if (score < 0) {
663
+ insights.push("Controversial or contested viewpoint");
664
+ }
665
+ if (isEdited) {
666
+ insights.push("Refined for clarity or accuracy");
667
+ }
668
+ if (isOp) {
669
+ insights.push("Author's perspective adds context to original post");
670
+ }
671
+ return insights.length ? insights.join("\n - ") : "Standard engagement with discussion";
672
+ }
673
+ function formatUserInfo(user) {
674
+ const status = [];
675
+ if (user.isMod) status.push("Moderator");
676
+ if (user.isGold) status.push("Reddit Gold Member");
677
+ if (user.isEmployee) status.push("Reddit Employee");
678
+ const accountAgeDays = (Date.now() / 1e3 - user.createdUtc) / (24 * 3600);
679
+ const karmaRatio = user.commentKarma / (user.linkKarma || 1);
680
+ return {
681
+ username: user.name,
682
+ karma: {
683
+ commentKarma: user.commentKarma,
684
+ postKarma: user.linkKarma,
685
+ totalKarma: user.totalKarma
686
+ },
687
+ accountStatus: status.length ? status : ["Regular User"],
688
+ accountCreated: formatTimestamp(user.createdUtc),
689
+ profileUrl: user.profileUrl,
690
+ activityAnalysis: analyzeUserActivity(karmaRatio, user.isMod, accountAgeDays),
691
+ recommendations: getUserRecommendations(karmaRatio, user.isMod, accountAgeDays)
692
+ };
693
+ }
694
+ function formatPostInfo(post) {
695
+ const contentType = post.isSelf ? "Text Post" : "Link Post";
696
+ const content = post.isSelf ? post.selftext || "" : post.url || "";
697
+ const flags = [];
698
+ if (post.over18) flags.push("NSFW");
699
+ if (post.spoiler) flags.push("Spoiler");
700
+ if (post.edited) flags.push("Edited");
701
+ return {
702
+ title: post.title,
703
+ type: contentType,
704
+ content: content.length > 300 ? content.substring(0, 297) + "..." : content,
705
+ author: post.author,
706
+ subreddit: post.subreddit,
707
+ stats: {
708
+ score: post.score,
709
+ upvoteRatio: post.upvoteRatio,
710
+ comments: post.numComments
711
+ },
712
+ metadata: {
713
+ posted: formatTimestamp(post.createdUtc),
714
+ flags,
715
+ flair: post.linkFlairText || "None"
716
+ },
717
+ links: {
718
+ fullPost: `https://reddit.com${post.permalink}`,
719
+ shortLink: `https://redd.it/${post.id}`
720
+ },
721
+ engagementAnalysis: analyzePostEngagement(post.score, post.upvoteRatio, post.numComments),
722
+ bestTimeToEngage: getBestEngagementTime(post.createdUtc)
723
+ };
724
+ }
725
+ function formatSubredditInfo(subreddit) {
726
+ const flags = [];
727
+ if (subreddit.over18) flags.push("NSFW");
728
+ if (subreddit.subredditType) flags.push(`Type: ${subreddit.subredditType}`);
729
+ const ageDays = (Date.now() / 1e3 - subreddit.createdUtc) / (24 * 3600);
730
+ return {
731
+ name: subreddit.displayName,
732
+ title: subreddit.title,
733
+ stats: {
734
+ subscribers: subreddit.subscribers,
735
+ activeUsers: subreddit.activeUserCount !== void 0 ? subreddit.activeUserCount : "Unknown"
736
+ },
737
+ description: {
738
+ short: subreddit.publicDescription,
739
+ full: subreddit.description.length > 300 ? subreddit.description.substring(0, 297) + "..." : subreddit.description
740
+ },
741
+ metadata: {
742
+ created: formatTimestamp(subreddit.createdUtc),
743
+ flags: flags.length ? flags : ["None"]
744
+ },
745
+ links: {
746
+ subreddit: `https://reddit.com${subreddit.url}`,
747
+ wiki: `https://reddit.com/r/${subreddit.displayName}/wiki`
748
+ },
749
+ communityAnalysis: analyzeSubredditHealth(subreddit.subscribers, subreddit.activeUserCount, ageDays),
750
+ engagementTips: getSubredditEngagementTips(subreddit)
751
+ };
752
+ }
753
+ function formatCommentInfo(comment) {
754
+ const flags = [];
755
+ if (comment.edited) flags.push("Edited");
756
+ if (comment.isSubmitter) flags.push("OP");
757
+ return {
758
+ author: comment.author,
759
+ content: comment.body.length > 300 ? comment.body.substring(0, 297) + "..." : comment.body,
760
+ stats: {
761
+ score: comment.score,
762
+ controversiality: comment.controversiality
763
+ },
764
+ context: {
765
+ subreddit: comment.subreddit,
766
+ thread: comment.submissionTitle
767
+ },
768
+ metadata: {
769
+ posted: formatTimestamp(comment.createdUtc),
770
+ flags: flags.length ? flags : ["None"]
771
+ },
772
+ link: `https://reddit.com${comment.permalink}`,
773
+ commentAnalysis: analyzeCommentImpact(comment.score, comment.edited, comment.isSubmitter)
774
+ };
775
+ }
776
+ function formatPost(post) {
777
+ return {
778
+ title: post.title,
779
+ author: post.author,
780
+ subreddit: post.subreddit,
781
+ score: post.score,
782
+ upvoteRatio: Math.round(post.upvoteRatio * 100),
783
+ numComments: post.numComments,
784
+ createdAt: formatTimestamp(post.createdUtc),
785
+ selftext: post.selftext,
786
+ permalink: post.permalink,
787
+ nsfw: post.over18,
788
+ spoiler: post.spoiler
789
+ };
790
+ }
791
+
792
+ // src/tools/user-tools.ts
793
+ var import_types = require("@modelcontextprotocol/sdk/types.js");
794
+ async function getUserInfo(params) {
795
+ const { username } = params;
796
+ const client = getRedditClient();
797
+ if (!client) {
798
+ throw new import_types.McpError(import_types.ErrorCode.InternalError, "Reddit client not initialized");
799
+ }
800
+ try {
801
+ const user = await client.getUser(username);
802
+ const formattedUser = formatUserInfo(user);
803
+ return {
804
+ content: [
805
+ {
806
+ type: "text",
807
+ text: `
808
+ # User Information: u/${formattedUser.username}
809
+
810
+ ## Profile Overview
811
+ - Username: u/${formattedUser.username}
812
+ - Karma:
813
+ - Comment Karma: ${formattedUser.karma.commentKarma.toLocaleString()}
814
+ - Post Karma: ${formattedUser.karma.postKarma.toLocaleString()}
815
+ - Total Karma: ${formattedUser.karma.totalKarma.toLocaleString()}
816
+ - Account Status: ${formattedUser.accountStatus.join(", ")}
817
+ - Account Created: ${formattedUser.accountCreated}
818
+ - Profile URL: ${formattedUser.profileUrl}
819
+
820
+ ## Activity Analysis
821
+ - ${formattedUser.activityAnalysis.replace(/\n - /g, "\n- ")}
822
+
823
+ ## Recommendations
824
+ - ${formattedUser.recommendations.replace(/\n - /g, "\n- ")}
825
+ `
826
+ }
827
+ ]
828
+ };
829
+ } catch (error) {
830
+ throw new import_types.McpError(import_types.ErrorCode.InternalError, `Failed to fetch user data: ${String(error)}`);
831
+ }
832
+ }
833
+ async function getUserPosts(params) {
834
+ const { username, sort = "new", time_filter = "all", limit = 10 } = params;
835
+ const client = getRedditClient();
836
+ if (!client) {
837
+ throw new import_types.McpError(import_types.ErrorCode.InternalError, "Reddit client not initialized");
838
+ }
839
+ try {
840
+ const posts = await client.getUserPosts(username, {
841
+ sort,
842
+ timeFilter: time_filter,
843
+ limit
844
+ });
845
+ return {
846
+ content: [
847
+ {
848
+ type: "text",
849
+ text: `# Posts by u/${username}
850
+
851
+ ## Sort: ${sort} | Time: ${time_filter} | Count: ${posts.length}
852
+
853
+ ${posts.map((post, index) => {
854
+ const date = new Date(post.createdUtc * 1e3).toLocaleString();
855
+ const selftext = post.selftext ? `
856
+ ${post.selftext.substring(0, 200)}${post.selftext.length > 200 ? "..." : ""}
857
+ ` : "";
858
+ return `### ${index + 1}. ${post.title}
859
+ - Subreddit: r/${post.subreddit}
860
+ - Score: ${post.score} (${Math.round(post.upvoteRatio * 100)}% upvoted)
861
+ - Comments: ${post.numComments}
862
+ - Posted: ${date}
863
+ ${selftext}
864
+ - Link: https://reddit.com${post.permalink}
865
+ ${post.over18 ? "- **NSFW**" : ""}
866
+ ${post.spoiler ? "- **Spoiler**" : ""}`;
867
+ }).join("\n\n---\n\n")}`
868
+ }
869
+ ]
870
+ };
871
+ } catch (error) {
872
+ throw new import_types.McpError(import_types.ErrorCode.InternalError, `Failed to fetch user posts: ${String(error)}`);
873
+ }
874
+ }
875
+ async function getUserComments(params) {
876
+ const { username, sort = "new", time_filter = "all", limit = 10 } = params;
877
+ const client = getRedditClient();
878
+ if (!client) {
879
+ throw new import_types.McpError(import_types.ErrorCode.InternalError, "Reddit client not initialized");
880
+ }
881
+ try {
882
+ const comments = await client.getUserComments(username, {
883
+ sort,
884
+ timeFilter: time_filter,
885
+ limit
886
+ });
887
+ return {
888
+ content: [
889
+ {
890
+ type: "text",
891
+ text: `# Comments by u/${username}
892
+
893
+ ## Sort: ${sort} | Time: ${time_filter} | Count: ${comments.length}
894
+
895
+ ${comments.map((comment, index) => {
896
+ const date = new Date(comment.createdUtc * 1e3).toLocaleString();
897
+ const edited = comment.edited ? " *(edited)*" : "";
898
+ const body = comment.body.length > 300 ? comment.body.substring(0, 300) + "..." : comment.body;
899
+ return `### ${index + 1}. In r/${comment.subreddit} on "${comment.submissionTitle}"
900
+ - Score: ${comment.score} points
901
+ - Posted: ${date}${edited}
902
+ - Link: https://reddit.com${comment.permalink}
903
+
904
+ ${body}`;
905
+ }).join("\n\n---\n\n")}`
906
+ }
907
+ ]
908
+ };
909
+ } catch (error) {
910
+ throw new import_types.McpError(import_types.ErrorCode.InternalError, `Failed to fetch user comments: ${String(error)}`);
911
+ }
912
+ }
913
+
914
+ // src/tools/post-tools.ts
915
+ var import_types2 = require("@modelcontextprotocol/sdk/types.js");
916
+ async function getRedditPost(params) {
917
+ const { subreddit, post_id } = params;
918
+ const client = getRedditClient();
919
+ if (!client) {
920
+ throw new import_types2.McpError(import_types2.ErrorCode.InternalError, "Reddit client not initialized");
921
+ }
922
+ try {
923
+ const post = await client.getPost(post_id, subreddit);
924
+ const formattedPost = formatPostInfo(post);
925
+ return {
926
+ content: [
927
+ {
928
+ type: "text",
929
+ text: `
930
+ # Post from r/${formattedPost.subreddit}
931
+
932
+ ## Post Details
933
+ - Title: ${formattedPost.title}
934
+ - Type: ${formattedPost.type}
935
+ - Author: u/${formattedPost.author}
936
+
937
+ ## Content
938
+ ${formattedPost.content}
939
+
940
+ ## Stats
941
+ - Score: ${formattedPost.stats.score.toLocaleString()}
942
+ - Upvote Ratio: ${(formattedPost.stats.upvoteRatio * 100).toFixed(1)}%
943
+ - Comments: ${formattedPost.stats.comments.toLocaleString()}
944
+
945
+ ## Metadata
946
+ - Posted: ${formattedPost.metadata.posted}
947
+ - Flags: ${formattedPost.metadata.flags.length ? formattedPost.metadata.flags.join(", ") : "None"}
948
+ - Flair: ${formattedPost.metadata.flair}
949
+
950
+ ## Links
951
+ - Full Post: ${formattedPost.links.fullPost}
952
+ - Short Link: ${formattedPost.links.shortLink}
953
+
954
+ ## Engagement Analysis
955
+ - ${formattedPost.engagementAnalysis.replace(/\n - /g, "\n- ")}
956
+
957
+ ## Best Time to Engage
958
+ ${formattedPost.bestTimeToEngage}
959
+ `
960
+ }
961
+ ]
962
+ };
963
+ } catch (error) {
964
+ throw new import_types2.McpError(import_types2.ErrorCode.InternalError, `Failed to fetch post data: ${String(error)}`);
965
+ }
966
+ }
967
+ async function getTopPosts(params) {
968
+ const { subreddit, time_filter = "week", limit = 10 } = params;
969
+ const client = getRedditClient();
970
+ if (!client) {
971
+ throw new import_types2.McpError(import_types2.ErrorCode.InternalError, "Reddit client not initialized");
972
+ }
973
+ try {
974
+ const posts = await client.getTopPosts(subreddit, time_filter, limit);
975
+ const formattedPosts = posts.map(formatPostInfo);
976
+ const postSummaries = formattedPosts.map(
977
+ (post, index) => `
978
+ ### ${index + 1}. ${post.title}
979
+ - Author: u/${post.author}
980
+ - Score: ${post.stats.score.toLocaleString()} (${(post.stats.upvoteRatio * 100).toFixed(1)}% upvoted)
981
+ - Comments: ${post.stats.comments.toLocaleString()}
982
+ - Posted: ${post.metadata.posted}
983
+ - Link: ${post.links.shortLink}
984
+ `
985
+ ).join("\n");
986
+ return {
987
+ content: [
988
+ {
989
+ type: "text",
990
+ text: `
991
+ # Top Posts from r/${subreddit} (${time_filter})
992
+
993
+ ${postSummaries}
994
+ `
995
+ }
996
+ ]
997
+ };
998
+ } catch (error) {
999
+ throw new import_types2.McpError(import_types2.ErrorCode.InternalError, `Failed to fetch top posts: ${String(error)}`);
1000
+ }
1001
+ }
1002
+ async function createPost(params) {
1003
+ const { subreddit, title, content, is_self = true } = params;
1004
+ const client = getRedditClient();
1005
+ if (!client) {
1006
+ throw new import_types2.McpError(import_types2.ErrorCode.InternalError, "Reddit client not initialized");
1007
+ }
1008
+ try {
1009
+ const post = await client.createPost(subreddit, title, content, is_self);
1010
+ const formattedPost = formatPostInfo(post);
1011
+ return {
1012
+ content: [
1013
+ {
1014
+ type: "text",
1015
+ text: `
1016
+ # Post Created Successfully
1017
+
1018
+ ## Post Details
1019
+ - Title: ${formattedPost.title}
1020
+ - Subreddit: r/${formattedPost.subreddit}
1021
+ - Type: ${formattedPost.type}
1022
+ - Link: ${formattedPost.links.fullPost}
1023
+
1024
+ Your post has been successfully submitted to r/${formattedPost.subreddit}.
1025
+ `
1026
+ }
1027
+ ]
1028
+ };
1029
+ } catch (error) {
1030
+ throw new import_types2.McpError(import_types2.ErrorCode.InternalError, `Failed to create post: ${String(error)}`);
1031
+ }
1032
+ }
1033
+ async function replyToPost(params) {
1034
+ const { post_id, content } = params;
1035
+ const client = getRedditClient();
1036
+ if (!client) {
1037
+ throw new import_types2.McpError(import_types2.ErrorCode.InternalError, "Reddit client not initialized");
1038
+ }
1039
+ try {
1040
+ const comment = await client.replyToPost(post_id, content);
1041
+ const formattedComment = formatCommentInfo(comment);
1042
+ return {
1043
+ content: [
1044
+ {
1045
+ type: "text",
1046
+ text: `
1047
+ # Reply Posted Successfully
1048
+
1049
+ ## Comment Details
1050
+ - Author: u/${formattedComment.author}
1051
+ - Subreddit: r/${formattedComment.context.subreddit}
1052
+ - Thread: ${formattedComment.context.thread}
1053
+ - Link: ${formattedComment.link}
1054
+
1055
+ Your reply has been successfully posted.
1056
+ `
1057
+ }
1058
+ ]
1059
+ };
1060
+ } catch (error) {
1061
+ throw new import_types2.McpError(import_types2.ErrorCode.InternalError, `Failed to reply to post: ${String(error)}`);
1062
+ }
1063
+ }
1064
+
1065
+ // src/tools/subreddit-tools.ts
1066
+ var import_types3 = require("@modelcontextprotocol/sdk/types.js");
1067
+ async function getSubredditInfo(params) {
1068
+ const { subreddit_name } = params;
1069
+ const client = getRedditClient();
1070
+ if (!client) {
1071
+ throw new import_types3.McpError(import_types3.ErrorCode.InternalError, "Reddit client not initialized");
1072
+ }
1073
+ try {
1074
+ const subreddit = await client.getSubredditInfo(subreddit_name);
1075
+ const formattedSubreddit = formatSubredditInfo(subreddit);
1076
+ return {
1077
+ content: [
1078
+ {
1079
+ type: "text",
1080
+ text: `
1081
+ # Subreddit Information: r/${formattedSubreddit.name}
1082
+
1083
+ ## Overview
1084
+ - Name: r/${formattedSubreddit.name}
1085
+ - Title: ${formattedSubreddit.title}
1086
+ - Subscribers: ${formattedSubreddit.stats.subscribers.toLocaleString()}
1087
+ - Active Users: ${typeof formattedSubreddit.stats.activeUsers === "number" ? formattedSubreddit.stats.activeUsers.toLocaleString() : formattedSubreddit.stats.activeUsers}
1088
+
1089
+ ## Description
1090
+ ${formattedSubreddit.description.short}
1091
+
1092
+ ## Detailed Description
1093
+ ${formattedSubreddit.description.full}
1094
+
1095
+ ## Metadata
1096
+ - Created: ${formattedSubreddit.metadata.created}
1097
+ - Flags: ${formattedSubreddit.metadata.flags.join(", ")}
1098
+
1099
+ ## Links
1100
+ - Subreddit: ${formattedSubreddit.links.subreddit}
1101
+ - Wiki: ${formattedSubreddit.links.wiki}
1102
+
1103
+ ## Community Analysis
1104
+ - ${formattedSubreddit.communityAnalysis.replace(/\n - /g, "\n- ")}
1105
+
1106
+ ## Engagement Tips
1107
+ - ${formattedSubreddit.engagementTips.replace(/\n - /g, "\n- ")}
1108
+ `
1109
+ }
1110
+ ]
1111
+ };
1112
+ } catch (error) {
1113
+ throw new import_types3.McpError(import_types3.ErrorCode.InternalError, `Failed to fetch subreddit data: ${String(error)}`);
1114
+ }
1115
+ }
1116
+ async function getTrendingSubreddits() {
1117
+ const client = getRedditClient();
1118
+ if (!client) {
1119
+ throw new import_types3.McpError(import_types3.ErrorCode.InternalError, "Reddit client not initialized");
1120
+ }
1121
+ try {
1122
+ const trendingSubreddits = await client.getTrendingSubreddits();
1123
+ return {
1124
+ content: [
1125
+ {
1126
+ type: "text",
1127
+ text: `
1128
+ # Trending Subreddits
1129
+
1130
+ ${trendingSubreddits.map((subreddit, index) => `${index + 1}. r/${subreddit}`).join("\n")}
1131
+ `
1132
+ }
1133
+ ]
1134
+ };
1135
+ } catch (error) {
1136
+ throw new import_types3.McpError(import_types3.ErrorCode.InternalError, `Failed to fetch trending subreddits: ${String(error)}`);
1137
+ }
1138
+ }
1139
+
1140
+ // src/tools/search-tools.ts
1141
+ var import_types4 = require("@modelcontextprotocol/sdk/types.js");
1142
+ async function searchReddit(params) {
1143
+ const { query, subreddit, sort = "relevance", time_filter = "all", limit = 10, type = "link" } = params;
1144
+ const client = getRedditClient();
1145
+ if (!client) {
1146
+ throw new import_types4.McpError(import_types4.ErrorCode.InternalError, "Reddit client not initialized");
1147
+ }
1148
+ if (!query || query.trim().length === 0) {
1149
+ throw new import_types4.McpError(import_types4.ErrorCode.InvalidParams, "Search query cannot be empty");
1150
+ }
1151
+ try {
1152
+ const posts = await client.searchReddit(query, {
1153
+ subreddit,
1154
+ sort,
1155
+ timeFilter: time_filter,
1156
+ limit,
1157
+ type
1158
+ });
1159
+ return {
1160
+ content: [
1161
+ {
1162
+ type: "text",
1163
+ text: `# Reddit Search Results for: "${query}"${subreddit ? ` in r/${subreddit}` : ""}
1164
+
1165
+ ## Search Parameters
1166
+ - Sort: ${sort}
1167
+ - Time Filter: ${time_filter}
1168
+ - Type: ${type}
1169
+ - Results: ${posts.length}
1170
+
1171
+ ${posts.map((post, index) => {
1172
+ const formatted = formatPost(post);
1173
+ return `### ${index + 1}. ${formatted.title}
1174
+ - Author: u/${formatted.author}
1175
+ - Subreddit: r/${formatted.subreddit}
1176
+ - Score: ${formatted.score} (${formatted.upvoteRatio}% upvoted)
1177
+ - Comments: ${formatted.numComments}
1178
+ - Posted: ${formatted.createdAt}
1179
+ ${formatted.selftext ? `
1180
+ ${formatted.selftext.substring(0, 200)}${formatted.selftext.length > 200 ? "..." : ""}
1181
+ ` : ""}
1182
+ - Link: https://reddit.com${formatted.permalink}
1183
+ ${formatted.nsfw ? "- **NSFW**" : ""}
1184
+ ${formatted.spoiler ? "- **Spoiler**" : ""}
1185
+ `;
1186
+ }).join("\n")}`
1187
+ }
1188
+ ]
1189
+ };
1190
+ } catch (error) {
1191
+ throw new import_types4.McpError(import_types4.ErrorCode.InternalError, `Failed to search Reddit: ${String(error)}`);
1192
+ }
1193
+ }
1194
+
1195
+ // src/tools/comment-tools.ts
1196
+ var import_types5 = require("@modelcontextprotocol/sdk/types.js");
1197
+ async function getPostComments(params) {
1198
+ const { post_id, subreddit, sort = "best", limit = 100 } = params;
1199
+ const client = getRedditClient();
1200
+ if (!client) {
1201
+ throw new import_types5.McpError(import_types5.ErrorCode.InternalError, "Reddit client not initialized");
1202
+ }
1203
+ if (!post_id || !subreddit) {
1204
+ throw new import_types5.McpError(import_types5.ErrorCode.InvalidParams, "post_id and subreddit are required");
1205
+ }
1206
+ try {
1207
+ const { post, comments } = await client.getPostComments(post_id, subreddit, {
1208
+ sort,
1209
+ limit
1210
+ });
1211
+ const formattedPost = formatPost(post);
1212
+ const formatComment = (comment) => {
1213
+ const edited = comment.edited ? " *(edited)*" : "";
1214
+ const submitter = comment.isSubmitter ? " **[OP]**" : "";
1215
+ const depth = comment.depth || 0;
1216
+ const prefix = " ".repeat(depth) + (depth > 0 ? "\u2514\u2500 " : "");
1217
+ return `${prefix}**u/${comment.author}**${submitter} \u2022 ${comment.score} points \u2022 ${new Date(comment.createdUtc * 1e3).toLocaleString()}${edited}
1218
+ ${prefix}${comment.body.split("\n").join(`
1219
+ ${prefix}`)}`;
1220
+ };
1221
+ return {
1222
+ content: [
1223
+ {
1224
+ type: "text",
1225
+ text: `# Comments for: ${formattedPost.title}
1226
+
1227
+ ## Post Details
1228
+ - Author: u/${formattedPost.author}
1229
+ - Subreddit: r/${formattedPost.subreddit}
1230
+ - Score: ${formattedPost.score} (${formattedPost.upvoteRatio}% upvoted)
1231
+ - Posted: ${formattedPost.createdAt}
1232
+ - Link: https://reddit.com${formattedPost.permalink}
1233
+
1234
+ ## Post Content
1235
+ ${formattedPost.selftext || "[Link post - no text content]"}
1236
+
1237
+ ## Comments (${comments.length} loaded, sorted by ${sort})
1238
+
1239
+ ${comments.map((comment) => formatComment(comment)).join("\n\n---\n\n")}`
1240
+ }
1241
+ ]
1242
+ };
1243
+ } catch (error) {
1244
+ throw new import_types5.McpError(import_types5.ErrorCode.InternalError, `Failed to fetch comments: ${String(error)}`);
1245
+ }
1246
+ }
1247
+
1248
+ // src/index.ts
1249
+ var import_dotenv = __toESM(require("dotenv"));
1250
+ import_dotenv.default.config();
1251
+ var RedditServer = class {
1252
+ server;
1253
+ constructor() {
1254
+ this.server = new import_server.Server(
1255
+ {
1256
+ name: "reddit-mcp-server",
1257
+ version: "0.1.0"
1258
+ },
1259
+ {
1260
+ capabilities: {
1261
+ tools: {}
1262
+ }
1263
+ }
1264
+ );
1265
+ this.initializeRedditClient();
1266
+ this.setupToolHandlers();
1267
+ this.server.onerror = async (error) => {
1268
+ await this.server.sendLoggingMessage({
1269
+ level: "error",
1270
+ logger: "reddit-server",
1271
+ data: `Server error: ${error}`
1272
+ });
1273
+ };
1274
+ process.on("SIGINT", async () => {
1275
+ await this.server.close();
1276
+ process.exit(0);
1277
+ });
1278
+ }
1279
+ initializeRedditClient() {
1280
+ const clientId = process.env.REDDIT_CLIENT_ID;
1281
+ const clientSecret = process.env.REDDIT_CLIENT_SECRET;
1282
+ const userAgent = process.env.REDDIT_USER_AGENT || "RedditMCPServer/0.1.0";
1283
+ const username = process.env.REDDIT_USERNAME;
1284
+ const password = process.env.REDDIT_PASSWORD;
1285
+ if (!clientId || !clientSecret) {
1286
+ process.exit(1);
1287
+ }
1288
+ try {
1289
+ initializeRedditClient({
1290
+ clientId,
1291
+ clientSecret,
1292
+ userAgent,
1293
+ username,
1294
+ password
1295
+ });
1296
+ } catch {
1297
+ process.exit(1);
1298
+ }
1299
+ }
1300
+ setupToolHandlers() {
1301
+ this.server.setRequestHandler(import_types6.ListToolsRequestSchema, async () => ({
1302
+ tools: [
1303
+ {
1304
+ name: "test_reddit_mcp_server",
1305
+ description: "Test the Reddit MCP Server",
1306
+ inputSchema: {
1307
+ type: "object",
1308
+ properties: {
1309
+ // No input parameters, this will just return a test message
1310
+ }
1311
+ }
1312
+ },
1313
+ {
1314
+ name: "get_reddit_post",
1315
+ description: "Get a Reddit post",
1316
+ inputSchema: {
1317
+ type: "object",
1318
+ properties: {
1319
+ subreddit: {
1320
+ type: "string",
1321
+ description: "The subreddit to fetch posts from"
1322
+ },
1323
+ post_id: {
1324
+ type: "string",
1325
+ description: "The ID of the post to fetch"
1326
+ }
1327
+ },
1328
+ required: ["subreddit", "post_id"]
1329
+ }
1330
+ },
1331
+ {
1332
+ name: "get_top_posts",
1333
+ description: "Get top posts from a subreddit",
1334
+ inputSchema: {
1335
+ type: "object",
1336
+ properties: {
1337
+ subreddit: {
1338
+ type: "string",
1339
+ description: "Name of the subreddit"
1340
+ },
1341
+ time_filter: {
1342
+ type: "string",
1343
+ description: "Time period to filter posts (e.g. 'day', 'week', 'month', 'year', 'all')",
1344
+ enum: ["day", "week", "month", "year", "all"],
1345
+ default: "week"
1346
+ },
1347
+ limit: {
1348
+ type: "integer",
1349
+ description: "Number of posts to fetch",
1350
+ default: 10
1351
+ }
1352
+ },
1353
+ required: ["subreddit"]
1354
+ }
1355
+ },
1356
+ {
1357
+ name: "get_user_info",
1358
+ description: "Get information about a Reddit user",
1359
+ inputSchema: {
1360
+ type: "object",
1361
+ properties: {
1362
+ username: {
1363
+ type: "string",
1364
+ description: "The username of the Reddit user to get info for"
1365
+ }
1366
+ },
1367
+ required: ["username"]
1368
+ }
1369
+ },
1370
+ {
1371
+ name: "get_subreddit_info",
1372
+ description: "Get information about a subreddit",
1373
+ inputSchema: {
1374
+ type: "object",
1375
+ properties: {
1376
+ subreddit_name: {
1377
+ type: "string",
1378
+ description: "Name of the subreddit"
1379
+ }
1380
+ },
1381
+ required: ["subreddit_name"]
1382
+ }
1383
+ },
1384
+ {
1385
+ name: "get_trending_subreddits",
1386
+ description: "Get currently trending subreddits",
1387
+ inputSchema: {
1388
+ type: "object",
1389
+ properties: {}
1390
+ }
1391
+ },
1392
+ {
1393
+ name: "create_post",
1394
+ description: "Create a new post in a subreddit",
1395
+ inputSchema: {
1396
+ type: "object",
1397
+ properties: {
1398
+ subreddit: {
1399
+ type: "string",
1400
+ description: "Name of the subreddit to post in"
1401
+ },
1402
+ title: {
1403
+ type: "string",
1404
+ description: "Title of the post"
1405
+ },
1406
+ content: {
1407
+ type: "string",
1408
+ description: "Content of the post (text for self posts, URL for link posts)"
1409
+ },
1410
+ is_self: {
1411
+ type: "boolean",
1412
+ description: "Whether this is a self (text) post (true) or link post (false)",
1413
+ default: true
1414
+ }
1415
+ },
1416
+ required: ["subreddit", "title", "content"]
1417
+ }
1418
+ },
1419
+ {
1420
+ name: "reply_to_post",
1421
+ description: "Post a reply to an existing Reddit post",
1422
+ inputSchema: {
1423
+ type: "object",
1424
+ properties: {
1425
+ post_id: {
1426
+ type: "string",
1427
+ description: "The ID of the post to reply to"
1428
+ },
1429
+ content: {
1430
+ type: "string",
1431
+ description: "The content of the reply"
1432
+ },
1433
+ subreddit: {
1434
+ type: "string",
1435
+ description: "The subreddit name if known (for validation)"
1436
+ }
1437
+ },
1438
+ required: ["post_id", "content"]
1439
+ }
1440
+ },
1441
+ {
1442
+ name: "search_reddit",
1443
+ description: "Search for posts on Reddit",
1444
+ inputSchema: {
1445
+ type: "object",
1446
+ properties: {
1447
+ query: {
1448
+ type: "string",
1449
+ description: "The search query"
1450
+ },
1451
+ subreddit: {
1452
+ type: "string",
1453
+ description: "Search within a specific subreddit (optional)"
1454
+ },
1455
+ sort: {
1456
+ type: "string",
1457
+ description: "Sort order: relevance, hot, top, new, comments",
1458
+ enum: ["relevance", "hot", "top", "new", "comments"],
1459
+ default: "relevance"
1460
+ },
1461
+ time_filter: {
1462
+ type: "string",
1463
+ description: "Time filter: hour, day, week, month, year, all",
1464
+ enum: ["hour", "day", "week", "month", "year", "all"],
1465
+ default: "all"
1466
+ },
1467
+ limit: {
1468
+ type: "number",
1469
+ description: "Maximum number of results to return",
1470
+ minimum: 1,
1471
+ maximum: 100,
1472
+ default: 10
1473
+ },
1474
+ type: {
1475
+ type: "string",
1476
+ description: "Type of content: link (posts), sr (subreddits), user (users)",
1477
+ enum: ["link", "sr", "user"],
1478
+ default: "link"
1479
+ }
1480
+ },
1481
+ required: ["query"]
1482
+ }
1483
+ },
1484
+ {
1485
+ name: "get_post_comments",
1486
+ description: "Get comments for a specific Reddit post",
1487
+ inputSchema: {
1488
+ type: "object",
1489
+ properties: {
1490
+ post_id: {
1491
+ type: "string",
1492
+ description: "The ID of the post"
1493
+ },
1494
+ subreddit: {
1495
+ type: "string",
1496
+ description: "The subreddit where the post is located"
1497
+ },
1498
+ sort: {
1499
+ type: "string",
1500
+ description: "Comment sort order: best, top, new, controversial, old, qa",
1501
+ enum: ["best", "top", "new", "controversial", "old", "qa"],
1502
+ default: "best"
1503
+ },
1504
+ limit: {
1505
+ type: "number",
1506
+ description: "Maximum number of comments to load",
1507
+ minimum: 1,
1508
+ maximum: 500,
1509
+ default: 100
1510
+ }
1511
+ },
1512
+ required: ["post_id", "subreddit"]
1513
+ }
1514
+ },
1515
+ {
1516
+ name: "get_user_posts",
1517
+ description: "Get posts submitted by a specific user",
1518
+ inputSchema: {
1519
+ type: "object",
1520
+ properties: {
1521
+ username: {
1522
+ type: "string",
1523
+ description: "The username to get posts for"
1524
+ },
1525
+ sort: {
1526
+ type: "string",
1527
+ description: "Sort order: new, hot, top, controversial",
1528
+ enum: ["new", "hot", "top", "controversial"],
1529
+ default: "new"
1530
+ },
1531
+ time_filter: {
1532
+ type: "string",
1533
+ description: "Time filter for top/controversial: hour, day, week, month, year, all",
1534
+ enum: ["hour", "day", "week", "month", "year", "all"],
1535
+ default: "all"
1536
+ },
1537
+ limit: {
1538
+ type: "number",
1539
+ description: "Maximum number of posts to return",
1540
+ minimum: 1,
1541
+ maximum: 100,
1542
+ default: 10
1543
+ }
1544
+ },
1545
+ required: ["username"]
1546
+ }
1547
+ },
1548
+ {
1549
+ name: "get_user_comments",
1550
+ description: "Get comments made by a specific user",
1551
+ inputSchema: {
1552
+ type: "object",
1553
+ properties: {
1554
+ username: {
1555
+ type: "string",
1556
+ description: "The username to get comments for"
1557
+ },
1558
+ sort: {
1559
+ type: "string",
1560
+ description: "Sort order: new, hot, top, controversial",
1561
+ enum: ["new", "hot", "top", "controversial"],
1562
+ default: "new"
1563
+ },
1564
+ time_filter: {
1565
+ type: "string",
1566
+ description: "Time filter for top/controversial: hour, day, week, month, year, all",
1567
+ enum: ["hour", "day", "week", "month", "year", "all"],
1568
+ default: "all"
1569
+ },
1570
+ limit: {
1571
+ type: "number",
1572
+ description: "Maximum number of comments to return",
1573
+ minimum: 1,
1574
+ maximum: 100,
1575
+ default: 10
1576
+ }
1577
+ },
1578
+ required: ["username"]
1579
+ }
1580
+ }
1581
+ ]
1582
+ }));
1583
+ this.server.setRequestHandler(import_types6.CallToolRequestSchema, async (request) => {
1584
+ try {
1585
+ const toolName = request.params.name;
1586
+ const toolParams = request.params.arguments || {};
1587
+ await this.server.sendLoggingMessage({
1588
+ level: "debug",
1589
+ logger: "reddit-server",
1590
+ data: `Tool call: ${toolName}`
1591
+ });
1592
+ switch (toolName) {
1593
+ case "test_reddit_mcp_server":
1594
+ return {
1595
+ content: [
1596
+ {
1597
+ type: "text",
1598
+ text: "Hello, world! The Reddit MCP Server is working correctly."
1599
+ }
1600
+ ]
1601
+ };
1602
+ case "get_reddit_post":
1603
+ return await getRedditPost(toolParams);
1604
+ case "get_top_posts":
1605
+ return await getTopPosts(
1606
+ toolParams
1607
+ );
1608
+ case "get_user_info":
1609
+ return await getUserInfo(toolParams);
1610
+ case "get_subreddit_info":
1611
+ return await getSubredditInfo(toolParams);
1612
+ case "get_trending_subreddits":
1613
+ return await getTrendingSubreddits();
1614
+ case "create_post":
1615
+ return await createPost(
1616
+ toolParams
1617
+ );
1618
+ case "reply_to_post":
1619
+ return await replyToPost(
1620
+ toolParams
1621
+ );
1622
+ case "search_reddit":
1623
+ return await searchReddit(
1624
+ toolParams
1625
+ );
1626
+ case "get_post_comments":
1627
+ return await getPostComments(
1628
+ toolParams
1629
+ );
1630
+ case "get_user_posts":
1631
+ return await getUserPosts(
1632
+ toolParams
1633
+ );
1634
+ case "get_user_comments":
1635
+ return await getUserComments(
1636
+ toolParams
1637
+ );
1638
+ default:
1639
+ throw new import_types6.McpError(import_types6.ErrorCode.MethodNotFound, `Tool with name ${toolName} not found`);
1640
+ }
1641
+ } catch (error) {
1642
+ if (error instanceof Error) {
1643
+ await this.server.sendLoggingMessage({
1644
+ level: "error",
1645
+ logger: "reddit-server",
1646
+ data: `Error calling tool: ${error.message}`
1647
+ });
1648
+ throw new import_types6.McpError(import_types6.ErrorCode.InternalError, `Failed to fetch data: ${error.message}`);
1649
+ }
1650
+ throw error;
1651
+ }
1652
+ });
1653
+ }
1654
+ async run() {
1655
+ const transport = new import_stdio.StdioServerTransport();
1656
+ await this.server.connect(transport);
1657
+ await this.server.sendLoggingMessage({
1658
+ level: "info",
1659
+ logger: "reddit-server",
1660
+ data: "Reddit MCP Server is running"
1661
+ });
1662
+ const username = process.env.REDDIT_USERNAME;
1663
+ const password = process.env.REDDIT_PASSWORD;
1664
+ await this.server.sendLoggingMessage({
1665
+ level: "info",
1666
+ logger: "reddit-server",
1667
+ data: username && password ? `Authenticated as user: ${username}` : "Running in read-only mode (no user authentication)"
1668
+ });
1669
+ }
1670
+ };
1671
+ if (require.main === module) {
1672
+ const server2 = new RedditServer();
1673
+ server2.run().catch(() => {
1674
+ process.exit(1);
1675
+ });
1676
+ }
1677
+
1678
+ // src/bin.ts
1679
+ var server = new RedditServer();
1680
+ server.run().catch(console.error);