reddit-mcp-server 1.2.1 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,10 +1,7 @@
1
- const require_chunk = require('./chunk-kSYXY2_d.js');
2
- let fastmcp = require("fastmcp");
3
- let zod = require("zod");
4
- let crypto = require("crypto");
5
- crypto = require_chunk.__toESM(crypto);
6
- let dotenv = require("dotenv");
7
- dotenv = require_chunk.__toESM(dotenv);
1
+ import crypto from "crypto";
2
+ import dotenv from "dotenv";
3
+ import { FastMCP } from "fastmcp";
4
+ import { z } from "zod";
8
5
 
9
6
  //#region src/client/reddit-client.ts
10
7
  var RedditClient = class {
@@ -21,7 +18,8 @@ var RedditClient = class {
21
18
  hasCredentials;
22
19
  safeMode;
23
20
  lastWriteTime = 0;
24
- recentContentHashes = /* @__PURE__ */ new Set();
21
+ recentContentRecords = [];
22
+ botDisclosure;
25
23
  constructor(config) {
26
24
  this.clientId = config.clientId;
27
25
  this.clientSecret = config.clientSecret;
@@ -38,6 +36,10 @@ var RedditClient = class {
38
36
  duplicateCheck: false,
39
37
  maxRecentHashes: 10
40
38
  };
39
+ this.botDisclosure = config.botDisclosure || {
40
+ enabled: false,
41
+ footer: ""
42
+ };
41
43
  }
42
44
  determineBaseUrl() {
43
45
  switch (this.authMode) {
@@ -87,8 +89,8 @@ var RedditClient = class {
87
89
  if (this.accessToken && now < this.tokenExpiry) return;
88
90
  const authUrl = "https://www.reddit.com/api/v1/access_token";
89
91
  const authData = new URLSearchParams();
90
- const username = this.username;
91
- const password = this.password;
92
+ const { username } = this;
93
+ const { password } = this;
92
94
  if (!!(username && password)) {
93
95
  authData.append("grant_type", "password");
94
96
  authData.append("username", username);
@@ -114,7 +116,7 @@ var RedditClient = class {
114
116
  this.authenticated = true;
115
117
  } catch (error) {
116
118
  if (error instanceof Error) throw error;
117
- throw new Error("Failed to authenticate with Reddit API");
119
+ throw new Error("Failed to authenticate with Reddit API", { cause: error });
118
120
  }
119
121
  }
120
122
  async checkAuthentication() {
@@ -143,23 +145,31 @@ var RedditClient = class {
143
145
  this.lastWriteTime = Date.now();
144
146
  }
145
147
  hashContent(content) {
146
- return crypto.default.createHash("md5").update(content.trim().toLowerCase()).digest("hex");
148
+ return crypto.createHash("sha256").update(content.trim().toLowerCase()).digest("hex");
147
149
  }
148
- checkDuplicateContent(content) {
150
+ checkDuplicateContent(content, subreddit) {
149
151
  if (!this.safeMode.enabled || !this.safeMode.duplicateCheck) return;
150
152
  const hash = this.hashContent(content);
151
- if (this.recentContentHashes.has(hash)) throw new Error("Duplicate content detected. Reddit's spam filter may ban your account for posting identical content. Please modify your content and try again.");
152
- this.recentContentHashes.add(hash);
153
- if (this.recentContentHashes.size > this.safeMode.maxRecentHashes) {
154
- const first = this.recentContentHashes.values().next().value;
155
- if (first) this.recentContentHashes.delete(first);
153
+ for (const record of this.recentContentRecords) if (record.hash === hash) {
154
+ if (subreddit && record.subreddit && subreddit !== record.subreddit) throw new Error("Cross-subreddit duplicate detected. Reddit's Responsible Builder Policy prohibits posting identical or substantially similar content across multiple subreddits. Please create unique content for each subreddit.");
155
+ throw new Error("Duplicate content detected. Reddit's spam filter may ban your account for posting identical content. Please modify your content and try again.");
156
156
  }
157
+ this.recentContentRecords.push({
158
+ hash,
159
+ subreddit: subreddit || "",
160
+ timestamp: Date.now()
161
+ });
162
+ while (this.recentContentRecords.length > this.safeMode.maxRecentHashes) this.recentContentRecords.shift();
163
+ }
164
+ appendBotDisclosure(content) {
165
+ if (!this.botDisclosure.enabled || !this.botDisclosure.footer) return content;
166
+ return `${content}${this.botDisclosure.footer}`;
157
167
  }
158
168
  async getUser(username) {
159
169
  try {
160
170
  const response = await this.makeRequest(`/user/${username}/about.json`);
161
171
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
162
- const data = (await response.json()).data;
172
+ const { data } = await response.json();
163
173
  return {
164
174
  name: data.name,
165
175
  id: data.id,
@@ -180,7 +190,7 @@ var RedditClient = class {
180
190
  try {
181
191
  const response = await this.makeRequest(`/r/${subredditName}/about.json`);
182
192
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
183
- const data = (await response.json()).data;
193
+ const { data } = await response.json();
184
194
  return {
185
195
  displayName: data.display_name,
186
196
  title: data.title,
@@ -278,7 +288,8 @@ var RedditClient = class {
278
288
  async createPost(subreddit, title, content, isSelf = true) {
279
289
  this.validateWriteAccess();
280
290
  await this.enforceWriteRateLimit();
281
- this.checkDuplicateContent(title + content);
291
+ this.checkDuplicateContent(title + content, subreddit);
292
+ const finalContent = isSelf ? this.appendBotDisclosure(content) : content;
282
293
  try {
283
294
  var _json$json, _json$json2, _json$json3;
284
295
  const kind = isSelf ? "self" : "link";
@@ -286,37 +297,25 @@ var RedditClient = class {
286
297
  params.append("sr", subreddit);
287
298
  params.append("kind", kind);
288
299
  params.append("title", title);
289
- params.append(isSelf ? "text" : "url", content);
300
+ params.append(isSelf ? "text" : "url", finalContent);
290
301
  params.append("api_type", "json");
291
302
  const response = await this.makeRequest("/api/submit", {
292
303
  method: "POST",
293
304
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
294
305
  body: params.toString()
295
306
  });
296
- if (!response.ok) {
297
- const errorText = await response.text();
298
- console.error(`[Reddit API] Create post failed: ${response.status} ${response.statusText}`);
299
- console.error(`[Reddit API] Error response: ${errorText}`);
300
- throw new Error(`HTTP ${response.status}: ${errorText}`);
301
- }
307
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
302
308
  const json = await response.json();
303
- console.error(`[Reddit API] Create post response:`, JSON.stringify(json, null, 2));
304
309
  if (((_json$json = json.json) === null || _json$json === void 0 ? void 0 : _json$json.errors) && json.json.errors.length > 0) {
305
- const errors = json.json.errors.map((e) => e.join(": ")).join(", ");
306
- console.error(`[Reddit API] Post creation errors: ${errors}`);
310
+ const errors = json.json.errors.map((e) => e[1] || e[0]).join(", ");
307
311
  throw new Error(`Reddit API errors: ${errors}`);
308
312
  }
309
313
  const postId = ((_json$json2 = json.json) === null || _json$json2 === void 0 || (_json$json2 = _json$json2.data) === null || _json$json2 === void 0 ? void 0 : _json$json2.id) || ((_json$json3 = json.json) === null || _json$json3 === void 0 || (_json$json3 = _json$json3.data) === null || _json$json3 === void 0 || (_json$json3 = _json$json3.name) === null || _json$json3 === void 0 ? void 0 : _json$json3.replace("t3_", ""));
310
- if (!postId) {
311
- console.error(`[Reddit API] No post ID in response`);
312
- throw new Error("No post ID returned from Reddit");
313
- }
314
- console.error(`[Reddit API] Post created with ID: ${postId}`);
314
+ if (!postId) throw new Error("No post ID returned from Reddit");
315
315
  return await this.getPost(postId, subreddit);
316
316
  } catch (error) {
317
- console.error(`[Reddit API] Create post exception:`, error);
318
- if (error instanceof Error && error.message.includes("HTTP")) throw error;
319
- throw new Error(`Failed to create post in ${subreddit}: ${error instanceof Error ? error.message : String(error)}`);
317
+ if (error instanceof Error) throw error;
318
+ throw new Error(`Failed to create post in ${subreddit}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
320
319
  }
321
320
  }
322
321
  async checkPostExists(postId) {
@@ -332,26 +331,22 @@ var RedditClient = class {
332
331
  this.validateWriteAccess();
333
332
  await this.enforceWriteRateLimit();
334
333
  this.checkDuplicateContent(content);
334
+ const finalContent = this.appendBotDisclosure(content);
335
335
  try {
336
336
  var _json$json4, _json$json5;
337
- if (!await this.checkPostExists(postId)) throw new Error(`Post with ID ${postId} does not exist or is not accessible`);
337
+ const fullThingId = postId.startsWith("t3_") || postId.startsWith("t1_") ? postId : `t3_${postId}`;
338
+ if (!postId.startsWith("t1_") && !await this.checkPostExists(postId.replace(/^t3_/, ""))) throw new Error(`Post with ID ${postId} does not exist or is not accessible`);
338
339
  const params = new URLSearchParams();
339
- params.append("thing_id", `t3_${postId}`);
340
- params.append("text", content);
340
+ params.append("thing_id", fullThingId);
341
+ params.append("text", finalContent);
341
342
  params.append("api_type", "json");
342
343
  const response = await this.makeRequest("/api/comment", {
343
344
  method: "POST",
344
345
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
345
346
  body: params.toString()
346
347
  });
347
- if (!response.ok) {
348
- const errorText = await response.text();
349
- console.error(`[Reddit API] Reply to post failed: ${response.status} ${response.statusText}`);
350
- console.error(`[Reddit API] Error response: ${errorText}`);
351
- throw new Error(`HTTP ${response.status}: ${errorText}`);
352
- }
348
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
353
349
  const json = await response.json();
354
- console.error(`[Reddit API] Reply response:`, JSON.stringify(json, null, 2));
355
350
  if (((_json$json4 = json.json) === null || _json$json4 === void 0 || (_json$json4 = _json$json4.data) === null || _json$json4 === void 0 ? void 0 : _json$json4.things) && json.json.data.things.length > 0) {
356
351
  const commentData = json.json.data.things[0].data;
357
352
  const author = this.username ?? "[unknown]";
@@ -369,17 +364,12 @@ var RedditClient = class {
369
364
  permalink: commentData.permalink
370
365
  };
371
366
  } else if (((_json$json5 = json.json) === null || _json$json5 === void 0 ? void 0 : _json$json5.errors) && json.json.errors.length > 0) {
372
- const errors = json.json.errors.map((e) => e.join(": ")).join(", ");
373
- console.error(`[Reddit API] Reply errors: ${errors}`);
367
+ const errors = json.json.errors.map((e) => e[1] || e[0]).join(", ");
374
368
  throw new Error(`Reddit API errors: ${errors}`);
375
- } else {
376
- console.error(`[Reddit API] Unexpected reply response format`);
377
- throw new Error("Failed to parse reply response");
378
- }
369
+ } else throw new Error("Failed to parse reply response");
379
370
  } catch (error) {
380
- console.error(`[Reddit API] Reply to post exception:`, error);
381
- if (error instanceof Error && error.message.includes("HTTP")) throw error;
382
- throw new Error(`Failed to reply to post ${postId}: ${error instanceof Error ? error.message : String(error)}`);
371
+ if (error instanceof Error) throw error;
372
+ throw new Error(`Failed to reply to post ${postId}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
383
373
  }
384
374
  }
385
375
  async deletePost(thingId) {
@@ -404,7 +394,7 @@ var RedditClient = class {
404
394
  } catch (error) {
405
395
  console.error(`[Reddit API] Delete exception:`, error);
406
396
  if (error instanceof Error && error.message.includes("HTTP")) throw error;
407
- throw new Error(`Failed to delete content ${thingId}: ${error instanceof Error ? error.message : String(error)}`);
397
+ throw new Error(`Failed to delete content ${thingId}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
408
398
  }
409
399
  }
410
400
  async deleteComment(thingId) {
@@ -415,37 +405,29 @@ var RedditClient = class {
415
405
  this.validateWriteAccess();
416
406
  await this.enforceWriteRateLimit();
417
407
  this.checkDuplicateContent(newText);
408
+ const finalText = this.appendBotDisclosure(newText);
418
409
  try {
419
410
  var _json$json6;
420
411
  const fullThingId = thingId.startsWith("t3_") || thingId.startsWith("t1_") ? thingId : `t3_${thingId}`;
421
412
  const params = new URLSearchParams();
422
413
  params.append("thing_id", fullThingId);
423
- params.append("text", newText);
414
+ params.append("text", finalText);
424
415
  params.append("api_type", "json");
425
416
  const response = await this.makeRequest("/api/editusertext", {
426
417
  method: "POST",
427
418
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
428
419
  body: params.toString()
429
420
  });
430
- if (!response.ok) {
431
- const errorText = await response.text();
432
- console.error(`[Reddit API] Edit failed: ${response.status} ${response.statusText}`);
433
- console.error(`[Reddit API] Error response: ${errorText}`);
434
- throw new Error(`HTTP ${response.status}: ${errorText}`);
435
- }
421
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
436
422
  const json = await response.json();
437
- console.error(`[Reddit API] Edit response:`, JSON.stringify(json, null, 2));
438
423
  if (((_json$json6 = json.json) === null || _json$json6 === void 0 ? void 0 : _json$json6.errors) && json.json.errors.length > 0) {
439
- const errors = json.json.errors.map((e) => e.join(": ")).join(", ");
440
- console.error(`[Reddit API] Edit errors: ${errors}`);
424
+ const errors = json.json.errors.map((e) => e[1] || e[0]).join(", ");
441
425
  throw new Error(`Reddit API errors: ${errors}`);
442
426
  }
443
- console.error(`[Reddit API] Successfully edited ${fullThingId}`);
444
427
  return true;
445
428
  } catch (error) {
446
- console.error(`[Reddit API] Edit exception:`, error);
447
- if (error instanceof Error && error.message.includes("HTTP")) throw error;
448
- throw new Error(`Failed to edit content ${thingId}: ${error instanceof Error ? error.message : String(error)}`);
429
+ if (error instanceof Error) throw error;
430
+ throw new Error(`Failed to edit content ${thingId}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
449
431
  }
450
432
  }
451
433
  async editComment(thingId, newText) {
@@ -540,7 +522,7 @@ var RedditClient = class {
540
522
  depth,
541
523
  parentId: item.data.parent_id
542
524
  });
543
- const replies = item.data.replies;
525
+ const { replies } = item.data;
544
526
  if (replies && typeof replies !== "string" && ((_replies$data = replies.data) === null || _replies$data === void 0 ? void 0 : _replies$data.children)) parseComments(replies.data.children, depth + 1);
545
527
  }
546
528
  };
@@ -734,7 +716,7 @@ function formatPostInfo(post) {
734
716
  return {
735
717
  title: post.title,
736
718
  type: contentType,
737
- content: content.length > 300 ? content.substring(0, 297) + "..." : content,
719
+ content: content.length > 300 ? `${content.substring(0, 297)}...` : content,
738
720
  author: post.author,
739
721
  subreddit: post.subreddit,
740
722
  stats: {
@@ -769,7 +751,7 @@ function formatSubredditInfo(subreddit) {
769
751
  },
770
752
  description: {
771
753
  short: subreddit.publicDescription,
772
- full: subreddit.description.length > 300 ? subreddit.description.substring(0, 297) + "..." : subreddit.description
754
+ full: subreddit.description.length > 300 ? `${subreddit.description.substring(0, 297)}...` : subreddit.description
773
755
  },
774
756
  metadata: {
775
757
  created: formatTimestamp(subreddit.createdUtc),
@@ -786,8 +768,8 @@ function formatSubredditInfo(subreddit) {
786
768
 
787
769
  //#endregion
788
770
  //#region src/index.ts
789
- dotenv.default.config();
790
- const VERSION = "1.2.1";
771
+ dotenv.config();
772
+ const VERSION = "1.4.0";
791
773
  function validateUserAgent(userAgent, username) {
792
774
  if (!/^[\w-]+:[\w-]+:[\d.]+ \(by \/u\/\w+\)$/.test(userAgent)) {
793
775
  console.error("[Warning] User-Agent does not follow Reddit's recommended format");
@@ -806,8 +788,8 @@ function buildUserAgent(customAgent, username) {
806
788
  console.error(`[Setup] Auto-generated User-Agent: ${autoAgent}`);
807
789
  return autoAgent;
808
790
  }
809
- const fallbackAgent = `RedditMCPServer/${VERSION}`;
810
- validateUserAgent(fallbackAgent);
791
+ const fallbackAgent = `typescript:reddit-mcp-server:${VERSION} (by /u/anonymous)`;
792
+ console.error("[Setup] No REDDIT_USERNAME set — using anonymous User-Agent. Set REDDIT_USERNAME for a personalized agent.");
811
793
  return fallbackAgent;
812
794
  }
813
795
  function buildSafeModeConfig(safeMode) {
@@ -842,7 +824,7 @@ async function setupRedditClient() {
842
824
  const username = process.env.REDDIT_USERNAME;
843
825
  const password = process.env.REDDIT_PASSWORD;
844
826
  const authMode = process.env.REDDIT_AUTH_MODE || "auto";
845
- const safeMode = process.env.REDDIT_SAFE_MODE || "off";
827
+ const safeMode = process.env.REDDIT_SAFE_MODE || "standard";
846
828
  if (![
847
829
  "auto",
848
830
  "authenticated",
@@ -868,6 +850,11 @@ async function setupRedditClient() {
868
850
  const hasCredentials = !!(clientId && clientSecret);
869
851
  const userAgent = buildUserAgent(customUserAgent, username);
870
852
  const safeModeConfig = buildSafeModeConfig(safeMode);
853
+ const botDisclosureMode = process.env.REDDIT_BOT_DISCLOSURE || "off";
854
+ const botDisclosureConfig = {
855
+ enabled: botDisclosureMode === "auto",
856
+ footer: botDisclosureMode === "auto" ? process.env.REDDIT_BOT_FOOTER || "\n\n---\n^(🤖 I am a bot | Built with) [^reddit-mcp-server](https://github.com/jordanburke/reddit-mcp-server)" : ""
857
+ };
871
858
  try {
872
859
  const client = initializeRedditClient({
873
860
  clientId: clientId || "",
@@ -876,7 +863,8 @@ async function setupRedditClient() {
876
863
  username,
877
864
  password,
878
865
  authMode,
879
- safeMode: safeModeConfig
866
+ safeMode: safeModeConfig,
867
+ botDisclosure: botDisclosureConfig
880
868
  });
881
869
  console.error("[Setup] Reddit client initialized");
882
870
  console.error(`[Setup] Authentication mode: ${authMode}`);
@@ -904,9 +892,11 @@ async function setupRedditClient() {
904
892
  console.error(`[Setup] ✓ Safe mode enabled: ${safeModeConfig.mode}`);
905
893
  console.error(`[Setup] - Write delay: ${safeModeConfig.writeDelayMs}ms between operations`);
906
894
  console.error(`[Setup] - Duplicate detection: enabled (tracking last ${safeModeConfig.maxRecentHashes} items)`);
907
- } else {
908
- console.error("[Setup] Safe mode: off (no write operation safeguards)");
909
- console.error("[Setup] Consider enabling REDDIT_SAFE_MODE=standard for spam protection");
895
+ } else console.error("[Setup] Safe mode: off (explicitly disabled — ensure compliance with Reddit's Responsible Builder Policy)");
896
+ if (botDisclosureConfig.enabled) console.error("[Setup] Bot disclosure: enabled (automated content will include bot footer)");
897
+ else {
898
+ console.error("[Setup] Bot disclosure: off");
899
+ console.error("[Setup] For Reddit policy compliance, consider REDDIT_BOT_DISCLOSURE=auto");
910
900
  }
911
901
  } catch (error) {
912
902
  console.error("[Error] ✗ Reddit API connection failed:", error instanceof Error ? error.message : error);
@@ -914,7 +904,9 @@ async function setupRedditClient() {
914
904
  process.exit(1);
915
905
  }
916
906
  }
917
- const server = new fastmcp.FastMCP({
907
+ const oauthToken = process.env.OAUTH_TOKEN || crypto.randomBytes(32).toString("hex");
908
+ if (process.env.OAUTH_ENABLED === "true" && !process.env.OAUTH_TOKEN) console.error(`[Auth] Generated OAuth token: ${oauthToken}`);
909
+ const server = new FastMCP({
918
910
  name: "reddit-mcp-server",
919
911
  version: VERSION,
920
912
  instructions: `A comprehensive Reddit MCP server that provides tools for interacting with Reddit API.
@@ -928,26 +920,29 @@ Available capabilities:
928
920
  - Delete your own posts and comments (with authentication)
929
921
  - Analyze engagement metrics and community insights
930
922
 
931
- For write operations (posting, replying, editing, deleting), ensure REDDIT_USERNAME and REDDIT_PASSWORD are configured.`,
923
+ For write operations (posting, replying, editing, deleting), ensure REDDIT_USERNAME and REDDIT_PASSWORD are configured.
924
+
925
+ IMPORTANT - Reddit Responsible Builder Policy compliance:
926
+ - Data retrieved via these tools must NOT be used for AI model training without Reddit's written approval
927
+ - Data must NOT be sold, licensed, or commercially redistributed
928
+ - Do NOT attempt to de-anonymize or re-identify Reddit users
929
+ - Do NOT post identical or substantially similar content across multiple subreddits
930
+ - Do NOT use these tools to manipulate votes, karma, or circumvent Reddit safety mechanisms
931
+ - All bot-generated content must clearly disclose its automated nature
932
+ - Bots must NOT send private/direct messages without explicit user consent
933
+ For details: https://support.reddithelp.com/hc/en-us/articles/42728983564564-Responsible-Builder-Policy`,
932
934
  ...process.env.OAUTH_ENABLED === "true" && { authenticate: async (request) => {
933
935
  const authHeader = request.headers.authorization;
934
- const expectedToken = process.env.OAUTH_TOKEN;
935
- if (!expectedToken) {
936
- const token = Array.from({ length: 32 }, () => "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".charAt(Math.floor(Math.random() * 62))).join("");
937
- console.log(`[Auth] Generated OAuth token: ${token}`);
938
- throw new Response(JSON.stringify({
939
- error: "No OAuth token configured",
940
- generatedToken: token
941
- }), {
942
- status: 401,
943
- headers: { "Content-Type": "application/json" }
944
- });
945
- }
946
936
  if (!(authHeader === null || authHeader === void 0 ? void 0 : authHeader.startsWith("Bearer "))) throw new Response(null, {
947
937
  status: 401,
948
938
  statusText: "Missing or invalid Authorization header"
949
939
  });
950
- if (authHeader.slice(7) !== expectedToken) throw new Response(null, {
940
+ const token = authHeader.slice(7);
941
+ const tokenBuffer = Buffer.from(token);
942
+ const expectedBuffer = Buffer.from(oauthToken);
943
+ const tokenHash = crypto.createHash("sha256").update(tokenBuffer).digest();
944
+ const expectedHash = crypto.createHash("sha256").update(expectedBuffer).digest();
945
+ if (!crypto.timingSafeEqual(tokenHash, expectedHash)) throw new Response(null, {
951
946
  status: 403,
952
947
  statusText: "Invalid token"
953
948
  });
@@ -957,7 +952,7 @@ For write operations (posting, replying, editing, deleting), ensure REDDIT_USERN
957
952
  server.addTool({
958
953
  name: "test_reddit_mcp_server",
959
954
  description: "Test the Reddit MCP Server connection and configuration",
960
- parameters: zod.z.object({}),
955
+ parameters: z.object({}),
961
956
  execute: async () => {
962
957
  const client = getRedditClient();
963
958
  const hasAuth = client ? "✓" : "✗";
@@ -974,7 +969,7 @@ Ready to handle Reddit API requests!`;
974
969
  server.addTool({
975
970
  name: "get_user_info",
976
971
  description: "Get detailed information about a Reddit user including karma, account status, and activity analysis",
977
- parameters: zod.z.object({ username: zod.z.string().describe("The Reddit username (without u/ prefix)") }),
972
+ parameters: z.object({ username: z.string().describe("The Reddit username (without u/ prefix)") }),
978
973
  execute: async (args) => {
979
974
  const client = getRedditClient();
980
975
  if (!client) throw new Error("Reddit client not initialized");
@@ -992,7 +987,7 @@ server.addTool({
992
987
  - Profile URL: ${formattedUser.profileUrl}
993
988
 
994
989
  ## Activity Analysis
995
- - ${formattedUser.activityAnalysis.replace(/\n - /g, "\n- ")}
990
+ - ${formattedUser.activityAnalysis.replace(/\n {2}- /g, "\n- ")}
996
991
 
997
992
  ## Recommendations
998
993
  - ${formattedUser.recommendations.replace(/\n {2}- /g, "\n- ")}`;
@@ -1001,14 +996,14 @@ server.addTool({
1001
996
  server.addTool({
1002
997
  name: "get_user_posts",
1003
998
  description: "Get recent posts by a Reddit user with sorting and filtering options",
1004
- parameters: zod.z.object({
1005
- username: zod.z.string().describe("The Reddit username (without u/ prefix)"),
1006
- sort: zod.z.enum([
999
+ parameters: z.object({
1000
+ username: z.string().describe("The Reddit username (without u/ prefix)"),
1001
+ sort: z.enum([
1007
1002
  "new",
1008
1003
  "hot",
1009
1004
  "top"
1010
1005
  ]).default("new").describe("Sort order for posts"),
1011
- time_filter: zod.z.enum([
1006
+ time_filter: z.enum([
1012
1007
  "hour",
1013
1008
  "day",
1014
1009
  "week",
@@ -1016,7 +1011,7 @@ server.addTool({
1016
1011
  "year",
1017
1012
  "all"
1018
1013
  ]).default("all").describe("Time filter for top posts"),
1019
- limit: zod.z.number().min(1).max(100).default(10).describe("Number of posts to retrieve")
1014
+ limit: z.number().min(1).max(100).default(10).describe("Number of posts to retrieve")
1020
1015
  }),
1021
1016
  execute: async (args) => {
1022
1017
  const client = getRedditClient();
@@ -1044,14 +1039,14 @@ ${postSummaries}`;
1044
1039
  server.addTool({
1045
1040
  name: "get_user_comments",
1046
1041
  description: "Get recent comments by a Reddit user with sorting and filtering options",
1047
- parameters: zod.z.object({
1048
- username: zod.z.string().describe("The Reddit username (without u/ prefix)"),
1049
- sort: zod.z.enum([
1042
+ parameters: z.object({
1043
+ username: z.string().describe("The Reddit username (without u/ prefix)"),
1044
+ sort: z.enum([
1050
1045
  "new",
1051
1046
  "hot",
1052
1047
  "top"
1053
1048
  ]).default("new").describe("Sort order for comments"),
1054
- time_filter: zod.z.enum([
1049
+ time_filter: z.enum([
1055
1050
  "hour",
1056
1051
  "day",
1057
1052
  "week",
@@ -1059,7 +1054,7 @@ server.addTool({
1059
1054
  "year",
1060
1055
  "all"
1061
1056
  ]).default("all").describe("Time filter for top comments"),
1062
- limit: zod.z.number().min(1).max(100).default(10).describe("Number of comments to retrieve")
1057
+ limit: z.number().min(1).max(100).default(10).describe("Number of comments to retrieve")
1063
1058
  }),
1064
1059
  execute: async (args) => {
1065
1060
  const client = getRedditClient();
@@ -1071,7 +1066,7 @@ server.addTool({
1071
1066
  });
1072
1067
  if (comments.length === 0) return `No comments found for u/${args.username} with the specified filters.`;
1073
1068
  const commentSummaries = comments.map((comment, index) => {
1074
- const truncatedBody = comment.body.length > 300 ? comment.body.substring(0, 300) + "..." : comment.body;
1069
+ const truncatedBody = comment.body.length > 300 ? `${comment.body.substring(0, 300)}...` : comment.body;
1075
1070
  const flags = [...comment.edited ? ["*(edited)*"] : [], ...comment.isSubmitter ? ["**OP**"] : []];
1076
1071
  return `### ${index + 1}. Comment ${flags.join(" ")}
1077
1072
  In r/${comment.subreddit} on "${comment.submissionTitle}"
@@ -1090,9 +1085,9 @@ ${commentSummaries}`;
1090
1085
  server.addTool({
1091
1086
  name: "get_reddit_post",
1092
1087
  description: "Get detailed information about a specific Reddit post including content, stats, and engagement analysis",
1093
- parameters: zod.z.object({
1094
- subreddit: zod.z.string().describe("The subreddit name (without r/ prefix)"),
1095
- post_id: zod.z.string().describe("The Reddit post ID")
1088
+ parameters: z.object({
1089
+ subreddit: z.string().describe("The subreddit name (without r/ prefix)"),
1090
+ post_id: z.string().describe("The Reddit post ID")
1096
1091
  }),
1097
1092
  execute: async (args) => {
1098
1093
  const client = getRedditClient();
@@ -1123,7 +1118,7 @@ ${formattedPost.content}
1123
1118
  - Short Link: ${formattedPost.links.shortLink}
1124
1119
 
1125
1120
  ## Engagement Analysis
1126
- - ${formattedPost.engagementAnalysis.replace(/\n - /g, "\n- ")}
1121
+ - ${formattedPost.engagementAnalysis.replace(/\n {2}- /g, "\n- ")}
1127
1122
 
1128
1123
  ## Best Time to Engage
1129
1124
  ${formattedPost.bestTimeToEngage}`;
@@ -1132,9 +1127,9 @@ ${formattedPost.bestTimeToEngage}`;
1132
1127
  server.addTool({
1133
1128
  name: "get_top_posts",
1134
1129
  description: "Get top posts from a subreddit or from the Reddit home feed",
1135
- parameters: zod.z.object({
1136
- subreddit: zod.z.string().optional().describe("The subreddit name (without r/ prefix). Leave empty for home feed"),
1137
- time_filter: zod.z.enum([
1130
+ parameters: z.object({
1131
+ subreddit: z.string().optional().describe("The subreddit name (without r/ prefix). Leave empty for home feed"),
1132
+ time_filter: z.enum([
1138
1133
  "hour",
1139
1134
  "day",
1140
1135
  "week",
@@ -1142,7 +1137,7 @@ server.addTool({
1142
1137
  "year",
1143
1138
  "all"
1144
1139
  ]).default("week").describe("Time period for top posts"),
1145
- limit: zod.z.number().min(1).max(100).default(10).describe("Number of posts to retrieve")
1140
+ limit: z.number().min(1).max(100).default(10).describe("Number of posts to retrieve")
1146
1141
  }),
1147
1142
  execute: async (args) => {
1148
1143
  const client = getRedditClient();
@@ -1163,7 +1158,7 @@ ${postSummaries}`;
1163
1158
  server.addTool({
1164
1159
  name: "get_subreddit_info",
1165
1160
  description: "Get detailed information about a subreddit including description, stats, and community analysis",
1166
- parameters: zod.z.object({ subreddit_name: zod.z.string().describe("The subreddit name (without r/ prefix)") }),
1161
+ parameters: z.object({ subreddit_name: z.string().describe("The subreddit name (without r/ prefix)") }),
1167
1162
  execute: async (args) => {
1168
1163
  const client = getRedditClient();
1169
1164
  if (!client) throw new Error("Reddit client not initialized");
@@ -1191,16 +1186,16 @@ ${formattedSubreddit.description.full}
1191
1186
  - Wiki: ${formattedSubreddit.links.wiki}
1192
1187
 
1193
1188
  ## Community Analysis
1194
- - ${formattedSubreddit.communityAnalysis.replace(/\n - /g, "\n- ")}
1189
+ - ${formattedSubreddit.communityAnalysis.replace(/\n {2}- /g, "\n- ")}
1195
1190
 
1196
1191
  ## Engagement Tips
1197
- - ${formattedSubreddit.engagementTips.replace(/\n - /g, "\n- ")}`;
1192
+ - ${formattedSubreddit.engagementTips.replace(/\n {2}- /g, "\n- ")}`;
1198
1193
  }
1199
1194
  });
1200
1195
  server.addTool({
1201
1196
  name: "get_trending_subreddits",
1202
1197
  description: "Get a list of currently trending subreddits",
1203
- parameters: zod.z.object({}),
1198
+ parameters: z.object({}),
1204
1199
  execute: async () => {
1205
1200
  const client = getRedditClient();
1206
1201
  if (!client) throw new Error("Reddit client not initialized");
@@ -1212,17 +1207,17 @@ ${(await client.getTrendingSubreddits()).map((subreddit, index) => `${index + 1}
1212
1207
  server.addTool({
1213
1208
  name: "search_reddit",
1214
1209
  description: "Search Reddit for posts and content across subreddits",
1215
- parameters: zod.z.object({
1216
- query: zod.z.string().describe("Search query"),
1217
- subreddit: zod.z.string().optional().describe("Limit search to specific subreddit (without r/ prefix)"),
1218
- sort: zod.z.enum([
1210
+ parameters: z.object({
1211
+ query: z.string().describe("Search query"),
1212
+ subreddit: z.string().optional().describe("Limit search to specific subreddit (without r/ prefix)"),
1213
+ sort: z.enum([
1219
1214
  "relevance",
1220
1215
  "hot",
1221
1216
  "top",
1222
1217
  "new",
1223
1218
  "comments"
1224
1219
  ]).default("relevance").describe("Sort order"),
1225
- time_filter: zod.z.enum([
1220
+ time_filter: z.enum([
1226
1221
  "hour",
1227
1222
  "day",
1228
1223
  "week",
@@ -1230,8 +1225,8 @@ server.addTool({
1230
1225
  "year",
1231
1226
  "all"
1232
1227
  ]).default("all").describe("Time filter"),
1233
- limit: zod.z.number().min(1).max(100).default(10).describe("Number of results"),
1234
- type: zod.z.enum([
1228
+ limit: z.number().min(1).max(100).default(10).describe("Number of results"),
1229
+ type: z.enum([
1235
1230
  "link",
1236
1231
  "sr",
1237
1232
  "user"
@@ -1273,11 +1268,11 @@ ${searchResults}`;
1273
1268
  server.addTool({
1274
1269
  name: "create_post",
1275
1270
  description: "Create a new post in a subreddit (requires REDDIT_USERNAME and REDDIT_PASSWORD). WARNING: Rapid posting or duplicate content may trigger Reddit's spam detection and result in account bans. Consider enabling REDDIT_SAFE_MODE=standard for protection.",
1276
- parameters: zod.z.object({
1277
- subreddit: zod.z.string().describe("The subreddit name (without r/ prefix)"),
1278
- title: zod.z.string().describe("The post title"),
1279
- content: zod.z.string().describe("The post content (text for self posts, URL for link posts)"),
1280
- is_self: zod.z.boolean().default(true).describe("Whether this is a self post (text) or link post")
1271
+ parameters: z.object({
1272
+ subreddit: z.string().describe("The subreddit name (without r/ prefix)"),
1273
+ title: z.string().describe("The post title"),
1274
+ content: z.string().describe("The post content (text for self posts, URL for link posts)"),
1275
+ is_self: z.boolean().default(true).describe("Whether this is a self post (text) or link post")
1281
1276
  }),
1282
1277
  execute: async (args) => {
1283
1278
  const client = getRedditClient();
@@ -1298,9 +1293,9 @@ Your post has been successfully submitted to r/${formattedPost.subreddit}.`;
1298
1293
  server.addTool({
1299
1294
  name: "reply_to_post",
1300
1295
  description: "Post a reply to an existing Reddit post or comment (requires REDDIT_USERNAME and REDDIT_PASSWORD). WARNING: Rapid commenting or duplicate content may trigger Reddit's spam detection. Enable REDDIT_SAFE_MODE=standard for rate limiting and duplicate detection.",
1301
- parameters: zod.z.object({
1302
- post_id: zod.z.string().describe("The Reddit post ID (thing_id, e.g., t3_xxxxx for posts, t1_xxxxx for comments)"),
1303
- content: zod.z.string().describe("The reply content")
1296
+ parameters: z.object({
1297
+ post_id: z.string().describe("The Reddit post ID (thing_id, e.g., t3_xxxxx for posts, t1_xxxxx for comments)"),
1298
+ content: z.string().describe("The reply content")
1304
1299
  }),
1305
1300
  execute: async (args) => {
1306
1301
  const client = getRedditClient();
@@ -1320,7 +1315,7 @@ Your reply has been successfully posted.`;
1320
1315
  server.addTool({
1321
1316
  name: "delete_post",
1322
1317
  description: "Delete your own Reddit post (requires REDDIT_USERNAME and REDDIT_PASSWORD). WARNING: This action is permanent and cannot be undone!",
1323
- parameters: zod.z.object({ thing_id: zod.z.string().describe("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.") }),
1318
+ parameters: z.object({ thing_id: z.string().describe("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.") }),
1324
1319
  execute: async (args) => {
1325
1320
  const client = getRedditClient();
1326
1321
  if (!client) throw new Error("Reddit client not initialized");
@@ -1336,7 +1331,7 @@ The post ${args.thing_id} has been permanently deleted from Reddit.
1336
1331
  server.addTool({
1337
1332
  name: "delete_comment",
1338
1333
  description: "Delete your own Reddit comment (requires REDDIT_USERNAME and REDDIT_PASSWORD). WARNING: This action is permanent and cannot be undone!",
1339
- parameters: zod.z.object({ thing_id: zod.z.string().describe("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.") }),
1334
+ parameters: z.object({ thing_id: z.string().describe("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.") }),
1340
1335
  execute: async (args) => {
1341
1336
  const client = getRedditClient();
1342
1337
  if (!client) throw new Error("Reddit client not initialized");
@@ -1352,9 +1347,9 @@ The comment ${args.thing_id} has been permanently deleted from Reddit.
1352
1347
  server.addTool({
1353
1348
  name: "edit_post",
1354
1349
  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. WARNING: Rapid edits may trigger spam detection. Enable REDDIT_SAFE_MODE for protection.",
1355
- parameters: zod.z.object({
1356
- thing_id: zod.z.string().describe("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."),
1357
- new_text: zod.z.string().describe("The new text content for the post. Supports Reddit markdown formatting.")
1350
+ parameters: z.object({
1351
+ thing_id: z.string().describe("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."),
1352
+ new_text: z.string().describe("The new text content for the post. Supports Reddit markdown formatting.")
1358
1353
  }),
1359
1354
  execute: async (args) => {
1360
1355
  const client = getRedditClient();
@@ -1375,9 +1370,9 @@ The post ${args.thing_id} has been updated with your new content.
1375
1370
  server.addTool({
1376
1371
  name: "edit_comment",
1377
1372
  description: "Edit your own Reddit comment (requires REDDIT_USERNAME and REDDIT_PASSWORD). Update the text content of a comment you previously posted. WARNING: Rapid edits may trigger spam detection. Enable REDDIT_SAFE_MODE for protection.",
1378
- parameters: zod.z.object({
1379
- thing_id: zod.z.string().describe("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."),
1380
- new_text: zod.z.string().describe("The new text content for the comment. Supports Reddit markdown formatting.")
1373
+ parameters: z.object({
1374
+ thing_id: z.string().describe("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."),
1375
+ new_text: z.string().describe("The new text content for the comment. Supports Reddit markdown formatting.")
1381
1376
  }),
1382
1377
  execute: async (args) => {
1383
1378
  const client = getRedditClient();
@@ -1394,10 +1389,10 @@ The comment ${args.thing_id} has been updated with your new content.
1394
1389
  server.addTool({
1395
1390
  name: "get_post_comments",
1396
1391
  description: "Get comments from a specific Reddit post",
1397
- parameters: zod.z.object({
1398
- post_id: zod.z.string().describe("The Reddit post ID"),
1399
- subreddit: zod.z.string().describe("The subreddit name (without r/ prefix)"),
1400
- sort: zod.z.enum([
1392
+ parameters: z.object({
1393
+ post_id: z.string().describe("The Reddit post ID"),
1394
+ subreddit: z.string().describe("The subreddit name (without r/ prefix)"),
1395
+ sort: z.enum([
1401
1396
  "best",
1402
1397
  "top",
1403
1398
  "new",
@@ -1405,7 +1400,7 @@ server.addTool({
1405
1400
  "old",
1406
1401
  "qa"
1407
1402
  ]).default("best").describe("Comment sort order"),
1408
- limit: zod.z.number().min(1).max(500).default(100).describe("Maximum number of comments to retrieve")
1403
+ limit: z.number().min(1).max(500).default(100).describe("Maximum number of comments to retrieve")
1409
1404
  }),
1410
1405
  execute: async (args) => {
1411
1406
  const client = getRedditClient();
@@ -1415,8 +1410,8 @@ server.addTool({
1415
1410
  sort: args.sort,
1416
1411
  limit: args.limit
1417
1412
  });
1418
- const post = data.post;
1419
- const comments = data.comments;
1413
+ const { post } = data;
1414
+ const { comments } = data;
1420
1415
  let response = `# Comments for: ${post.title}
1421
1416
 
1422
1417
  **Post by u/${post.author} in r/${post.subreddit}**
@@ -1449,7 +1444,7 @@ async function main() {
1449
1444
  await setupRedditClient();
1450
1445
  const useHttp = process.env.TRANSPORT_TYPE === "httpStream" || process.env.TRANSPORT_TYPE === "http";
1451
1446
  const port = parseInt(process.env.PORT || "3000");
1452
- const host = process.env.HOST || "0.0.0.0";
1447
+ const host = process.env.HOST || "127.0.0.1";
1453
1448
  if (useHttp) {
1454
1449
  console.error(`[Setup] Starting HTTP server on ${host}:${port}`);
1455
1450
  await server.start({
@@ -1482,4 +1477,5 @@ process.on("SIGTERM", async () => {
1482
1477
  main().catch(console.error);
1483
1478
 
1484
1479
  //#endregion
1480
+ export { };
1485
1481
  //# sourceMappingURL=index.js.map