reddit-mcp-server 1.1.0 → 1.1.2

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 CHANGED
@@ -5,10 +5,6 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __export = (target, all) => {
9
- for (var name in all)
10
- __defProp(target, name, { get: all[name], enumerable: true });
11
- };
12
8
  var __copyProps = (to, from, except, desc) => {
13
9
  if (from && typeof from === "object" || typeof from === "function") {
14
10
  for (let key of __getOwnPropNames(from))
@@ -25,17 +21,10 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
25
21
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
22
  mod
27
23
  ));
28
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
24
 
30
25
  // src/index.ts
31
- var src_exports = {};
32
- __export(src_exports, {
33
- RedditServer: () => RedditServer
34
- });
35
- module.exports = __toCommonJS(src_exports);
36
- var import_server = require("@modelcontextprotocol/sdk/server/index.js");
37
- var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
38
- var import_types6 = require("@modelcontextprotocol/sdk/types.js");
26
+ var import_fastmcp = require("fastmcp");
27
+ var import_zod = require("zod");
39
28
 
40
29
  // src/client/reddit-client.ts
41
30
  var RedditClient = class {
@@ -90,7 +79,8 @@ var RedditClient = class {
90
79
  }
91
80
  const authUrl = "https://www.reddit.com/api/v1/access_token";
92
81
  const authData = new URLSearchParams();
93
- if (this.username && this.password) {
82
+ const isUserAuth = !!(this.username && this.password);
83
+ if (isUserAuth) {
94
84
  authData.append("grant_type", "password");
95
85
  authData.append("username", this.username);
96
86
  authData.append("password", this.password);
@@ -108,13 +98,17 @@ var RedditClient = class {
108
98
  body: authData.toString()
109
99
  });
110
100
  if (!response.ok) {
111
- throw new Error(`Authentication failed: ${response.status}`);
101
+ const statusText = response.statusText || "Unknown Error";
102
+ throw new Error(`Authentication failed: ${response.status} ${statusText}`);
112
103
  }
113
104
  const data = await response.json();
114
105
  this.accessToken = data.access_token;
115
106
  this.tokenExpiry = now + data.expires_in * 1e3;
116
107
  this.authenticated = true;
117
- } catch {
108
+ } catch (error) {
109
+ if (error instanceof Error) {
110
+ throw error;
111
+ }
118
112
  throw new Error("Failed to authenticate with Reddit API");
119
113
  }
120
114
  }
@@ -272,6 +266,7 @@ var RedditClient = class {
272
266
  }
273
267
  }
