reddit-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/dist/index.js ADDED
@@ -0,0 +1,1200 @@
1
+ #!/usr/bin/env node
2
+ #!/usr/bin/env node
3
+ "use strict";
4
+ var __create = Object.create;
5
+ var __defProp = Object.defineProperty;
6
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
7
+ var __getOwnPropNames = Object.getOwnPropertyNames;
8
+ var __getProtoOf = Object.getPrototypeOf;
9
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
19
+ // If the importer is in node compatibility mode or this is not an ESM
20
+ // file that has been converted to a CommonJS file using a Babel-
21
+ // compatible transform (i.e. "__esModule" has not been set), then set
22
+ // "default" to the CommonJS "module.exports" for node compatibility.
23
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
24
+ mod
25
+ ));
26
+
27
+ // src/index.ts
28
+ var import_server = require("@modelcontextprotocol/sdk/server/index.js");
29
+ var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
30
+ var import_types4 = require("@modelcontextprotocol/sdk/types.js");
31
+
32
+ // src/client/reddit-client.ts
33
+ var import_axios = __toESM(require("axios"));
34
+ var RedditClient = class {
35
+ clientId;
36
+ clientSecret;
37
+ userAgent;
38
+ username;
39
+ password;
40
+ accessToken;
41
+ tokenExpiry = 0;
42
+ api;
43
+ authenticated = false;
44
+ constructor(config) {
45
+ this.clientId = config.clientId;
46
+ this.clientSecret = config.clientSecret;
47
+ this.userAgent = config.userAgent;
48
+ this.username = config.username;
49
+ this.password = config.password;
50
+ this.api = import_axios.default.create({
51
+ baseURL: "https://oauth.reddit.com",
52
+ headers: {
53
+ "User-Agent": this.userAgent
54
+ }
55
+ });
56
+ this.api.interceptors.response.use(
57
+ (response) => response,
58
+ async (error) => {
59
+ if (error.response?.status === 401 && this.authenticated) {
60
+ await this.authenticate();
61
+ const originalRequest = error.config;
62
+ originalRequest.headers["Authorization"] = `Bearer ${this.accessToken}`;
63
+ return this.api(originalRequest);
64
+ }
65
+ return Promise.reject(error);
66
+ }
67
+ );
68
+ }
69
+ async authenticate() {
70
+ try {
71
+ const now = Date.now();
72
+ if (this.accessToken && now < this.tokenExpiry) {
73
+ return;
74
+ }
75
+ const authUrl = "https://www.reddit.com/api/v1/access_token";
76
+ const authData = new URLSearchParams();
77
+ if (this.username && this.password) {
78
+ console.log(
79
+ `[Auth] Authenticating with user credentials for ${this.username}`
80
+ );
81
+ authData.append("grant_type", "password");
82
+ authData.append("username", this.username);
83
+ authData.append("password", this.password);
84
+ } else {
85
+ console.log(
86
+ "[Auth] Authenticating with client credentials (read-only)"
87
+ );
88
+ authData.append("grant_type", "client_credentials");
89
+ }
90
+ const response = await import_axios.default.post(authUrl, authData, {
91
+ auth: {
92
+ username: this.clientId,
93
+ password: this.clientSecret
94
+ },
95
+ headers: {
96
+ "User-Agent": this.userAgent,
97
+ "Content-Type": "application/x-www-form-urlencoded"
98
+ }
99
+ });
100
+ this.accessToken = response.data.access_token;
101
+ this.tokenExpiry = now + response.data.expires_in * 1e3;
102
+ this.authenticated = true;
103
+ this.api.defaults.headers.common["Authorization"] = `Bearer ${this.accessToken}`;
104
+ console.log("[Auth] Successfully authenticated with Reddit API");
105
+ } catch (error) {
106
+ console.error("[Auth] Authentication error:", error);
107
+ throw new Error("Failed to authenticate with Reddit API");
108
+ }
109
+ }
110
+ async checkAuthentication() {
111
+ if (!this.authenticated) {
112
+ try {
113
+ await this.authenticate();
114
+ return true;
115
+ } catch (error) {
116
+ return false;
117
+ }
118
+ }
119
+ return true;
120
+ }
121
+ async getUser(username) {
122
+ await this.authenticate();
123
+ try {
124
+ const response = await this.api.get(`/user/${username}/about.json`);
125
+ const data = response.data.data;
126
+ return {
127
+ name: data.name,
128
+ id: data.id,
129
+ commentKarma: data.comment_karma,
130
+ linkKarma: data.link_karma,
131
+ totalKarma: data.total_karma || data.comment_karma + data.link_karma,
132
+ isMod: data.is_mod,
133
+ isGold: data.is_gold,
134
+ isEmployee: data.is_employee,
135
+ createdUtc: data.created_utc,
136
+ profileUrl: `https://reddit.com/user/${data.name}`
137
+ };
138
+ } catch (error) {
139
+ console.error(`[Error] Failed to get user info for ${username}:`, error);
140
+ throw new Error(`Failed to get user info for ${username}`);
141
+ }
142
+ }
143
+ async getSubredditInfo(subredditName) {
144
+ await this.authenticate();
145
+ try {
146
+ const response = await this.api.get(`/r/${subredditName}/about.json`);
147
+ const data = response.data.data;
148
+ return {
149
+ displayName: data.display_name,
150
+ title: data.title,
151
+ description: data.description || "",
152
+ publicDescription: data.public_description || "",
153
+ subscribers: data.subscribers,
154
+ activeUserCount: data.active_user_count,
155
+ createdUtc: data.created_utc,
156
+ over18: data.over18,
157
+ subredditType: data.subreddit_type,
158
+ url: data.url
159
+ };
160
+ } catch (error) {
161
+ console.error(
162
+ `[Error] Failed to get subreddit info for ${subredditName}:`,
163
+ error
164
+ );
165
+ throw new Error(`Failed to get subreddit info for ${subredditName}`);
166
+ }
167
+ }
168
+ async getTopPosts(subreddit, timeFilter = "week", limit = 10) {
169
+ await this.authenticate();
170
+ try {
171
+ const endpoint = subreddit ? `/r/${subreddit}/top.json` : "/top.json";
172
+ const response = await this.api.get(endpoint, {
173
+ params: {
174
+ t: timeFilter,
175
+ limit
176
+ }
177
+ });
178
+ return response.data.data.children.map((child) => {
179
+ const post = child.data;
180
+ return {
181
+ id: post.id,
182
+ title: post.title,
183
+ author: post.author,
184
+ subreddit: post.subreddit,
185
+ selftext: post.selftext,
186
+ url: post.url,
187
+ score: post.score,
188
+ upvoteRatio: post.upvote_ratio,
189
+ numComments: post.num_comments,
190
+ createdUtc: post.created_utc,
191
+ over18: post.over_18,
192
+ spoiler: post.spoiler,
193
+ edited: !!post.edited,
194
+ isSelf: post.is_self,
195
+ linkFlairText: post.link_flair_text,
196
+ permalink: post.permalink
197
+ };
198
+ });
199
+ } catch (error) {
200
+ console.error(
201
+ `[Error] Failed to get top posts for ${subreddit || "home"}:`,
202
+ error
203
+ );
204
+ throw new Error(`Failed to get top posts for ${subreddit || "home"}`);
205
+ }
206
+ }
207
+ async getPost(postId, subreddit) {
208
+ await this.authenticate();
209
+ try {
210
+ const endpoint = subreddit ? `/r/${subreddit}/comments/${postId}.json` : `/api/info.json?id=t3_${postId}`;
211
+ const response = await this.api.get(endpoint);
212
+ let post;
213
+ if (subreddit) {
214
+ post = response.data[0].data.children[0].data;
215
+ } else {
216
+ if (!response.data.data.children.length) {
217
+ throw new Error(`Post with ID ${postId} not found`);
218
+ }
219
+ post = response.data.data.children[0].data;
220
+ }
221
+ return {
222
+ id: post.id,
223
+ title: post.title,
224
+ author: post.author,
225
+ subreddit: post.subreddit,
226
+ selftext: post.selftext,
227
+ url: post.url,
228
+ score: post.score,
229
+ upvoteRatio: post.upvote_ratio,
230
+ numComments: post.num_comments,
231
+ createdUtc: post.created_utc,
232
+ over18: post.over_18,
233
+ spoiler: post.spoiler,
234
+ edited: !!post.edited,
235
+ isSelf: post.is_self,
236
+ linkFlairText: post.link_flair_text,
237
+ permalink: post.permalink
238
+ };
239
+ } catch (error) {
240
+ console.error(`[Error] Failed to get post with ID ${postId}:`, error);
241
+ throw new Error(`Failed to get post with ID ${postId}`);
242
+ }
243
+ }
244
+ async getTrendingSubreddits(limit = 5) {
245
+ await this.authenticate();
246
+ try {
247
+ const response = await this.api.get("/subreddits/popular.json", {
248
+ params: { limit }
249
+ });
250
+ return response.data.data.children.map(
251
+ (child) => child.data.display_name
252
+ );
253
+ } catch (error) {
254
+ console.error("[Error] Failed to get trending subreddits:", error);
255
+ throw new Error("Failed to get trending subreddits");
256
+ }
257
+ }
258
+ async createPost(subreddit, title, content, isSelf = true) {
259
+ await this.authenticate();
260
+ if (!this.username || !this.password) {
261
+ throw new Error("User authentication required for posting");
262
+ }
263
+ try {
264
+ const kind = isSelf ? "self" : "link";
265
+ const params = new URLSearchParams();
266
+ params.append("sr", subreddit);
267
+ params.append("kind", kind);
268
+ params.append("title", title);
269
+ params.append(isSelf ? "text" : "url", content);
270
+ const response = await this.api.post("/api/submit", params, {
271
+ headers: {
272
+ "Content-Type": "application/x-www-form-urlencoded"
273
+ }
274
+ });
275
+ if (response.data.success) {
276
+ const postId = response.data.data.id;
277
+ return await this.getPost(postId);
278
+ } else {
279
+ throw new Error("Failed to create post");
280
+ }
281
+ } catch (error) {
282
+ console.error(`[Error] Failed to create post in ${subreddit}:`, error);
283
+ throw new Error(`Failed to create post in ${subreddit}`);
284
+ }
285
+ }
286
+ async checkPostExists(postId) {
287
+ await this.authenticate();
288
+ try {
289
+ const response = await this.api.get(`/api/info.json?id=t3_${postId}`);
290
+ return response.data.data.children.length > 0;
291
+ } catch (error) {
292
+ return false;
293
+ }
294
+ }
295
+ async replyToPost(postId, content) {
296
+ await this.authenticate();
297
+ if (!this.username || !this.password) {
298
+ throw new Error("User authentication required for posting replies");
299
+ }
300
+ try {
301
+ if (!await this.checkPostExists(postId)) {
302
+ throw new Error(
303
+ `Post with ID ${postId} does not exist or is not accessible`
304
+ );
305
+ }
306
+ const params = new URLSearchParams();
307
+ params.append("thing_id", `t3_${postId}`);
308
+ params.append("text", content);
309
+ const response = await this.api.post("/api/comment", params, {
310
+ headers: {
311
+ "Content-Type": "application/x-www-form-urlencoded"
312
+ }
313
+ });
314
+ const commentData = response.data;
315
+ return {
316
+ id: commentData.id,
317
+ author: this.username,
318
+ body: content,
319
+ score: 1,
320
+ controversiality: 0,
321
+ subreddit: commentData.subreddit,
322
+ submissionTitle: commentData.link_title,
323
+ createdUtc: Date.now() / 1e3,
324
+ edited: false,
325
+ isSubmitter: false,
326
+ permalink: commentData.permalink
327
+ };
328
+ } catch (error) {
329
+ console.error(`[Error] Failed to reply to post ${postId}:`, error);
330
+ throw new Error(`Failed to reply to post ${postId}`);
331
+ }
332
+ }
333
+ };
334
+ var redditClient = null;
335
+ function initializeRedditClient(config) {
336
+ redditClient = new RedditClient(config);
337
+ return redditClient;
338
+ }
339
+ function getRedditClient() {
340
+ return redditClient;
341
+ }
342
+
343
+ // src/utils/formatters.ts
344
+ function formatTimestamp(timestamp) {
345
+ try {
346
+ const date = new Date(timestamp * 1e3);
347
+ return date.toISOString().replace("T", " ").replace(/\.\d+Z$/, " UTC");
348
+ } catch {
349
+ return String(timestamp);
350
+ }
351
+ }
352
+ function analyzeUserActivity(karmaRatio, isMod, accountAgeDays) {
353
+ const insights = [];
354
+ if (karmaRatio > 5) {
355
+ insights.push("Primarily a commenter, highly engaged in discussions");
356
+ } else if (karmaRatio < 0.2) {
357
+ insights.push("Content creator, focuses on sharing posts");
358
+ } else {
359
+ insights.push("Balanced participation in both posting and commenting");
360
+ }
361
+ if (accountAgeDays < 30) {
362
+ insights.push("New user, still exploring Reddit");
363
+ } else if (accountAgeDays > 365 * 5) {
364
+ insights.push("Long-time Redditor with extensive platform experience");
365
+ }
366
+ if (isMod) {
367
+ insights.push("Community leader who helps maintain subreddit quality");
368
+ }
369
+ return insights.join("\n - ");
370
+ }
371
+ function analyzePostEngagement(score, ratio, numComments) {
372
+ const insights = [];
373
+ if (score > 1e3 && ratio > 0.95) {
374
+ insights.push("Highly successful post with strong community approval");
375
+ } else if (score > 100 && ratio > 0.8) {
376
+ insights.push("Well-received post with good engagement");
377
+ } else if (ratio < 0.5) {
378
+ insights.push("Controversial post that sparked debate");
379
+ }
380
+ if (numComments > 100) {
381
+ insights.push("Generated significant discussion");
382
+ } else if (numComments > score * 0.5) {
383
+ insights.push("Highly discussable content with active comment section");
384
+ } else if (numComments === 0) {
385
+ insights.push("Yet to receive community interaction");
386
+ }
387
+ return insights.join("\n - ");
388
+ }
389
+ function analyzeSubredditHealth(subscribers, activeUsers, ageDays) {
390
+ const insights = [];
391
+ if (subscribers > 1e6) {
392
+ insights.push("Major subreddit with massive following");
393
+ } else if (subscribers > 1e5) {
394
+ insights.push("Well-established community");
395
+ } else if (subscribers < 1e3) {
396
+ insights.push("Niche community, potential for growth");
397
+ }
398
+ if (activeUsers) {
399
+ const activityRatio = activeUsers / subscribers;
400
+ if (activityRatio > 0.1) {
401
+ insights.push("Highly active community with strong engagement");
402
+ } else if (activityRatio < 0.01) {
403
+ insights.push("Could benefit from more community engagement initiatives");
404
+ }
405
+ }
406
+ if (ageDays > 365 * 5) {
407
+ insights.push("Mature subreddit with established culture");
408
+ } else if (ageDays < 90) {
409
+ insights.push("New subreddit still forming its community");
410
+ }
411
+ return insights.join("\n - ");
412
+ }
413
+ function getUserRecommendations(karmaRatio, isMod, accountAgeDays) {
414
+ const recommendations = [];
415
+ if (karmaRatio > 5) {
416
+ recommendations.push(
417
+ "Consider creating more posts to share your expertise"
418
+ );
419
+ } else if (karmaRatio < 0.2) {
420
+ recommendations.push(
421
+ "Engage more in discussions to build community connections"
422
+ );
423
+ }
424
+ if (accountAgeDays < 30) {
425
+ recommendations.push(
426
+ "Explore popular subreddits in your areas of interest"
427
+ );
428
+ recommendations.push("Read community guidelines before posting");
429
+ }
430
+ if (isMod) {
431
+ recommendations.push(
432
+ "Share moderation insights with other community leaders"
433
+ );
434
+ }
435
+ if (!recommendations.length) {
436
+ recommendations.push("Maintain your balanced engagement across Reddit");
437
+ }
438
+ return recommendations.join("\n - ");
439
+ }
440
+ function getBestEngagementTime(createdUtc) {
441
+ const postHour = new Date(createdUtc * 1e3).getHours();
442
+ if (14 <= postHour && postHour <= 18) {
443
+ return "Posted during peak engagement hours (2 PM - 6 PM), good timing!";
444
+ } else if (23 <= postHour || postHour <= 5) {
445
+ return "Consider posting during more active hours (morning to evening)";
446
+ } else {
447
+ return "Posted during moderate activity hours, timing could be optimized";
448
+ }
449
+ }
450
+ function getSubredditEngagementTips(subreddit) {
451
+ const tips = [];
452
+ if (subreddit.subscribers > 1e6) {
453
+ tips.push("Post during peak hours for maximum visibility");
454
+ tips.push("Ensure content is highly polished due to high competition");
455
+ } else if (subreddit.subscribers < 1e3) {
456
+ tips.push("Engage actively to help grow the community");
457
+ tips.push("Consider cross-posting to related larger subreddits");
458
+ }
459
+ if (subreddit.activeUserCount) {
460
+ const activityRatio = subreddit.activeUserCount / subreddit.subscribers;
461
+ if (activityRatio > 0.1) {
462
+ tips.push("Quick responses recommended due to high activity");
463
+ }
464
+ }
465
+ return tips.length ? tips.join("\n - ") : "Regular engagement recommended to maintain community presence";
466
+ }
467
+ function analyzeCommentImpact(score, isEdited, isOp) {
468
+ const insights = [];
469
+ if (score > 100) {
470
+ insights.push(
471
+ "Highly upvoted comment with significant community agreement"
472
+ );
473
+ } else if (score < 0) {
474
+ insights.push("Controversial or contested viewpoint");
475
+ }
476
+ if (isEdited) {
477
+ insights.push("Refined for clarity or accuracy");
478
+ }
479
+ if (isOp) {
480
+ insights.push("Author's perspective adds context to original post");
481
+ }
482
+ return insights.length ? insights.join("\n - ") : "Standard engagement with discussion";
483
+ }
484
+ function formatUserInfo(user) {
485
+ const status = [];
486
+ if (user.isMod) status.push("Moderator");
487
+ if (user.isGold) status.push("Reddit Gold Member");
488
+ if (user.isEmployee) status.push("Reddit Employee");
489
+ const accountAgeDays = (Date.now() / 1e3 - user.createdUtc) / (24 * 3600);
490
+ const karmaRatio = user.commentKarma / (user.linkKarma || 1);
491
+ return {
492
+ username: user.name,
493
+ karma: {
494
+ commentKarma: user.commentKarma,
495
+ postKarma: user.linkKarma,
496
+ totalKarma: user.totalKarma
497
+ },
498
+ accountStatus: status.length ? status : ["Regular User"],
499
+ accountCreated: formatTimestamp(user.createdUtc),
500
+ profileUrl: user.profileUrl,
501
+ activityAnalysis: analyzeUserActivity(
502
+ karmaRatio,
503
+ user.isMod,
504
+ accountAgeDays
505
+ ),
506
+ recommendations: getUserRecommendations(
507
+ karmaRatio,
508
+ user.isMod,
509
+ accountAgeDays
510
+ )
511
+ };
512
+ }
513
+ function formatPostInfo(post) {
514
+ const contentType = post.isSelf ? "Text Post" : "Link Post";
515
+ const content = post.isSelf ? post.selftext || "" : post.url || "";
516
+ const flags = [];
517
+ if (post.over18) flags.push("NSFW");
518
+ if (post.spoiler) flags.push("Spoiler");
519
+ if (post.edited) flags.push("Edited");
520
+ return {
521
+ title: post.title,
522
+ type: contentType,
523
+ content: content.length > 300 ? content.substring(0, 297) + "..." : content,
524
+ author: post.author,
525
+ subreddit: post.subreddit,
526
+ stats: {
527
+ score: post.score,
528
+ upvoteRatio: post.upvoteRatio,
529
+ comments: post.numComments
530
+ },
531
+ metadata: {
532
+ posted: formatTimestamp(post.createdUtc),
533
+ flags,
534
+ flair: post.linkFlairText || "None"
535
+ },
536
+ links: {
537
+ fullPost: `https://reddit.com${post.permalink}`,
538
+ shortLink: `https://redd.it/${post.id}`
539
+ },
540
+ engagementAnalysis: analyzePostEngagement(
541
+ post.score,
542
+ post.upvoteRatio,
543
+ post.numComments
544
+ ),
545
+ bestTimeToEngage: getBestEngagementTime(post.createdUtc)
546
+ };
547
+ }
548
+ function formatSubredditInfo(subreddit) {
549
+ const flags = [];
550
+ if (subreddit.over18) flags.push("NSFW");
551
+ if (subreddit.subredditType) flags.push(`Type: ${subreddit.subredditType}`);
552
+ const ageDays = (Date.now() / 1e3 - subreddit.createdUtc) / (24 * 3600);
553
+ return {
554
+ name: subreddit.displayName,
555
+ title: subreddit.title,
556
+ stats: {
557
+ subscribers: subreddit.subscribers,
558
+ activeUsers: subreddit.activeUserCount !== void 0 ? subreddit.activeUserCount : "Unknown"
559
+ },
560
+ description: {
561
+ short: subreddit.publicDescription,
562
+ full: subreddit.description.length > 300 ? subreddit.description.substring(0, 297) + "..." : subreddit.description
563
+ },
564
+ metadata: {
565
+ created: formatTimestamp(subreddit.createdUtc),
566
+ flags: flags.length ? flags : ["None"]
567
+ },
568
+ links: {
569
+ subreddit: `https://reddit.com${subreddit.url}`,
570
+ wiki: `https://reddit.com/r/${subreddit.displayName}/wiki`
571
+ },
572
+ communityAnalysis: analyzeSubredditHealth(
573
+ subreddit.subscribers,
574
+ subreddit.activeUserCount,
575
+ ageDays
576
+ ),
577
+ engagementTips: getSubredditEngagementTips(subreddit)
578
+ };
579
+ }
580
+ function formatCommentInfo(comment) {
581
+ const flags = [];
582
+ if (comment.edited) flags.push("Edited");
583
+ if (comment.isSubmitter) flags.push("OP");
584
+ return {
585
+ author: comment.author,
586
+ content: comment.body.length > 300 ? comment.body.substring(0, 297) + "..." : comment.body,
587
+ stats: {
588
+ score: comment.score,
589
+ controversiality: comment.controversiality
590
+ },
591
+ context: {
592
+ subreddit: comment.subreddit,
593
+ thread: comment.submissionTitle
594
+ },
595
+ metadata: {
596
+ posted: formatTimestamp(comment.createdUtc),
597
+ flags: flags.length ? flags : ["None"]
598
+ },
599
+ link: `https://reddit.com${comment.permalink}`,
600
+ commentAnalysis: analyzeCommentImpact(
601
+ comment.score,
602
+ comment.edited,
603
+ comment.isSubmitter
604
+ )
605
+ };
606
+ }
607
+
608
+ // src/tools/user-tools.ts
609
+ var import_types = require("@modelcontextprotocol/sdk/types.js");
610
+ async function getUserInfo(params) {
611
+ const { username } = params;
612
+ const client = getRedditClient();
613
+ if (!client) {
614
+ throw new import_types.McpError(
615
+ import_types.ErrorCode.InternalError,
616
+ "Reddit client not initialized"
617
+ );
618
+ }
619
+ try {
620
+ console.log(`[Tool] Getting info for u/${username}`);
621
+ const user = await client.getUser(username);
622
+ const formattedUser = formatUserInfo(user);
623
+ return {
624
+ content: [
625
+ {
626
+ type: "text",
627
+ text: `
628
+ # User Information: u/${formattedUser.username}
629
+
630
+ ## Profile Overview
631
+ - Username: u/${formattedUser.username}
632
+ - Karma:
633
+ - Comment Karma: ${formattedUser.karma.commentKarma.toLocaleString()}
634
+ - Post Karma: ${formattedUser.karma.postKarma.toLocaleString()}
635
+ - Total Karma: ${formattedUser.karma.totalKarma.toLocaleString()}
636
+ - Account Status: ${formattedUser.accountStatus.join(", ")}
637
+ - Account Created: ${formattedUser.accountCreated}
638
+ - Profile URL: ${formattedUser.profileUrl}
639
+
640
+ ## Activity Analysis
641
+ - ${formattedUser.activityAnalysis.replace(/\n - /g, "\n- ")}
642
+
643
+ ## Recommendations
644
+ - ${formattedUser.recommendations.replace(/\n - /g, "\n- ")}
645
+ `
646
+ }
647
+ ]
648
+ };
649
+ } catch (error) {
650
+ console.error(`[Error] Error getting user info: ${error}`);
651
+ throw new import_types.McpError(
652
+ import_types.ErrorCode.InternalError,
653
+ `Failed to fetch user data: ${error}`
654
+ );
655
+ }
656
+ }
657
+
658
+ // src/tools/post-tools.ts
659
+ var import_types2 = require("@modelcontextprotocol/sdk/types.js");
660
+ async function getRedditPost(params) {
661
+ const { subreddit, post_id } = params;
662
+ const client = getRedditClient();
663
+ if (!client) {
664
+ throw new import_types2.McpError(
665
+ import_types2.ErrorCode.InternalError,
666
+ "Reddit client not initialized"
667
+ );
668
+ }
669
+ try {
670
+ console.log(`[Tool] Getting post ${post_id} from r/${subreddit}`);
671
+ const post = await client.getPost(post_id, subreddit);
672
+ const formattedPost = formatPostInfo(post);
673
+ return {
674
+ content: [
675
+ {
676
+ type: "text",
677
+ text: `
678
+ # Post from r/${formattedPost.subreddit}
679
+
680
+ ## Post Details
681
+ - Title: ${formattedPost.title}
682
+ - Type: ${formattedPost.type}
683
+ - Author: u/${formattedPost.author}
684
+
685
+ ## Content
686
+ ${formattedPost.content}
687
+
688
+ ## Stats
689
+ - Score: ${formattedPost.stats.score.toLocaleString()}
690
+ - Upvote Ratio: ${(formattedPost.stats.upvoteRatio * 100).toFixed(1)}%
691
+ - Comments: ${formattedPost.stats.comments.toLocaleString()}
692
+
693
+ ## Metadata
694
+ - Posted: ${formattedPost.metadata.posted}
695
+ - Flags: ${formattedPost.metadata.flags.length ? formattedPost.metadata.flags.join(", ") : "None"}
696
+ - Flair: ${formattedPost.metadata.flair}
697
+
698
+ ## Links
699
+ - Full Post: ${formattedPost.links.fullPost}
700
+ - Short Link: ${formattedPost.links.shortLink}
701
+
702
+ ## Engagement Analysis
703
+ - ${formattedPost.engagementAnalysis.replace(/\n - /g, "\n- ")}
704
+
705
+ ## Best Time to Engage
706
+ ${formattedPost.bestTimeToEngage}
707
+ `
708
+ }
709
+ ]
710
+ };
711
+ } catch (error) {
712
+ console.error(`[Error] Error getting post: ${error}`);
713
+ throw new import_types2.McpError(
714
+ import_types2.ErrorCode.InternalError,
715
+ `Failed to fetch post data: ${error}`
716
+ );
717
+ }
718
+ }
719
+ async function getTopPosts(params) {
720
+ const { subreddit, time_filter = "week", limit = 10 } = params;
721
+ const client = getRedditClient();
722
+ if (!client) {
723
+ throw new import_types2.McpError(
724
+ import_types2.ErrorCode.InternalError,
725
+ "Reddit client not initialized"
726
+ );
727
+ }
728
+ try {
729
+ console.log(`[Tool] Getting top posts from r/${subreddit}`);
730
+ const posts = await client.getTopPosts(subreddit, time_filter, limit);
731
+ const formattedPosts = posts.map(formatPostInfo);
732
+ const postSummaries = formattedPosts.map(
733
+ (post, index) => `
734
+ ### ${index + 1}. ${post.title}
735
+ - Author: u/${post.author}
736
+ - Score: ${post.stats.score.toLocaleString()} (${(post.stats.upvoteRatio * 100).toFixed(1)}% upvoted)
737
+ - Comments: ${post.stats.comments.toLocaleString()}
738
+ - Posted: ${post.metadata.posted}
739
+ - Link: ${post.links.shortLink}
740
+ `
741
+ ).join("\n");
742
+ return {
743
+ content: [
744
+ {
745
+ type: "text",
746
+ text: `
747
+ # Top Posts from r/${subreddit} (${time_filter})
748
+
749
+ ${postSummaries}
750
+ `
751
+ }
752
+ ]
753
+ };
754
+ } catch (error) {
755
+ console.error(`[Error] Error getting top posts: ${error}`);
756
+ throw new import_types2.McpError(
757
+ import_types2.ErrorCode.InternalError,
758
+ `Failed to fetch top posts: ${error}`
759
+ );
760
+ }
761
+ }
762
+ async function createPost(params) {
763
+ const { subreddit, title, content, is_self = true } = params;
764
+ const client = getRedditClient();
765
+ if (!client) {
766
+ throw new import_types2.McpError(
767
+ import_types2.ErrorCode.InternalError,
768
+ "Reddit client not initialized"
769
+ );
770
+ }
771
+ try {
772
+ console.log(
773
+ `[Tool] Creating ${is_self ? "text" : "link"} post in r/${subreddit}`
774
+ );
775
+ const post = await client.createPost(subreddit, title, content, is_self);
776
+ const formattedPost = formatPostInfo(post);
777
+ return {
778
+ content: [
779
+ {
780
+ type: "text",
781
+ text: `
782
+ # Post Created Successfully
783
+
784
+ ## Post Details
785
+ - Title: ${formattedPost.title}
786
+ - Subreddit: r/${formattedPost.subreddit}
787
+ - Type: ${formattedPost.type}
788
+ - Link: ${formattedPost.links.fullPost}
789
+
790
+ Your post has been successfully submitted to r/${formattedPost.subreddit}.
791
+ `
792
+ }
793
+ ]
794
+ };
795
+ } catch (error) {
796
+ console.error(`[Error] Error creating post: ${error}`);
797
+ throw new import_types2.McpError(
798
+ import_types2.ErrorCode.InternalError,
799
+ `Failed to create post: ${error}`
800
+ );
801
+ }
802
+ }
803
+ async function replyToPost(params) {
804
+ const { post_id, content, subreddit } = params;
805
+ const client = getRedditClient();
806
+ if (!client) {
807
+ throw new import_types2.McpError(
808
+ import_types2.ErrorCode.InternalError,
809
+ "Reddit client not initialized"
810
+ );
811
+ }
812
+ try {
813
+ console.log(`[Tool] Replying to post ${post_id}`);
814
+ const comment = await client.replyToPost(post_id, content);
815
+ const formattedComment = formatCommentInfo(comment);
816
+ return {
817
+ content: [
818
+ {
819
+ type: "text",
820
+ text: `
821
+ # Reply Posted Successfully
822
+
823
+ ## Comment Details
824
+ - Author: u/${formattedComment.author}
825
+ - Subreddit: r/${formattedComment.context.subreddit}
826
+ - Thread: ${formattedComment.context.thread}
827
+ - Link: ${formattedComment.link}
828
+
829
+ Your reply has been successfully posted.
830
+ `
831
+ }
832
+ ]
833
+ };
834
+ } catch (error) {
835
+ console.error(`[Error] Error replying to post: ${error}`);
836
+ throw new import_types2.McpError(
837
+ import_types2.ErrorCode.InternalError,
838
+ `Failed to reply to post: ${error}`
839
+ );
840
+ }
841
+ }
842
+
843
+ // src/tools/subreddit-tools.ts
844
+ var import_types3 = require("@modelcontextprotocol/sdk/types.js");
845
+ async function getSubredditInfo(params) {
846
+ const { subreddit_name } = params;
847
+ const client = getRedditClient();
848
+ if (!client) {
849
+ throw new import_types3.McpError(
850
+ import_types3.ErrorCode.InternalError,
851
+ "Reddit client not initialized"
852
+ );
853
+ }
854
+ try {
855
+ console.log(`[Tool] Getting info for r/${subreddit_name}`);
856
+ const subreddit = await client.getSubredditInfo(subreddit_name);
857
+ const formattedSubreddit = formatSubredditInfo(subreddit);
858
+ return {
859
+ content: [
860
+ {
861
+ type: "text",
862
+ text: `
863
+ # Subreddit Information: r/${formattedSubreddit.name}
864
+
865
+ ## Overview
866
+ - Name: r/${formattedSubreddit.name}
867
+ - Title: ${formattedSubreddit.title}
868
+ - Subscribers: ${formattedSubreddit.stats.subscribers.toLocaleString()}
869
+ - Active Users: ${typeof formattedSubreddit.stats.activeUsers === "number" ? formattedSubreddit.stats.activeUsers.toLocaleString() : formattedSubreddit.stats.activeUsers}
870
+
871
+ ## Description
872
+ ${formattedSubreddit.description.short}
873
+
874
+ ## Detailed Description
875
+ ${formattedSubreddit.description.full}
876
+
877
+ ## Metadata
878
+ - Created: ${formattedSubreddit.metadata.created}
879
+ - Flags: ${formattedSubreddit.metadata.flags.join(", ")}
880
+
881
+ ## Links
882
+ - Subreddit: ${formattedSubreddit.links.subreddit}
883
+ - Wiki: ${formattedSubreddit.links.wiki}
884
+
885
+ ## Community Analysis
886
+ - ${formattedSubreddit.communityAnalysis.replace(/\n - /g, "\n- ")}
887
+
888
+ ## Engagement Tips
889
+ - ${formattedSubreddit.engagementTips.replace(/\n - /g, "\n- ")}
890
+ `
891
+ }
892
+ ]
893
+ };
894
+ } catch (error) {
895
+ console.error(`[Error] Error getting subreddit info: ${error}`);
896
+ throw new import_types3.McpError(
897
+ import_types3.ErrorCode.InternalError,
898
+ `Failed to fetch subreddit data: ${error}`
899
+ );
900
+ }
901
+ }
902
+ async function getTrendingSubreddits() {
903
+ const client = getRedditClient();
904
+ if (!client) {
905
+ throw new import_types3.McpError(
906
+ import_types3.ErrorCode.InternalError,
907
+ "Reddit client not initialized"
908
+ );
909
+ }
910
+ try {
911
+ console.log("[Tool] Getting trending subreddits");
912
+ const trendingSubreddits = await client.getTrendingSubreddits();
913
+ return {
914
+ content: [
915
+ {
916
+ type: "text",
917
+ text: `
918
+ # Trending Subreddits
919
+
920
+ ${trendingSubreddits.map((subreddit, index) => `${index + 1}. r/${subreddit}`).join("\n")}
921
+ `
922
+ }
923
+ ]
924
+ };
925
+ } catch (error) {
926
+ console.error(`[Error] Error getting trending subreddits: ${error}`);
927
+ throw new import_types3.McpError(
928
+ import_types3.ErrorCode.InternalError,
929
+ `Failed to fetch trending subreddits: ${error}`
930
+ );
931
+ }
932
+ }
933
+
934
+ // src/index.ts
935
+ var import_dotenv = __toESM(require("dotenv"));
936
+ import_dotenv.default.config();
937
+ var RedditServer = class {
938
+ server;
939
+ constructor() {
940
+ console.log("[Setup] Initializing Reddit Server...");
941
+ this.initializeRedditClient();
942
+ this.server = new import_server.Server(
943
+ {
944
+ name: "reddit-mcp-server",
945
+ version: "0.1.0"
946
+ },
947
+ {
948
+ capabilities: {
949
+ tools: {}
950
+ }
951
+ }
952
+ );
953
+ this.setupToolHandlers();
954
+ this.server.onerror = (error) => console.error("[Error] Server error:", error);
955
+ process.on("SIGINT", async () => {
956
+ await this.server.close();
957
+ process.exit(0);
958
+ });
959
+ }
960
+ initializeRedditClient() {
961
+ const clientId = process.env.REDDIT_CLIENT_ID;
962
+ const clientSecret = process.env.REDDIT_CLIENT_SECRET;
963
+ const userAgent = process.env.REDDIT_USER_AGENT || "RedditMCPServer/0.1.0";
964
+ const username = process.env.REDDIT_USERNAME;
965
+ const password = process.env.REDDIT_PASSWORD;
966
+ if (!clientId || !clientSecret) {
967
+ console.error(
968
+ "[Error] Missing required Reddit API credentials. Please set REDDIT_CLIENT_ID and REDDIT_CLIENT_SECRET environment variables."
969
+ );
970
+ process.exit(1);
971
+ }
972
+ try {
973
+ initializeRedditClient({
974
+ clientId,
975
+ clientSecret,
976
+ userAgent,
977
+ username,
978
+ password
979
+ });
980
+ console.log("[Setup] Reddit client initialized");
981
+ if (username && password) {
982
+ console.log(`[Setup] Authenticated as user: ${username}`);
983
+ } else {
984
+ console.log(
985
+ "[Setup] Running in read-only mode (no user authentication)"
986
+ );
987
+ }
988
+ } catch (error) {
989
+ console.error("[Error] Failed to initialize Reddit client:", error);
990
+ process.exit(1);
991
+ }
992
+ }
993
+ setupToolHandlers() {
994
+ this.server.setRequestHandler(import_types4.ListToolsRequestSchema, async () => ({
995
+ tools: [
996
+ {
997
+ name: "test_reddit_mcp_server",
998
+ description: "Test the Reddit MCP Server",
999
+ inputSchema: {
1000
+ type: "object",
1001
+ properties: {
1002
+ // No input parameters, this will just return a test message
1003
+ }
1004
+ }
1005
+ },
1006
+ {
1007
+ name: "get_reddit_post",
1008
+ description: "Get a Reddit post",
1009
+ inputSchema: {
1010
+ type: "object",
1011
+ properties: {
1012
+ subreddit: {
1013
+ type: "string",
1014
+ description: "The subreddit to fetch posts from"
1015
+ },
1016
+ post_id: {
1017
+ type: "string",
1018
+ description: "The ID of the post to fetch"
1019
+ }
1020
+ },
1021
+ required: ["subreddit", "post_id"]
1022
+ }
1023
+ },
1024
+ {
1025
+ name: "get_top_posts",
1026
+ description: "Get top posts from a subreddit",
1027
+ inputSchema: {
1028
+ type: "object",
1029
+ properties: {
1030
+ subreddit: {
1031
+ type: "string",
1032
+ description: "Name of the subreddit"
1033
+ },
1034
+ time_filter: {
1035
+ type: "string",
1036
+ description: "Time period to filter posts (e.g. 'day', 'week', 'month', 'year', 'all')",
1037
+ enum: ["day", "week", "month", "year", "all"],
1038
+ default: "week"
1039
+ },
1040
+ limit: {
1041
+ type: "integer",
1042
+ description: "Number of posts to fetch",
1043
+ default: 10
1044
+ }
1045
+ },
1046
+ required: ["subreddit"]
1047
+ }
1048
+ },
1049
+ {
1050
+ name: "get_user_info",
1051
+ description: "Get information about a Reddit user",
1052
+ inputSchema: {
1053
+ type: "object",
1054
+ properties: {
1055
+ username: {
1056
+ type: "string",
1057
+ description: "The username of the Reddit user to get info for"
1058
+ }
1059
+ },
1060
+ required: ["username"]
1061
+ }
1062
+ },
1063
+ {
1064
+ name: "get_subreddit_info",
1065
+ description: "Get information about a subreddit",
1066
+ inputSchema: {
1067
+ type: "object",
1068
+ properties: {
1069
+ subreddit_name: {
1070
+ type: "string",
1071
+ description: "Name of the subreddit"
1072
+ }
1073
+ },
1074
+ required: ["subreddit_name"]
1075
+ }
1076
+ },
1077
+ {
1078
+ name: "get_trending_subreddits",
1079
+ description: "Get currently trending subreddits",
1080
+ inputSchema: {
1081
+ type: "object",
1082
+ properties: {}
1083
+ }
1084
+ },
1085
+ {
1086
+ name: "create_post",
1087
+ description: "Create a new post in a subreddit",
1088
+ inputSchema: {
1089
+ type: "object",
1090
+ properties: {
1091
+ subreddit: {
1092
+ type: "string",
1093
+ description: "Name of the subreddit to post in"
1094
+ },
1095
+ title: {
1096
+ type: "string",
1097
+ description: "Title of the post"
1098
+ },
1099
+ content: {
1100
+ type: "string",
1101
+ description: "Content of the post (text for self posts, URL for link posts)"
1102
+ },
1103
+ is_self: {
1104
+ type: "boolean",
1105
+ description: "Whether this is a self (text) post (true) or link post (false)",
1106
+ default: true
1107
+ }
1108
+ },
1109
+ required: ["subreddit", "title", "content"]
1110
+ }
1111
+ },
1112
+ {
1113
+ name: "reply_to_post",
1114
+ description: "Post a reply to an existing Reddit post",
1115
+ inputSchema: {
1116
+ type: "object",
1117
+ properties: {
1118
+ post_id: {
1119
+ type: "string",
1120
+ description: "The ID of the post to reply to"
1121
+ },
1122
+ content: {
1123
+ type: "string",
1124
+ description: "The content of the reply"
1125
+ },
1126
+ subreddit: {
1127
+ type: "string",
1128
+ description: "The subreddit name if known (for validation)"
1129
+ }
1130
+ },
1131
+ required: ["post_id", "content"]
1132
+ }
1133
+ }
1134
+ ]
1135
+ }));
1136
+ this.server.setRequestHandler(import_types4.CallToolRequestSchema, async (request) => {
1137
+ try {
1138
+ const toolName = request.params.name;
1139
+ const toolParams = request.params.arguments || {};
1140
+ console.log(`[Request] Tool call: ${toolName}`, toolParams);
1141
+ switch (toolName) {
1142
+ case "test_reddit_mcp_server":
1143
+ return {
1144
+ content: [
1145
+ {
1146
+ type: "text",
1147
+ text: "Hello, world! The Reddit MCP Server is working correctly."
1148
+ }
1149
+ ]
1150
+ };
1151
+ case "get_reddit_post":
1152
+ return await getRedditPost(
1153
+ toolParams
1154
+ );
1155
+ case "get_top_posts":
1156
+ return await getTopPosts(
1157
+ toolParams
1158
+ );
1159
+ case "get_user_info":
1160
+ return await getUserInfo(toolParams);
1161
+ case "get_subreddit_info":
1162
+ return await getSubredditInfo(
1163
+ toolParams
1164
+ );
1165
+ case "get_trending_subreddits":
1166
+ return await getTrendingSubreddits();
1167
+ case "create_post":
1168
+ return await createPost(
1169
+ toolParams
1170
+ );
1171
+ case "reply_to_post":
1172
+ return await replyToPost(
1173
+ toolParams
1174
+ );
1175
+ default:
1176
+ throw new import_types4.McpError(
1177
+ import_types4.ErrorCode.MethodNotFound,
1178
+ `Tool with name ${toolName} not found`
1179
+ );
1180
+ }
1181
+ } catch (error) {
1182
+ if (error instanceof Error) {
1183
+ console.error("[Error] Error calling tool:", error.message);
1184
+ throw new import_types4.McpError(
1185
+ import_types4.ErrorCode.InternalError,
1186
+ `Failed to fetch data: ${error.message}`
1187
+ );
1188
+ }
1189
+ throw error;
1190
+ }
1191
+ });
1192
+ }
1193
+ async run() {
1194
+ const transport = new import_stdio.StdioServerTransport();
1195
+ await this.server.connect(transport);
1196
+ console.log("[Server] Server is running");
1197
+ }
1198
+ };
1199
+ var server = new RedditServer();
1200
+ server.run().catch(console.error);