reddit-mcp-server 1.1.2 → 1.2.1

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