274
268
  async createPost(subreddit, title, content, isSelf = true) {
269
+ var _a, _b, _c, _d, _e, _f;
275
270
  await this.authenticate();
276
271
  if (!this.username || !this.password) {
277
272
  throw new Error("User authentication required for posting");
@@ -283,6 +278,7 @@ var RedditClient = class {
283
278
  params.append("kind", kind);
284
279
  params.append("title", title);
285
280
  params.append(isSelf ? "text" : "url", content);
281
+ params.append("api_type", "json");
286
282
  const response = await this.makeRequest("/api/submit", {
287
283
  method: "POST",
288
284
  headers: {
@@ -291,17 +287,33 @@ var RedditClient = class {
291
287
  body: params.toString()
292
288
  });
293
289
  if (!response.ok) {
294
- throw new Error(`HTTP ${response.status}`);
290
+ const errorText = await response.text();
291
+ console.error(`[Reddit API] Create post failed: ${response.status} ${response.statusText}`);
292
+ console.error(`[Reddit API] Error response: ${errorText}`);
293
+ throw new Error(`HTTP ${response.status}: ${errorText}`);
295
294
  }
296
295
  const json = await response.json();
297
- if (json.success) {
298
- const postId = json.data.id;
299
- return await this.getPost(postId);
300
- } else {
301
- throw new Error("Failed to create post");
296
+ console.error(`[Reddit API] Create post response:`, JSON.stringify(json, null, 2));
297
+ if (((_a = json.json) == null ? void 0 : _a.errors) && json.json.errors.length > 0) {
298
+ const errors = json.json.errors.map((e) => e.join(": ")).join(", ");
299
+ console.error(`[Reddit API] Post creation errors: ${errors}`);
300
+ throw new Error(`Reddit API errors: ${errors}`);
302
301
  }
303
- } catch {
304
- throw new Error(`Failed to create post in ${subreddit}`);
302
+ 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_", ""));
303
+ if (!postId) {
304
+ console.error(`[Reddit API] No post ID in response`);
305
+ throw new Error("No post ID returned from Reddit");
306
+ }
307
+ console.error(`[Reddit API] Post created with ID: ${postId}`);
308
+ return await this.getPost(postId, subreddit);
309
+ } catch (error) {
310
+ console.error(`[Reddit API] Create post exception:`, error);
311
+ if (error instanceof Error && error.message.includes("HTTP")) {
312
+ throw error;
313
+ }
314
+ throw new Error(
315
+ `Failed to create post in ${subreddit}: ${error instanceof Error ? error.message : String(error)}`
316
+ );
305
317
  }
306
318
  }
307
319
  async checkPostExists(postId) {
@@ -329,6 +341,7 @@ var RedditClient = class {
329
341
  const params = new URLSearchParams();
330
342
  params.append("thing_id", `t3_${postId}`);
331
343
  params.append("text", content);
344
+ params.append("api_type", "json");
332
345
  const response = await this.makeRequest("/api/comment", {
333
346
  method: "POST",
334
347
  headers: {
@@ -337,26 +350,126 @@ var RedditClient = class {
337
350
  body: params.toString()
338
351
  });
339
352
  if (!response.ok) {
340
- throw new Error(`HTTP ${response.status}`);
353
+ const errorText = await response.text();
354
+ console.error(`[Reddit API] Reply to post failed: ${response.status} ${response.statusText}`);
355
+ console.error(`[Reddit API] Error response: ${errorText}`);
356
+ throw new Error(`HTTP ${response.status}: ${errorText}`);
341
357
  }
342
- const commentData = await response.json();
343
- return {
344
- id: commentData.id,
345
- author: this.username,
346
- body: content,
347
- score: 1,
348
- controversiality: 0,
349
- subreddit: commentData.subreddit,
350
- submissionTitle: commentData.link_title,
351
- createdUtc: Date.now() / 1e3,
352
- edited: false,
353
- isSubmitter: false,
354
- permalink: commentData.permalink
355
- };
356
- } catch {
357
- throw new Error(`Failed to reply to post ${postId}`);
358
+ const json = await response.json();
359
+ console.error(`[Reddit API] Reply response:`, JSON.stringify(json, null, 2));
360
+ if (json.json && json.json.data && json.json.data.things) {
361
+ const commentData = json.json.data.things[0].data;
362
+ return {
363
+ id: commentData.id,
364
+ author: this.username,
365
+ body: content,
366
+ score: 1,
367
+ controversiality: 0,
368
+ subreddit: commentData.subreddit,
369
+ submissionTitle: commentData.link_title || "",
370
+ createdUtc: Date.now() / 1e3,
371
+ edited: false,
372
+ isSubmitter: false,
373
+ permalink: commentData.permalink
374
+ };
375
+ } else if (json.json && json.json.errors && json.json.errors.length > 0) {
376
+ const errors = json.json.errors.map((e) => e.join(": ")).join(", ");
377
+ console.error(`[Reddit API] Reply errors: ${errors}`);
378
+ throw new Error(`Reddit API errors: ${errors}`);
379
+ } else {
380
+ console.error(`[Reddit API] Unexpected reply response format`);
381
+ throw new Error("Failed to parse reply response");
382
+ }
383
+ } catch (error) {
384
+ console.error(`[Reddit API] Reply to post exception:`, error);
385
+ if (error instanceof Error && error.message.includes("HTTP")) {
386
+ throw error;
387
+ }
388
+ throw new Error(`Failed to reply to post ${postId}: ${error instanceof Error ? error.message : String(error)}`);
358
389
  }
359
390
  }
391
+ async deletePost(thingId) {
392
+ await this.authenticate();
393
+ if (!this.username || !this.password) {
394
+ throw new Error("User authentication required for deleting content");
395
+ }
396
+ try {
397
+ const fullThingId = thingId.startsWith("t3_") || thingId.startsWith("t1_") ? thingId : `t3_${thingId}`;
398
+ const params = new URLSearchParams();
399
+ params.append("id", fullThingId);
400
+ const response = await this.makeRequest("/api/del", {
401
+ method: "POST",
402
+ headers: {
403
+ "Content-Type": "application/x-www-form-urlencoded"
404
+ },
405
+ body: params.toString()
406
+ });
407
+ if (!response.ok) {
408
+ const errorText = await response.text();
409
+ console.error(`[Reddit API] Delete failed: ${response.status} ${response.statusText}`);
410
+ console.error(`[Reddit API] Error response: ${errorText}`);
411
+ throw new Error(`HTTP ${response.status}: ${errorText}`);
412
+ }
413
+ console.error(`[Reddit API] Successfully deleted ${fullThingId}`);
414
+ return true;
415
+ } catch (error) {
416
+ console.error(`[Reddit API] Delete exception:`, error);
417
+ if (error instanceof Error && error.message.includes("HTTP")) {
418
+ throw error;
419
+ }
420
+ throw new Error(`Failed to delete content ${thingId}: ${error instanceof Error ? error.message : String(error)}`);
421
+ }
422
+ }
423
+ async deleteComment(thingId) {
424
+ const fullThingId = thingId.startsWith("t1_") ? thingId : `t1_${thingId}`;
425
+ return this.deletePost(fullThingId);
426
+ }
427
+ async editPost(thingId, newText) {
428
+ var _a;
429
+ await this.authenticate();
430
+ if (!this.username || !this.password) {
431
+ throw new Error("User authentication required for editing content");
432
+ }
433
+ try {
434
+ const fullThingId = thingId.startsWith("t3_") || thingId.startsWith("t1_") ? thingId : `t3_${thingId}`;
435
+ const params = new URLSearchParams();
436
+ params.append("thing_id", fullThingId);
437
+ params.append("text", newText);
438
+ params.append("api_type", "json");
439
+ const response = await this.makeRequest("/api/editusertext", {
440
+ method: "POST",
441
+ headers: {
442
+ "Content-Type": "application/x-www-form-urlencoded"
443
+ },
444
+ body: params.toString()
445
+ });
446
+ if (!response.ok) {
447
+ const errorText = await response.text();
448
+ console.error(`[Reddit API] Edit failed: ${response.status} ${response.statusText}`);
449
+ console.error(`[Reddit API] Error response: ${errorText}`);
450
+ throw new Error(`HTTP ${response.status}: ${errorText}`);
451
+ }
452
+ const json = await response.json();
453
+ console.error(`[Reddit API] Edit response:`, JSON.stringify(json, null, 2));
454
+ if (((_a = json.json) == null ? void 0 : _a.errors) && json.json.errors.length > 0) {
455
+ const errors = json.json.errors.map((e) => e.join(": ")).join(", ");
456
+ console.error(`[Reddit API] Edit errors: ${errors}`);
457
+ throw new Error(`Reddit API errors: ${errors}`);
458
+ }
459
+ console.error(`[Reddit API] Successfully edited ${fullThingId}`);
460
+ return true;
461
+ } catch (error) {
462
+ console.error(`[Reddit API] Edit exception:`, error);
463
+ if (error instanceof Error && error.message.includes("HTTP")) {
464
+ throw error;
465
+ }
466
+ throw new Error(`Failed to edit content ${thingId}: ${error instanceof Error ? error.message : String(error)}`);
467
+ }
468
+ }
469
+ async editComment(thingId, newText) {
470
+ const fullThingId = thingId.startsWith("t1_") ? thingId : `t1_${thingId}`;
471
+ return this.editPost(fullThingId, newText);
472
+ }
360
473
  async searchReddit(query, options = {}) {
361
474
  await this.authenticate();
362
475
  try {
@@ -664,21 +777,6 @@ function getSubredditEngagementTips(subreddit) {
664
777
  }
665
778
  return tips.length ? tips.join("\n - ") : "Regular engagement recommended to maintain community presence";
666
779
  }
667
- function analyzeCommentImpact(score, isEdited, isOp) {
668
- const insights = [];
669
- if (score > 100) {
670
- insights.push("Highly upvoted comment with significant community agreement");
671
- } else if (score < 0) {
672
- insights.push("Controversial or contested viewpoint");
673
- }
674
- if (isEdited) {
675
- insights.push("Refined for clarity or accuracy");
676
- }
677
- if (isOp) {
678
- insights.push("Author's perspective adds context to original post");
679
- }
680
- return insights.length ? insights.join("\n - ") : "Standard engagement with discussion";
681
- }
682
780
  function formatUserInfo(user) {
683
781
  const status = [];
684
782
  if (user.isMod) status.push("Moderator");
@@ -759,62 +857,137 @@ function formatSubredditInfo(subreddit) {
759
857
  engagementTips: getSubredditEngagementTips(subreddit)
760
858
  };
761
859
  }
762
- function formatCommentInfo(comment) {
763
- const flags = [];
764
- if (comment.edited) flags.push("Edited");
765
- if (comment.isSubmitter) flags.push("OP");
766
- return {
767
- author: comment.author,
768
- content: comment.body.length > 300 ? comment.body.substring(0, 297) + "..." : comment.body,
769
- stats: {
770
- score: comment.score,
771
- controversiality: comment.controversiality
772
- },
773
- context: {
774
- subreddit: comment.subreddit,
775
- thread: comment.submissionTitle
776
- },
777
- metadata: {
778
- posted: formatTimestamp(comment.createdUtc),
779
- flags: flags.length ? flags : ["None"]
780
- },
781
- link: `https://reddit.com${comment.permalink}`,
782
- commentAnalysis: analyzeCommentImpact(comment.score, comment.edited, comment.isSubmitter)
783
- };
784
- }
785
- function formatPost(post) {
786
- return {
787
- title: post.title,
788
- author: post.author,
789
- subreddit: post.subreddit,
790
- score: post.score,
791
- upvoteRatio: Math.round(post.upvoteRatio * 100),
792
- numComments: post.numComments,
793
- createdAt: formatTimestamp(post.createdUtc),
794
- selftext: post.selftext,
795
- permalink: post.permalink,
796
- nsfw: post.over18,
797
- spoiler: post.spoiler
798
- };
799
- }
800
860
 
801
- // src/tools/user-tools.ts
802
- var import_types = require("@modelcontextprotocol/sdk/types.js");
803
- async function getUserInfo(params) {
804
- const { username } = params;
805
- const client = getRedditClient();
806
- if (!client) {
807
- throw new import_types.McpError(import_types.ErrorCode.InternalError, "Reddit client not initialized");
861
+ // src/index.ts
862
+ var import_dotenv = __toESM(require("dotenv"));
863
+ import_dotenv.default.config();
864
+ async function setupRedditClient() {
865
+ const clientId = process.env.REDDIT_CLIENT_ID;
866
+ const clientSecret = process.env.REDDIT_CLIENT_SECRET;
867
+ const userAgent = process.env.REDDIT_USER_AGENT || "RedditMCPServer/1.1.0";
868
+ const username = process.env.REDDIT_USERNAME;
869
+ const password = process.env.REDDIT_PASSWORD;
870
+ if (!clientId || !clientSecret) {
871
+ console.error(
872
+ "[Error] Missing required Reddit API credentials. Please set REDDIT_CLIENT_ID and REDDIT_CLIENT_SECRET environment variables."
873
+ );
874
+ process.exit(1);
808
875
  }
809
876
  try {
810
- const user = await client.getUser(username);
877
+ const client = initializeRedditClient({
878
+ clientId,
879
+ clientSecret,
880
+ userAgent,
881
+ username,
882
+ password
883
+ });
884
+ console.error("[Setup] Reddit client initialized");
885
+ console.error("[Setup] Testing Reddit API connection...");
886
+ const isConnected = await client.checkAuthentication();
887
+ if (!isConnected) {
888
+ console.error("[Error] \u2717 Failed to connect to Reddit API");
889
+ console.error("[Error] Please check your REDDIT_CLIENT_ID and REDDIT_CLIENT_SECRET");
890
+ process.exit(1);
891
+ }
892
+ console.error("[Setup] \u2713 Reddit API connection successful");
893
+ if (username && password) {
894
+ console.error(`[Setup] \u2713 User authenticated as: ${username}`);
895
+ console.error("[Setup] Write operations enabled (posting, replying, editing, deleting)");
896
+ } else {
897
+ console.error("[Setup] Running in read-only mode (client credentials only)");
898
+ console.error("[Setup] For write operations, set REDDIT_USERNAME and REDDIT_PASSWORD");
899
+ }
900
+ } catch (error) {
901
+ console.error("[Error] \u2717 Reddit API connection failed:", error instanceof Error ? error.message : error);
902
+ console.error("[Error] Please verify your Reddit API credentials");
903
+ process.exit(1);
904
+ }
905
+ }
906
+ var server = new import_fastmcp.FastMCP({
907
+ name: "reddit-mcp-server",
908
+ version: "1.1.0",
909
+ instructions: `A comprehensive Reddit MCP server that provides tools for interacting with Reddit API.
910
+
911
+ Available capabilities:
912
+ - Fetch Reddit posts, comments, and user information
913
+ - Get subreddit details and statistics
914
+ - Search Reddit content across posts and subreddits
915
+ - Create posts and reply to posts/comments (with authentication)
916
+ - Edit your own posts and comments (with authentication)
917
+ - Delete your own posts and comments (with authentication)
918
+ - Analyze engagement metrics and community insights
919
+
920
+ For write operations (posting, replying, editing, deleting), ensure REDDIT_USERNAME and REDDIT_PASSWORD are configured.`,
921
+ // Optional OAuth configuration for HTTP transport
922
+ ...process.env.OAUTH_ENABLED === "true" && {
923
+ authenticate: async (request) => {
924
+ const authHeader = request.headers.authorization;
925
+ const expectedToken = process.env.OAUTH_TOKEN;
926
+ if (!expectedToken) {
927
+ const token2 = Array.from(
928
+ { length: 32 },
929
+ () => "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".charAt(Math.floor(Math.random() * 62))
930
+ ).join("");
931
+ console.log(`[Auth] Generated OAuth token: ${token2}`);
932
+ throw new Response(
933
+ JSON.stringify({
934
+ error: "No OAuth token configured",
935
+ generatedToken: token2
936
+ }),
937
+ {
938
+ status: 401,
939
+ headers: { "Content-Type": "application/json" }
940
+ }
941
+ );
942
+ }
943
+ if (!(authHeader == null ? void 0 : authHeader.startsWith("Bearer "))) {
944
+ throw new Response(null, {
945
+ status: 401,
946
+ statusText: "Missing or invalid Authorization header"
947
+ });
948
+ }
949
+ const token = authHeader.slice(7);
950
+ if (token !== expectedToken) {
951
+ throw new Response(null, {
952
+ status: 403,
953
+ statusText: "Invalid token"
954
+ });
955
+ }
956
+ return { authenticated: true };
957
+ }
958
+ }
959
+ });
960
+ server.addTool({
961
+ name: "test_reddit_mcp_server",
962
+ description: "Test the Reddit MCP Server connection and configuration",
963
+ parameters: import_zod.z.object({}),
964
+ execute: async () => {
965
+ const client = getRedditClient();
966
+ const hasAuth = client ? "\u2713" : "\u2717";
967
+ const hasWriteAccess = process.env.REDDIT_USERNAME && process.env.REDDIT_PASSWORD ? "\u2713" : "\u2717";
968
+ return `Reddit MCP Server Status:
969
+ - Server: \u2713 Running
970
+ - Reddit Client: ${hasAuth} ${client ? "Initialized" : "Not initialized"}
971
+ - Write Access: ${hasWriteAccess} ${hasWriteAccess === "\u2713" ? "Available" : "Read-only mode"}
972
+ - Version: 1.1.0
973
+
974
+ Ready to handle Reddit API requests!`;
975
+ }
976
+ });
977
+ server.addTool({
978
+ name: "get_user_info",
979
+ description: "Get detailed information about a Reddit user including karma, account status, and activity analysis",
980
+ parameters: import_zod.z.object({
981
+ username: import_zod.z.string().describe("The Reddit username (without u/ prefix)")
982
+ }),
983
+ execute: async (args) => {
984
+ const client = getRedditClient();
985
+ if (!client) {
986
+ throw new Error("Reddit client not initialized");
987
+ }
988
+ const user = await client.getUser(args.username);
811
989
  const formattedUser = formatUserInfo(user);
812
- return {
813
- content: [
814
- {
815
- type: "text",
816
- text: `
817
- # User Information: u/${formattedUser.username}
990
+ return `# User Information: u/${formattedUser.username}
818
991
 
819
992
  ## Profile Overview
820
993
  - Username: u/${formattedUser.username}
@@ -830,113 +1003,99 @@ async function getUserInfo(params) {
830
1003
  - ${formattedUser.activityAnalysis.replace(/\n - /g, "\n- ")}
831
1004
 
832
1005
  ## Recommendations
833
- - ${formattedUser.recommendations.replace(/\n - /g, "\n- ")}
834
- `
835
- }
836
- ]
837
- };
838
- } catch (error) {
839
- throw new import_types.McpError(import_types.ErrorCode.InternalError, `Failed to fetch user data: ${String(error)}`);
1006
+ - ${formattedUser.recommendations.replace(/\n {2}- /g, "\n- ")}`;
840
1007
  }
841
- }
842
- async function getUserPosts(params) {
843
- const { username, sort = "new", time_filter = "all", limit = 10 } = params;
844
- const client = getRedditClient();
845
- if (!client) {
846
- throw new import_types.McpError(import_types.ErrorCode.InternalError, "Reddit client not initialized");
847
- }
848
- try {
849
- const posts = await client.getUserPosts(username, {
850
- sort,
851
- timeFilter: time_filter,
852
- limit
1008
+ });
1009
+ server.addTool({
1010
+ name: "get_user_posts",
1011
+ description: "Get recent posts by a Reddit user with sorting and filtering options",
1012
+ parameters: import_zod.z.object({
1013
+ username: import_zod.z.string().describe("The Reddit username (without u/ prefix)"),
1014
+ sort: import_zod.z.enum(["new", "hot", "top"]).default("new").describe("Sort order for posts"),
1015
+ time_filter: import_zod.z.enum(["hour", "day", "week", "month", "year", "all"]).default("all").describe("Time filter for top posts"),
1016
+ limit: import_zod.z.number().min(1).max(100).default(10).describe("Number of posts to retrieve")
1017
+ }),
1018
+ execute: async (args) => {
1019
+ const client = getRedditClient();
1020
+ if (!client) {
1021
+ throw new Error("Reddit client not initialized");
1022
+ }
1023
+ const posts = await client.getUserPosts(args.username, {
1024
+ sort: args.sort,
1025
+ timeFilter: args.time_filter,
1026
+ limit: args.limit
853
1027
  });
854
- return {
855
- content: [
856
- {
857
- type: "text",
858
- text: `# Posts by u/${username}
859
-
860
- ## Sort: ${sort} | Time: ${time_filter} | Count: ${posts.length}
861
-
862
- ${posts.map((post, index) => {
863
- const date = new Date(post.createdUtc * 1e3).toLocaleString();
864
- const selftext = post.selftext ? `
865
- ${post.selftext.substring(0, 200)}${post.selftext.length > 200 ? "..." : ""}
866
- ` : "";
867
- return `### ${index + 1}. ${post.title}
1028
+ if (posts.length === 0) {
1029
+ return `No posts found for u/${args.username} with the specified filters.`;
1030
+ }
1031
+ const postSummaries = posts.map((post, index) => {
1032
+ const flags = [...post.over18 ? ["**NSFW**"] : [], ...post.spoiler ? ["**Spoiler**"] : []];
1033
+ return `### ${index + 1}. ${post.title} ${flags.join(" ")}
868
1034
  - Subreddit: r/${post.subreddit}
869
- - Score: ${post.score} (${Math.round(post.upvoteRatio * 100)}% upvoted)
870
- - Comments: ${post.numComments}
871
- - Posted: ${date}
872
- ${selftext}
873
- - Link: https://reddit.com${post.permalink}
874
- ${post.over18 ? "- **NSFW**" : ""}
875
- ${post.spoiler ? "- **Spoiler**" : ""}`;
876
- }).join("\n\n---\n\n")}`
877
- }
878
- ]
879
- };
880
- } catch (error) {
881
- throw new import_types.McpError(import_types.ErrorCode.InternalError, `Failed to fetch user posts: ${String(error)}`);
882
- }
883
- }
884
- async function getUserComments(params) {
885
- const { username, sort = "new", time_filter = "all", limit = 10 } = params;
886
- const client = getRedditClient();
887
- if (!client) {
888
- throw new import_types.McpError(import_types.ErrorCode.InternalError, "Reddit client not initialized");
1035
+ - Score: ${post.score.toLocaleString()} (${(post.upvoteRatio * 100).toFixed(1)}% upvoted)
1036
+ - Comments: ${post.numComments.toLocaleString()}
1037
+ - Posted: ${new Date(post.createdUtc * 1e3).toLocaleString()}
1038
+ - Link: https://reddit.com${post.permalink}`;
1039
+ }).join("\n\n");
1040
+ return `# Posts by u/${args.username} (${args.sort} - ${args.time_filter})
1041
+
1042
+ ${postSummaries}`;
889
1043
  }
890
- try {
891
- const comments = await client.getUserComments(username, {
892
- sort,
893
- timeFilter: time_filter,
894
- limit
1044
+ });
1045
+ server.addTool({
1046
+ name: "get_user_comments",
1047
+ description: "Get recent comments by a Reddit user with sorting and filtering options",
1048
+ parameters: import_zod.z.object({
1049
+ username: import_zod.z.string().describe("The Reddit username (without u/ prefix)"),
1050
+ sort: import_zod.z.enum(["new", "hot", "top"]).default("new").describe("Sort order for comments"),
1051
+ time_filter: import_zod.z.enum(["hour", "day", "week", "month", "year", "all"]).default("all").describe("Time filter for top comments"),
1052
+ limit: import_zod.z.number().min(1).max(100).default(10).describe("Number of comments to retrieve")
1053
+ }),
1054
+ execute: async (args) => {
1055
+ const client = getRedditClient();
1056
+ if (!client) {
1057
+ throw new Error("Reddit client not initialized");
1058
+ }
1059
+ const comments = await client.getUserComments(args.username, {
1060
+ sort: args.sort,
1061
+ timeFilter: args.time_filter,
1062
+ limit: args.limit
895
1063
  });
896
- return {
897
- content: [
898
- {
899
- type: "text",
900
- text: `# Comments by u/${username}
1064
+ if (comments.length === 0) {
1065
+ return `No comments found for u/${args.username} with the specified filters.`;
1066
+ }
1067
+ const commentSummaries = comments.map((comment, index) => {
1068
+ const truncatedBody = comment.body.length > 300 ? comment.body.substring(0, 300) + "..." : comment.body;
1069
+ const flags = [...comment.edited ? ["*(edited)*"] : [], ...comment.isSubmitter ? ["**OP**"] : []];
1070
+ return `### ${index + 1}. Comment ${flags.join(" ")}
1071
+ In r/${comment.subreddit} on "${comment.submissionTitle}"
901
1072
 
902
- ## Sort: ${sort} | Time: ${time_filter} | Count: ${comments.length}
1073
+ > ${truncatedBody}
903
1074
 
904
- ${comments.map((comment, index) => {
905
- const date = new Date(comment.createdUtc * 1e3).toLocaleString();
906
- const edited = comment.edited ? " *(edited)*" : "";
907
- const body = comment.body.length > 300 ? comment.body.substring(0, 300) + "..." : comment.body;
908
- return `### ${index + 1}. In r/${comment.subreddit} on "${comment.submissionTitle}"
909
- - Score: ${comment.score} points
910
- - Posted: ${date}${edited}
911
- - Link: https://reddit.com${comment.permalink}
1075
+ - Score: ${comment.score.toLocaleString()}
1076
+ - Posted: ${new Date(comment.createdUtc * 1e3).toLocaleString()}
1077
+ - Link: https://reddit.com${comment.permalink}`;
1078
+ }).join("\n\n");
1079
+ return `# Comments by u/${args.username} (${args.sort} - ${args.time_filter})
912
1080
 
913
- ${body}`;
914
- }).join("\n\n---\n\n")}`
915
- }
916
- ]
917
- };
918
- } catch (error) {
919
- throw new import_types.McpError(import_types.ErrorCode.InternalError, `Failed to fetch user comments: ${String(error)}`);
920
- }
921
- }
922
-
923
- // src/tools/post-tools.ts
924
- var import_types2 = require("@modelcontextprotocol/sdk/types.js");
925
- async function getRedditPost(params) {
926
- const { subreddit, post_id } = params;
927
- const client = getRedditClient();
928
- if (!client) {
929
- throw new import_types2.McpError(import_types2.ErrorCode.InternalError, "Reddit client not initialized");
1081
+ ${commentSummaries}`;
930
1082
  }
931
- try {
932
- const post = await client.getPost(post_id, subreddit);
1083
+ });
1084
+ server.addTool({
1085
+ name: "get_reddit_post",
1086
+ description: "Get detailed information about a specific Reddit post including content, stats, and engagement analysis",
1087
+ parameters: import_zod.z.object({
1088
+ subreddit: import_zod.z.string().describe("The subreddit name (without r/ prefix)"),
1089
+ post_id: import_zod.z.string().describe("The Reddit post ID")
1090
+ }),
1091
+ execute: async (args) => {
1092
+ const client = getRedditClient();
1093
+ if (!client) {
1094
+ throw new Error("Reddit client not initialized");
1095
+ }
1096
+ const post = await client.getPost(args.post_id, args.subreddit);
933
1097
  const formattedPost = formatPostInfo(post);
934
- return {
935
- content: [
936
- {
937
- type: "text",
938
- text: `
939
- # Post from r/${formattedPost.subreddit}
1098
+ return `# Post from r/${formattedPost.subreddit}
940
1099
 
941
1100
  ## Post Details
942
1101
  - Title: ${formattedPost.title}
@@ -964,130 +1123,56 @@ ${formattedPost.content}
964
1123
  - ${formattedPost.engagementAnalysis.replace(/\n - /g, "\n- ")}
965
1124
 
966
1125
  ## Best Time to Engage
967
- ${formattedPost.bestTimeToEngage}
968
- `
969
- }
970
- ]
971
- };
972
- } catch (error) {
973
- throw new import_types2.McpError(import_types2.ErrorCode.InternalError, `Failed to fetch post data: ${String(error)}`);
974
- }
975
- }
976
- async function getTopPosts(params) {
977
- const { subreddit, time_filter = "week", limit = 10 } = params;
978
- const client = getRedditClient();
979
- if (!client) {
980
- throw new import_types2.McpError(import_types2.ErrorCode.InternalError, "Reddit client not initialized");
1126
+ ${formattedPost.bestTimeToEngage}`;
981
1127
  }
982
- try {
983
- const posts = await client.getTopPosts(subreddit, time_filter, limit);
1128
+ });
1129
+ server.addTool({
1130
+ name: "get_top_posts",
1131
+ description: "Get top posts from a subreddit or from the Reddit home feed",
1132
+ parameters: import_zod.z.object({
1133
+ subreddit: import_zod.z.string().optional().describe("The subreddit name (without r/ prefix). Leave empty for home feed"),
1134
+ time_filter: import_zod.z.enum(["hour", "day", "week", "month", "year", "all"]).default("week").describe("Time period for top posts"),
1135
+ limit: import_zod.z.number().min(1).max(100).default(10).describe("Number of posts to retrieve")
1136
+ }),
1137
+ execute: async (args) => {
1138
+ const client = getRedditClient();
1139
+ if (!client) {
1140
+ throw new Error("Reddit client not initialized");
1141
+ }
1142
+ const posts = await client.getTopPosts(args.subreddit || "", args.time_filter, args.limit);
1143
+ if (posts.length === 0) {
1144
+ const location2 = args.subreddit ? `r/${args.subreddit}` : "home feed";
1145
+ return `No posts found in ${location2} for the specified time period.`;
1146
+ }
984
1147
  const formattedPosts = posts.map(formatPostInfo);
985
1148
  const postSummaries = formattedPosts.map(
986
- (post, index) => `
987
- ### ${index + 1}. ${post.title}
1149
+ (post, index) => `### ${index + 1}. ${post.title}
988
1150
  - Author: u/${post.author}
989
1151
  - Score: ${post.stats.score.toLocaleString()} (${(post.stats.upvoteRatio * 100).toFixed(1)}% upvoted)
990
1152
  - Comments: ${post.stats.comments.toLocaleString()}
991
1153
  - Posted: ${post.metadata.posted}
992
- - Link: ${post.links.shortLink}
993
- `
994
- ).join("\n");
995
- return {
996
- content: [
997
- {
998
- type: "text",
999
- text: `
1000
- # Top Posts from r/${subreddit} (${time_filter})
1001
-
1002
- ${postSummaries}
1003
- `
1004
- }
1005
- ]
1006
- };
1007
- } catch (error) {
1008
- throw new import_types2.McpError(import_types2.ErrorCode.InternalError, `Failed to fetch top posts: ${String(error)}`);
1009
- }
1010
- }
1011
- async function createPost(params) {
1012
- const { subreddit, title, content, is_self = true } = params;
1013
- const client = getRedditClient();
1014
- if (!client) {
1015
- throw new import_types2.McpError(import_types2.ErrorCode.InternalError, "Reddit client not initialized");
1016
- }
1017
- try {
1018
- const post = await client.createPost(subreddit, title, content, is_self);
1019
- const formattedPost = formatPostInfo(post);
1020
- return {
1021
- content: [
1022
- {
1023
- type: "text",
1024
- text: `
1025
- # Post Created Successfully
1026
-
1027
- ## Post Details
1028
- - Title: ${formattedPost.title}
1029
- - Subreddit: r/${formattedPost.subreddit}
1030
- - Type: ${formattedPost.type}
1031
- - Link: ${formattedPost.links.fullPost}
1154
+ - Link: ${post.links.shortLink}`
1155
+ ).join("\n\n");
1156
+ const location = args.subreddit ? `r/${args.subreddit}` : "Home Feed";
1157
+ return `# Top Posts from ${location} (${args.time_filter})
1032
1158
 
1033
- Your post has been successfully submitted to r/${formattedPost.subreddit}.
1034
- `
1035
- }
1036
- ]
1037
- };
1038
- } catch (error) {
1039
- throw new import_types2.McpError(import_types2.ErrorCode.InternalError, `Failed to create post: ${String(error)}`);
1159
+ ${postSummaries}`;
1040
1160
  }
1041
- }
1042
- async function replyToPost(params) {
1043
- const { post_id, content } = params;
1044
- const client = getRedditClient();
1045
- if (!client) {
1046
- throw new import_types2.McpError(import_types2.ErrorCode.InternalError, "Reddit client not initialized");
1047
- }
1048
- try {
1049
- const comment = await client.replyToPost(post_id, content);
1050
- const formattedComment = formatCommentInfo(comment);
1051
- return {
1052
- content: [
1053
- {
1054
- type: "text",
1055
- text: `
1056
- # Reply Posted Successfully
1057
-
1058
- ## Comment Details
1059
- - Author: u/${formattedComment.author}
1060
- - Subreddit: r/${formattedComment.context.subreddit}
1061
- - Thread: ${formattedComment.context.thread}
1062
- - Link: ${formattedComment.link}
1063
-
1064
- Your reply has been successfully posted.
1065
- `
1066
- }
1067
- ]
1068
- };
1069
- } catch (error) {
1070
- throw new import_types2.McpError(import_types2.ErrorCode.InternalError, `Failed to reply to post: ${String(error)}`);
1071
- }
1072
- }
1073
-
1074
- // src/tools/subreddit-tools.ts
1075
- var import_types3 = require("@modelcontextprotocol/sdk/types.js");
1076
- async function getSubredditInfo(params) {
1077
- const { subreddit_name } = params;
1078
- const client = getRedditClient();
1079
- if (!client) {
1080
- throw new import_types3.McpError(import_types3.ErrorCode.InternalError, "Reddit client not initialized");
1081
- }
1082
- try {
1083
- const subreddit = await client.getSubredditInfo(subreddit_name);
1161
+ });
1162
+ server.addTool({
1163
+ name: "get_subreddit_info",
1164
+ description: "Get detailed information about a subreddit including description, stats, and community analysis",
1165
+ parameters: import_zod.z.object({
1166
+ subreddit_name: import_zod.z.string().describe("The subreddit name (without r/ prefix)")
1167
+ }),
1168
+ execute: async (args) => {
1169
+ const client = getRedditClient();
1170
+ if (!client) {
1171
+ throw new Error("Reddit client not initialized");
1172
+ }
1173
+ const subreddit = await client.getSubredditInfo(args.subreddit_name);
1084
1174
  const formattedSubreddit = formatSubredditInfo(subreddit);
1085
- return {
1086
- content: [
1087
- {
1088
- type: "text",
1089
- text: `
1090
- # Subreddit Information: r/${formattedSubreddit.name}
1175
+ return `# Subreddit Information: r/${formattedSubreddit.name}
1091
1176
 
1092
1177
  ## Overview
1093
1178
  - Name: r/${formattedSubreddit.name}
@@ -1113,582 +1198,327 @@ ${formattedSubreddit.description.full}
1113
1198
  - ${formattedSubreddit.communityAnalysis.replace(/\n - /g, "\n- ")}
1114
1199
 
1115
1200
  ## Engagement Tips
1116
- - ${formattedSubreddit.engagementTips.replace(/\n - /g, "\n- ")}
1117
- `
1118
- }
1119
- ]
1120
- };
1121
- } catch (error) {
1122
- throw new import_types3.McpError(import_types3.ErrorCode.InternalError, `Failed to fetch subreddit data: ${String(error)}`);
1201
+ - ${formattedSubreddit.engagementTips.replace(/\n - /g, "\n- ")}`;
1123
1202
  }
1124
- }
1125
- async function getTrendingSubreddits() {
1126
- const client = getRedditClient();
1127
- if (!client) {
1128
- throw new import_types3.McpError(import_types3.ErrorCode.InternalError, "Reddit client not initialized");
1129
- }
1130
- try {
1203
+ });
1204
+ server.addTool({
1205
+ name: "get_trending_subreddits",
1206
+ description: "Get a list of currently trending subreddits",
1207
+ parameters: import_zod.z.object({}),
1208
+ execute: async () => {
1209
+ const client = getRedditClient();
1210
+ if (!client) {
1211
+ throw new Error("Reddit client not initialized");
1212
+ }
1131
1213
  const trendingSubreddits = await client.getTrendingSubreddits();
1132
- return {
1133
- content: [
1134
- {
1135
- type: "text",
1136
- text: `
1137
- # Trending Subreddits
1214
+ return `# Trending Subreddits
1138
1215
 
1139
- ${trendingSubreddits.map((subreddit, index) => `${index + 1}. r/${subreddit}`).join("\n")}
1140
- `
1141
- }
1142
- ]
1143
- };
1144
- } catch (error) {
1145
- throw new import_types3.McpError(import_types3.ErrorCode.InternalError, `Failed to fetch trending subreddits: ${String(error)}`);
1216
+ ${trendingSubreddits.map((subreddit, index) => `${index + 1}. r/${subreddit}`).join("\n")}`;
1146
1217
  }
1147
- }
1148
-
1149
- // src/tools/search-tools.ts
1150
- var import_types4 = require("@modelcontextprotocol/sdk/types.js");
1151
- async function searchReddit(params) {
1152
- const { query, subreddit, sort = "relevance", time_filter = "all", limit = 10, type = "link" } = params;
1153
- const client = getRedditClient();
1154
- if (!client) {
1155
- throw new import_types4.McpError(import_types4.ErrorCode.InternalError, "Reddit client not initialized");
1156
- }
1157
- if (!query || query.trim().length === 0) {
1158
- throw new import_types4.McpError(import_types4.ErrorCode.InvalidParams, "Search query cannot be empty");
1159
- }
1160
- try {
1161
- const posts = await client.searchReddit(query, {
1162
- subreddit,
1163
- sort,
1164
- timeFilter: time_filter,
1165
- limit,
1166
- type
1218
+ });
1219
+ server.addTool({
1220
+ name: "search_reddit",
1221
+ description: "Search Reddit for posts and content across subreddits",
1222
+ parameters: import_zod.z.object({
1223
+ query: import_zod.z.string().describe("Search query"),
1224
+ subreddit: import_zod.z.string().optional().describe("Limit search to specific subreddit (without r/ prefix)"),
1225
+ sort: import_zod.z.enum(["relevance", "hot", "top", "new", "comments"]).default("relevance").describe("Sort order"),
1226
+ time_filter: import_zod.z.enum(["hour", "day", "week", "month", "year", "all"]).default("all").describe("Time filter"),
1227
+ limit: import_zod.z.number().min(1).max(100).default(10).describe("Number of results"),
1228
+ type: import_zod.z.enum(["link", "sr", "user"]).default("link").describe("Type of content to search")
1229
+ }),
1230
+ execute: async (args) => {
1231
+ const client = getRedditClient();
1232
+ if (!client) {
1233
+ throw new Error("Reddit client not initialized");
1234
+ }
1235
+ if (!args.query || args.query.trim() === "") {
1236
+ throw new Error("Search query cannot be empty");
1237
+ }
1238
+ const posts = await client.searchReddit(args.query, {
1239
+ subreddit: args.subreddit,
1240
+ sort: args.sort,
1241
+ timeFilter: args.time_filter,
1242
+ limit: args.limit,
1243
+ type: args.type
1167
1244
  });
1168
- return {
1169
- content: [
1170
- {
1171
- type: "text",
1172
- text: `# Reddit Search Results for: "${query}"${subreddit ? ` in r/${subreddit}` : ""}
1173
-
1174
- ## Search Parameters
1175
- - Sort: ${sort}
1176
- - Time Filter: ${time_filter}
1177
- - Type: ${type}
1178
- - Results: ${posts.length}
1245
+ if (posts.length === 0) {
1246
+ const searchLocation2 = args.subreddit ? ` in r/${args.subreddit}` : "";
1247
+ return `No results found for "${args.query}"${searchLocation2}.`;
1248
+ }
1249
+ const searchResults = posts.map((post, index) => {
1250
+ const flags = [...post.over18 ? ["**NSFW**"] : [], ...post.spoiler ? ["**Spoiler**"] : []];
1251
+ return `### ${index + 1}. ${post.title} ${flags.join(" ")}
1252
+ - Subreddit: r/${post.subreddit}
1253
+ - Author: u/${post.author}
1254
+ - Score: ${post.score.toLocaleString()} (${(post.upvoteRatio * 100).toFixed(1)}% upvoted)
1255
+ - Comments: ${post.numComments.toLocaleString()}
1256
+ - Posted: ${new Date(post.createdUtc * 1e3).toLocaleString()}
1257
+ - Link: https://reddit.com${post.permalink}`;
1258
+ }).join("\n\n");
1259
+ const searchLocation = args.subreddit ? ` in r/${args.subreddit}` : "";
1260
+ return `# Reddit Search Results for: "${args.query}"${searchLocation}
1179
1261
 
1180
- ${posts.map((post, index) => {
1181
- const formatted = formatPost(post);
1182
- return `### ${index + 1}. ${formatted.title}
1183
- - Author: u/${formatted.author}
1184
- - Subreddit: r/${formatted.subreddit}
1185
- - Score: ${formatted.score} (${formatted.upvoteRatio}% upvoted)
1186
- - Comments: ${formatted.numComments}
1187
- - Posted: ${formatted.createdAt}
1188
- ${formatted.selftext ? `
1189
- ${formatted.selftext.substring(0, 200)}${formatted.selftext.length > 200 ? "..." : ""}
1190
- ` : ""}
1191
- - Link: https://reddit.com${formatted.permalink}
1192
- ${formatted.nsfw ? "- **NSFW**" : ""}
1193
- ${formatted.spoiler ? "- **Spoiler**" : ""}
1194
- `;
1195
- }).join("\n")}`
1196
- }
1197
- ]
1198
- };
1199
- } catch (error) {
1200
- throw new import_types4.McpError(import_types4.ErrorCode.InternalError, `Failed to search Reddit: ${String(error)}`);
1201
- }
1202
- }
1262
+ Sorted by: ${args.sort} | Time: ${args.time_filter} | Type: ${args.type}
1203
1263
 
1204
- // src/tools/comment-tools.ts
1205
- var import_types5 = require("@modelcontextprotocol/sdk/types.js");
1206
- async function getPostComments(params) {
1207
- const { post_id, subreddit, sort = "best", limit = 100 } = params;
1208
- const client = getRedditClient();
1209
- if (!client) {
1210
- throw new import_types5.McpError(import_types5.ErrorCode.InternalError, "Reddit client not initialized");
1211
- }
1212
- if (!post_id || !subreddit) {
1213
- throw new import_types5.McpError(import_types5.ErrorCode.InvalidParams, "post_id and subreddit are required");
1264
+ ${searchResults}`;
1214
1265
  }
1215
- try {
1216
- const { post, comments } = await client.getPostComments(post_id, subreddit, {
1217
- sort,
1218
- limit
1219
- });
1220
- const formattedPost = formatPost(post);
1221
- const formatComment = (comment) => {
1222
- const edited = comment.edited ? " *(edited)*" : "";
1223
- const submitter = comment.isSubmitter ? " **[OP]**" : "";
1224
- const depth = comment.depth || 0;
1225
- const prefix = " ".repeat(depth) + (depth > 0 ? "\u2514\u2500 " : "");
1226
- return `${prefix}**u/${comment.author}**${submitter} \u2022 ${comment.score} points \u2022 ${new Date(comment.createdUtc * 1e3).toLocaleString()}${edited}
1227
- ${prefix}${comment.body.split("\n").join(`
1228
- ${prefix}`)}`;
1229
- };
1230
- return {
1231
- content: [
1232
- {
1233
- type: "text",
1234
- text: `# Comments for: ${formattedPost.title}
1266
+ });
1267
+ server.addTool({
1268
+ name: "create_post",
1269
+ description: "Create a new post in a subreddit (requires REDDIT_USERNAME and REDDIT_PASSWORD)",
1270
+ parameters: import_zod.z.object({
1271
+ subreddit: import_zod.z.string().describe("The subreddit name (without r/ prefix)"),
1272
+ title: import_zod.z.string().describe("The post title"),
1273
+ content: import_zod.z.string().describe("The post content (text for self posts, URL for link posts)"),
1274
+ is_self: import_zod.z.boolean().default(true).describe("Whether this is a self post (text) or link post")
1275
+ }),
1276
+ execute: async (args) => {
1277
+ const client = getRedditClient();
1278
+ if (!client) {
1279
+ throw new Error("Reddit client not initialized");
1280
+ }
1281
+ if (!process.env.REDDIT_USERNAME || !process.env.REDDIT_PASSWORD) {
1282
+ throw new Error(
1283
+ "User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables."
1284
+ );
1285
+ }
1286
+ const post = await client.createPost(args.subreddit, args.title, args.content, args.is_self);
1287
+ const formattedPost = formatPostInfo(post);
1288
+ return `# Post Created Successfully
1235
1289
 
1236
1290
  ## Post Details
1237
- - Author: u/${formattedPost.author}
1291
+ - Title: ${formattedPost.title}
1238
1292
  - Subreddit: r/${formattedPost.subreddit}
1239
- - Score: ${formattedPost.score} (${formattedPost.upvoteRatio}% upvoted)
1240
- - Posted: ${formattedPost.createdAt}
1241
- - Link: https://reddit.com${formattedPost.permalink}
1293
+ - Type: ${formattedPost.type}
1294
+ - Link: ${formattedPost.links.fullPost}
1242
1295
 
1243
- ## Post Content
1244
- ${formattedPost.selftext || "[Link post - no text content]"}
1296
+ Your post has been successfully submitted to r/${formattedPost.subreddit}.`;
1297
+ }
1298
+ });
1299
+ server.addTool({
1300
+ name: "reply_to_post",
1301
+ description: "Post a reply to an existing Reddit post or comment (requires REDDIT_USERNAME and REDDIT_PASSWORD)",
1302
+ parameters: import_zod.z.object({
1303
+ post_id: import_zod.z.string().describe("The Reddit post ID (thing_id, e.g., t3_xxxxx for posts, t1_xxxxx for comments)"),
1304
+ content: import_zod.z.string().describe("The reply content")
1305
+ }),
1306
+ execute: async (args) => {
1307
+ const client = getRedditClient();
1308
+ if (!client) {
1309
+ throw new Error("Reddit client not initialized");
1310
+ }
1311
+ if (!process.env.REDDIT_USERNAME || !process.env.REDDIT_PASSWORD) {
1312
+ throw new Error(
1313
+ "User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables."
1314
+ );
1315
+ }
1316
+ const comment = await client.replyToPost(args.post_id, args.content);
1317
+ return `# Reply Posted Successfully
1245
1318
 
1246
- ## Comments (${comments.length} loaded, sorted by ${sort})
1319
+ ## Comment Details
1320
+ - Posted to: ${args.post_id}
1321
+ - Author: u/${process.env.REDDIT_USERNAME}
1322
+ - Comment ID: ${comment.id}
1247
1323
 
1248
- ${comments.map((comment) => formatComment(comment)).join("\n\n---\n\n")}`
1249
- }
1250
- ]
1251
- };
1252
- } catch (error) {
1253
- throw new import_types5.McpError(import_types5.ErrorCode.InternalError, `Failed to fetch comments: ${String(error)}`);
1324
+ Your reply has been successfully posted.`;
1254
1325
  }
1255
- }
1326
+ });
1327
+ server.addTool({
1328
+ name: "delete_post",
1329
+ description: "Delete your own Reddit post (requires REDDIT_USERNAME and REDDIT_PASSWORD). WARNING: This action is permanent and cannot be undone!",
1330
+ parameters: import_zod.z.object({
1331
+ thing_id: import_zod.z.string().describe(
1332
+ "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."
1333
+ )
1334
+ }),
1335
+ execute: async (args) => {
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
+ await client.deletePost(args.thing_id);
1346
+ return `# Post Deleted Successfully
1256
1347
 
1257
- // src/index.ts
1258
- var import_dotenv = __toESM(require("dotenv"));
1259
- import_dotenv.default.config();
1260
- var RedditServer = class {
1261
- server;
1262
- constructor() {
1263
- this.server = new import_server.Server(
1264
- {
1265
- name: "reddit-mcp-server",
1266
- version: "0.1.0"
1267
- },
1268
- {
1269
- capabilities: {
1270
- tools: {},
1271
- logging: {}
1272
- }
1273
- }
1274
- );
1275
- this.initializeRedditClient();
1276
- this.setupToolHandlers();
1277
- this.server.onerror = async (error) => {
1278
- await this.server.sendLoggingMessage({
1279
- level: "error",
1280
- logger: "reddit-server",
1281
- data: `Server error: ${error}`
1282
- });
1283
- };
1284
- process.on("SIGINT", async () => {
1285
- await this.server.close();
1286
- process.exit(0);
1287
- });
1348
+ The post ${args.thing_id} has been permanently deleted from Reddit.
1349
+
1350
+ **Note**: This action cannot be undone. The post content has been removed and cannot be recovered.`;
1288
1351
  }
1289
- initializeRedditClient() {
1290
- const clientId = process.env.REDDIT_CLIENT_ID;
1291
- const clientSecret = process.env.REDDIT_CLIENT_SECRET;
1292
- const userAgent = process.env.REDDIT_USER_AGENT || "RedditMCPServer/0.1.0";
1293
- const username = process.env.REDDIT_USERNAME;
1294
- const password = process.env.REDDIT_PASSWORD;
1295
- if (!clientId || !clientSecret) {
1296
- console.error(
1297
- "[Error] Missing required Reddit API credentials. Please set REDDIT_CLIENT_ID and REDDIT_CLIENT_SECRET environment variables."
1352
+ });
1353
+ server.addTool({
1354
+ name: "delete_comment",
1355
+ description: "Delete your own Reddit comment (requires REDDIT_USERNAME and REDDIT_PASSWORD). WARNING: This action is permanent and cannot be undone!",
1356
+ parameters: import_zod.z.object({
1357
+ thing_id: import_zod.z.string().describe(
1358
+ "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."
1359
+ )
1360
+ }),
1361
+ execute: async (args) => {
1362
+ const client = getRedditClient();
1363
+ if (!client) {
1364
+ throw new Error("Reddit client not initialized");
1365
+ }
1366
+ if (!process.env.REDDIT_USERNAME || !process.env.REDDIT_PASSWORD) {
1367
+ throw new Error(
1368
+ "User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables."
1298
1369
  );
1299
- process.exit(1);
1300
1370
  }
1301
- try {
1302
- initializeRedditClient({
1303
- clientId,
1304
- clientSecret,
1305
- userAgent,
1306
- username,
1307
- password
1308
- });
1309
- console.error("[Setup] Reddit client initialized");
1310
- if (username && password) {
1311
- console.error(`[Setup] Authenticated as user: ${username}`);
1312
- } else {
1313
- console.error("[Setup] Running in read-only mode (no user authentication)");
1314
- }
1315
- } catch (error) {
1316
- console.error("[Error] Failed to initialize Reddit client:", error);
1317
- process.exit(1);
1371
+ await client.deleteComment(args.thing_id);
1372
+ return `# Comment Deleted Successfully
1373
+
1374
+ The comment ${args.thing_id} has been permanently deleted from Reddit.
1375
+
1376
+ **Note**: This action cannot be undone. The comment content has been removed and cannot be recovered.`;
1377
+ }
1378
+ });
1379
+ server.addTool({
1380
+ name: "edit_post",
1381
+ 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.",
1382
+ parameters: import_zod.z.object({
1383
+ thing_id: import_zod.z.string().describe(
1384
+ "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."
1385
+ ),
1386
+ new_text: import_zod.z.string().describe("The new text content for the post. Supports Reddit markdown formatting.")
1387
+ }),
1388
+ execute: async (args) => {
1389
+ const client = getRedditClient();
1390
+ if (!client) {
1391
+ throw new Error("Reddit client not initialized");
1392
+ }
1393
+ if (!process.env.REDDIT_USERNAME || !process.env.REDDIT_PASSWORD) {
1394
+ throw new Error(
1395
+ "User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables."
1396
+ );
1318
1397
  }
1398
+ await client.editPost(args.thing_id, args.new_text);
1399
+ return `# Post Edited Successfully
1400
+
1401
+ The post ${args.thing_id} has been updated with your new content.
1402
+
1403
+ **Note**:
1404
+ - Only self (text) posts can be edited
1405
+ - Post titles cannot be edited
1406
+ - Link posts cannot be edited
1407
+ - An "edited" marker will appear on your post`;
1319
1408
  }
1320
- setupToolHandlers() {
1321
- this.server.setRequestHandler(import_types6.ListToolsRequestSchema, async () => ({
1322
- tools: [
1323
- {
1324
- name: "test_reddit_mcp_server",
1325
- description: "Test the Reddit MCP Server",
1326
- inputSchema: {
1327
- type: "object",
1328
- properties: {
1329
- // No input parameters, this will just return a test message
1330
- }
1331
- }
1332
- },
1333
- {
1334
- name: "get_reddit_post",
1335
- description: "Get a Reddit post",
1336
- inputSchema: {
1337
- type: "object",
1338
- properties: {
1339
- subreddit: {
1340
- type: "string",
1341
- description: "The subreddit to fetch posts from"
1342
- },
1343
- post_id: {
1344
- type: "string",
1345
- description: "The ID of the post to fetch"
1346
- }
1347
- },
1348
- required: ["subreddit", "post_id"]
1349
- }
1350
- },
1351
- {
1352
- name: "get_top_posts",
1353
- description: "Get top posts from a subreddit",
1354
- inputSchema: {
1355
- type: "object",
1356
- properties: {
1357
- subreddit: {
1358
- type: "string",
1359
- description: "Name of the subreddit"
1360
- },
1361
- time_filter: {
1362
- type: "string",
1363
- description: "Time period to filter posts (e.g. 'day', 'week', 'month', 'year', 'all')",
1364
- enum: ["day", "week", "month", "year", "all"],
1365
- default: "week"
1366
- },
1367
- limit: {
1368
- type: "integer",
1369
- description: "Number of posts to fetch",
1370
- default: 10
1371
- }
1372
- },
1373
- required: ["subreddit"]
1374
- }
1375
- },
1376
- {
1377
- name: "get_user_info",
1378
- description: "Get information about a Reddit user",
1379
- inputSchema: {
1380
- type: "object",
1381
- properties: {
1382
- username: {
1383
- type: "string",
1384
- description: "The username of the Reddit user to get info for"
1385
- }
1386
- },
1387
- required: ["username"]
1388
- }
1389
- },
1390
- {
1391
- name: "get_subreddit_info",
1392
- description: "Get information about a subreddit",
1393
- inputSchema: {
1394
- type: "object",
1395
- properties: {
1396
- subreddit_name: {
1397
- type: "string",
1398
- description: "Name of the subreddit"
1399
- }
1400
- },
1401
- required: ["subreddit_name"]
1402
- }
1403
- },
1404
- {
1405
- name: "get_trending_subreddits",
1406
- description: "Get currently trending subreddits",
1407
- inputSchema: {
1408
- type: "object",
1409
- properties: {}
1410
- }
1411
- },
1412
- {
1413
- name: "create_post",
1414
- description: "Create a new post in a subreddit",
1415
- inputSchema: {
1416
- type: "object",
1417
- properties: {
1418
- subreddit: {
1419
- type: "string",
1420
- description: "Name of the subreddit to post in"
1421
- },
1422
- title: {
1423
- type: "string",
1424
- description: "Title of the post"
1425
- },
1426
- content: {
1427
- type: "string",
1428
- description: "Content of the post (text for self posts, URL for link posts)"
1429
- },
1430
- is_self: {
1431
- type: "boolean",
1432
- description: "Whether this is a self (text) post (true) or link post (false)",
1433
- default: true
1434
- }
1435
- },
1436
- required: ["subreddit", "title", "content"]
1437
- }
1438
- },
1439
- {
1440
- name: "reply_to_post",
1441
- description: "Post a reply to an existing Reddit post",
1442
- inputSchema: {
1443
- type: "object",
1444
- properties: {
1445
- post_id: {
1446
- type: "string",
1447
- description: "The ID of the post to reply to"
1448
- },
1449
- content: {
1450
- type: "string",
1451
- description: "The content of the reply"
1452
- },
1453
- subreddit: {
1454
- type: "string",
1455
- description: "The subreddit name if known (for validation)"
1456
- }
1457
- },
1458
- required: ["post_id", "content"]
1459
- }
1460
- },
1461
- {
1462
- name: "search_reddit",
1463
- description: "Search for posts on Reddit",
1464
- inputSchema: {
1465
- type: "object",
1466
- properties: {
1467
- query: {
1468
- type: "string",
1469
- description: "The search query"
1470
- },
1471
- subreddit: {
1472
- type: "string",
1473
- description: "Search within a specific subreddit (optional)"
1474
- },
1475
- sort: {
1476
- type: "string",
1477
- description: "Sort order: relevance, hot, top, new, comments",
1478
- enum: ["relevance", "hot", "top", "new", "comments"],
1479
- default: "relevance"
1480
- },
1481
- time_filter: {
1482
- type: "string",
1483
- description: "Time filter: hour, day, week, month, year, all",
1484
- enum: ["hour", "day", "week", "month", "year", "all"],
1485
- default: "all"
1486
- },
1487
- limit: {
1488
- type: "number",
1489
- description: "Maximum number of results to return",
1490
- minimum: 1,
1491
- maximum: 100,
1492
- default: 10
1493
- },
1494
- type: {
1495
- type: "string",
1496
- description: "Type of content: link (posts), sr (subreddits), user (users)",
1497
- enum: ["link", "sr", "user"],
1498
- default: "link"
1499
- }
1500
- },
1501
- required: ["query"]
1502
- }
1503
- },
1504
- {
1505
- name: "get_post_comments",
1506
- description: "Get comments for a specific Reddit post",
1507
- inputSchema: {
1508
- type: "object",
1509
- properties: {
1510
- post_id: {
1511
- type: "string",
1512
- description: "The ID of the post"
1513
- },
1514
- subreddit: {
1515
- type: "string",
1516
- description: "The subreddit where the post is located"
1517
- },
1518
- sort: {
1519
- type: "string",
1520
- description: "Comment sort order: best, top, new, controversial, old, qa",
1521
- enum: ["best", "top", "new", "controversial", "old", "qa"],
1522
- default: "best"
1523
- },
1524
- limit: {
1525
- type: "number",
1526
- description: "Maximum number of comments to load",
1527
- minimum: 1,
1528
- maximum: 500,
1529
- default: 100
1530
- }
1531
- },
1532
- required: ["post_id", "subreddit"]
1533
- }
1534
- },
1535
- {
1536
- name: "get_user_posts",
1537
- description: "Get posts submitted by a specific user",
1538
- inputSchema: {
1539
- type: "object",
1540
- properties: {
1541
- username: {
1542
- type: "string",
1543
- description: "The username to get posts for"
1544
- },
1545
- sort: {
1546
- type: "string",
1547
- description: "Sort order: new, hot, top, controversial",
1548
- enum: ["new", "hot", "top", "controversial"],
1549
- default: "new"
1550
- },
1551
- time_filter: {
1552
- type: "string",
1553
- description: "Time filter for top/controversial: hour, day, week, month, year, all",
1554
- enum: ["hour", "day", "week", "month", "year", "all"],
1555
- default: "all"
1556
- },
1557
- limit: {
1558
- type: "number",
1559
- description: "Maximum number of posts to return",
1560
- minimum: 1,
1561
- maximum: 100,
1562
- default: 10
1563
- }
1564
- },
1565
- required: ["username"]
1566
- }
1567
- },
1568
- {
1569
- name: "get_user_comments",
1570
- description: "Get comments made by a specific user",
1571
- inputSchema: {
1572
- type: "object",
1573
- properties: {
1574
- username: {
1575
- type: "string",
1576
- description: "The username to get comments for"
1577
- },
1578
- sort: {
1579
- type: "string",
1580
- description: "Sort order: new, hot, top, controversial",
1581
- enum: ["new", "hot", "top", "controversial"],
1582
- default: "new"
1583
- },
1584
- time_filter: {
1585
- type: "string",
1586
- description: "Time filter for top/controversial: hour, day, week, month, year, all",
1587
- enum: ["hour", "day", "week", "month", "year", "all"],
1588
- default: "all"
1589
- },
1590
- limit: {
1591
- type: "number",
1592
- description: "Maximum number of comments to return",
1593
- minimum: 1,
1594
- maximum: 100,
1595
- default: 10
1596
- }
1597
- },
1598
- required: ["username"]
1599
- }
1600
- }
1601
- ]
1602
- }));
1603
- this.server.setRequestHandler(import_types6.CallToolRequestSchema, async (request) => {
1604
- try {
1605
- const toolName = request.params.name;
1606
- const toolParams = request.params.arguments || {};
1607
- await this.server.sendLoggingMessage({
1608
- level: "debug",
1609
- logger: "reddit-server",
1610
- data: `Tool call: ${toolName}`
1611
- });
1612
- switch (toolName) {
1613
- case "test_reddit_mcp_server":
1614
- return {
1615
- content: [
1616
- {
1617
- type: "text",
1618
- text: "Hello, world! The Reddit MCP Server is working correctly."
1619
- }
1620
- ]
1621
- };
1622
- case "get_reddit_post":
1623
- return await getRedditPost(toolParams);
1624
- case "get_top_posts":
1625
- return await getTopPosts(
1626
- toolParams
1627
- );
1628
- case "get_user_info":
1629
- return await getUserInfo(toolParams);
1630
- case "get_subreddit_info":
1631
- return await getSubredditInfo(toolParams);
1632
- case "get_trending_subreddits":
1633
- return await getTrendingSubreddits();
1634
- case "create_post":
1635
- return await createPost(
1636
- toolParams
1637
- );
1638
- case "reply_to_post":
1639
- return await replyToPost(
1640
- toolParams
1641
- );
1642
- case "search_reddit":
1643
- return await searchReddit(
1644
- toolParams
1645
- );
1646
- case "get_post_comments":
1647
- return await getPostComments(
1648
- toolParams
1649
- );
1650
- case "get_user_posts":
1651
- return await getUserPosts(
1652
- toolParams
1653
- );
1654
- case "get_user_comments":
1655
- return await getUserComments(
1656
- toolParams
1657
- );
1658
- default:
1659
- throw new import_types6.McpError(import_types6.ErrorCode.MethodNotFound, `Tool with name ${toolName} not found`);
1660
- }
1661
- } catch (error) {
1662
- if (error instanceof Error) {
1663
- await this.server.sendLoggingMessage({
1664
- level: "error",
1665
- logger: "reddit-server",
1666
- data: `Error calling tool: ${error.message}`
1667
- });
1668
- throw new import_types6.McpError(import_types6.ErrorCode.InternalError, `Failed to fetch data: ${error.message}`);
1669
- }
1670
- throw error;
1671
- }
1672
- });
1409
+ });
1410
+ server.addTool({
1411
+ name: "edit_comment",
1412
+ description: "Edit your own Reddit comment (requires REDDIT_USERNAME and REDDIT_PASSWORD). Update the text content of a comment you previously posted.",
1413
+ parameters: import_zod.z.object({
1414
+ thing_id: import_zod.z.string().describe(
1415
+ "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."
1416
+ ),
1417
+ new_text: import_zod.z.string().describe("The new text content for the comment. Supports Reddit markdown formatting.")
1418
+ }),
1419
+ execute: async (args) => {
1420
+ const client = getRedditClient();
1421
+ if (!client) {
1422
+ throw new Error("Reddit client not initialized");
1423
+ }
1424
+ if (!process.env.REDDIT_USERNAME || !process.env.REDDIT_PASSWORD) {
1425
+ throw new Error(
1426
+ "User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables."
1427
+ );
1428
+ }
1429
+ await client.editComment(args.thing_id, args.new_text);
1430
+ return `# Comment Edited Successfully
1431
+
1432
+ The comment ${args.thing_id} has been updated with your new content.
1433
+
1434
+ **Note**: An "edited" marker will appear on your comment to show it has been modified.`;
1673
1435
  }
1674
- async run() {
1675
- const transport = new import_stdio.StdioServerTransport();
1676
- await this.server.connect(transport);
1677
- await this.server.sendLoggingMessage({
1678
- level: "info",
1679
- logger: "reddit-server",
1680
- data: "Reddit MCP Server is running"
1681
- });
1682
- const username = process.env.REDDIT_USERNAME;
1683
- const password = process.env.REDDIT_PASSWORD;
1684
- await this.server.sendLoggingMessage({
1685
- level: "info",
1686
- logger: "reddit-server",
1687
- data: username && password ? `Authenticated as user: ${username}` : "Running in read-only mode (no user authentication)"
1436
+ });
1437
+ server.addTool({
1438
+ name: "get_post_comments",
1439
+ description: "Get comments from a specific Reddit post",
1440
+ parameters: import_zod.z.object({
1441
+ post_id: import_zod.z.string().describe("The Reddit post ID"),
1442
+ subreddit: import_zod.z.string().describe("The subreddit name (without r/ prefix)"),
1443
+ sort: import_zod.z.enum(["best", "top", "new", "controversial", "old", "qa"]).default("best").describe("Comment sort order"),
1444
+ limit: import_zod.z.number().min(1).max(500).default(100).describe("Maximum number of comments to retrieve")
1445
+ }),
1446
+ execute: async (args) => {
1447
+ const client = getRedditClient();
1448
+ if (!client) {
1449
+ throw new Error("Reddit client not initialized");
1450
+ }
1451
+ if (!args.post_id || !args.subreddit) {
1452
+ throw new Error("post_id and subreddit are required");
1453
+ }
1454
+ const data = await client.getPostComments(args.post_id, args.subreddit, {
1455
+ sort: args.sort,
1456
+ limit: args.limit
1688
1457
  });
1458
+ const post = data.post;
1459
+ const comments = data.comments;
1460
+ let response = `# Comments for: ${post.title}
1461
+
1462
+ **Post by u/${post.author} in r/${post.subreddit}**
1463
+ - Score: ${post.score.toLocaleString()} | Comments: ${post.numComments.toLocaleString()}
1464
+ - Posted: ${new Date(post.createdUtc * 1e3).toLocaleString()}
1465
+
1466
+ ---
1467
+
1468
+ `;
1469
+ if (comments.length === 0) {
1470
+ response += "No comments found for this post.";
1471
+ return response;
1472
+ }
1473
+ const commentSummaries = comments.map((comment) => {
1474
+ const indent = "\u2514\u2500".repeat(Math.min(comment.depth || 0, 3));
1475
+ const authorBadge = comment.isSubmitter ? " **[OP]**" : "";
1476
+ const editedBadge = comment.edited ? " *(edited)*" : "";
1477
+ return `${indent} **u/${comment.author}**${authorBadge}${editedBadge} (${comment.score.toLocaleString()} points)
1478
+
1479
+ ${comment.body}
1480
+
1481
+ ---`;
1482
+ }).join("\n\n");
1483
+ response += commentSummaries;
1484
+ return response;
1689
1485
  }
1690
- };
1691
- // Annotate the CommonJS export names for ESM import in node:
1692
- 0 && (module.exports = {
1693
- RedditServer
1694
1486
  });
1487
+ async function main() {
1488
+ try {
1489
+ await setupRedditClient();
1490
+ const useStdio = process.env.TRANSPORT_TYPE === "stdio";
1491
+ const port = parseInt(process.env.PORT || "3000");
1492
+ const host = process.env.HOST || "0.0.0.0";
1493
+ if (useStdio) {
1494
+ console.error("[Setup] Starting in stdio mode (CLI/npx)");
1495
+ await server.start({
1496
+ transportType: "stdio"
1497
+ });
1498
+ } else {
1499
+ console.error(`[Setup] Starting HTTP server on ${host}:${port}`);
1500
+ await server.start({
1501
+ transportType: "httpStream",
1502
+ httpStream: {
1503
+ port,
1504
+ host,
1505
+ endpoint: "/mcp"
1506
+ }
1507
+ });
1508
+ console.error(`[Setup] HTTP server ready at http://${host}:${port}/mcp`);
1509
+ console.error(`[Setup] SSE endpoint available at http://${host}:${port}/sse`);
1510
+ }
1511
+ } catch (error) {
1512
+ console.error("[Error] Failed to start server:", error);
1513
+ process.exit(1);
1514
+ }
1515
+ }
1516
+ process.on("SIGINT", async () => {
1517
+ console.error("[Shutdown] Shutting down Reddit MCP Server...");
1518
+ process.exit(0);
1519
+ });
1520
+ process.on("SIGTERM", async () => {
1521
+ console.error("[Shutdown] Shutting down Reddit MCP Server...");
1522
+ process.exit(0);
1523
+ });
1524
+ main().catch(console.error);