reddit-mcp-server 1.3.2 → 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/README.md CHANGED
@@ -20,6 +20,8 @@ A Model Context Protocol (MCP) server for interacting with Reddit - fetch posts,
20
20
  | **Edit Posts/Comments** | :white_check_mark: | :x: |
21
21
  | **Delete Posts/Comments** | :white_check_mark: | :x: |
22
22
  | **Spam Protection (Safe Mode)** | :white_check_mark: | :x: |
23
+ | **Bot Disclosure Footer** | :white_check_mark: | :x: |
24
+ | **Policy Compliance Built-in** | :white_check_mark: | :x: |
23
25
  | Browse Subreddits | :white_check_mark: | :white_check_mark: |
24
26
  | Search Reddit | :white_check_mark: | :white_check_mark: |
25
27
  | User Analysis | :white_check_mark: | :white_check_mark: |
@@ -91,15 +93,17 @@ claude mcp add --transport stdio reddit -- npx reddit-mcp-server
91
93
 
92
94
  ### Environment Variables
93
95
 
94
- | Variable | Required | Default | Description |
95
- | ---------------------- | -------- | -------------- | --------------------------------------------------------- |
96
- | `REDDIT_CLIENT_ID` | No\* | - | Reddit app client ID |
97
- | `REDDIT_CLIENT_SECRET` | No\* | - | Reddit app client secret |
98
- | `REDDIT_USERNAME` | No | - | Reddit username (for write operations) |
99
- | `REDDIT_PASSWORD` | No | - | Reddit password (for write operations) |
100
- | `REDDIT_USER_AGENT` | No | Auto-generated | Custom User-Agent string |
101
- | `REDDIT_AUTH_MODE` | No | `auto` | Authentication mode: `auto`, `authenticated`, `anonymous` |
102
- | `REDDIT_SAFE_MODE` | No | `off` | Write safeguards: `off`, `standard`, `strict` |
96
+ | Variable | Required | Default | Description |
97
+ | ----------------------- | -------- | -------------- | --------------------------------------------------------- |
98
+ | `REDDIT_CLIENT_ID` | No\* | - | Reddit app client ID |
99
+ | `REDDIT_CLIENT_SECRET` | No\* | - | Reddit app client secret |
100
+ | `REDDIT_USERNAME` | No | - | Reddit username (for write operations) |
101
+ | `REDDIT_PASSWORD` | No | - | Reddit password (for write operations) |
102
+ | `REDDIT_USER_AGENT` | No | Auto-generated | Custom User-Agent string |
103
+ | `REDDIT_AUTH_MODE` | No | `auto` | Authentication mode: `auto`, `authenticated`, `anonymous` |
104
+ | `REDDIT_SAFE_MODE` | No | `standard` | Write safeguards: `off`, `standard`, `strict` |
105
+ | `REDDIT_BOT_DISCLOSURE` | No | `off` | Bot disclosure footer: `auto`, `off` |
106
+ | `REDDIT_BOT_FOOTER` | No | Built-in | Custom bot footer text (when disclosure is `auto`) |
103
107
 
104
108
  \*Required only if using `authenticated` mode.
105
109
 
@@ -125,7 +129,7 @@ claude mcp add --transport stdio reddit -- npx reddit-mcp-server
125
129
 
126
130
  ## Safe Mode (Spam Protection)
127
131
 
128
- **New!** Protect your Reddit account from spam detection and bans with built-in safeguards.
132
+ Protect your Reddit account from spam detection and bans with built-in safeguards. **Enabled by default** (`standard` mode) per Reddit's Responsible Builder Policy.
129
133
 
130
134
  ### Why Use Safe Mode?
131
135
 
@@ -133,40 +137,57 @@ Reddit's spam detection can flag accounts for:
133
137
 
134
138
  - Rapid posting or commenting
135
139
  - Duplicate or similar content
140
+ - Posting the same content across multiple subreddits
136
141
  - Non-standard User-Agent strings
137
142
 
138
143
  Safe Mode helps prevent these issues automatically.
139
144
 
140
145
  ### Mode Options
141
146
 
142
- | Mode | Write Delay | Duplicate Detection | Use Case |
143
- | ---------- | ----------- | ------------------- | ------------------------------ |
144
- | `off` | None | No | Default, no safeguards |
145
- | `standard` | 2 seconds | Last 10 items | Recommended for normal use |
146
- | `strict` | 5 seconds | Last 20 items | For cautious automated posting |
147
+ | Mode | Write Delay | Duplicate Detection | Use Case |
148
+ | ---------- | ----------- | ------------------------- | ------------------------------ |
149
+ | `off` | None | No | Explicit opt-out only |
150
+ | `standard` | 2 seconds | Last 10 items + cross-sub | **Default**, recommended |
151
+ | `strict` | 5 seconds | Last 20 items + cross-sub | For cautious automated posting |
147
152
 
148
- ### Enable Safe Mode
153
+ ### Disable Safe Mode
154
+
155
+ Safe mode is enabled by default. To explicitly disable:
149
156
 
150
157
  ```bash
151
- export REDDIT_SAFE_MODE=standard
158
+ export REDDIT_SAFE_MODE=off
152
159
  npx reddit-mcp-server
153
160
  ```
154
161
 
155
- Or in your MCP config:
156
-
157
- ```json
158
- {
159
- "env": {
160
- "REDDIT_SAFE_MODE": "standard"
161
- }
162
- }
163
- ```
164
-
165
162
  ### What Safe Mode Does
166
163
 
167
164
  1. **Rate Limiting**: Enforces minimum delays between write operations
168
165
  2. **Duplicate Detection**: Blocks identical content from being posted twice
169
- 3. **Smart User-Agent**: Auto-generates Reddit-compliant User-Agent format when username is provided
166
+ 3. **Cross-Subreddit Detection**: Prevents posting the same content to multiple subreddits (per Reddit policy)
167
+ 4. **Smart User-Agent**: Auto-generates Reddit-compliant User-Agent format when username is provided
168
+
169
+ ## Bot Disclosure
170
+
171
+ Reddit's Responsible Builder Policy requires bots to disclose their automated nature. Enable automatic bot footers on all posted content:
172
+
173
+ ```bash
174
+ export REDDIT_BOT_DISCLOSURE=auto
175
+ npx reddit-mcp-server
176
+ ```
177
+
178
+ When enabled, a footer is appended to all posts, replies, and edits:
179
+
180
+ ```
181
+ ---
182
+ 🤖 I am a bot | Built with reddit-mcp-server
183
+ ```
184
+
185
+ Customize the footer with `REDDIT_BOT_FOOTER`:
186
+
187
+ ```bash
188
+ export REDDIT_BOT_DISCLOSURE=auto
189
+ export REDDIT_BOT_FOOTER=$'\n\n---\n^(🤖 Custom bot footer text)'
190
+ ```
170
191
 
171
192
  ## Authentication Modes
172
193
 
@@ -307,6 +328,17 @@ docker build -t reddit-mcp-server .
307
328
  docker run -d --name reddit-mcp -p 3000:3000 --env-file .env reddit-mcp-server
308
329
  ```
309
330
 
331
+ ## Reddit Responsible Builder Policy
332
+
333
+ This server is designed with [Reddit's Responsible Builder Policy](https://support.reddithelp.com/hc/en-us/articles/42728983564564-Responsible-Builder-Policy) in mind:
334
+
335
+ - **Safe mode on by default** — rate limiting and duplicate detection prevent spam
336
+ - **Cross-subreddit duplicate detection** — blocks identical content across subreddits
337
+ - **Bot disclosure support** — optional automated footer for transparency
338
+ - **No voting/karma manipulation** — upvote/downvote tools are intentionally excluded
339
+ - **No private messaging** — DM tools are intentionally excluded
340
+ - **Policy-aware AI instructions** — MCP server instructions remind AI assistants of data usage restrictions
341
+
310
342
  ## Credits
311
343
 
312
344
  - Fork of [reddit-mcp-server](https://github.com/alexandros-lekkas/reddit-mcp-server) by Alexandros Lekkas
package/dist/bin.js CHANGED
@@ -3,12 +3,12 @@
3
3
  if (!process.env.TRANSPORT_TYPE) process.env.TRANSPORT_TYPE = "stdio";
4
4
  const args = process.argv.slice(2);
5
5
  if (args.includes("--version") || args.includes("-v")) {
6
- console.log("1.3.2");
6
+ console.log("1.4.0");
7
7
  process.exit(0);
8
8
  }
9
9
  if (args.includes("--help") || args.includes("-h")) {
10
10
  console.log(`
11
- Reddit MCP Server v1.3.2
11
+ Reddit MCP Server v1.4.0
12
12
 
13
13
  Usage: reddit-mcp-server [options]
14
14
 
@@ -17,11 +17,15 @@ Options:
17
17
  -h, --help Show help
18
18
 
19
19
  Environment Variables:
20
- REDDIT_CLIENT_ID Reddit API client ID (required)
21
- REDDIT_CLIENT_SECRET Reddit API client secret (required)
20
+ REDDIT_CLIENT_ID Reddit API client ID (optional, for OAuth)
21
+ REDDIT_CLIENT_SECRET Reddit API client secret (optional, for OAuth)
22
22
  REDDIT_USERNAME Reddit username (optional, for write operations)
23
23
  REDDIT_PASSWORD Reddit password (optional, for write operations)
24
24
  REDDIT_USER_AGENT Custom user agent (optional)
25
+ REDDIT_AUTH_MODE Authentication mode: auto, authenticated, anonymous (default: auto)
26
+ REDDIT_SAFE_MODE Spam protection: off, standard, strict (default: standard)
27
+ REDDIT_BOT_DISCLOSURE Bot disclosure footer: auto, off (default: off)
28
+ REDDIT_BOT_FOOTER Custom bot footer text (optional, used with REDDIT_BOT_DISCLOSURE=auto)
25
29
 
26
30
  For more information, visit: https://github.com/jordanburke/reddit-mcp-server
27
31
  `);
package/dist/bin.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"bin.js","names":[],"sources":["../src/bin.ts"],"sourcesContent":["#!/usr/bin/env node\n\ndeclare const __VERSION__: string\n\n// Force stdio mode for CLI/npx usage (unless explicitly overridden)\nif (!process.env.TRANSPORT_TYPE) {\n process.env.TRANSPORT_TYPE = \"stdio\"\n}\n\n// Handle command line arguments BEFORE any other imports\nconst args = process.argv.slice(2)\n\nif (args.includes(\"--version\") || args.includes(\"-v\")) {\n console.log(__VERSION__)\n process.exit(0)\n}\n\nif (args.includes(\"--help\") || args.includes(\"-h\")) {\n console.log(`\nReddit MCP Server v${__VERSION__}\n\nUsage: reddit-mcp-server [options]\n\nOptions:\n -v, --version Show version number\n -h, --help Show help\n\nEnvironment Variables:\n REDDIT_CLIENT_ID Reddit API client ID (required)\n REDDIT_CLIENT_SECRET Reddit API client secret (required)\n REDDIT_USERNAME Reddit username (optional, for write operations)\n REDDIT_PASSWORD Reddit password (optional, for write operations)\n REDDIT_USER_AGENT Custom user agent (optional)\n\nFor more information, visit: https://github.com/jordanburke/reddit-mcp-server\n`)\n process.exit(0)\n}\n\n// Import and start server if not showing version/help\nasync function main() {\n // Import and run the main function from the FastMCP server\n await import(\"./index.js\")\n // The index.js exports main() directly, so we just need to execute the file\n // The main() is already executed when the module is imported\n}\n\nmain().then()\n"],"mappings":";;AAKA,IAAI,CAAC,QAAQ,IAAI,eACf,SAAQ,IAAI,iBAAiB;AAI/B,MAAM,OAAO,QAAQ,KAAK,MAAM,EAAE;AAElC,IAAI,KAAK,SAAS,YAAY,IAAI,KAAK,SAAS,KAAK,EAAE;AACrD,SAAQ,YAAgB;AACxB,SAAQ,KAAK,EAAE;;AAGjB,IAAI,KAAK,SAAS,SAAS,IAAI,KAAK,SAAS,KAAK,EAAE;AAClD,SAAQ,IAAI;;;;;;;;;;;;;;;;;EAiBZ;AACA,SAAQ,KAAK,EAAE;;AAIjB,eAAe,OAAO;AAEpB,OAAM,OAAO;;AAKf,MAAM,CAAC,MAAM"}
1
+ {"version":3,"file":"bin.js","names":[],"sources":["../src/bin.ts"],"sourcesContent":["#!/usr/bin/env node\n\ndeclare const __VERSION__: string\n\n// Force stdio mode for CLI/npx usage (unless explicitly overridden)\nif (!process.env.TRANSPORT_TYPE) {\n process.env.TRANSPORT_TYPE = \"stdio\"\n}\n\n// Handle command line arguments BEFORE any other imports\nconst args = process.argv.slice(2)\n\nif (args.includes(\"--version\") || args.includes(\"-v\")) {\n console.log(__VERSION__)\n process.exit(0)\n}\n\nif (args.includes(\"--help\") || args.includes(\"-h\")) {\n console.log(`\nReddit MCP Server v${__VERSION__}\n\nUsage: reddit-mcp-server [options]\n\nOptions:\n -v, --version Show version number\n -h, --help Show help\n\nEnvironment Variables:\n REDDIT_CLIENT_ID Reddit API client ID (optional, for OAuth)\n REDDIT_CLIENT_SECRET Reddit API client secret (optional, for OAuth)\n REDDIT_USERNAME Reddit username (optional, for write operations)\n REDDIT_PASSWORD Reddit password (optional, for write operations)\n REDDIT_USER_AGENT Custom user agent (optional)\n REDDIT_AUTH_MODE Authentication mode: auto, authenticated, anonymous (default: auto)\n REDDIT_SAFE_MODE Spam protection: off, standard, strict (default: standard)\n REDDIT_BOT_DISCLOSURE Bot disclosure footer: auto, off (default: off)\n REDDIT_BOT_FOOTER Custom bot footer text (optional, used with REDDIT_BOT_DISCLOSURE=auto)\n\nFor more information, visit: https://github.com/jordanburke/reddit-mcp-server\n`)\n process.exit(0)\n}\n\n// Import and start server if not showing version/help\nasync function main() {\n // Import and run the main function from the FastMCP server\n await import(\"./index.js\")\n // The index.js exports main() directly, so we just need to execute the file\n // The main() is already executed when the module is imported\n}\n\nmain().then()\n"],"mappings":";;AAKA,IAAI,CAAC,QAAQ,IAAI,eACf,SAAQ,IAAI,iBAAiB;AAI/B,MAAM,OAAO,QAAQ,KAAK,MAAM,EAAE;AAElC,IAAI,KAAK,SAAS,YAAY,IAAI,KAAK,SAAS,KAAK,EAAE;AACrD,SAAQ,YAAgB;AACxB,SAAQ,KAAK,EAAE;;AAGjB,IAAI,KAAK,SAAS,SAAS,IAAI,KAAK,SAAS,KAAK,EAAE;AAClD,SAAQ,IAAI;;;;;;;;;;;;;;;;;;;;;EAqBZ;AACA,SAAQ,KAAK,EAAE;;AAIjB,eAAe,OAAO;AAEpB,OAAM,OAAO;;AAKf,MAAM,CAAC,MAAM"}
package/dist/index.js CHANGED
@@ -18,7 +18,8 @@ var RedditClient = class {
18
18
  hasCredentials;
19
19
  safeMode;
20
20
  lastWriteTime = 0;
21
- recentContentHashes = /* @__PURE__ */ new Set();
21
+ recentContentRecords = [];
22
+ botDisclosure;
22
23
  constructor(config) {
23
24
  this.clientId = config.clientId;
24
25
  this.clientSecret = config.clientSecret;
@@ -35,6 +36,10 @@ var RedditClient = class {
35
36
  duplicateCheck: false,
36
37
  maxRecentHashes: 10
37
38
  };
39
+ this.botDisclosure = config.botDisclosure || {
40
+ enabled: false,
41
+ footer: ""
42
+ };
38
43
  }
39
44
  determineBaseUrl() {
40
45
  switch (this.authMode) {
@@ -142,15 +147,23 @@ var RedditClient = class {
142
147
  hashContent(content) {
143
148
  return crypto.createHash("sha256").update(content.trim().toLowerCase()).digest("hex");
144
149
  }
145
- checkDuplicateContent(content) {
150
+ checkDuplicateContent(content, subreddit) {
146
151
  if (!this.safeMode.enabled || !this.safeMode.duplicateCheck) return;
147
152
  const hash = this.hashContent(content);
148
- 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.");
149
- this.recentContentHashes.add(hash);
150
- if (this.recentContentHashes.size > this.safeMode.maxRecentHashes) {
151
- const first = this.recentContentHashes.values().next().value;
152
- 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.");
153
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}`;
154
167
  }
155
168
  async getUser(username) {
156
169
  try {
@@ -275,7 +288,8 @@ var RedditClient = class {
275
288
  async createPost(subreddit, title, content, isSelf = true) {
276
289
  this.validateWriteAccess();
277
290
  await this.enforceWriteRateLimit();
278
- this.checkDuplicateContent(title + content);
291
+ this.checkDuplicateContent(title + content, subreddit);
292
+ const finalContent = isSelf ? this.appendBotDisclosure(content) : content;
279
293
  try {
280
294
  var _json$json, _json$json2, _json$json3;
281
295
  const kind = isSelf ? "self" : "link";
@@ -283,7 +297,7 @@ var RedditClient = class {
283
297
  params.append("sr", subreddit);
284
298
  params.append("kind", kind);
285
299
  params.append("title", title);
286
- params.append(isSelf ? "text" : "url", content);
300
+ params.append(isSelf ? "text" : "url", finalContent);
287
301
  params.append("api_type", "json");
288
302
  const response = await this.makeRequest("/api/submit", {
289
303
  method: "POST",
@@ -317,13 +331,14 @@ var RedditClient = class {
317
331
  this.validateWriteAccess();
318
332
  await this.enforceWriteRateLimit();
319
333
  this.checkDuplicateContent(content);
334
+ const finalContent = this.appendBotDisclosure(content);
320
335
  try {
321
336
  var _json$json4, _json$json5;
322
337
  const fullThingId = postId.startsWith("t3_") || postId.startsWith("t1_") ? postId : `t3_${postId}`;
323
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`);
324
339
  const params = new URLSearchParams();
325
340
  params.append("thing_id", fullThingId);
326
- params.append("text", content);
341
+ params.append("text", finalContent);
327
342
  params.append("api_type", "json");
328
343
  const response = await this.makeRequest("/api/comment", {
329
344
  method: "POST",
@@ -390,12 +405,13 @@ var RedditClient = class {
390
405
  this.validateWriteAccess();
391
406
  await this.enforceWriteRateLimit();
392
407
  this.checkDuplicateContent(newText);
408
+ const finalText = this.appendBotDisclosure(newText);
393
409
  try {
394
410
  var _json$json6;
395
411
  const fullThingId = thingId.startsWith("t3_") || thingId.startsWith("t1_") ? thingId : `t3_${thingId}`;
396
412
  const params = new URLSearchParams();
397
413
  params.append("thing_id", fullThingId);
398
- params.append("text", newText);
414
+ params.append("text", finalText);
399
415
  params.append("api_type", "json");
400
416
  const response = await this.makeRequest("/api/editusertext", {
401
417
  method: "POST",
@@ -753,7 +769,7 @@ function formatSubredditInfo(subreddit) {
753
769
  //#endregion
754
770
  //#region src/index.ts
755
771
  dotenv.config();
756
- const VERSION = "1.3.2";
772
+ const VERSION = "1.4.0";
757
773
  function validateUserAgent(userAgent, username) {
758
774
  if (!/^[\w-]+:[\w-]+:[\d.]+ \(by \/u\/\w+\)$/.test(userAgent)) {
759
775
  console.error("[Warning] User-Agent does not follow Reddit's recommended format");
@@ -808,7 +824,7 @@ async function setupRedditClient() {
808
824
  const username = process.env.REDDIT_USERNAME;
809
825
  const password = process.env.REDDIT_PASSWORD;
810
826
  const authMode = process.env.REDDIT_AUTH_MODE || "auto";
811
- const safeMode = process.env.REDDIT_SAFE_MODE || "off";
827
+ const safeMode = process.env.REDDIT_SAFE_MODE || "standard";
812
828
  if (![
813
829
  "auto",
814
830
  "authenticated",
@@ -834,6 +850,11 @@ async function setupRedditClient() {
834
850
  const hasCredentials = !!(clientId && clientSecret);
835
851
  const userAgent = buildUserAgent(customUserAgent, username);
836
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
+ };
837
858
  try {
838
859
  const client = initializeRedditClient({
839
860
  clientId: clientId || "",
@@ -842,7 +863,8 @@ async function setupRedditClient() {
842
863
  username,
843
864
  password,
844
865
  authMode,
845
- safeMode: safeModeConfig
866
+ safeMode: safeModeConfig,
867
+ botDisclosure: botDisclosureConfig
846
868
  });
847
869
  console.error("[Setup] Reddit client initialized");
848
870
  console.error(`[Setup] Authentication mode: ${authMode}`);
@@ -870,9 +892,11 @@ async function setupRedditClient() {
870
892
  console.error(`[Setup] ✓ Safe mode enabled: ${safeModeConfig.mode}`);
871
893
  console.error(`[Setup] - Write delay: ${safeModeConfig.writeDelayMs}ms between operations`);
872
894
  console.error(`[Setup] - Duplicate detection: enabled (tracking last ${safeModeConfig.maxRecentHashes} items)`);
873
- } else {
874
- console.error("[Setup] Safe mode: off (no write operation safeguards)");
875
- 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");
876
900
  }
877
901
  } catch (error) {
878
902
  console.error("[Error] ✗ Reddit API connection failed:", error instanceof Error ? error.message : error);
@@ -896,7 +920,17 @@ Available capabilities:
896
920
  - Delete your own posts and comments (with authentication)
897
921
  - Analyze engagement metrics and community insights
898
922
 
899
- 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`,
900
934
  ...process.env.OAUTH_ENABLED === "true" && { authenticate: async (request) => {
901
935
  const authHeader = request.headers.authorization;
902
936
  if (!(authHeader === null || authHeader === void 0 ? void 0 : authHeader.startsWith("Bearer "))) throw new Response(null, {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/client/reddit-client.ts","../src/utils/formatters.ts","../src/index.ts"],"sourcesContent":["import crypto from \"crypto\"\n\nimport type {\n RedditApiCommentResponse,\n RedditApiCommentTreeData,\n RedditApiEditResponse,\n RedditApiInfoResponse,\n RedditApiListingResponse,\n RedditApiPopularSubredditsResponse,\n RedditApiPostCommentsResponse,\n RedditApiPostData,\n RedditApiSubmitResponse,\n RedditApiSubredditResponse,\n RedditApiUserResponse,\n RedditAuthMode,\n RedditClientConfig,\n RedditComment,\n RedditPost,\n RedditSubreddit,\n RedditUser,\n SafeModeConfig,\n} from \"../types\"\n\nexport class RedditClient {\n private clientId: string\n private clientSecret: string\n private userAgent: string\n private username?: string\n private password?: string\n private accessToken?: string\n private tokenExpiry: number = 0\n private baseUrl: string\n private authenticated: boolean = false\n private authMode: RedditAuthMode\n private hasCredentials: boolean\n\n // Safe mode properties\n private safeMode: SafeModeConfig\n private lastWriteTime: number = 0\n private recentContentHashes: Set<string> = new Set()\n\n constructor(config: RedditClientConfig) {\n this.clientId = config.clientId\n this.clientSecret = config.clientSecret\n this.userAgent = config.userAgent\n this.username = config.username\n this.password = config.password\n this.authMode = config.authMode || \"auto\"\n this.hasCredentials = !!(this.clientId && this.clientSecret)\n this.baseUrl = this.determineBaseUrl()\n\n // Initialize safe mode config with defaults\n this.safeMode = config.safeMode || {\n enabled: false,\n mode: \"off\",\n writeDelayMs: 0,\n duplicateCheck: false,\n maxRecentHashes: 10,\n }\n }\n\n private determineBaseUrl(): string {\n switch (this.authMode) {\n case \"authenticated\":\n return \"https://oauth.reddit.com\"\n case \"anonymous\":\n return \"https://www.reddit.com\"\n case \"auto\":\n return this.hasCredentials ? \"https://oauth.reddit.com\" : \"https://www.reddit.com\"\n }\n }\n\n private async makeRequest(path: string, options: RequestInit = {}): Promise<Response> {\n const requiresAuth = this.authMode === \"authenticated\" || (this.authMode === \"auto\" && this.hasCredentials)\n\n // Authenticate only if required\n if (requiresAuth && (Date.now() >= this.tokenExpiry || !this.authenticated)) {\n await this.authenticate()\n }\n\n const url = `${this.baseUrl}${path}`\n const headers: Record<string, string> = {\n \"User-Agent\": this.userAgent,\n ...(options.headers as Record<string, string>),\n }\n\n // Add Bearer token only if authenticated\n if (requiresAuth && this.accessToken) {\n headers.Authorization = `Bearer ${this.accessToken}`\n }\n\n const response = await fetch(url, {\n ...options,\n headers,\n })\n\n // If unauthorized, try to refresh token and retry once\n if (response.status === 401 && this.authenticated) {\n await this.authenticate()\n const retryHeaders = {\n ...headers,\n Authorization: `Bearer ${this.accessToken}`,\n }\n return fetch(url, {\n ...options,\n headers: retryHeaders,\n })\n }\n\n return response\n }\n\n async authenticate(): Promise<void> {\n // Skip in anonymous mode\n if (this.authMode === \"anonymous\") {\n this.authenticated = false\n return\n }\n\n // Require credentials in authenticated mode\n if (this.authMode === \"authenticated\" && !this.hasCredentials) {\n throw new Error(\"Authenticated mode requires REDDIT_CLIENT_ID and REDDIT_CLIENT_SECRET\")\n }\n\n // Skip if auto mode without credentials\n if (this.authMode === \"auto\" && !this.hasCredentials) {\n this.authenticated = false\n return\n }\n\n try {\n const now = Date.now()\n if (this.accessToken && now < this.tokenExpiry) {\n return\n }\n\n const authUrl = \"https://www.reddit.com/api/v1/access_token\"\n const authData = new URLSearchParams()\n\n const { username } = this\n const { password } = this\n const isUserAuth = !!(username && password)\n if (isUserAuth) {\n // Authenticating with user credentials\n authData.append(\"grant_type\", \"password\")\n authData.append(\"username\", username)\n authData.append(\"password\", password)\n } else {\n // Authenticating with client credentials (read-only)\n authData.append(\"grant_type\", \"client_credentials\")\n }\n\n const credentials = Buffer.from(`${this.clientId}:${this.clientSecret}`).toString(\"base64\")\n const response = await fetch(authUrl, {\n method: \"POST\",\n headers: {\n \"User-Agent\": this.userAgent,\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n Authorization: `Basic ${credentials}`,\n },\n body: authData.toString(),\n })\n\n if (!response.ok) {\n const statusText = response.statusText || \"Unknown Error\"\n throw new Error(`Authentication failed: ${response.status} ${statusText}`)\n }\n\n const data = (await response.json()) as { access_token: string; expires_in: number }\n this.accessToken = data.access_token\n this.tokenExpiry = now + data.expires_in * 1000\n this.authenticated = true\n } catch (error) {\n // Re-throw with more specific error message\n if (error instanceof Error) {\n throw error\n }\n throw new Error(\"Failed to authenticate with Reddit API\", { cause: error })\n }\n }\n\n async checkAuthentication(): Promise<boolean> {\n if (!this.authenticated) {\n try {\n await this.authenticate()\n return true\n } catch {\n return false\n }\n }\n return true\n }\n\n private validateWriteAccess(): void {\n if (!this.username || !this.password) {\n if (this.authMode === \"anonymous\") {\n throw new Error(\n \"Write operations not available in anonymous mode. \" +\n \"Set REDDIT_USERNAME, REDDIT_PASSWORD and use 'auto' or 'authenticated' mode.\",\n )\n }\n throw new Error(\"Write operations require REDDIT_USERNAME and REDDIT_PASSWORD\")\n }\n }\n\n private async enforceWriteRateLimit(): Promise<void> {\n if (!this.safeMode.enabled || this.safeMode.writeDelayMs <= 0) {\n return\n }\n\n const now = Date.now()\n const elapsed = now - this.lastWriteTime\n if (elapsed < this.safeMode.writeDelayMs) {\n const waitTime = this.safeMode.writeDelayMs - elapsed\n console.error(`[SafeMode] Rate limit: waiting ${waitTime}ms before write operation`)\n await new Promise((resolve) => setTimeout(resolve, waitTime))\n }\n this.lastWriteTime = Date.now()\n }\n\n private hashContent(content: string): string {\n return crypto.createHash(\"sha256\").update(content.trim().toLowerCase()).digest(\"hex\")\n }\n\n private checkDuplicateContent(content: string): void {\n if (!this.safeMode.enabled || !this.safeMode.duplicateCheck) {\n return\n }\n\n const hash = this.hashContent(content)\n if (this.recentContentHashes.has(hash)) {\n throw new Error(\n \"Duplicate content detected. Reddit's spam filter may ban your account for posting identical content. \" +\n \"Please modify your content and try again.\",\n )\n }\n\n // Add to recent hashes\n this.recentContentHashes.add(hash)\n\n // Remove oldest hash if over limit\n if (this.recentContentHashes.size > this.safeMode.maxRecentHashes) {\n const first = this.recentContentHashes.values().next().value\n if (first) {\n this.recentContentHashes.delete(first)\n }\n }\n }\n\n async getUser(username: string): Promise<RedditUser> {\n try {\n const response = await this.makeRequest(`/user/${username}/about.json`)\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiUserResponse\n const { data } = json\n\n return {\n name: data.name,\n id: data.id,\n commentKarma: data.comment_karma,\n linkKarma: data.link_karma,\n totalKarma: data.total_karma || data.comment_karma + data.link_karma,\n isMod: data.is_mod,\n isGold: data.is_gold,\n isEmployee: data.is_employee,\n createdUtc: data.created_utc,\n profileUrl: `https://reddit.com/user/${data.name}`,\n }\n } catch {\n // Failed to get user info\n throw new Error(`Failed to get user info for ${username}`)\n }\n }\n\n async getSubredditInfo(subredditName: string): Promise<RedditSubreddit> {\n try {\n const response = await this.makeRequest(`/r/${subredditName}/about.json`)\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiSubredditResponse\n const { data } = json\n\n return {\n displayName: data.display_name,\n title: data.title,\n description: data.description || \"\",\n publicDescription: data.public_description || \"\",\n subscribers: data.subscribers,\n activeUserCount: data.active_user_count ?? undefined,\n createdUtc: data.created_utc,\n over18: data.over18,\n subredditType: data.subreddit_type,\n url: data.url,\n }\n } catch {\n // Failed to get subreddit info\n throw new Error(`Failed to get subreddit info for ${subredditName}`)\n }\n }\n\n async getTopPosts(subreddit: string, timeFilter: string = \"week\", limit: number = 10): Promise<RedditPost[]> {\n try {\n const endpoint = subreddit ? `/r/${subreddit}/top.json` : \"/top.json\"\n const params = new URLSearchParams({\n t: timeFilter,\n limit: limit.toString(),\n })\n\n const response = await this.makeRequest(`${endpoint}?${params}`)\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiListingResponse<RedditApiPostData>\n\n return json.data.children.map((child) => {\n const post = child.data\n return {\n id: post.id,\n title: post.title,\n author: post.author,\n subreddit: post.subreddit,\n selftext: post.selftext,\n url: post.url,\n score: post.score,\n upvoteRatio: post.upvote_ratio,\n numComments: post.num_comments,\n createdUtc: post.created_utc,\n over18: post.over_18,\n spoiler: post.spoiler,\n edited: !!post.edited,\n isSelf: post.is_self,\n linkFlairText: post.link_flair_text ?? undefined,\n permalink: post.permalink,\n }\n })\n } catch {\n // Failed to get top posts\n throw new Error(`Failed to get top posts for ${subreddit || \"home\"}`)\n }\n }\n\n async getPost(postId: string, subreddit?: string): Promise<RedditPost> {\n try {\n const endpoint = subreddit ? `/r/${subreddit}/comments/${postId}.json` : `/api/info.json?id=t3_${postId}`\n const response = await this.makeRequest(endpoint)\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`)\n }\n\n let post: RedditApiPostData\n if (subreddit) {\n // When using the comments endpoint - returns array [postListing, commentsListing]\n const json = (await response.json()) as [RedditApiListingResponse<RedditApiPostData>, unknown]\n post = json[0].data.children[0].data\n } else {\n // When using the info endpoint\n const json = (await response.json()) as RedditApiInfoResponse\n if (!json.data.children.length) {\n throw new Error(`Post with ID ${postId} not found`)\n }\n post = json.data.children[0].data\n }\n\n return {\n id: post.id,\n title: post.title,\n author: post.author,\n subreddit: post.subreddit,\n selftext: post.selftext,\n url: post.url,\n score: post.score,\n upvoteRatio: post.upvote_ratio,\n numComments: post.num_comments,\n createdUtc: post.created_utc,\n over18: post.over_18,\n spoiler: post.spoiler,\n edited: !!post.edited,\n isSelf: post.is_self,\n linkFlairText: post.link_flair_text ?? undefined,\n permalink: post.permalink,\n }\n } catch {\n // Failed to get post\n throw new Error(`Failed to get post with ID ${postId}`)\n }\n }\n\n async getTrendingSubreddits(limit: number = 5): Promise<string[]> {\n try {\n const params = new URLSearchParams({ limit: limit.toString() })\n const response = await this.makeRequest(`/subreddits/popular.json?${params}`)\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiPopularSubredditsResponse\n return json.data.children.map((child) => child.data.display_name)\n } catch {\n // Failed to get trending subreddits\n throw new Error(\"Failed to get trending subreddits\")\n }\n }\n\n async createPost(subreddit: string, title: string, content: string, isSelf: boolean = true): Promise<RedditPost> {\n this.validateWriteAccess()\n\n // Safe mode checks\n await this.enforceWriteRateLimit()\n this.checkDuplicateContent(title + content)\n\n try {\n const kind = isSelf ? \"self\" : \"link\"\n const params = new URLSearchParams()\n params.append(\"sr\", subreddit)\n params.append(\"kind\", kind)\n params.append(\"title\", title)\n params.append(isSelf ? \"text\" : \"url\", content)\n params.append(\"api_type\", \"json\") // Request standard JSON response format\n\n const response = await this.makeRequest(\"/api/submit\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n },\n body: params.toString(),\n })\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiSubmitResponse\n\n // With api_type=json, response has json.data.id or json.errors\n if (json.json?.errors && json.json.errors.length > 0) {\n const errors = json.json.errors.map((e) => e[1] || e[0]).join(\", \")\n throw new Error(`Reddit API errors: ${errors}`)\n }\n\n // Extract post ID from standard JSON response\n const postId = json.json?.data?.id || json.json?.data?.name?.replace(\"t3_\", \"\")\n\n if (!postId) {\n throw new Error(\"No post ID returned from Reddit\")\n }\n\n return await this.getPost(postId, subreddit)\n } catch (error) {\n if (error instanceof Error) {\n throw error\n }\n throw new Error(\n `Failed to create post in ${subreddit}: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error },\n )\n }\n }\n\n async checkPostExists(postId: string): Promise<boolean> {\n try {\n const response = await this.makeRequest(`/api/info.json?id=t3_${postId}`)\n if (!response.ok) {\n return false\n }\n\n const json = (await response.json()) as RedditApiInfoResponse\n return json.data.children.length > 0\n } catch {\n return false\n }\n }\n\n async replyToPost(postId: string, content: string): Promise<RedditComment> {\n this.validateWriteAccess()\n\n // Safe mode checks\n await this.enforceWriteRateLimit()\n this.checkDuplicateContent(content)\n\n try {\n const fullThingId = postId.startsWith(\"t3_\") || postId.startsWith(\"t1_\") ? postId : `t3_${postId}`\n\n // Only check existence for posts (t3_), not comments (t1_)\n if (!postId.startsWith(\"t1_\") && !(await this.checkPostExists(postId.replace(/^t3_/, \"\")))) {\n throw new Error(`Post with ID ${postId} does not exist or is not accessible`)\n }\n\n const params = new URLSearchParams()\n params.append(\"thing_id\", fullThingId)\n params.append(\"text\", content)\n params.append(\"api_type\", \"json\") // Request standard JSON response format\n\n const response = await this.makeRequest(\"/api/comment\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n },\n body: params.toString(),\n })\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`)\n }\n\n // Extract comment data from response\n const json = (await response.json()) as RedditApiCommentResponse\n\n if (json.json?.data?.things && json.json.data.things.length > 0) {\n const commentData = json.json.data.things[0].data\n // username is guaranteed to be defined because validateWriteAccess() was called above\n const author = this.username ?? \"[unknown]\"\n return {\n id: commentData.id,\n author,\n body: content,\n score: 1,\n controversiality: 0,\n subreddit: commentData.subreddit,\n submissionTitle: commentData.link_title ?? \"\",\n createdUtc: Date.now() / 1000,\n edited: false,\n isSubmitter: false,\n permalink: commentData.permalink,\n }\n } else if (json.json?.errors && json.json.errors.length > 0) {\n const errors = json.json.errors.map((e) => e[1] || e[0]).join(\", \")\n throw new Error(`Reddit API errors: ${errors}`)\n } else {\n throw new Error(\"Failed to parse reply response\")\n }\n } catch (error) {\n if (error instanceof Error) {\n throw error\n }\n throw new Error(`Failed to reply to post ${postId}: ${error instanceof Error ? error.message : String(error)}`, {\n cause: error,\n })\n }\n }\n\n async deletePost(thingId: string): Promise<boolean> {\n this.validateWriteAccess()\n\n try {\n // Ensure thing ID has the correct prefix (t3_ for posts, t1_ for comments)\n const fullThingId = thingId.startsWith(\"t3_\") || thingId.startsWith(\"t1_\") ? thingId : `t3_${thingId}`\n\n const params = new URLSearchParams()\n params.append(\"id\", fullThingId)\n\n const response = await this.makeRequest(\"/api/del\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n },\n body: params.toString(),\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n console.error(`[Reddit API] Delete failed: ${response.status} ${response.statusText}`)\n console.error(`[Reddit API] Error response: ${errorText}`)\n throw new Error(`HTTP ${response.status}: ${errorText}`)\n }\n\n console.error(`[Reddit API] Successfully deleted ${fullThingId}`)\n return true\n } catch (error) {\n console.error(`[Reddit API] Delete exception:`, error)\n if (error instanceof Error && error.message.includes(\"HTTP\")) {\n throw error\n }\n throw new Error(\n `Failed to delete content ${thingId}: ${error instanceof Error ? error.message : String(error)}`,\n {\n cause: error,\n },\n )\n }\n }\n\n async deleteComment(thingId: string): Promise<boolean> {\n // deleteComment is just an alias for deletePost since /api/del handles both\n // Ensure the thing ID has the correct prefix for comments (t1_)\n const fullThingId = thingId.startsWith(\"t1_\") ? thingId : `t1_${thingId}`\n return this.deletePost(fullThingId)\n }\n\n async editPost(thingId: string, newText: string): Promise<boolean> {\n this.validateWriteAccess()\n\n // Safe mode checks\n await this.enforceWriteRateLimit()\n this.checkDuplicateContent(newText)\n\n try {\n // Ensure thing ID has the correct prefix (t3_ for posts, t1_ for comments)\n const fullThingId = thingId.startsWith(\"t3_\") || thingId.startsWith(\"t1_\") ? thingId : `t3_${thingId}`\n\n const params = new URLSearchParams()\n params.append(\"thing_id\", fullThingId)\n params.append(\"text\", newText)\n params.append(\"api_type\", \"json\")\n\n const response = await this.makeRequest(\"/api/editusertext\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n },\n body: params.toString(),\n })\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiEditResponse\n\n // Check for errors in response\n if (json.json?.errors && json.json.errors.length > 0) {\n const errors = json.json.errors.map((e) => e[1] || e[0]).join(\", \")\n throw new Error(`Reddit API errors: ${errors}`)\n }\n\n return true\n } catch (error) {\n if (error instanceof Error) {\n throw error\n }\n throw new Error(`Failed to edit content ${thingId}: ${error instanceof Error ? error.message : String(error)}`, {\n cause: error,\n })\n }\n }\n\n async editComment(thingId: string, newText: string): Promise<boolean> {\n // editComment is just an alias for editPost since /api/editusertext handles both\n // Ensure the thing ID has the correct prefix for comments (t1_)\n const fullThingId = thingId.startsWith(\"t1_\") ? thingId : `t1_${thingId}`\n return this.editPost(fullThingId, newText)\n }\n\n async searchReddit(\n query: string,\n options: {\n subreddit?: string\n sort?: string\n timeFilter?: string\n limit?: number\n type?: string\n } = {},\n ): Promise<RedditPost[]> {\n try {\n const { subreddit, sort = \"relevance\", timeFilter = \"all\", limit = 25, type = \"link\" } = options\n const endpoint = subreddit ? `/r/${subreddit}/search.json` : \"/search.json\"\n\n const params = new URLSearchParams({\n q: query,\n sort,\n t: timeFilter,\n limit: limit.toString(),\n type,\n ...(subreddit && { restrict_sr: \"true\" }),\n })\n\n const response = await this.makeRequest(`${endpoint}?${params}`)\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiListingResponse<RedditApiPostData>\n\n return json.data.children\n .filter((child) => child.kind === \"t3\") // Only posts\n .map((child) => {\n const post = child.data\n return {\n id: post.id,\n title: post.title,\n author: post.author,\n subreddit: post.subreddit,\n selftext: post.selftext || \"\",\n url: post.url,\n score: post.score,\n upvoteRatio: post.upvote_ratio,\n numComments: post.num_comments,\n createdUtc: post.created_utc,\n over18: post.over_18,\n spoiler: post.spoiler,\n edited: !!post.edited,\n isSelf: post.is_self,\n linkFlairText: post.link_flair_text ?? undefined,\n permalink: post.permalink,\n }\n })\n } catch {\n throw new Error(`Failed to search Reddit for: ${query}`)\n }\n }\n\n async getPostComments(\n postId: string,\n subreddit: string,\n options: {\n sort?: string\n limit?: number\n } = {},\n ): Promise<{ post: RedditPost; comments: RedditComment[] }> {\n try {\n const { sort = \"best\", limit = 100 } = options\n const params = new URLSearchParams({\n sort,\n limit: limit.toString(),\n })\n\n const response = await this.makeRequest(`/r/${subreddit}/comments/${postId}.json?${params}`)\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiPostCommentsResponse\n\n // First element is the post, second is the comments\n const postData = json[0].data.children[0].data\n const post: RedditPost = {\n id: postData.id,\n title: postData.title,\n author: postData.author,\n subreddit: postData.subreddit,\n selftext: postData.selftext || \"\",\n url: postData.url,\n score: postData.score,\n upvoteRatio: postData.upvote_ratio,\n numComments: postData.num_comments,\n createdUtc: postData.created_utc,\n over18: postData.over_18,\n spoiler: postData.spoiler,\n edited: !!postData.edited,\n isSelf: postData.is_self,\n linkFlairText: postData.link_flair_text ?? undefined,\n permalink: postData.permalink,\n }\n\n const comments: RedditComment[] = []\n const parseComments = (\n commentData: Array<{ kind: string; data: RedditApiCommentTreeData }>,\n depth: number = 0,\n ) => {\n for (const item of commentData) {\n if (item.kind === \"t1\" && item.data.body) {\n comments.push({\n id: item.data.id,\n author: item.data.author,\n body: item.data.body,\n score: item.data.score,\n controversiality: item.data.controversiality,\n subreddit: item.data.subreddit,\n submissionTitle: post.title,\n createdUtc: item.data.created_utc,\n edited: !!item.data.edited,\n isSubmitter: item.data.is_submitter,\n permalink: item.data.permalink,\n depth,\n parentId: item.data.parent_id,\n })\n\n // Parse replies recursively\n const { replies } = item.data\n if (replies && typeof replies !== \"string\" && replies.data?.children) {\n parseComments(replies.data.children, depth + 1)\n }\n }\n }\n }\n\n if (json[1]?.data?.children) {\n parseComments(json[1].data.children)\n }\n\n return { post, comments }\n } catch {\n throw new Error(`Failed to get comments for post ${postId}`)\n }\n }\n\n async getUserPosts(\n username: string,\n options: {\n sort?: string\n timeFilter?: string\n limit?: number\n } = {},\n ): Promise<RedditPost[]> {\n try {\n const { sort = \"new\", timeFilter = \"all\", limit = 25 } = options\n const params = new URLSearchParams({\n sort,\n t: timeFilter,\n limit: limit.toString(),\n })\n\n const response = await this.makeRequest(`/user/${username}/submitted.json?${params}`)\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiListingResponse<RedditApiPostData>\n\n return json.data.children\n .filter((child) => child.kind === \"t3\")\n .map((child) => {\n const post = child.data\n return {\n id: post.id,\n title: post.title,\n author: post.author,\n subreddit: post.subreddit,\n selftext: post.selftext || \"\",\n url: post.url,\n score: post.score,\n upvoteRatio: post.upvote_ratio,\n numComments: post.num_comments,\n createdUtc: post.created_utc,\n over18: post.over_18,\n spoiler: post.spoiler,\n edited: !!post.edited,\n isSelf: post.is_self,\n linkFlairText: post.link_flair_text ?? undefined,\n permalink: post.permalink,\n }\n })\n } catch {\n throw new Error(`Failed to get posts for user ${username}`)\n }\n }\n\n async getUserComments(\n username: string,\n options: {\n sort?: string\n timeFilter?: string\n limit?: number\n } = {},\n ): Promise<RedditComment[]> {\n try {\n const { sort = \"new\", timeFilter = \"all\", limit = 25 } = options\n const params = new URLSearchParams({\n sort,\n t: timeFilter,\n limit: limit.toString(),\n })\n\n const response = await this.makeRequest(`/user/${username}/comments.json?${params}`)\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiListingResponse<RedditApiCommentTreeData>\n\n return json.data.children\n .filter((child) => child.kind === \"t1\")\n .map((child) => {\n const comment = child.data\n return {\n id: comment.id,\n author: comment.author,\n body: comment.body ?? \"\",\n score: comment.score,\n controversiality: comment.controversiality,\n subreddit: comment.subreddit,\n submissionTitle: comment.link_title ?? \"\",\n createdUtc: comment.created_utc,\n edited: !!comment.edited,\n isSubmitter: comment.is_submitter,\n permalink: comment.permalink,\n }\n })\n } catch {\n throw new Error(`Failed to get comments for user ${username}`)\n }\n }\n}\n\n// Create and export singleton instance\nlet redditClient: RedditClient | null = null\n\nexport function initializeRedditClient(config: RedditClientConfig): RedditClient {\n redditClient = new RedditClient(config)\n return redditClient\n}\n\nexport function getRedditClient(): RedditClient | null {\n return redditClient\n}\n","import type {\n FormattedCommentInfo,\n FormattedPostInfo,\n FormattedSubredditInfo,\n FormattedUserInfo,\n RedditComment,\n RedditPost,\n RedditSubreddit,\n RedditUser,\n} from \"../types\"\n\nexport function formatTimestamp(timestamp: number): string {\n try {\n const date = new Date(timestamp * 1000)\n return date\n .toISOString()\n .replace(\"T\", \" \")\n .replace(/\\.\\d+Z$/, \" UTC\")\n } catch {\n return String(timestamp)\n }\n}\n\nexport function analyzeUserActivity(karmaRatio: number, isMod: boolean, accountAgeDays: number): string {\n const insights: string[] = []\n\n // Analyze karma ratio\n if (karmaRatio > 5) {\n insights.push(\"Primarily a commenter, highly engaged in discussions\")\n } else if (karmaRatio < 0.2) {\n insights.push(\"Content creator, focuses on sharing posts\")\n } else {\n insights.push(\"Balanced participation in both posting and commenting\")\n }\n\n // Analyze account age and status\n if (accountAgeDays < 30) {\n insights.push(\"New user, still exploring Reddit\")\n } else if (accountAgeDays > 365 * 5) {\n insights.push(\"Long-time Redditor with extensive platform experience\")\n }\n\n if (isMod) {\n insights.push(\"Community leader who helps maintain subreddit quality\")\n }\n\n return insights.join(\"\\n - \")\n}\n\nexport function analyzePostEngagement(score: number, ratio: number, numComments: number): string {\n const insights: string[] = []\n\n // Analyze score and ratio\n if (score > 1000 && ratio > 0.95) {\n insights.push(\"Highly successful post with strong community approval\")\n } else if (score > 100 && ratio > 0.8) {\n insights.push(\"Well-received post with good engagement\")\n } else if (ratio < 0.5) {\n insights.push(\"Controversial post that sparked debate\")\n }\n\n // Analyze comment activity\n if (numComments > 100) {\n insights.push(\"Generated significant discussion\")\n } else if (numComments > score * 0.5) {\n insights.push(\"Highly discussable content with active comment section\")\n } else if (numComments === 0) {\n insights.push(\"Yet to receive community interaction\")\n }\n\n return insights.join(\"\\n - \")\n}\n\nexport function analyzeSubredditHealth(subscribers: number, activeUsers: number | undefined, ageDays: number): string {\n const insights: string[] = []\n\n // Analyze size and activity\n if (subscribers > 1000000) {\n insights.push(\"Major subreddit with massive following\")\n } else if (subscribers > 100000) {\n insights.push(\"Well-established community\")\n } else if (subscribers < 1000) {\n insights.push(\"Niche community, potential for growth\")\n }\n\n if (activeUsers) {\n // If we have active users data\n const activityRatio = activeUsers / subscribers\n if (activityRatio > 0.1) {\n insights.push(\"Highly active community with strong engagement\")\n } else if (activityRatio < 0.01) {\n insights.push(\"Could benefit from more community engagement initiatives\")\n }\n }\n\n // Analyze age and maturity\n if (ageDays > 365 * 5) {\n insights.push(\"Mature subreddit with established culture\")\n } else if (ageDays < 90) {\n insights.push(\"New subreddit still forming its community\")\n }\n\n return insights.join(\"\\n - \")\n}\n\nexport function getUserRecommendations(karmaRatio: number, isMod: boolean, accountAgeDays: number): string {\n const recommendations: string[] = []\n\n if (karmaRatio > 5) {\n recommendations.push(\"Consider creating more posts to share your expertise\")\n } else if (karmaRatio < 0.2) {\n recommendations.push(\"Engage more in discussions to build community connections\")\n }\n\n if (accountAgeDays < 30) {\n recommendations.push(\"Explore popular subreddits in your areas of interest\")\n recommendations.push(\"Read community guidelines before posting\")\n }\n\n if (isMod) {\n recommendations.push(\"Share moderation insights with other community leaders\")\n }\n\n if (!recommendations.length) {\n recommendations.push(\"Maintain your balanced engagement across Reddit\")\n }\n\n return recommendations.join(\"\\n - \")\n}\n\nexport function getBestEngagementTime(createdUtc: number): string {\n const postHour = new Date(createdUtc * 1000).getHours()\n\n // Simple time zone analysis\n if (14 <= postHour && postHour <= 18) {\n // Peak Reddit hours\n return \"Posted during peak engagement hours (2 PM - 6 PM), good timing!\"\n } else if (23 <= postHour || postHour <= 5) {\n return \"Consider posting during more active hours (morning to evening)\"\n } else {\n return \"Posted during moderate activity hours, timing could be optimized\"\n }\n}\n\nexport function getSubredditEngagementTips(subreddit: RedditSubreddit): string {\n const tips: string[] = []\n\n if (subreddit.subscribers > 1000000) {\n tips.push(\"Post during peak hours for maximum visibility\")\n tips.push(\"Ensure content is highly polished due to high competition\")\n } else if (subreddit.subscribers < 1000) {\n tips.push(\"Engage actively to help grow the community\")\n tips.push(\"Consider cross-posting to related larger subreddits\")\n }\n\n if (subreddit.activeUserCount) {\n const activityRatio = subreddit.activeUserCount / subreddit.subscribers\n if (activityRatio > 0.1) {\n tips.push(\"Quick responses recommended due to high activity\")\n }\n }\n\n return tips.length ? tips.join(\"\\n - \") : \"Regular engagement recommended to maintain community presence\"\n}\n\nexport function analyzeCommentImpact(score: number, isEdited: boolean, isOp: boolean): string {\n const insights: string[] = []\n\n if (score > 100) {\n insights.push(\"Highly upvoted comment with significant community agreement\")\n } else if (score < 0) {\n insights.push(\"Controversial or contested viewpoint\")\n }\n\n if (isEdited) {\n insights.push(\"Refined for clarity or accuracy\")\n }\n\n if (isOp) {\n insights.push(\"Author's perspective adds context to original post\")\n }\n\n return insights.length ? insights.join(\"\\n - \") : \"Standard engagement with discussion\"\n}\n\nexport function formatUserInfo(user: RedditUser): FormattedUserInfo {\n const status: string[] = []\n if (user.isMod) status.push(\"Moderator\")\n if (user.isGold) status.push(\"Reddit Gold Member\")\n if (user.isEmployee) status.push(\"Reddit Employee\")\n\n const accountAgeDays = (Date.now() / 1000 - user.createdUtc) / (24 * 3600) // age in days\n const karmaRatio = user.commentKarma / (user.linkKarma || 1)\n\n return {\n username: user.name,\n karma: {\n commentKarma: user.commentKarma,\n postKarma: user.linkKarma,\n totalKarma: user.totalKarma,\n },\n accountStatus: status.length ? status : [\"Regular User\"],\n accountCreated: formatTimestamp(user.createdUtc),\n profileUrl: user.profileUrl,\n activityAnalysis: analyzeUserActivity(karmaRatio, user.isMod, accountAgeDays),\n recommendations: getUserRecommendations(karmaRatio, user.isMod, accountAgeDays),\n }\n}\n\nexport function formatPostInfo(post: RedditPost): FormattedPostInfo {\n const contentType = post.isSelf ? \"Text Post\" : \"Link Post\"\n const content = post.isSelf ? post.selftext || \"\" : post.url || \"\"\n\n const flags: string[] = []\n if (post.over18) flags.push(\"NSFW\")\n if (post.spoiler) flags.push(\"Spoiler\")\n if (post.edited) flags.push(\"Edited\")\n\n return {\n title: post.title,\n type: contentType,\n content: content.length > 300 ? `${content.substring(0, 297)}...` : content,\n author: post.author,\n subreddit: post.subreddit,\n stats: {\n score: post.score,\n upvoteRatio: post.upvoteRatio,\n comments: post.numComments,\n },\n metadata: {\n posted: formatTimestamp(post.createdUtc),\n flags,\n flair: post.linkFlairText || \"None\",\n },\n links: {\n fullPost: `https://reddit.com${post.permalink}`,\n shortLink: `https://redd.it/${post.id}`,\n },\n engagementAnalysis: analyzePostEngagement(post.score, post.upvoteRatio, post.numComments),\n bestTimeToEngage: getBestEngagementTime(post.createdUtc),\n }\n}\n\nexport function formatSubredditInfo(subreddit: RedditSubreddit): FormattedSubredditInfo {\n const flags: string[] = []\n if (subreddit.over18) flags.push(\"NSFW\")\n if (subreddit.subredditType) flags.push(`Type: ${subreddit.subredditType}`)\n\n const ageDays = (Date.now() / 1000 - subreddit.createdUtc) / (24 * 3600)\n\n return {\n name: subreddit.displayName,\n title: subreddit.title,\n stats: {\n subscribers: subreddit.subscribers,\n activeUsers: subreddit.activeUserCount !== undefined ? subreddit.activeUserCount : \"Unknown\",\n },\n description: {\n short: subreddit.publicDescription,\n full:\n subreddit.description.length > 300 ? `${subreddit.description.substring(0, 297)}...` : subreddit.description,\n },\n metadata: {\n created: formatTimestamp(subreddit.createdUtc),\n flags: flags.length ? flags : [\"None\"],\n },\n links: {\n subreddit: `https://reddit.com${subreddit.url}`,\n wiki: `https://reddit.com/r/${subreddit.displayName}/wiki`,\n },\n communityAnalysis: analyzeSubredditHealth(subreddit.subscribers, subreddit.activeUserCount, ageDays),\n engagementTips: getSubredditEngagementTips(subreddit),\n }\n}\n\nexport function formatCommentInfo(comment: RedditComment): FormattedCommentInfo {\n const flags: string[] = []\n if (comment.edited) flags.push(\"Edited\")\n if (comment.isSubmitter) flags.push(\"OP\")\n\n return {\n author: comment.author,\n content: comment.body.length > 300 ? `${comment.body.substring(0, 297)}...` : comment.body,\n stats: {\n score: comment.score,\n controversiality: comment.controversiality,\n },\n context: {\n subreddit: comment.subreddit,\n thread: comment.submissionTitle,\n },\n metadata: {\n posted: formatTimestamp(comment.createdUtc),\n flags: flags.length ? flags : [\"None\"],\n },\n link: `https://reddit.com${comment.permalink}`,\n commentAnalysis: analyzeCommentImpact(comment.score, comment.edited, comment.isSubmitter),\n }\n}\n\n// Simple formatter for posts (used in search and comment tools)\nexport function formatPost(post: RedditPost) {\n return {\n title: post.title,\n author: post.author,\n subreddit: post.subreddit,\n score: post.score,\n upvoteRatio: Math.round(post.upvoteRatio * 100),\n numComments: post.numComments,\n createdAt: formatTimestamp(post.createdUtc),\n selftext: post.selftext,\n permalink: post.permalink,\n nsfw: post.over18,\n spoiler: post.spoiler,\n }\n}\n","import crypto from \"crypto\"\nimport dotenv from \"dotenv\"\nimport { FastMCP } from \"fastmcp\"\nimport { z } from \"zod\"\n\nimport { getRedditClient, initializeRedditClient } from \"./client/reddit-client\"\nimport type { RedditAuthMode, RedditSafeMode, SafeModeConfig } from \"./types\"\nimport { formatPostInfo, formatSubredditInfo, formatUserInfo } from \"./utils/formatters\"\n\n// Load environment variables\ndotenv.config()\n\n// Version injected at build time by tsdown\ndeclare const __VERSION__: string\nconst VERSION = (typeof __VERSION__ !== \"undefined\" ? __VERSION__ : \"0.0.0-dev\") as `${number}.${number}.${number}`\n\n// User-Agent validation and building\nfunction validateUserAgent(userAgent: string, username?: string): void {\n const recommendedPattern = /^[\\w-]+:[\\w-]+:[\\d.]+ \\(by \\/u\\/\\w+\\)$/\n if (!recommendedPattern.test(userAgent)) {\n console.error(\"[Warning] User-Agent does not follow Reddit's recommended format\")\n console.error(\"[Warning] Recommended: 'platform:app_id:version (by /u/username)'\")\n console.error(\"[Warning] Non-standard User-Agents may increase ban risk\")\n if (username) {\n console.error(`[Warning] Consider using: 'typescript:reddit-mcp-server:${VERSION} (by /u/${username})'`)\n }\n }\n}\n\nfunction buildUserAgent(customAgent?: string, username?: string): string {\n // If custom agent provided, use it (but validate)\n if (customAgent) {\n validateUserAgent(customAgent, username)\n return customAgent\n }\n\n // Auto-format with username if available\n if (username) {\n const autoAgent = `typescript:reddit-mcp-server:${VERSION} (by /u/${username})`\n console.error(`[Setup] Auto-generated User-Agent: ${autoAgent}`)\n return autoAgent\n }\n\n // Fallback using recommended format without username\n const fallbackAgent = `typescript:reddit-mcp-server:${VERSION} (by /u/anonymous)`\n console.error(\n \"[Setup] No REDDIT_USERNAME set — using anonymous User-Agent. Set REDDIT_USERNAME for a personalized agent.\",\n )\n return fallbackAgent\n}\n\n// Safe mode configuration\nfunction buildSafeModeConfig(safeMode: RedditSafeMode): SafeModeConfig {\n switch (safeMode) {\n case \"off\":\n return {\n enabled: false,\n mode: \"off\",\n writeDelayMs: 0,\n duplicateCheck: false,\n maxRecentHashes: 10,\n }\n case \"standard\":\n return {\n enabled: true,\n mode: \"standard\",\n writeDelayMs: 2000, // 2 seconds between writes\n duplicateCheck: true,\n maxRecentHashes: 10,\n }\n case \"strict\":\n return {\n enabled: true,\n mode: \"strict\",\n writeDelayMs: 5000, // 5 seconds between writes\n duplicateCheck: true,\n maxRecentHashes: 20,\n }\n }\n}\n\n// Initialize Reddit client\nasync function setupRedditClient() {\n const clientId = process.env.REDDIT_CLIENT_ID\n const clientSecret = process.env.REDDIT_CLIENT_SECRET\n const customUserAgent = process.env.REDDIT_USER_AGENT\n const username = process.env.REDDIT_USERNAME\n const password = process.env.REDDIT_PASSWORD\n const authMode = (process.env.REDDIT_AUTH_MODE || \"auto\") as RedditAuthMode\n const safeMode = (process.env.REDDIT_SAFE_MODE || \"off\") as RedditSafeMode\n\n // Validate auth mode\n if (![\"auto\", \"authenticated\", \"anonymous\"].includes(authMode)) {\n console.error(`[Error] Invalid REDDIT_AUTH_MODE: ${authMode}`)\n console.error(\"[Error] Valid options are: auto, authenticated, anonymous\")\n process.exit(1)\n }\n\n // Validate safe mode\n if (![\"off\", \"standard\", \"strict\"].includes(safeMode)) {\n console.error(`[Error] Invalid REDDIT_SAFE_MODE: ${safeMode}`)\n console.error(\"[Error] Valid options are: off, standard, strict\")\n process.exit(1)\n }\n\n // In authenticated mode, require credentials\n if (authMode === \"authenticated\" && (!clientId || !clientSecret)) {\n console.error(\"[Error] Authenticated mode requires REDDIT_CLIENT_ID and REDDIT_CLIENT_SECRET\")\n process.exit(1)\n }\n\n // For auto/anonymous, credentials are optional\n const hasCredentials = !!(clientId && clientSecret)\n\n // Build user-agent (auto-format with username if available)\n const userAgent = buildUserAgent(customUserAgent, username)\n\n // Build safe mode config\n const safeModeConfig = buildSafeModeConfig(safeMode)\n\n try {\n const client = initializeRedditClient({\n clientId: clientId || \"\",\n clientSecret: clientSecret || \"\",\n userAgent,\n username,\n password,\n authMode,\n safeMode: safeModeConfig,\n })\n\n console.error(\"[Setup] Reddit client initialized\")\n console.error(`[Setup] Authentication mode: ${authMode}`)\n\n if (authMode === \"anonymous\" || !hasCredentials) {\n console.error(\"[Setup] Using anonymous Reddit API (~10 req/min)\")\n console.error(\"[Setup] No authentication required - ready to use!\")\n } else {\n console.error(\"[Setup] Testing Reddit API connection...\")\n const isConnected = await client.checkAuthentication()\n\n if (!isConnected) {\n console.error(\"[Error] ✗ Failed to connect to Reddit API\")\n console.error(\"[Error] Please check your REDDIT_CLIENT_ID and REDDIT_CLIENT_SECRET\")\n process.exit(1)\n }\n\n console.error(\"[Setup] ✓ Reddit API connection successful\")\n console.error(\"[Setup] Using OAuth Reddit API (60-100 req/min)\")\n }\n\n if (username && password) {\n console.error(`[Setup] ✓ User authenticated as: ${username}`)\n console.error(\"[Setup] Write operations enabled (posting, replying, editing, deleting)\")\n } else {\n console.error(\"[Setup] Read-only mode (no user credentials)\")\n console.error(\"[Setup] For write operations, set REDDIT_USERNAME and REDDIT_PASSWORD\")\n }\n\n // Log safe mode status\n if (safeModeConfig.enabled) {\n console.error(`[Setup] ✓ Safe mode enabled: ${safeModeConfig.mode}`)\n console.error(`[Setup] - Write delay: ${safeModeConfig.writeDelayMs}ms between operations`)\n console.error(`[Setup] - Duplicate detection: enabled (tracking last ${safeModeConfig.maxRecentHashes} items)`)\n } else {\n console.error(\"[Setup] Safe mode: off (no write operation safeguards)\")\n console.error(\"[Setup] Consider enabling REDDIT_SAFE_MODE=standard for spam protection\")\n }\n } catch (error) {\n console.error(\"[Error] ✗ Reddit API connection failed:\", error instanceof Error ? error.message : error)\n console.error(\"[Error] Please verify your Reddit API credentials\")\n process.exit(1)\n }\n}\n\n// OAuth token: generate once at startup, never expose in responses\nconst oauthToken = process.env.OAUTH_TOKEN || crypto.randomBytes(32).toString(\"hex\")\nif (process.env.OAUTH_ENABLED === \"true\" && !process.env.OAUTH_TOKEN) {\n console.error(`[Auth] Generated OAuth token: ${oauthToken}`)\n}\n\n// Create FastMCP server\nconst server = new FastMCP({\n name: \"reddit-mcp-server\",\n version: VERSION,\n instructions: `A comprehensive Reddit MCP server that provides tools for interacting with Reddit API.\n\nAvailable capabilities:\n- Fetch Reddit posts, comments, and user information\n- Get subreddit details and statistics\n- Search Reddit content across posts and subreddits\n- Create posts and reply to posts/comments (with authentication)\n- Edit your own posts and comments (with authentication)\n- Delete your own posts and comments (with authentication)\n- Analyze engagement metrics and community insights\n\nFor write operations (posting, replying, editing, deleting), ensure REDDIT_USERNAME and REDDIT_PASSWORD are configured.`,\n\n // Optional OAuth configuration for HTTP transport\n ...(process.env.OAUTH_ENABLED === \"true\" && {\n authenticate: async (request) => {\n const authHeader = request.headers.authorization\n if (!authHeader?.startsWith(\"Bearer \")) {\n throw new Response(null, {\n status: 401,\n statusText: \"Missing or invalid Authorization header\",\n })\n }\n\n const token = authHeader.slice(7)\n const tokenBuffer = Buffer.from(token)\n const expectedBuffer = Buffer.from(oauthToken)\n // Constant-time comparison: hash both so timingSafeEqual always compares equal-length buffers\n const tokenHash = crypto.createHash(\"sha256\").update(tokenBuffer).digest()\n const expectedHash = crypto.createHash(\"sha256\").update(expectedBuffer).digest()\n if (!crypto.timingSafeEqual(tokenHash, expectedHash)) {\n throw new Response(null, {\n status: 403,\n statusText: \"Invalid token\",\n })\n }\n\n return { authenticated: true }\n },\n }),\n})\n\n// Test tool\nserver.addTool({\n name: \"test_reddit_mcp_server\",\n description: \"Test the Reddit MCP Server connection and configuration\",\n parameters: z.object({}),\n execute: async () => {\n const client = getRedditClient()\n const hasAuth = client ? \"✓\" : \"✗\"\n const hasWriteAccess = process.env.REDDIT_USERNAME && process.env.REDDIT_PASSWORD ? \"✓\" : \"✗\"\n\n return `Reddit MCP Server Status:\n- Server: ✓ Running\n- Reddit Client: ${hasAuth} ${client ? \"Initialized\" : \"Not initialized\"}\n- Write Access: ${hasWriteAccess} ${hasWriteAccess === \"✓\" ? \"Available\" : \"Read-only mode\"}\n- Version: ${VERSION}\n\nReady to handle Reddit API requests!`\n },\n})\n\n// User tools\nserver.addTool({\n name: \"get_user_info\",\n description: \"Get detailed information about a Reddit user including karma, account status, and activity analysis\",\n parameters: z.object({\n username: z.string().describe(\"The Reddit username (without u/ prefix)\"),\n }),\n execute: async (args) => {\n const client = getRedditClient()\n if (!client) {\n throw new Error(\"Reddit client not initialized\")\n }\n\n const user = await client.getUser(args.username)\n const formattedUser = formatUserInfo(user)\n\n return `# User Information: u/${formattedUser.username}\n\n## Profile Overview\n- Username: u/${formattedUser.username}\n- Karma:\n - Comment Karma: ${formattedUser.karma.commentKarma.toLocaleString()}\n - Post Karma: ${formattedUser.karma.postKarma.toLocaleString()}\n - Total Karma: ${formattedUser.karma.totalKarma.toLocaleString()}\n- Account Status: ${formattedUser.accountStatus.join(\", \")}\n- Account Created: ${formattedUser.accountCreated}\n- Profile URL: ${formattedUser.profileUrl}\n\n## Activity Analysis\n- ${formattedUser.activityAnalysis.replace(/\\n {2}- /g, \"\\n- \")}\n\n## Recommendations\n- ${formattedUser.recommendations.replace(/\\n {2}- /g, \"\\n- \")}`\n },\n})\n\nserver.addTool({\n name: \"get_user_posts\",\n description: \"Get recent posts by a Reddit user with sorting and filtering options\",\n parameters: z.object({\n username: z.string().describe(\"The Reddit username (without u/ prefix)\"),\n sort: z.enum([\"new\", \"hot\", \"top\"]).default(\"new\").describe(\"Sort order for posts\"),\n time_filter: z\n .enum([\"hour\", \"day\", \"week\", \"month\", \"year\", \"all\"])\n .default(\"all\")\n .describe(\"Time filter for top posts\"),\n limit: z.number().min(1).max(100).default(10).describe(\"Number of posts to retrieve\"),\n }),\n execute: async (args) => {\n const client = getRedditClient()\n if (!client) {\n throw new Error(\"Reddit client not initialized\")\n }\n\n const posts = await client.getUserPosts(args.username, {\n sort: args.sort,\n timeFilter: args.time_filter,\n limit: args.limit,\n })\n\n if (posts.length === 0) {\n return `No posts found for u/${args.username} with the specified filters.`\n }\n\n const postSummaries = posts\n .map((post, index) => {\n const flags = [...(post.over18 ? [\"**NSFW**\"] : []), ...(post.spoiler ? [\"**Spoiler**\"] : [])]\n\n return `### ${index + 1}. ${post.title} ${flags.join(\" \")}\n- Subreddit: r/${post.subreddit}\n- Score: ${post.score.toLocaleString()} (${(post.upvoteRatio * 100).toFixed(1)}% upvoted)\n- Comments: ${post.numComments.toLocaleString()}\n- Posted: ${new Date(post.createdUtc * 1000).toLocaleString()}\n- Link: https://reddit.com${post.permalink}`\n })\n .join(\"\\n\\n\")\n\n return `# Posts by u/${args.username} (${args.sort} - ${args.time_filter})\n\n${postSummaries}`\n },\n})\n\nserver.addTool({\n name: \"get_user_comments\",\n description: \"Get recent comments by a Reddit user with sorting and filtering options\",\n parameters: z.object({\n username: z.string().describe(\"The Reddit username (without u/ prefix)\"),\n sort: z.enum([\"new\", \"hot\", \"top\"]).default(\"new\").describe(\"Sort order for comments\"),\n time_filter: z\n .enum([\"hour\", \"day\", \"week\", \"month\", \"year\", \"all\"])\n .default(\"all\")\n .describe(\"Time filter for top comments\"),\n limit: z.number().min(1).max(100).default(10).describe(\"Number of comments to retrieve\"),\n }),\n execute: async (args) => {\n const client = getRedditClient()\n if (!client) {\n throw new Error(\"Reddit client not initialized\")\n }\n\n const comments = await client.getUserComments(args.username, {\n sort: args.sort,\n timeFilter: args.time_filter,\n limit: args.limit,\n })\n\n if (comments.length === 0) {\n return `No comments found for u/${args.username} with the specified filters.`\n }\n\n const commentSummaries = comments\n .map((comment, index) => {\n const truncatedBody = comment.body.length > 300 ? `${comment.body.substring(0, 300)}...` : comment.body\n\n const flags = [...(comment.edited ? [\"*(edited)*\"] : []), ...(comment.isSubmitter ? [\"**OP**\"] : [])]\n\n return `### ${index + 1}. Comment ${flags.join(\" \")}\nIn r/${comment.subreddit} on \"${comment.submissionTitle}\"\n\n> ${truncatedBody}\n\n- Score: ${comment.score.toLocaleString()}\n- Posted: ${new Date(comment.createdUtc * 1000).toLocaleString()}\n- Link: https://reddit.com${comment.permalink}`\n })\n .join(\"\\n\\n\")\n\n return `# Comments by u/${args.username} (${args.sort} - ${args.time_filter})\n\n${commentSummaries}`\n },\n})\n\n// Post tools\nserver.addTool({\n name: \"get_reddit_post\",\n description:\n \"Get detailed information about a specific Reddit post including content, stats, and engagement analysis\",\n parameters: z.object({\n subreddit: z.string().describe(\"The subreddit name (without r/ prefix)\"),\n post_id: z.string().describe(\"The Reddit post ID\"),\n }),\n execute: async (args) => {\n const client = getRedditClient()\n if (!client) {\n throw new Error(\"Reddit client not initialized\")\n }\n\n const post = await client.getPost(args.post_id, args.subreddit)\n const formattedPost = formatPostInfo(post)\n\n return `# Post from r/${formattedPost.subreddit}\n\n## Post Details\n- Title: ${formattedPost.title}\n- Type: ${formattedPost.type}\n- Author: u/${formattedPost.author}\n\n## Content\n${formattedPost.content}\n\n## Stats\n- Score: ${formattedPost.stats.score.toLocaleString()}\n- Upvote Ratio: ${(formattedPost.stats.upvoteRatio * 100).toFixed(1)}%\n- Comments: ${formattedPost.stats.comments.toLocaleString()}\n\n## Metadata\n- Posted: ${formattedPost.metadata.posted}\n- Flags: ${formattedPost.metadata.flags.length ? formattedPost.metadata.flags.join(\", \") : \"None\"}\n- Flair: ${formattedPost.metadata.flair}\n\n## Links\n- Full Post: ${formattedPost.links.fullPost}\n- Short Link: ${formattedPost.links.shortLink}\n\n## Engagement Analysis\n- ${formattedPost.engagementAnalysis.replace(/\\n {2}- /g, \"\\n- \")}\n\n## Best Time to Engage\n${formattedPost.bestTimeToEngage}`\n },\n})\n\nserver.addTool({\n name: \"get_top_posts\",\n description: \"Get top posts from a subreddit or from the Reddit home feed\",\n parameters: z.object({\n subreddit: z.string().optional().describe(\"The subreddit name (without r/ prefix). Leave empty for home feed\"),\n time_filter: z\n .enum([\"hour\", \"day\", \"week\", \"month\", \"year\", \"all\"])\n .default(\"week\")\n .describe(\"Time period for top posts\"),\n limit: z.number().min(1).max(100).default(10).describe(\"Number of posts to retrieve\"),\n }),\n execute: async (args) => {\n const client = getRedditClient()\n if (!client) {\n throw new Error(\"Reddit client not initialized\")\n }\n\n const posts = await client.getTopPosts(args.subreddit || \"\", args.time_filter, args.limit)\n\n if (posts.length === 0) {\n const location = args.subreddit ? `r/${args.subreddit}` : \"home feed\"\n return `No posts found in ${location} for the specified time period.`\n }\n\n const formattedPosts = posts.map(formatPostInfo)\n const postSummaries = formattedPosts\n .map(\n (post, index) => `### ${index + 1}. ${post.title}\n- Author: u/${post.author}\n- Score: ${post.stats.score.toLocaleString()} (${(post.stats.upvoteRatio * 100).toFixed(1)}% upvoted)\n- Comments: ${post.stats.comments.toLocaleString()}\n- Posted: ${post.metadata.posted}\n- Link: ${post.links.shortLink}`,\n )\n .join(\"\\n\\n\")\n\n const location = args.subreddit ? `r/${args.subreddit}` : \"Home Feed\"\n return `# Top Posts from ${location} (${args.time_filter})\n\n${postSummaries}`\n },\n})\n\n// Subreddit tools\nserver.addTool({\n name: \"get_subreddit_info\",\n description: \"Get detailed information about a subreddit including description, stats, and community analysis\",\n parameters: z.object({\n subreddit_name: z.string().describe(\"The subreddit name (without r/ prefix)\"),\n }),\n execute: async (args) => {\n const client = getRedditClient()\n if (!client) {\n throw new Error(\"Reddit client not initialized\")\n }\n\n const subreddit = await client.getSubredditInfo(args.subreddit_name)\n const formattedSubreddit = formatSubredditInfo(subreddit)\n\n return `# Subreddit Information: r/${formattedSubreddit.name}\n\n## Overview\n- Name: r/${formattedSubreddit.name}\n- Title: ${formattedSubreddit.title}\n- Subscribers: ${formattedSubreddit.stats.subscribers.toLocaleString()}\n- Active Users: ${\n typeof formattedSubreddit.stats.activeUsers === \"number\"\n ? formattedSubreddit.stats.activeUsers.toLocaleString()\n : formattedSubreddit.stats.activeUsers\n }\n\n## Description\n${formattedSubreddit.description.short}\n\n## Detailed Description\n${formattedSubreddit.description.full}\n\n## Metadata\n- Created: ${formattedSubreddit.metadata.created}\n- Flags: ${formattedSubreddit.metadata.flags.join(\", \")}\n\n## Links\n- Subreddit: ${formattedSubreddit.links.subreddit}\n- Wiki: ${formattedSubreddit.links.wiki}\n\n## Community Analysis\n- ${formattedSubreddit.communityAnalysis.replace(/\\n {2}- /g, \"\\n- \")}\n\n## Engagement Tips\n- ${formattedSubreddit.engagementTips.replace(/\\n {2}- /g, \"\\n- \")}`\n },\n})\n\nserver.addTool({\n name: \"get_trending_subreddits\",\n description: \"Get a list of currently trending subreddits\",\n parameters: z.object({}),\n execute: async () => {\n const client = getRedditClient()\n if (!client) {\n throw new Error(\"Reddit client not initialized\")\n }\n\n const trendingSubreddits = await client.getTrendingSubreddits()\n\n return `# Trending Subreddits\n\n${trendingSubreddits.map((subreddit, index) => `${index + 1}. r/${subreddit}`).join(\"\\n\")}`\n },\n})\n\n// Search tools\nserver.addTool({\n name: \"search_reddit\",\n description: \"Search Reddit for posts and content across subreddits\",\n parameters: z.object({\n query: z.string().describe(\"Search query\"),\n subreddit: z.string().optional().describe(\"Limit search to specific subreddit (without r/ prefix)\"),\n sort: z.enum([\"relevance\", \"hot\", \"top\", \"new\", \"comments\"]).default(\"relevance\").describe(\"Sort order\"),\n time_filter: z.enum([\"hour\", \"day\", \"week\", \"month\", \"year\", \"all\"]).default(\"all\").describe(\"Time filter\"),\n limit: z.number().min(1).max(100).default(10).describe(\"Number of results\"),\n type: z.enum([\"link\", \"sr\", \"user\"]).default(\"link\").describe(\"Type of content to search\"),\n }),\n execute: async (args) => {\n const client = getRedditClient()\n if (!client) {\n throw new Error(\"Reddit client not initialized\")\n }\n\n if (!args.query || args.query.trim() === \"\") {\n throw new Error(\"Search query cannot be empty\")\n }\n\n const posts = await client.searchReddit(args.query, {\n subreddit: args.subreddit,\n sort: args.sort,\n timeFilter: args.time_filter,\n limit: args.limit,\n type: args.type,\n })\n\n if (posts.length === 0) {\n const searchLocation = args.subreddit ? ` in r/${args.subreddit}` : \"\"\n return `No results found for \"${args.query}\"${searchLocation}.`\n }\n\n const searchResults = posts\n .map((post, index) => {\n const flags = [...(post.over18 ? [\"**NSFW**\"] : []), ...(post.spoiler ? [\"**Spoiler**\"] : [])]\n\n return `### ${index + 1}. ${post.title} ${flags.join(\" \")}\n- Subreddit: r/${post.subreddit}\n- Author: u/${post.author}\n- Score: ${post.score.toLocaleString()} (${(post.upvoteRatio * 100).toFixed(1)}% upvoted)\n- Comments: ${post.numComments.toLocaleString()}\n- Posted: ${new Date(post.createdUtc * 1000).toLocaleString()}\n- Link: https://reddit.com${post.permalink}`\n })\n .join(\"\\n\\n\")\n\n const searchLocation = args.subreddit ? ` in r/${args.subreddit}` : \"\"\n return `# Reddit Search Results for: \"${args.query}\"${searchLocation}\n\nSorted by: ${args.sort} | Time: ${args.time_filter} | Type: ${args.type}\n\n${searchResults}`\n },\n})\n\n// Write tools (require user authentication)\nserver.addTool({\n name: \"create_post\",\n description:\n \"Create a new post in a subreddit (requires REDDIT_USERNAME and REDDIT_PASSWORD). \" +\n \"WARNING: Rapid posting or duplicate content may trigger Reddit's spam detection and result in account bans. \" +\n \"Consider enabling REDDIT_SAFE_MODE=standard for protection.\",\n parameters: z.object({\n subreddit: z.string().describe(\"The subreddit name (without r/ prefix)\"),\n title: z.string().describe(\"The post title\"),\n content: z.string().describe(\"The post content (text for self posts, URL for link posts)\"),\n is_self: z.boolean().default(true).describe(\"Whether this is a self post (text) or link post\"),\n }),\n execute: async (args) => {\n const client = getRedditClient()\n if (!client) {\n throw new Error(\"Reddit client not initialized\")\n }\n\n // Check if user credentials are configured\n if (!process.env.REDDIT_USERNAME || !process.env.REDDIT_PASSWORD) {\n throw new Error(\n \"User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables.\",\n )\n }\n\n const post = await client.createPost(args.subreddit, args.title, args.content, args.is_self)\n const formattedPost = formatPostInfo(post)\n\n return `# Post Created Successfully\n\n## Post Details\n- Title: ${formattedPost.title}\n- Subreddit: r/${formattedPost.subreddit}\n- Type: ${formattedPost.type}\n- Link: ${formattedPost.links.fullPost}\n\nYour post has been successfully submitted to r/${formattedPost.subreddit}.`\n },\n})\n\nserver.addTool({\n name: \"reply_to_post\",\n description:\n \"Post a reply to an existing Reddit post or comment (requires REDDIT_USERNAME and REDDIT_PASSWORD). \" +\n \"WARNING: Rapid commenting or duplicate content may trigger Reddit's spam detection. \" +\n \"Enable REDDIT_SAFE_MODE=standard for rate limiting and duplicate detection.\",\n parameters: z.object({\n post_id: z.string().describe(\"The Reddit post ID (thing_id, e.g., t3_xxxxx for posts, t1_xxxxx for comments)\"),\n content: z.string().describe(\"The reply content\"),\n }),\n execute: async (args) => {\n const client = getRedditClient()\n if (!client) {\n throw new Error(\"Reddit client not initialized\")\n }\n\n // Check if user credentials are configured\n if (!process.env.REDDIT_USERNAME || !process.env.REDDIT_PASSWORD) {\n throw new Error(\n \"User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables.\",\n )\n }\n\n const comment = await client.replyToPost(args.post_id, args.content)\n\n return `# Reply Posted Successfully\n\n## Comment Details\n- Posted to: ${args.post_id}\n- Author: u/${process.env.REDDIT_USERNAME}\n- Comment ID: ${comment.id}\n\nYour reply has been successfully posted.`\n },\n})\n\nserver.addTool({\n name: \"delete_post\",\n description:\n \"Delete your own Reddit post (requires REDDIT_USERNAME and REDDIT_PASSWORD). WARNING: This action is permanent and cannot be undone!\",\n parameters: z.object({\n thing_id: z\n .string()\n .describe(\n \"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.\",\n ),\n }),\n execute: async (args) => {\n const client = getRedditClient()\n if (!client) {\n throw new Error(\"Reddit client not initialized\")\n }\n\n // Check if user credentials are configured\n if (!process.env.REDDIT_USERNAME || !process.env.REDDIT_PASSWORD) {\n throw new Error(\n \"User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables.\",\n )\n }\n\n await client.deletePost(args.thing_id)\n\n return `# Post Deleted Successfully\n\nThe post ${args.thing_id} has been permanently deleted from Reddit.\n\n**Note**: This action cannot be undone. The post content has been removed and cannot be recovered.`\n },\n})\n\nserver.addTool({\n name: \"delete_comment\",\n description:\n \"Delete your own Reddit comment (requires REDDIT_USERNAME and REDDIT_PASSWORD). WARNING: This action is permanent and cannot be undone!\",\n parameters: z.object({\n thing_id: z\n .string()\n .describe(\n \"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.\",\n ),\n }),\n execute: async (args) => {\n const client = getRedditClient()\n if (!client) {\n throw new Error(\"Reddit client not initialized\")\n }\n\n // Check if user credentials are configured\n if (!process.env.REDDIT_USERNAME || !process.env.REDDIT_PASSWORD) {\n throw new Error(\n \"User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables.\",\n )\n }\n\n await client.deleteComment(args.thing_id)\n\n return `# Comment Deleted Successfully\n\nThe comment ${args.thing_id} has been permanently deleted from Reddit.\n\n**Note**: This action cannot be undone. The comment content has been removed and cannot be recovered.`\n },\n})\n\nserver.addTool({\n name: \"edit_post\",\n description:\n \"Edit your own Reddit post (self-text posts only, requires REDDIT_USERNAME and REDDIT_PASSWORD). \" +\n \"You can only edit the text content of self posts, not titles or link posts. \" +\n \"WARNING: Rapid edits may trigger spam detection. Enable REDDIT_SAFE_MODE for protection.\",\n parameters: z.object({\n thing_id: z\n .string()\n .describe(\n \"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.\",\n ),\n new_text: z.string().describe(\"The new text content for the post. Supports Reddit markdown formatting.\"),\n }),\n execute: async (args) => {\n const client = getRedditClient()\n if (!client) {\n throw new Error(\"Reddit client not initialized\")\n }\n\n // Check if user credentials are configured\n if (!process.env.REDDIT_USERNAME || !process.env.REDDIT_PASSWORD) {\n throw new Error(\n \"User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables.\",\n )\n }\n\n await client.editPost(args.thing_id, args.new_text)\n\n return `# Post Edited Successfully\n\nThe post ${args.thing_id} has been updated with your new content.\n\n**Note**:\n- Only self (text) posts can be edited\n- Post titles cannot be edited\n- Link posts cannot be edited\n- An \"edited\" marker will appear on your post`\n },\n})\n\nserver.addTool({\n name: \"edit_comment\",\n description:\n \"Edit your own Reddit comment (requires REDDIT_USERNAME and REDDIT_PASSWORD). \" +\n \"Update the text content of a comment you previously posted. \" +\n \"WARNING: Rapid edits may trigger spam detection. Enable REDDIT_SAFE_MODE for protection.\",\n parameters: z.object({\n thing_id: z\n .string()\n .describe(\n \"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.\",\n ),\n new_text: z.string().describe(\"The new text content for the comment. Supports Reddit markdown formatting.\"),\n }),\n execute: async (args) => {\n const client = getRedditClient()\n if (!client) {\n throw new Error(\"Reddit client not initialized\")\n }\n\n // Check if user credentials are configured\n if (!process.env.REDDIT_USERNAME || !process.env.REDDIT_PASSWORD) {\n throw new Error(\n \"User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables.\",\n )\n }\n\n await client.editComment(args.thing_id, args.new_text)\n\n return `# Comment Edited Successfully\n\nThe comment ${args.thing_id} has been updated with your new content.\n\n**Note**: An \"edited\" marker will appear on your comment to show it has been modified.`\n },\n})\n\n// Comment tools\nserver.addTool({\n name: \"get_post_comments\",\n description: \"Get comments from a specific Reddit post\",\n parameters: z.object({\n post_id: z.string().describe(\"The Reddit post ID\"),\n subreddit: z.string().describe(\"The subreddit name (without r/ prefix)\"),\n sort: z.enum([\"best\", \"top\", \"new\", \"controversial\", \"old\", \"qa\"]).default(\"best\").describe(\"Comment sort order\"),\n limit: z.number().min(1).max(500).default(100).describe(\"Maximum number of comments to retrieve\"),\n }),\n execute: async (args) => {\n const client = getRedditClient()\n if (!client) {\n throw new Error(\"Reddit client not initialized\")\n }\n\n if (!args.post_id || !args.subreddit) {\n throw new Error(\"post_id and subreddit are required\")\n }\n\n const data = await client.getPostComments(args.post_id, args.subreddit, {\n sort: args.sort,\n limit: args.limit,\n })\n\n const { post } = data\n const { comments } = data\n\n let response = `# Comments for: ${post.title}\n\n**Post by u/${post.author} in r/${post.subreddit}**\n- Score: ${post.score.toLocaleString()} | Comments: ${post.numComments.toLocaleString()}\n- Posted: ${new Date(post.createdUtc * 1000).toLocaleString()}\n\n---\n\n`\n\n if (comments.length === 0) {\n response += \"No comments found for this post.\"\n return response\n }\n\n const commentSummaries = comments\n .map((comment) => {\n const indent = \"└─\".repeat(Math.min(comment.depth || 0, 3))\n const authorBadge = comment.isSubmitter ? \" **[OP]**\" : \"\"\n const editedBadge = comment.edited ? \" *(edited)*\" : \"\"\n\n return `${indent} **u/${comment.author}**${authorBadge}${editedBadge} (${comment.score.toLocaleString()} points)\n\n${comment.body}\n\n---`\n })\n .join(\"\\n\\n\")\n\n response += commentSummaries\n return response\n },\n})\n\n// Initialize and start server\nasync function main() {\n try {\n await setupRedditClient()\n\n // Default to stdio for local/MCP client usage\n // Use HTTP only when explicitly requested (e.g., for Docker)\n const useHttp = process.env.TRANSPORT_TYPE === \"httpStream\" || process.env.TRANSPORT_TYPE === \"http\"\n const port = parseInt(process.env.PORT || \"3000\")\n const host = process.env.HOST || \"127.0.0.1\"\n\n if (useHttp) {\n console.error(`[Setup] Starting HTTP server on ${host}:${port}`)\n await server.start({\n transportType: \"httpStream\",\n httpStream: {\n port,\n host,\n endpoint: \"/mcp\",\n },\n })\n console.error(`[Setup] HTTP server ready at http://${host}:${port}/mcp`)\n console.error(`[Setup] SSE endpoint available at http://${host}:${port}/sse`)\n } else {\n console.error(\"[Setup] Starting in stdio mode\")\n await server.start({\n transportType: \"stdio\",\n })\n }\n } catch (error) {\n console.error(\"[Error] Failed to start server:\", error)\n process.exit(1)\n }\n}\n\n// Handle graceful shutdown\nprocess.on(\"SIGINT\", async () => {\n console.error(\"[Shutdown] Shutting down Reddit MCP Server...\")\n process.exit(0)\n})\n\nprocess.on(\"SIGTERM\", async () => {\n console.error(\"[Shutdown] Shutting down Reddit MCP Server...\")\n process.exit(0)\n})\n\nmain().catch(console.error)\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/client/reddit-client.ts","../src/utils/formatters.ts","../src/index.ts"],"sourcesContent":["import crypto from \"crypto\"\n\nimport type {\n BotDisclosureConfig,\n ContentRecord,\n RedditApiCommentResponse,\n RedditApiCommentTreeData,\n RedditApiEditResponse,\n RedditApiInfoResponse,\n RedditApiListingResponse,\n RedditApiPopularSubredditsResponse,\n RedditApiPostCommentsResponse,\n RedditApiPostData,\n RedditApiSubmitResponse,\n RedditApiSubredditResponse,\n RedditApiUserResponse,\n RedditAuthMode,\n RedditClientConfig,\n RedditComment,\n RedditPost,\n RedditSubreddit,\n RedditUser,\n SafeModeConfig,\n} from \"../types\"\n\nexport class RedditClient {\n private clientId: string\n private clientSecret: string\n private userAgent: string\n private username?: string\n private password?: string\n private accessToken?: string\n private tokenExpiry: number = 0\n private baseUrl: string\n private authenticated: boolean = false\n private authMode: RedditAuthMode\n private hasCredentials: boolean\n\n // Safe mode properties\n private safeMode: SafeModeConfig\n private lastWriteTime: number = 0\n private recentContentRecords: ContentRecord[] = []\n\n // Bot disclosure\n private botDisclosure: BotDisclosureConfig\n\n constructor(config: RedditClientConfig) {\n this.clientId = config.clientId\n this.clientSecret = config.clientSecret\n this.userAgent = config.userAgent\n this.username = config.username\n this.password = config.password\n this.authMode = config.authMode || \"auto\"\n this.hasCredentials = !!(this.clientId && this.clientSecret)\n this.baseUrl = this.determineBaseUrl()\n\n // Initialize safe mode config with defaults\n this.safeMode = config.safeMode || {\n enabled: false,\n mode: \"off\",\n writeDelayMs: 0,\n duplicateCheck: false,\n maxRecentHashes: 10,\n }\n\n // Initialize bot disclosure config\n this.botDisclosure = config.botDisclosure || { enabled: false, footer: \"\" }\n }\n\n private determineBaseUrl(): string {\n switch (this.authMode) {\n case \"authenticated\":\n return \"https://oauth.reddit.com\"\n case \"anonymous\":\n return \"https://www.reddit.com\"\n case \"auto\":\n return this.hasCredentials ? \"https://oauth.reddit.com\" : \"https://www.reddit.com\"\n }\n }\n\n private async makeRequest(path: string, options: RequestInit = {}): Promise<Response> {\n const requiresAuth = this.authMode === \"authenticated\" || (this.authMode === \"auto\" && this.hasCredentials)\n\n // Authenticate only if required\n if (requiresAuth && (Date.now() >= this.tokenExpiry || !this.authenticated)) {\n await this.authenticate()\n }\n\n const url = `${this.baseUrl}${path}`\n const headers: Record<string, string> = {\n \"User-Agent\": this.userAgent,\n ...(options.headers as Record<string, string>),\n }\n\n // Add Bearer token only if authenticated\n if (requiresAuth && this.accessToken) {\n headers.Authorization = `Bearer ${this.accessToken}`\n }\n\n const response = await fetch(url, {\n ...options,\n headers,\n })\n\n // If unauthorized, try to refresh token and retry once\n if (response.status === 401 && this.authenticated) {\n await this.authenticate()\n const retryHeaders = {\n ...headers,\n Authorization: `Bearer ${this.accessToken}`,\n }\n return fetch(url, {\n ...options,\n headers: retryHeaders,\n })\n }\n\n return response\n }\n\n async authenticate(): Promise<void> {\n // Skip in anonymous mode\n if (this.authMode === \"anonymous\") {\n this.authenticated = false\n return\n }\n\n // Require credentials in authenticated mode\n if (this.authMode === \"authenticated\" && !this.hasCredentials) {\n throw new Error(\"Authenticated mode requires REDDIT_CLIENT_ID and REDDIT_CLIENT_SECRET\")\n }\n\n // Skip if auto mode without credentials\n if (this.authMode === \"auto\" && !this.hasCredentials) {\n this.authenticated = false\n return\n }\n\n try {\n const now = Date.now()\n if (this.accessToken && now < this.tokenExpiry) {\n return\n }\n\n const authUrl = \"https://www.reddit.com/api/v1/access_token\"\n const authData = new URLSearchParams()\n\n const { username } = this\n const { password } = this\n const isUserAuth = !!(username && password)\n if (isUserAuth) {\n // Authenticating with user credentials\n authData.append(\"grant_type\", \"password\")\n authData.append(\"username\", username)\n authData.append(\"password\", password)\n } else {\n // Authenticating with client credentials (read-only)\n authData.append(\"grant_type\", \"client_credentials\")\n }\n\n const credentials = Buffer.from(`${this.clientId}:${this.clientSecret}`).toString(\"base64\")\n const response = await fetch(authUrl, {\n method: \"POST\",\n headers: {\n \"User-Agent\": this.userAgent,\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n Authorization: `Basic ${credentials}`,\n },\n body: authData.toString(),\n })\n\n if (!response.ok) {\n const statusText = response.statusText || \"Unknown Error\"\n throw new Error(`Authentication failed: ${response.status} ${statusText}`)\n }\n\n const data = (await response.json()) as { access_token: string; expires_in: number }\n this.accessToken = data.access_token\n this.tokenExpiry = now + data.expires_in * 1000\n this.authenticated = true\n } catch (error) {\n // Re-throw with more specific error message\n if (error instanceof Error) {\n throw error\n }\n throw new Error(\"Failed to authenticate with Reddit API\", { cause: error })\n }\n }\n\n async checkAuthentication(): Promise<boolean> {\n if (!this.authenticated) {\n try {\n await this.authenticate()\n return true\n } catch {\n return false\n }\n }\n return true\n }\n\n private validateWriteAccess(): void {\n if (!this.username || !this.password) {\n if (this.authMode === \"anonymous\") {\n throw new Error(\n \"Write operations not available in anonymous mode. \" +\n \"Set REDDIT_USERNAME, REDDIT_PASSWORD and use 'auto' or 'authenticated' mode.\",\n )\n }\n throw new Error(\"Write operations require REDDIT_USERNAME and REDDIT_PASSWORD\")\n }\n }\n\n private async enforceWriteRateLimit(): Promise<void> {\n if (!this.safeMode.enabled || this.safeMode.writeDelayMs <= 0) {\n return\n }\n\n const now = Date.now()\n const elapsed = now - this.lastWriteTime\n if (elapsed < this.safeMode.writeDelayMs) {\n const waitTime = this.safeMode.writeDelayMs - elapsed\n console.error(`[SafeMode] Rate limit: waiting ${waitTime}ms before write operation`)\n await new Promise((resolve) => setTimeout(resolve, waitTime))\n }\n this.lastWriteTime = Date.now()\n }\n\n private hashContent(content: string): string {\n return crypto.createHash(\"sha256\").update(content.trim().toLowerCase()).digest(\"hex\")\n }\n\n private checkDuplicateContent(content: string, subreddit?: string): void {\n if (!this.safeMode.enabled || !this.safeMode.duplicateCheck) {\n return\n }\n\n const hash = this.hashContent(content)\n\n // Check for duplicates\n for (const record of this.recentContentRecords) {\n if (record.hash === hash) {\n if (subreddit && record.subreddit && subreddit !== record.subreddit) {\n throw new Error(\n \"Cross-subreddit duplicate detected. Reddit's Responsible Builder Policy prohibits \" +\n \"posting identical or substantially similar content across multiple subreddits. \" +\n \"Please create unique content for each subreddit.\",\n )\n }\n throw new Error(\n \"Duplicate content detected. Reddit's spam filter may ban your account for posting identical content. \" +\n \"Please modify your content and try again.\",\n )\n }\n }\n\n // Add to recent records\n this.recentContentRecords.push({\n hash,\n subreddit: subreddit || \"\",\n timestamp: Date.now(),\n })\n\n // Remove oldest records if over limit (FIFO)\n while (this.recentContentRecords.length > this.safeMode.maxRecentHashes) {\n this.recentContentRecords.shift()\n }\n }\n\n private appendBotDisclosure(content: string): string {\n if (!this.botDisclosure.enabled || !this.botDisclosure.footer) {\n return content\n }\n return `${content}${this.botDisclosure.footer}`\n }\n\n async getUser(username: string): Promise<RedditUser> {\n try {\n const response = await this.makeRequest(`/user/${username}/about.json`)\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiUserResponse\n const { data } = json\n\n return {\n name: data.name,\n id: data.id,\n commentKarma: data.comment_karma,\n linkKarma: data.link_karma,\n totalKarma: data.total_karma || data.comment_karma + data.link_karma,\n isMod: data.is_mod,\n isGold: data.is_gold,\n isEmployee: data.is_employee,\n createdUtc: data.created_utc,\n profileUrl: `https://reddit.com/user/${data.name}`,\n }\n } catch {\n // Failed to get user info\n throw new Error(`Failed to get user info for ${username}`)\n }\n }\n\n async getSubredditInfo(subredditName: string): Promise<RedditSubreddit> {\n try {\n const response = await this.makeRequest(`/r/${subredditName}/about.json`)\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiSubredditResponse\n const { data } = json\n\n return {\n displayName: data.display_name,\n title: data.title,\n description: data.description || \"\",\n publicDescription: data.public_description || \"\",\n subscribers: data.subscribers,\n activeUserCount: data.active_user_count ?? undefined,\n createdUtc: data.created_utc,\n over18: data.over18,\n subredditType: data.subreddit_type,\n url: data.url,\n }\n } catch {\n // Failed to get subreddit info\n throw new Error(`Failed to get subreddit info for ${subredditName}`)\n }\n }\n\n async getTopPosts(subreddit: string, timeFilter: string = \"week\", limit: number = 10): Promise<RedditPost[]> {\n try {\n const endpoint = subreddit ? `/r/${subreddit}/top.json` : \"/top.json\"\n const params = new URLSearchParams({\n t: timeFilter,\n limit: limit.toString(),\n })\n\n const response = await this.makeRequest(`${endpoint}?${params}`)\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiListingResponse<RedditApiPostData>\n\n return json.data.children.map((child) => {\n const post = child.data\n return {\n id: post.id,\n title: post.title,\n author: post.author,\n subreddit: post.subreddit,\n selftext: post.selftext,\n url: post.url,\n score: post.score,\n upvoteRatio: post.upvote_ratio,\n numComments: post.num_comments,\n createdUtc: post.created_utc,\n over18: post.over_18,\n spoiler: post.spoiler,\n edited: !!post.edited,\n isSelf: post.is_self,\n linkFlairText: post.link_flair_text ?? undefined,\n permalink: post.permalink,\n }\n })\n } catch {\n // Failed to get top posts\n throw new Error(`Failed to get top posts for ${subreddit || \"home\"}`)\n }\n }\n\n async getPost(postId: string, subreddit?: string): Promise<RedditPost> {\n try {\n const endpoint = subreddit ? `/r/${subreddit}/comments/${postId}.json` : `/api/info.json?id=t3_${postId}`\n const response = await this.makeRequest(endpoint)\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`)\n }\n\n let post: RedditApiPostData\n if (subreddit) {\n // When using the comments endpoint - returns array [postListing, commentsListing]\n const json = (await response.json()) as [RedditApiListingResponse<RedditApiPostData>, unknown]\n post = json[0].data.children[0].data\n } else {\n // When using the info endpoint\n const json = (await response.json()) as RedditApiInfoResponse\n if (!json.data.children.length) {\n throw new Error(`Post with ID ${postId} not found`)\n }\n post = json.data.children[0].data\n }\n\n return {\n id: post.id,\n title: post.title,\n author: post.author,\n subreddit: post.subreddit,\n selftext: post.selftext,\n url: post.url,\n score: post.score,\n upvoteRatio: post.upvote_ratio,\n numComments: post.num_comments,\n createdUtc: post.created_utc,\n over18: post.over_18,\n spoiler: post.spoiler,\n edited: !!post.edited,\n isSelf: post.is_self,\n linkFlairText: post.link_flair_text ?? undefined,\n permalink: post.permalink,\n }\n } catch {\n // Failed to get post\n throw new Error(`Failed to get post with ID ${postId}`)\n }\n }\n\n async getTrendingSubreddits(limit: number = 5): Promise<string[]> {\n try {\n const params = new URLSearchParams({ limit: limit.toString() })\n const response = await this.makeRequest(`/subreddits/popular.json?${params}`)\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiPopularSubredditsResponse\n return json.data.children.map((child) => child.data.display_name)\n } catch {\n // Failed to get trending subreddits\n throw new Error(\"Failed to get trending subreddits\")\n }\n }\n\n async createPost(subreddit: string, title: string, content: string, isSelf: boolean = true): Promise<RedditPost> {\n this.validateWriteAccess()\n\n // Safe mode checks\n await this.enforceWriteRateLimit()\n this.checkDuplicateContent(title + content, subreddit)\n\n // Apply bot disclosure to self post content\n const finalContent = isSelf ? this.appendBotDisclosure(content) : content\n\n try {\n const kind = isSelf ? \"self\" : \"link\"\n const params = new URLSearchParams()\n params.append(\"sr\", subreddit)\n params.append(\"kind\", kind)\n params.append(\"title\", title)\n params.append(isSelf ? \"text\" : \"url\", finalContent)\n params.append(\"api_type\", \"json\") // Request standard JSON response format\n\n const response = await this.makeRequest(\"/api/submit\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n },\n body: params.toString(),\n })\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiSubmitResponse\n\n // With api_type=json, response has json.data.id or json.errors\n if (json.json?.errors && json.json.errors.length > 0) {\n const errors = json.json.errors.map((e) => e[1] || e[0]).join(\", \")\n throw new Error(`Reddit API errors: ${errors}`)\n }\n\n // Extract post ID from standard JSON response\n const postId = json.json?.data?.id || json.json?.data?.name?.replace(\"t3_\", \"\")\n\n if (!postId) {\n throw new Error(\"No post ID returned from Reddit\")\n }\n\n return await this.getPost(postId, subreddit)\n } catch (error) {\n if (error instanceof Error) {\n throw error\n }\n throw new Error(\n `Failed to create post in ${subreddit}: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error },\n )\n }\n }\n\n async checkPostExists(postId: string): Promise<boolean> {\n try {\n const response = await this.makeRequest(`/api/info.json?id=t3_${postId}`)\n if (!response.ok) {\n return false\n }\n\n const json = (await response.json()) as RedditApiInfoResponse\n return json.data.children.length > 0\n } catch {\n return false\n }\n }\n\n async replyToPost(postId: string, content: string): Promise<RedditComment> {\n this.validateWriteAccess()\n\n // Safe mode checks\n await this.enforceWriteRateLimit()\n this.checkDuplicateContent(content)\n\n // Apply bot disclosure\n const finalContent = this.appendBotDisclosure(content)\n\n try {\n const fullThingId = postId.startsWith(\"t3_\") || postId.startsWith(\"t1_\") ? postId : `t3_${postId}`\n\n // Only check existence for posts (t3_), not comments (t1_)\n if (!postId.startsWith(\"t1_\") && !(await this.checkPostExists(postId.replace(/^t3_/, \"\")))) {\n throw new Error(`Post with ID ${postId} does not exist or is not accessible`)\n }\n\n const params = new URLSearchParams()\n params.append(\"thing_id\", fullThingId)\n params.append(\"text\", finalContent)\n params.append(\"api_type\", \"json\") // Request standard JSON response format\n\n const response = await this.makeRequest(\"/api/comment\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n },\n body: params.toString(),\n })\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`)\n }\n\n // Extract comment data from response\n const json = (await response.json()) as RedditApiCommentResponse\n\n if (json.json?.data?.things && json.json.data.things.length > 0) {\n const commentData = json.json.data.things[0].data\n // username is guaranteed to be defined because validateWriteAccess() was called above\n const author = this.username ?? \"[unknown]\"\n return {\n id: commentData.id,\n author,\n body: content,\n score: 1,\n controversiality: 0,\n subreddit: commentData.subreddit,\n submissionTitle: commentData.link_title ?? \"\",\n createdUtc: Date.now() / 1000,\n edited: false,\n isSubmitter: false,\n permalink: commentData.permalink,\n }\n } else if (json.json?.errors && json.json.errors.length > 0) {\n const errors = json.json.errors.map((e) => e[1] || e[0]).join(\", \")\n throw new Error(`Reddit API errors: ${errors}`)\n } else {\n throw new Error(\"Failed to parse reply response\")\n }\n } catch (error) {\n if (error instanceof Error) {\n throw error\n }\n throw new Error(`Failed to reply to post ${postId}: ${error instanceof Error ? error.message : String(error)}`, {\n cause: error,\n })\n }\n }\n\n async deletePost(thingId: string): Promise<boolean> {\n this.validateWriteAccess()\n\n try {\n // Ensure thing ID has the correct prefix (t3_ for posts, t1_ for comments)\n const fullThingId = thingId.startsWith(\"t3_\") || thingId.startsWith(\"t1_\") ? thingId : `t3_${thingId}`\n\n const params = new URLSearchParams()\n params.append(\"id\", fullThingId)\n\n const response = await this.makeRequest(\"/api/del\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n },\n body: params.toString(),\n })\n\n if (!response.ok) {\n const errorText = await response.text()\n console.error(`[Reddit API] Delete failed: ${response.status} ${response.statusText}`)\n console.error(`[Reddit API] Error response: ${errorText}`)\n throw new Error(`HTTP ${response.status}: ${errorText}`)\n }\n\n console.error(`[Reddit API] Successfully deleted ${fullThingId}`)\n return true\n } catch (error) {\n console.error(`[Reddit API] Delete exception:`, error)\n if (error instanceof Error && error.message.includes(\"HTTP\")) {\n throw error\n }\n throw new Error(\n `Failed to delete content ${thingId}: ${error instanceof Error ? error.message : String(error)}`,\n {\n cause: error,\n },\n )\n }\n }\n\n async deleteComment(thingId: string): Promise<boolean> {\n // deleteComment is just an alias for deletePost since /api/del handles both\n // Ensure the thing ID has the correct prefix for comments (t1_)\n const fullThingId = thingId.startsWith(\"t1_\") ? thingId : `t1_${thingId}`\n return this.deletePost(fullThingId)\n }\n\n async editPost(thingId: string, newText: string): Promise<boolean> {\n this.validateWriteAccess()\n\n // Safe mode checks\n await this.enforceWriteRateLimit()\n this.checkDuplicateContent(newText)\n\n // Apply bot disclosure\n const finalText = this.appendBotDisclosure(newText)\n\n try {\n // Ensure thing ID has the correct prefix (t3_ for posts, t1_ for comments)\n const fullThingId = thingId.startsWith(\"t3_\") || thingId.startsWith(\"t1_\") ? thingId : `t3_${thingId}`\n\n const params = new URLSearchParams()\n params.append(\"thing_id\", fullThingId)\n params.append(\"text\", finalText)\n params.append(\"api_type\", \"json\")\n\n const response = await this.makeRequest(\"/api/editusertext\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n },\n body: params.toString(),\n })\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiEditResponse\n\n // Check for errors in response\n if (json.json?.errors && json.json.errors.length > 0) {\n const errors = json.json.errors.map((e) => e[1] || e[0]).join(\", \")\n throw new Error(`Reddit API errors: ${errors}`)\n }\n\n return true\n } catch (error) {\n if (error instanceof Error) {\n throw error\n }\n throw new Error(`Failed to edit content ${thingId}: ${error instanceof Error ? error.message : String(error)}`, {\n cause: error,\n })\n }\n }\n\n async editComment(thingId: string, newText: string): Promise<boolean> {\n // editComment is just an alias for editPost since /api/editusertext handles both\n // Ensure the thing ID has the correct prefix for comments (t1_)\n const fullThingId = thingId.startsWith(\"t1_\") ? thingId : `t1_${thingId}`\n return this.editPost(fullThingId, newText)\n }\n\n async searchReddit(\n query: string,\n options: {\n subreddit?: string\n sort?: string\n timeFilter?: string\n limit?: number\n type?: string\n } = {},\n ): Promise<RedditPost[]> {\n try {\n const { subreddit, sort = \"relevance\", timeFilter = \"all\", limit = 25, type = \"link\" } = options\n const endpoint = subreddit ? `/r/${subreddit}/search.json` : \"/search.json\"\n\n const params = new URLSearchParams({\n q: query,\n sort,\n t: timeFilter,\n limit: limit.toString(),\n type,\n ...(subreddit && { restrict_sr: \"true\" }),\n })\n\n const response = await this.makeRequest(`${endpoint}?${params}`)\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiListingResponse<RedditApiPostData>\n\n return json.data.children\n .filter((child) => child.kind === \"t3\") // Only posts\n .map((child) => {\n const post = child.data\n return {\n id: post.id,\n title: post.title,\n author: post.author,\n subreddit: post.subreddit,\n selftext: post.selftext || \"\",\n url: post.url,\n score: post.score,\n upvoteRatio: post.upvote_ratio,\n numComments: post.num_comments,\n createdUtc: post.created_utc,\n over18: post.over_18,\n spoiler: post.spoiler,\n edited: !!post.edited,\n isSelf: post.is_self,\n linkFlairText: post.link_flair_text ?? undefined,\n permalink: post.permalink,\n }\n })\n } catch {\n throw new Error(`Failed to search Reddit for: ${query}`)\n }\n }\n\n async getPostComments(\n postId: string,\n subreddit: string,\n options: {\n sort?: string\n limit?: number\n } = {},\n ): Promise<{ post: RedditPost; comments: RedditComment[] }> {\n try {\n const { sort = \"best\", limit = 100 } = options\n const params = new URLSearchParams({\n sort,\n limit: limit.toString(),\n })\n\n const response = await this.makeRequest(`/r/${subreddit}/comments/${postId}.json?${params}`)\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiPostCommentsResponse\n\n // First element is the post, second is the comments\n const postData = json[0].data.children[0].data\n const post: RedditPost = {\n id: postData.id,\n title: postData.title,\n author: postData.author,\n subreddit: postData.subreddit,\n selftext: postData.selftext || \"\",\n url: postData.url,\n score: postData.score,\n upvoteRatio: postData.upvote_ratio,\n numComments: postData.num_comments,\n createdUtc: postData.created_utc,\n over18: postData.over_18,\n spoiler: postData.spoiler,\n edited: !!postData.edited,\n isSelf: postData.is_self,\n linkFlairText: postData.link_flair_text ?? undefined,\n permalink: postData.permalink,\n }\n\n const comments: RedditComment[] = []\n const parseComments = (\n commentData: Array<{ kind: string; data: RedditApiCommentTreeData }>,\n depth: number = 0,\n ) => {\n for (const item of commentData) {\n if (item.kind === \"t1\" && item.data.body) {\n comments.push({\n id: item.data.id,\n author: item.data.author,\n body: item.data.body,\n score: item.data.score,\n controversiality: item.data.controversiality,\n subreddit: item.data.subreddit,\n submissionTitle: post.title,\n createdUtc: item.data.created_utc,\n edited: !!item.data.edited,\n isSubmitter: item.data.is_submitter,\n permalink: item.data.permalink,\n depth,\n parentId: item.data.parent_id,\n })\n\n // Parse replies recursively\n const { replies } = item.data\n if (replies && typeof replies !== \"string\" && replies.data?.children) {\n parseComments(replies.data.children, depth + 1)\n }\n }\n }\n }\n\n if (json[1]?.data?.children) {\n parseComments(json[1].data.children)\n }\n\n return { post, comments }\n } catch {\n throw new Error(`Failed to get comments for post ${postId}`)\n }\n }\n\n async getUserPosts(\n username: string,\n options: {\n sort?: string\n timeFilter?: string\n limit?: number\n } = {},\n ): Promise<RedditPost[]> {\n try {\n const { sort = \"new\", timeFilter = \"all\", limit = 25 } = options\n const params = new URLSearchParams({\n sort,\n t: timeFilter,\n limit: limit.toString(),\n })\n\n const response = await this.makeRequest(`/user/${username}/submitted.json?${params}`)\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiListingResponse<RedditApiPostData>\n\n return json.data.children\n .filter((child) => child.kind === \"t3\")\n .map((child) => {\n const post = child.data\n return {\n id: post.id,\n title: post.title,\n author: post.author,\n subreddit: post.subreddit,\n selftext: post.selftext || \"\",\n url: post.url,\n score: post.score,\n upvoteRatio: post.upvote_ratio,\n numComments: post.num_comments,\n createdUtc: post.created_utc,\n over18: post.over_18,\n spoiler: post.spoiler,\n edited: !!post.edited,\n isSelf: post.is_self,\n linkFlairText: post.link_flair_text ?? undefined,\n permalink: post.permalink,\n }\n })\n } catch {\n throw new Error(`Failed to get posts for user ${username}`)\n }\n }\n\n async getUserComments(\n username: string,\n options: {\n sort?: string\n timeFilter?: string\n limit?: number\n } = {},\n ): Promise<RedditComment[]> {\n try {\n const { sort = \"new\", timeFilter = \"all\", limit = 25 } = options\n const params = new URLSearchParams({\n sort,\n t: timeFilter,\n limit: limit.toString(),\n })\n\n const response = await this.makeRequest(`/user/${username}/comments.json?${params}`)\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiListingResponse<RedditApiCommentTreeData>\n\n return json.data.children\n .filter((child) => child.kind === \"t1\")\n .map((child) => {\n const comment = child.data\n return {\n id: comment.id,\n author: comment.author,\n body: comment.body ?? \"\",\n score: comment.score,\n controversiality: comment.controversiality,\n subreddit: comment.subreddit,\n submissionTitle: comment.link_title ?? \"\",\n createdUtc: comment.created_utc,\n edited: !!comment.edited,\n isSubmitter: comment.is_submitter,\n permalink: comment.permalink,\n }\n })\n } catch {\n throw new Error(`Failed to get comments for user ${username}`)\n }\n }\n}\n\n// Create and export singleton instance\nlet redditClient: RedditClient | null = null\n\nexport function initializeRedditClient(config: RedditClientConfig): RedditClient {\n redditClient = new RedditClient(config)\n return redditClient\n}\n\nexport function getRedditClient(): RedditClient | null {\n return redditClient\n}\n","import type {\n FormattedCommentInfo,\n FormattedPostInfo,\n FormattedSubredditInfo,\n FormattedUserInfo,\n RedditComment,\n RedditPost,\n RedditSubreddit,\n RedditUser,\n} from \"../types\"\n\nexport function formatTimestamp(timestamp: number): string {\n try {\n const date = new Date(timestamp * 1000)\n return date\n .toISOString()\n .replace(\"T\", \" \")\n .replace(/\\.\\d+Z$/, \" UTC\")\n } catch {\n return String(timestamp)\n }\n}\n\nexport function analyzeUserActivity(karmaRatio: number, isMod: boolean, accountAgeDays: number): string {\n const insights: string[] = []\n\n // Analyze karma ratio\n if (karmaRatio > 5) {\n insights.push(\"Primarily a commenter, highly engaged in discussions\")\n } else if (karmaRatio < 0.2) {\n insights.push(\"Content creator, focuses on sharing posts\")\n } else {\n insights.push(\"Balanced participation in both posting and commenting\")\n }\n\n // Analyze account age and status\n if (accountAgeDays < 30) {\n insights.push(\"New user, still exploring Reddit\")\n } else if (accountAgeDays > 365 * 5) {\n insights.push(\"Long-time Redditor with extensive platform experience\")\n }\n\n if (isMod) {\n insights.push(\"Community leader who helps maintain subreddit quality\")\n }\n\n return insights.join(\"\\n - \")\n}\n\nexport function analyzePostEngagement(score: number, ratio: number, numComments: number): string {\n const insights: string[] = []\n\n // Analyze score and ratio\n if (score > 1000 && ratio > 0.95) {\n insights.push(\"Highly successful post with strong community approval\")\n } else if (score > 100 && ratio > 0.8) {\n insights.push(\"Well-received post with good engagement\")\n } else if (ratio < 0.5) {\n insights.push(\"Controversial post that sparked debate\")\n }\n\n // Analyze comment activity\n if (numComments > 100) {\n insights.push(\"Generated significant discussion\")\n } else if (numComments > score * 0.5) {\n insights.push(\"Highly discussable content with active comment section\")\n } else if (numComments === 0) {\n insights.push(\"Yet to receive community interaction\")\n }\n\n return insights.join(\"\\n - \")\n}\n\nexport function analyzeSubredditHealth(subscribers: number, activeUsers: number | undefined, ageDays: number): string {\n const insights: string[] = []\n\n // Analyze size and activity\n if (subscribers > 1000000) {\n insights.push(\"Major subreddit with massive following\")\n } else if (subscribers > 100000) {\n insights.push(\"Well-established community\")\n } else if (subscribers < 1000) {\n insights.push(\"Niche community, potential for growth\")\n }\n\n if (activeUsers) {\n // If we have active users data\n const activityRatio = activeUsers / subscribers\n if (activityRatio > 0.1) {\n insights.push(\"Highly active community with strong engagement\")\n } else if (activityRatio < 0.01) {\n insights.push(\"Could benefit from more community engagement initiatives\")\n }\n }\n\n // Analyze age and maturity\n if (ageDays > 365 * 5) {\n insights.push(\"Mature subreddit with established culture\")\n } else if (ageDays < 90) {\n insights.push(\"New subreddit still forming its community\")\n }\n\n return insights.join(\"\\n - \")\n}\n\nexport function getUserRecommendations(karmaRatio: number, isMod: boolean, accountAgeDays: number): string {\n const recommendations: string[] = []\n\n if (karmaRatio > 5) {\n recommendations.push(\"Consider creating more posts to share your expertise\")\n } else if (karmaRatio < 0.2) {\n recommendations.push(\"Engage more in discussions to build community connections\")\n }\n\n if (accountAgeDays < 30) {\n recommendations.push(\"Explore popular subreddits in your areas of interest\")\n recommendations.push(\"Read community guidelines before posting\")\n }\n\n if (isMod) {\n recommendations.push(\"Share moderation insights with other community leaders\")\n }\n\n if (!recommendations.length) {\n recommendations.push(\"Maintain your balanced engagement across Reddit\")\n }\n\n return recommendations.join(\"\\n - \")\n}\n\nexport function getBestEngagementTime(createdUtc: number): string {\n const postHour = new Date(createdUtc * 1000).getHours()\n\n // Simple time zone analysis\n if (14 <= postHour && postHour <= 18) {\n // Peak Reddit hours\n return \"Posted during peak engagement hours (2 PM - 6 PM), good timing!\"\n } else if (23 <= postHour || postHour <= 5) {\n return \"Consider posting during more active hours (morning to evening)\"\n } else {\n return \"Posted during moderate activity hours, timing could be optimized\"\n }\n}\n\nexport function getSubredditEngagementTips(subreddit: RedditSubreddit): string {\n const tips: string[] = []\n\n if (subreddit.subscribers > 1000000) {\n tips.push(\"Post during peak hours for maximum visibility\")\n tips.push(\"Ensure content is highly polished due to high competition\")\n } else if (subreddit.subscribers < 1000) {\n tips.push(\"Engage actively to help grow the community\")\n tips.push(\"Consider cross-posting to related larger subreddits\")\n }\n\n if (subreddit.activeUserCount) {\n const activityRatio = subreddit.activeUserCount / subreddit.subscribers\n if (activityRatio > 0.1) {\n tips.push(\"Quick responses recommended due to high activity\")\n }\n }\n\n return tips.length ? tips.join(\"\\n - \") : \"Regular engagement recommended to maintain community presence\"\n}\n\nexport function analyzeCommentImpact(score: number, isEdited: boolean, isOp: boolean): string {\n const insights: string[] = []\n\n if (score > 100) {\n insights.push(\"Highly upvoted comment with significant community agreement\")\n } else if (score < 0) {\n insights.push(\"Controversial or contested viewpoint\")\n }\n\n if (isEdited) {\n insights.push(\"Refined for clarity or accuracy\")\n }\n\n if (isOp) {\n insights.push(\"Author's perspective adds context to original post\")\n }\n\n return insights.length ? insights.join(\"\\n - \") : \"Standard engagement with discussion\"\n}\n\nexport function formatUserInfo(user: RedditUser): FormattedUserInfo {\n const status: string[] = []\n if (user.isMod) status.push(\"Moderator\")\n if (user.isGold) status.push(\"Reddit Gold Member\")\n if (user.isEmployee) status.push(\"Reddit Employee\")\n\n const accountAgeDays = (Date.now() / 1000 - user.createdUtc) / (24 * 3600) // age in days\n const karmaRatio = user.commentKarma / (user.linkKarma || 1)\n\n return {\n username: user.name,\n karma: {\n commentKarma: user.commentKarma,\n postKarma: user.linkKarma,\n totalKarma: user.totalKarma,\n },\n accountStatus: status.length ? status : [\"Regular User\"],\n accountCreated: formatTimestamp(user.createdUtc),\n profileUrl: user.profileUrl,\n activityAnalysis: analyzeUserActivity(karmaRatio, user.isMod, accountAgeDays),\n recommendations: getUserRecommendations(karmaRatio, user.isMod, accountAgeDays),\n }\n}\n\nexport function formatPostInfo(post: RedditPost): FormattedPostInfo {\n const contentType = post.isSelf ? \"Text Post\" : \"Link Post\"\n const content = post.isSelf ? post.selftext || \"\" : post.url || \"\"\n\n const flags: string[] = []\n if (post.over18) flags.push(\"NSFW\")\n if (post.spoiler) flags.push(\"Spoiler\")\n if (post.edited) flags.push(\"Edited\")\n\n return {\n title: post.title,\n type: contentType,\n content: content.length > 300 ? `${content.substring(0, 297)}...` : content,\n author: post.author,\n subreddit: post.subreddit,\n stats: {\n score: post.score,\n upvoteRatio: post.upvoteRatio,\n comments: post.numComments,\n },\n metadata: {\n posted: formatTimestamp(post.createdUtc),\n flags,\n flair: post.linkFlairText || \"None\",\n },\n links: {\n fullPost: `https://reddit.com${post.permalink}`,\n shortLink: `https://redd.it/${post.id}`,\n },\n engagementAnalysis: analyzePostEngagement(post.score, post.upvoteRatio, post.numComments),\n bestTimeToEngage: getBestEngagementTime(post.createdUtc),\n }\n}\n\nexport function formatSubredditInfo(subreddit: RedditSubreddit): FormattedSubredditInfo {\n const flags: string[] = []\n if (subreddit.over18) flags.push(\"NSFW\")\n if (subreddit.subredditType) flags.push(`Type: ${subreddit.subredditType}`)\n\n const ageDays = (Date.now() / 1000 - subreddit.createdUtc) / (24 * 3600)\n\n return {\n name: subreddit.displayName,\n title: subreddit.title,\n stats: {\n subscribers: subreddit.subscribers,\n activeUsers: subreddit.activeUserCount !== undefined ? subreddit.activeUserCount : \"Unknown\",\n },\n description: {\n short: subreddit.publicDescription,\n full:\n subreddit.description.length > 300 ? `${subreddit.description.substring(0, 297)}...` : subreddit.description,\n },\n metadata: {\n created: formatTimestamp(subreddit.createdUtc),\n flags: flags.length ? flags : [\"None\"],\n },\n links: {\n subreddit: `https://reddit.com${subreddit.url}`,\n wiki: `https://reddit.com/r/${subreddit.displayName}/wiki`,\n },\n communityAnalysis: analyzeSubredditHealth(subreddit.subscribers, subreddit.activeUserCount, ageDays),\n engagementTips: getSubredditEngagementTips(subreddit),\n }\n}\n\nexport function formatCommentInfo(comment: RedditComment): FormattedCommentInfo {\n const flags: string[] = []\n if (comment.edited) flags.push(\"Edited\")\n if (comment.isSubmitter) flags.push(\"OP\")\n\n return {\n author: comment.author,\n content: comment.body.length > 300 ? `${comment.body.substring(0, 297)}...` : comment.body,\n stats: {\n score: comment.score,\n controversiality: comment.controversiality,\n },\n context: {\n subreddit: comment.subreddit,\n thread: comment.submissionTitle,\n },\n metadata: {\n posted: formatTimestamp(comment.createdUtc),\n flags: flags.length ? flags : [\"None\"],\n },\n link: `https://reddit.com${comment.permalink}`,\n commentAnalysis: analyzeCommentImpact(comment.score, comment.edited, comment.isSubmitter),\n }\n}\n\n// Simple formatter for posts (used in search and comment tools)\nexport function formatPost(post: RedditPost) {\n return {\n title: post.title,\n author: post.author,\n subreddit: post.subreddit,\n score: post.score,\n upvoteRatio: Math.round(post.upvoteRatio * 100),\n numComments: post.numComments,\n createdAt: formatTimestamp(post.createdUtc),\n selftext: post.selftext,\n permalink: post.permalink,\n nsfw: post.over18,\n spoiler: post.spoiler,\n }\n}\n","import crypto from \"crypto\"\nimport dotenv from \"dotenv\"\nimport { FastMCP } from \"fastmcp\"\nimport { z } from \"zod\"\n\nimport { getRedditClient, initializeRedditClient } from \"./client/reddit-client\"\nimport type { BotDisclosureConfig, RedditAuthMode, RedditSafeMode, SafeModeConfig } from \"./types\"\nimport { formatPostInfo, formatSubredditInfo, formatUserInfo } from \"./utils/formatters\"\n\n// Load environment variables\ndotenv.config()\n\n// Version injected at build time by tsdown\ndeclare const __VERSION__: string\nconst VERSION = (typeof __VERSION__ !== \"undefined\" ? __VERSION__ : \"0.0.0-dev\") as `${number}.${number}.${number}`\n\n// User-Agent validation and building\nfunction validateUserAgent(userAgent: string, username?: string): void {\n const recommendedPattern = /^[\\w-]+:[\\w-]+:[\\d.]+ \\(by \\/u\\/\\w+\\)$/\n if (!recommendedPattern.test(userAgent)) {\n console.error(\"[Warning] User-Agent does not follow Reddit's recommended format\")\n console.error(\"[Warning] Recommended: 'platform:app_id:version (by /u/username)'\")\n console.error(\"[Warning] Non-standard User-Agents may increase ban risk\")\n if (username) {\n console.error(`[Warning] Consider using: 'typescript:reddit-mcp-server:${VERSION} (by /u/${username})'`)\n }\n }\n}\n\nfunction buildUserAgent(customAgent?: string, username?: string): string {\n // If custom agent provided, use it (but validate)\n if (customAgent) {\n validateUserAgent(customAgent, username)\n return customAgent\n }\n\n // Auto-format with username if available\n if (username) {\n const autoAgent = `typescript:reddit-mcp-server:${VERSION} (by /u/${username})`\n console.error(`[Setup] Auto-generated User-Agent: ${autoAgent}`)\n return autoAgent\n }\n\n // Fallback using recommended format without username\n const fallbackAgent = `typescript:reddit-mcp-server:${VERSION} (by /u/anonymous)`\n console.error(\n \"[Setup] No REDDIT_USERNAME set — using anonymous User-Agent. Set REDDIT_USERNAME for a personalized agent.\",\n )\n return fallbackAgent\n}\n\n// Safe mode configuration\nfunction buildSafeModeConfig(safeMode: RedditSafeMode): SafeModeConfig {\n switch (safeMode) {\n case \"off\":\n return {\n enabled: false,\n mode: \"off\",\n writeDelayMs: 0,\n duplicateCheck: false,\n maxRecentHashes: 10,\n }\n case \"standard\":\n return {\n enabled: true,\n mode: \"standard\",\n writeDelayMs: 2000, // 2 seconds between writes\n duplicateCheck: true,\n maxRecentHashes: 10,\n }\n case \"strict\":\n return {\n enabled: true,\n mode: \"strict\",\n writeDelayMs: 5000, // 5 seconds between writes\n duplicateCheck: true,\n maxRecentHashes: 20,\n }\n }\n}\n\n// Initialize Reddit client\nasync function setupRedditClient() {\n const clientId = process.env.REDDIT_CLIENT_ID\n const clientSecret = process.env.REDDIT_CLIENT_SECRET\n const customUserAgent = process.env.REDDIT_USER_AGENT\n const username = process.env.REDDIT_USERNAME\n const password = process.env.REDDIT_PASSWORD\n const authMode = (process.env.REDDIT_AUTH_MODE || \"auto\") as RedditAuthMode\n const safeMode = (process.env.REDDIT_SAFE_MODE || \"standard\") as RedditSafeMode\n\n // Validate auth mode\n if (![\"auto\", \"authenticated\", \"anonymous\"].includes(authMode)) {\n console.error(`[Error] Invalid REDDIT_AUTH_MODE: ${authMode}`)\n console.error(\"[Error] Valid options are: auto, authenticated, anonymous\")\n process.exit(1)\n }\n\n // Validate safe mode\n if (![\"off\", \"standard\", \"strict\"].includes(safeMode)) {\n console.error(`[Error] Invalid REDDIT_SAFE_MODE: ${safeMode}`)\n console.error(\"[Error] Valid options are: off, standard, strict\")\n process.exit(1)\n }\n\n // In authenticated mode, require credentials\n if (authMode === \"authenticated\" && (!clientId || !clientSecret)) {\n console.error(\"[Error] Authenticated mode requires REDDIT_CLIENT_ID and REDDIT_CLIENT_SECRET\")\n process.exit(1)\n }\n\n // For auto/anonymous, credentials are optional\n const hasCredentials = !!(clientId && clientSecret)\n\n // Build user-agent (auto-format with username if available)\n const userAgent = buildUserAgent(customUserAgent, username)\n\n // Build safe mode config\n const safeModeConfig = buildSafeModeConfig(safeMode)\n\n // Build bot disclosure config\n const botDisclosureMode = process.env.REDDIT_BOT_DISCLOSURE || \"off\"\n const defaultFooter =\n \"\\n\\n---\\n^(🤖 I am a bot | Built with) [^reddit-mcp-server](https://github.com/jordanburke/reddit-mcp-server)\"\n const botDisclosureConfig: BotDisclosureConfig = {\n enabled: botDisclosureMode === \"auto\",\n footer: botDisclosureMode === \"auto\" ? process.env.REDDIT_BOT_FOOTER || defaultFooter : \"\",\n }\n\n try {\n const client = initializeRedditClient({\n clientId: clientId || \"\",\n clientSecret: clientSecret || \"\",\n userAgent,\n username,\n password,\n authMode,\n safeMode: safeModeConfig,\n botDisclosure: botDisclosureConfig,\n })\n\n console.error(\"[Setup] Reddit client initialized\")\n console.error(`[Setup] Authentication mode: ${authMode}`)\n\n if (authMode === \"anonymous\" || !hasCredentials) {\n console.error(\"[Setup] Using anonymous Reddit API (~10 req/min)\")\n console.error(\"[Setup] No authentication required - ready to use!\")\n } else {\n console.error(\"[Setup] Testing Reddit API connection...\")\n const isConnected = await client.checkAuthentication()\n\n if (!isConnected) {\n console.error(\"[Error] ✗ Failed to connect to Reddit API\")\n console.error(\"[Error] Please check your REDDIT_CLIENT_ID and REDDIT_CLIENT_SECRET\")\n process.exit(1)\n }\n\n console.error(\"[Setup] ✓ Reddit API connection successful\")\n console.error(\"[Setup] Using OAuth Reddit API (60-100 req/min)\")\n }\n\n if (username && password) {\n console.error(`[Setup] ✓ User authenticated as: ${username}`)\n console.error(\"[Setup] Write operations enabled (posting, replying, editing, deleting)\")\n } else {\n console.error(\"[Setup] Read-only mode (no user credentials)\")\n console.error(\"[Setup] For write operations, set REDDIT_USERNAME and REDDIT_PASSWORD\")\n }\n\n // Log safe mode status\n if (safeModeConfig.enabled) {\n console.error(`[Setup] ✓ Safe mode enabled: ${safeModeConfig.mode}`)\n console.error(`[Setup] - Write delay: ${safeModeConfig.writeDelayMs}ms between operations`)\n console.error(`[Setup] - Duplicate detection: enabled (tracking last ${safeModeConfig.maxRecentHashes} items)`)\n } else {\n console.error(\n \"[Setup] Safe mode: off (explicitly disabled — ensure compliance with Reddit's Responsible Builder Policy)\",\n )\n }\n\n // Log bot disclosure status\n if (botDisclosureConfig.enabled) {\n console.error(\"[Setup] ✓ Bot disclosure: enabled (automated content will include bot footer)\")\n } else {\n console.error(\"[Setup] Bot disclosure: off\")\n console.error(\"[Setup] For Reddit policy compliance, consider REDDIT_BOT_DISCLOSURE=auto\")\n }\n } catch (error) {\n console.error(\"[Error] ✗ Reddit API connection failed:\", error instanceof Error ? error.message : error)\n console.error(\"[Error] Please verify your Reddit API credentials\")\n process.exit(1)\n }\n}\n\n// OAuth token: generate once at startup, never expose in responses\nconst oauthToken = process.env.OAUTH_TOKEN || crypto.randomBytes(32).toString(\"hex\")\nif (process.env.OAUTH_ENABLED === \"true\" && !process.env.OAUTH_TOKEN) {\n console.error(`[Auth] Generated OAuth token: ${oauthToken}`)\n}\n\n// Create FastMCP server\nconst server = new FastMCP({\n name: \"reddit-mcp-server\",\n version: VERSION,\n instructions: `A comprehensive Reddit MCP server that provides tools for interacting with Reddit API.\n\nAvailable capabilities:\n- Fetch Reddit posts, comments, and user information\n- Get subreddit details and statistics\n- Search Reddit content across posts and subreddits\n- Create posts and reply to posts/comments (with authentication)\n- Edit your own posts and comments (with authentication)\n- Delete your own posts and comments (with authentication)\n- Analyze engagement metrics and community insights\n\nFor write operations (posting, replying, editing, deleting), ensure REDDIT_USERNAME and REDDIT_PASSWORD are configured.\n\nIMPORTANT - Reddit Responsible Builder Policy compliance:\n- Data retrieved via these tools must NOT be used for AI model training without Reddit's written approval\n- Data must NOT be sold, licensed, or commercially redistributed\n- Do NOT attempt to de-anonymize or re-identify Reddit users\n- Do NOT post identical or substantially similar content across multiple subreddits\n- Do NOT use these tools to manipulate votes, karma, or circumvent Reddit safety mechanisms\n- All bot-generated content must clearly disclose its automated nature\n- Bots must NOT send private/direct messages without explicit user consent\nFor details: https://support.reddithelp.com/hc/en-us/articles/42728983564564-Responsible-Builder-Policy`,\n\n // Optional OAuth configuration for HTTP transport\n ...(process.env.OAUTH_ENABLED === \"true\" && {\n authenticate: async (request) => {\n const authHeader = request.headers.authorization\n if (!authHeader?.startsWith(\"Bearer \")) {\n throw new Response(null, {\n status: 401,\n statusText: \"Missing or invalid Authorization header\",\n })\n }\n\n const token = authHeader.slice(7)\n const tokenBuffer = Buffer.from(token)\n const expectedBuffer = Buffer.from(oauthToken)\n // Constant-time comparison: hash both so timingSafeEqual always compares equal-length buffers\n const tokenHash = crypto.createHash(\"sha256\").update(tokenBuffer).digest()\n const expectedHash = crypto.createHash(\"sha256\").update(expectedBuffer).digest()\n if (!crypto.timingSafeEqual(tokenHash, expectedHash)) {\n throw new Response(null, {\n status: 403,\n statusText: \"Invalid token\",\n })\n }\n\n return { authenticated: true }\n },\n }),\n})\n\n// Test tool\nserver.addTool({\n name: \"test_reddit_mcp_server\",\n description: \"Test the Reddit MCP Server connection and configuration\",\n parameters: z.object({}),\n execute: async () => {\n const client = getRedditClient()\n const hasAuth = client ? \"✓\" : \"✗\"\n const hasWriteAccess = process.env.REDDIT_USERNAME && process.env.REDDIT_PASSWORD ? \"✓\" : \"✗\"\n\n return `Reddit MCP Server Status:\n- Server: ✓ Running\n- Reddit Client: ${hasAuth} ${client ? \"Initialized\" : \"Not initialized\"}\n- Write Access: ${hasWriteAccess} ${hasWriteAccess === \"✓\" ? \"Available\" : \"Read-only mode\"}\n- Version: ${VERSION}\n\nReady to handle Reddit API requests!`\n },\n})\n\n// User tools\nserver.addTool({\n name: \"get_user_info\",\n description: \"Get detailed information about a Reddit user including karma, account status, and activity analysis\",\n parameters: z.object({\n username: z.string().describe(\"The Reddit username (without u/ prefix)\"),\n }),\n execute: async (args) => {\n const client = getRedditClient()\n if (!client) {\n throw new Error(\"Reddit client not initialized\")\n }\n\n const user = await client.getUser(args.username)\n const formattedUser = formatUserInfo(user)\n\n return `# User Information: u/${formattedUser.username}\n\n## Profile Overview\n- Username: u/${formattedUser.username}\n- Karma:\n - Comment Karma: ${formattedUser.karma.commentKarma.toLocaleString()}\n - Post Karma: ${formattedUser.karma.postKarma.toLocaleString()}\n - Total Karma: ${formattedUser.karma.totalKarma.toLocaleString()}\n- Account Status: ${formattedUser.accountStatus.join(\", \")}\n- Account Created: ${formattedUser.accountCreated}\n- Profile URL: ${formattedUser.profileUrl}\n\n## Activity Analysis\n- ${formattedUser.activityAnalysis.replace(/\\n {2}- /g, \"\\n- \")}\n\n## Recommendations\n- ${formattedUser.recommendations.replace(/\\n {2}- /g, \"\\n- \")}`\n },\n})\n\nserver.addTool({\n name: \"get_user_posts\",\n description: \"Get recent posts by a Reddit user with sorting and filtering options\",\n parameters: z.object({\n username: z.string().describe(\"The Reddit username (without u/ prefix)\"),\n sort: z.enum([\"new\", \"hot\", \"top\"]).default(\"new\").describe(\"Sort order for posts\"),\n time_filter: z\n .enum([\"hour\", \"day\", \"week\", \"month\", \"year\", \"all\"])\n .default(\"all\")\n .describe(\"Time filter for top posts\"),\n limit: z.number().min(1).max(100).default(10).describe(\"Number of posts to retrieve\"),\n }),\n execute: async (args) => {\n const client = getRedditClient()\n if (!client) {\n throw new Error(\"Reddit client not initialized\")\n }\n\n const posts = await client.getUserPosts(args.username, {\n sort: args.sort,\n timeFilter: args.time_filter,\n limit: args.limit,\n })\n\n if (posts.length === 0) {\n return `No posts found for u/${args.username} with the specified filters.`\n }\n\n const postSummaries = posts\n .map((post, index) => {\n const flags = [...(post.over18 ? [\"**NSFW**\"] : []), ...(post.spoiler ? [\"**Spoiler**\"] : [])]\n\n return `### ${index + 1}. ${post.title} ${flags.join(\" \")}\n- Subreddit: r/${post.subreddit}\n- Score: ${post.score.toLocaleString()} (${(post.upvoteRatio * 100).toFixed(1)}% upvoted)\n- Comments: ${post.numComments.toLocaleString()}\n- Posted: ${new Date(post.createdUtc * 1000).toLocaleString()}\n- Link: https://reddit.com${post.permalink}`\n })\n .join(\"\\n\\n\")\n\n return `# Posts by u/${args.username} (${args.sort} - ${args.time_filter})\n\n${postSummaries}`\n },\n})\n\nserver.addTool({\n name: \"get_user_comments\",\n description: \"Get recent comments by a Reddit user with sorting and filtering options\",\n parameters: z.object({\n username: z.string().describe(\"The Reddit username (without u/ prefix)\"),\n sort: z.enum([\"new\", \"hot\", \"top\"]).default(\"new\").describe(\"Sort order for comments\"),\n time_filter: z\n .enum([\"hour\", \"day\", \"week\", \"month\", \"year\", \"all\"])\n .default(\"all\")\n .describe(\"Time filter for top comments\"),\n limit: z.number().min(1).max(100).default(10).describe(\"Number of comments to retrieve\"),\n }),\n execute: async (args) => {\n const client = getRedditClient()\n if (!client) {\n throw new Error(\"Reddit client not initialized\")\n }\n\n const comments = await client.getUserComments(args.username, {\n sort: args.sort,\n timeFilter: args.time_filter,\n limit: args.limit,\n })\n\n if (comments.length === 0) {\n return `No comments found for u/${args.username} with the specified filters.`\n }\n\n const commentSummaries = comments\n .map((comment, index) => {\n const truncatedBody = comment.body.length > 300 ? `${comment.body.substring(0, 300)}...` : comment.body\n\n const flags = [...(comment.edited ? [\"*(edited)*\"] : []), ...(comment.isSubmitter ? [\"**OP**\"] : [])]\n\n return `### ${index + 1}. Comment ${flags.join(\" \")}\nIn r/${comment.subreddit} on \"${comment.submissionTitle}\"\n\n> ${truncatedBody}\n\n- Score: ${comment.score.toLocaleString()}\n- Posted: ${new Date(comment.createdUtc * 1000).toLocaleString()}\n- Link: https://reddit.com${comment.permalink}`\n })\n .join(\"\\n\\n\")\n\n return `# Comments by u/${args.username} (${args.sort} - ${args.time_filter})\n\n${commentSummaries}`\n },\n})\n\n// Post tools\nserver.addTool({\n name: \"get_reddit_post\",\n description:\n \"Get detailed information about a specific Reddit post including content, stats, and engagement analysis\",\n parameters: z.object({\n subreddit: z.string().describe(\"The subreddit name (without r/ prefix)\"),\n post_id: z.string().describe(\"The Reddit post ID\"),\n }),\n execute: async (args) => {\n const client = getRedditClient()\n if (!client) {\n throw new Error(\"Reddit client not initialized\")\n }\n\n const post = await client.getPost(args.post_id, args.subreddit)\n const formattedPost = formatPostInfo(post)\n\n return `# Post from r/${formattedPost.subreddit}\n\n## Post Details\n- Title: ${formattedPost.title}\n- Type: ${formattedPost.type}\n- Author: u/${formattedPost.author}\n\n## Content\n${formattedPost.content}\n\n## Stats\n- Score: ${formattedPost.stats.score.toLocaleString()}\n- Upvote Ratio: ${(formattedPost.stats.upvoteRatio * 100).toFixed(1)}%\n- Comments: ${formattedPost.stats.comments.toLocaleString()}\n\n## Metadata\n- Posted: ${formattedPost.metadata.posted}\n- Flags: ${formattedPost.metadata.flags.length ? formattedPost.metadata.flags.join(\", \") : \"None\"}\n- Flair: ${formattedPost.metadata.flair}\n\n## Links\n- Full Post: ${formattedPost.links.fullPost}\n- Short Link: ${formattedPost.links.shortLink}\n\n## Engagement Analysis\n- ${formattedPost.engagementAnalysis.replace(/\\n {2}- /g, \"\\n- \")}\n\n## Best Time to Engage\n${formattedPost.bestTimeToEngage}`\n },\n})\n\nserver.addTool({\n name: \"get_top_posts\",\n description: \"Get top posts from a subreddit or from the Reddit home feed\",\n parameters: z.object({\n subreddit: z.string().optional().describe(\"The subreddit name (without r/ prefix). Leave empty for home feed\"),\n time_filter: z\n .enum([\"hour\", \"day\", \"week\", \"month\", \"year\", \"all\"])\n .default(\"week\")\n .describe(\"Time period for top posts\"),\n limit: z.number().min(1).max(100).default(10).describe(\"Number of posts to retrieve\"),\n }),\n execute: async (args) => {\n const client = getRedditClient()\n if (!client) {\n throw new Error(\"Reddit client not initialized\")\n }\n\n const posts = await client.getTopPosts(args.subreddit || \"\", args.time_filter, args.limit)\n\n if (posts.length === 0) {\n const location = args.subreddit ? `r/${args.subreddit}` : \"home feed\"\n return `No posts found in ${location} for the specified time period.`\n }\n\n const formattedPosts = posts.map(formatPostInfo)\n const postSummaries = formattedPosts\n .map(\n (post, index) => `### ${index + 1}. ${post.title}\n- Author: u/${post.author}\n- Score: ${post.stats.score.toLocaleString()} (${(post.stats.upvoteRatio * 100).toFixed(1)}% upvoted)\n- Comments: ${post.stats.comments.toLocaleString()}\n- Posted: ${post.metadata.posted}\n- Link: ${post.links.shortLink}`,\n )\n .join(\"\\n\\n\")\n\n const location = args.subreddit ? `r/${args.subreddit}` : \"Home Feed\"\n return `# Top Posts from ${location} (${args.time_filter})\n\n${postSummaries}`\n },\n})\n\n// Subreddit tools\nserver.addTool({\n name: \"get_subreddit_info\",\n description: \"Get detailed information about a subreddit including description, stats, and community analysis\",\n parameters: z.object({\n subreddit_name: z.string().describe(\"The subreddit name (without r/ prefix)\"),\n }),\n execute: async (args) => {\n const client = getRedditClient()\n if (!client) {\n throw new Error(\"Reddit client not initialized\")\n }\n\n const subreddit = await client.getSubredditInfo(args.subreddit_name)\n const formattedSubreddit = formatSubredditInfo(subreddit)\n\n return `# Subreddit Information: r/${formattedSubreddit.name}\n\n## Overview\n- Name: r/${formattedSubreddit.name}\n- Title: ${formattedSubreddit.title}\n- Subscribers: ${formattedSubreddit.stats.subscribers.toLocaleString()}\n- Active Users: ${\n typeof formattedSubreddit.stats.activeUsers === \"number\"\n ? formattedSubreddit.stats.activeUsers.toLocaleString()\n : formattedSubreddit.stats.activeUsers\n }\n\n## Description\n${formattedSubreddit.description.short}\n\n## Detailed Description\n${formattedSubreddit.description.full}\n\n## Metadata\n- Created: ${formattedSubreddit.metadata.created}\n- Flags: ${formattedSubreddit.metadata.flags.join(\", \")}\n\n## Links\n- Subreddit: ${formattedSubreddit.links.subreddit}\n- Wiki: ${formattedSubreddit.links.wiki}\n\n## Community Analysis\n- ${formattedSubreddit.communityAnalysis.replace(/\\n {2}- /g, \"\\n- \")}\n\n## Engagement Tips\n- ${formattedSubreddit.engagementTips.replace(/\\n {2}- /g, \"\\n- \")}`\n },\n})\n\nserver.addTool({\n name: \"get_trending_subreddits\",\n description: \"Get a list of currently trending subreddits\",\n parameters: z.object({}),\n execute: async () => {\n const client = getRedditClient()\n if (!client) {\n throw new Error(\"Reddit client not initialized\")\n }\n\n const trendingSubreddits = await client.getTrendingSubreddits()\n\n return `# Trending Subreddits\n\n${trendingSubreddits.map((subreddit, index) => `${index + 1}. r/${subreddit}`).join(\"\\n\")}`\n },\n})\n\n// Search tools\nserver.addTool({\n name: \"search_reddit\",\n description: \"Search Reddit for posts and content across subreddits\",\n parameters: z.object({\n query: z.string().describe(\"Search query\"),\n subreddit: z.string().optional().describe(\"Limit search to specific subreddit (without r/ prefix)\"),\n sort: z.enum([\"relevance\", \"hot\", \"top\", \"new\", \"comments\"]).default(\"relevance\").describe(\"Sort order\"),\n time_filter: z.enum([\"hour\", \"day\", \"week\", \"month\", \"year\", \"all\"]).default(\"all\").describe(\"Time filter\"),\n limit: z.number().min(1).max(100).default(10).describe(\"Number of results\"),\n type: z.enum([\"link\", \"sr\", \"user\"]).default(\"link\").describe(\"Type of content to search\"),\n }),\n execute: async (args) => {\n const client = getRedditClient()\n if (!client) {\n throw new Error(\"Reddit client not initialized\")\n }\n\n if (!args.query || args.query.trim() === \"\") {\n throw new Error(\"Search query cannot be empty\")\n }\n\n const posts = await client.searchReddit(args.query, {\n subreddit: args.subreddit,\n sort: args.sort,\n timeFilter: args.time_filter,\n limit: args.limit,\n type: args.type,\n })\n\n if (posts.length === 0) {\n const searchLocation = args.subreddit ? ` in r/${args.subreddit}` : \"\"\n return `No results found for \"${args.query}\"${searchLocation}.`\n }\n\n const searchResults = posts\n .map((post, index) => {\n const flags = [...(post.over18 ? [\"**NSFW**\"] : []), ...(post.spoiler ? [\"**Spoiler**\"] : [])]\n\n return `### ${index + 1}. ${post.title} ${flags.join(\" \")}\n- Subreddit: r/${post.subreddit}\n- Author: u/${post.author}\n- Score: ${post.score.toLocaleString()} (${(post.upvoteRatio * 100).toFixed(1)}% upvoted)\n- Comments: ${post.numComments.toLocaleString()}\n- Posted: ${new Date(post.createdUtc * 1000).toLocaleString()}\n- Link: https://reddit.com${post.permalink}`\n })\n .join(\"\\n\\n\")\n\n const searchLocation = args.subreddit ? ` in r/${args.subreddit}` : \"\"\n return `# Reddit Search Results for: \"${args.query}\"${searchLocation}\n\nSorted by: ${args.sort} | Time: ${args.time_filter} | Type: ${args.type}\n\n${searchResults}`\n },\n})\n\n// Write tools (require user authentication)\nserver.addTool({\n name: \"create_post\",\n description:\n \"Create a new post in a subreddit (requires REDDIT_USERNAME and REDDIT_PASSWORD). \" +\n \"WARNING: Rapid posting or duplicate content may trigger Reddit's spam detection and result in account bans. \" +\n \"Consider enabling REDDIT_SAFE_MODE=standard for protection.\",\n parameters: z.object({\n subreddit: z.string().describe(\"The subreddit name (without r/ prefix)\"),\n title: z.string().describe(\"The post title\"),\n content: z.string().describe(\"The post content (text for self posts, URL for link posts)\"),\n is_self: z.boolean().default(true).describe(\"Whether this is a self post (text) or link post\"),\n }),\n execute: async (args) => {\n const client = getRedditClient()\n if (!client) {\n throw new Error(\"Reddit client not initialized\")\n }\n\n // Check if user credentials are configured\n if (!process.env.REDDIT_USERNAME || !process.env.REDDIT_PASSWORD) {\n throw new Error(\n \"User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables.\",\n )\n }\n\n const post = await client.createPost(args.subreddit, args.title, args.content, args.is_self)\n const formattedPost = formatPostInfo(post)\n\n return `# Post Created Successfully\n\n## Post Details\n- Title: ${formattedPost.title}\n- Subreddit: r/${formattedPost.subreddit}\n- Type: ${formattedPost.type}\n- Link: ${formattedPost.links.fullPost}\n\nYour post has been successfully submitted to r/${formattedPost.subreddit}.`\n },\n})\n\nserver.addTool({\n name: \"reply_to_post\",\n description:\n \"Post a reply to an existing Reddit post or comment (requires REDDIT_USERNAME and REDDIT_PASSWORD). \" +\n \"WARNING: Rapid commenting or duplicate content may trigger Reddit's spam detection. \" +\n \"Enable REDDIT_SAFE_MODE=standard for rate limiting and duplicate detection.\",\n parameters: z.object({\n post_id: z.string().describe(\"The Reddit post ID (thing_id, e.g., t3_xxxxx for posts, t1_xxxxx for comments)\"),\n content: z.string().describe(\"The reply content\"),\n }),\n execute: async (args) => {\n const client = getRedditClient()\n if (!client) {\n throw new Error(\"Reddit client not initialized\")\n }\n\n // Check if user credentials are configured\n if (!process.env.REDDIT_USERNAME || !process.env.REDDIT_PASSWORD) {\n throw new Error(\n \"User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables.\",\n )\n }\n\n const comment = await client.replyToPost(args.post_id, args.content)\n\n return `# Reply Posted Successfully\n\n## Comment Details\n- Posted to: ${args.post_id}\n- Author: u/${process.env.REDDIT_USERNAME}\n- Comment ID: ${comment.id}\n\nYour reply has been successfully posted.`\n },\n})\n\nserver.addTool({\n name: \"delete_post\",\n description:\n \"Delete your own Reddit post (requires REDDIT_USERNAME and REDDIT_PASSWORD). WARNING: This action is permanent and cannot be undone!\",\n parameters: z.object({\n thing_id: z\n .string()\n .describe(\n \"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.\",\n ),\n }),\n execute: async (args) => {\n const client = getRedditClient()\n if (!client) {\n throw new Error(\"Reddit client not initialized\")\n }\n\n // Check if user credentials are configured\n if (!process.env.REDDIT_USERNAME || !process.env.REDDIT_PASSWORD) {\n throw new Error(\n \"User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables.\",\n )\n }\n\n await client.deletePost(args.thing_id)\n\n return `# Post Deleted Successfully\n\nThe post ${args.thing_id} has been permanently deleted from Reddit.\n\n**Note**: This action cannot be undone. The post content has been removed and cannot be recovered.`\n },\n})\n\nserver.addTool({\n name: \"delete_comment\",\n description:\n \"Delete your own Reddit comment (requires REDDIT_USERNAME and REDDIT_PASSWORD). WARNING: This action is permanent and cannot be undone!\",\n parameters: z.object({\n thing_id: z\n .string()\n .describe(\n \"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.\",\n ),\n }),\n execute: async (args) => {\n const client = getRedditClient()\n if (!client) {\n throw new Error(\"Reddit client not initialized\")\n }\n\n // Check if user credentials are configured\n if (!process.env.REDDIT_USERNAME || !process.env.REDDIT_PASSWORD) {\n throw new Error(\n \"User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables.\",\n )\n }\n\n await client.deleteComment(args.thing_id)\n\n return `# Comment Deleted Successfully\n\nThe comment ${args.thing_id} has been permanently deleted from Reddit.\n\n**Note**: This action cannot be undone. The comment content has been removed and cannot be recovered.`\n },\n})\n\nserver.addTool({\n name: \"edit_post\",\n description:\n \"Edit your own Reddit post (self-text posts only, requires REDDIT_USERNAME and REDDIT_PASSWORD). \" +\n \"You can only edit the text content of self posts, not titles or link posts. \" +\n \"WARNING: Rapid edits may trigger spam detection. Enable REDDIT_SAFE_MODE for protection.\",\n parameters: z.object({\n thing_id: z\n .string()\n .describe(\n \"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.\",\n ),\n new_text: z.string().describe(\"The new text content for the post. Supports Reddit markdown formatting.\"),\n }),\n execute: async (args) => {\n const client = getRedditClient()\n if (!client) {\n throw new Error(\"Reddit client not initialized\")\n }\n\n // Check if user credentials are configured\n if (!process.env.REDDIT_USERNAME || !process.env.REDDIT_PASSWORD) {\n throw new Error(\n \"User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables.\",\n )\n }\n\n await client.editPost(args.thing_id, args.new_text)\n\n return `# Post Edited Successfully\n\nThe post ${args.thing_id} has been updated with your new content.\n\n**Note**:\n- Only self (text) posts can be edited\n- Post titles cannot be edited\n- Link posts cannot be edited\n- An \"edited\" marker will appear on your post`\n },\n})\n\nserver.addTool({\n name: \"edit_comment\",\n description:\n \"Edit your own Reddit comment (requires REDDIT_USERNAME and REDDIT_PASSWORD). \" +\n \"Update the text content of a comment you previously posted. \" +\n \"WARNING: Rapid edits may trigger spam detection. Enable REDDIT_SAFE_MODE for protection.\",\n parameters: z.object({\n thing_id: z\n .string()\n .describe(\n \"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.\",\n ),\n new_text: z.string().describe(\"The new text content for the comment. Supports Reddit markdown formatting.\"),\n }),\n execute: async (args) => {\n const client = getRedditClient()\n if (!client) {\n throw new Error(\"Reddit client not initialized\")\n }\n\n // Check if user credentials are configured\n if (!process.env.REDDIT_USERNAME || !process.env.REDDIT_PASSWORD) {\n throw new Error(\n \"User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables.\",\n )\n }\n\n await client.editComment(args.thing_id, args.new_text)\n\n return `# Comment Edited Successfully\n\nThe comment ${args.thing_id} has been updated with your new content.\n\n**Note**: An \"edited\" marker will appear on your comment to show it has been modified.`\n },\n})\n\n// Comment tools\nserver.addTool({\n name: \"get_post_comments\",\n description: \"Get comments from a specific Reddit post\",\n parameters: z.object({\n post_id: z.string().describe(\"The Reddit post ID\"),\n subreddit: z.string().describe(\"The subreddit name (without r/ prefix)\"),\n sort: z.enum([\"best\", \"top\", \"new\", \"controversial\", \"old\", \"qa\"]).default(\"best\").describe(\"Comment sort order\"),\n limit: z.number().min(1).max(500).default(100).describe(\"Maximum number of comments to retrieve\"),\n }),\n execute: async (args) => {\n const client = getRedditClient()\n if (!client) {\n throw new Error(\"Reddit client not initialized\")\n }\n\n if (!args.post_id || !args.subreddit) {\n throw new Error(\"post_id and subreddit are required\")\n }\n\n const data = await client.getPostComments(args.post_id, args.subreddit, {\n sort: args.sort,\n limit: args.limit,\n })\n\n const { post } = data\n const { comments } = data\n\n let response = `# Comments for: ${post.title}\n\n**Post by u/${post.author} in r/${post.subreddit}**\n- Score: ${post.score.toLocaleString()} | Comments: ${post.numComments.toLocaleString()}\n- Posted: ${new Date(post.createdUtc * 1000).toLocaleString()}\n\n---\n\n`\n\n if (comments.length === 0) {\n response += \"No comments found for this post.\"\n return response\n }\n\n const commentSummaries = comments\n .map((comment) => {\n const indent = \"└─\".repeat(Math.min(comment.depth || 0, 3))\n const authorBadge = comment.isSubmitter ? \" **[OP]**\" : \"\"\n const editedBadge = comment.edited ? \" *(edited)*\" : \"\"\n\n return `${indent} **u/${comment.author}**${authorBadge}${editedBadge} (${comment.score.toLocaleString()} points)\n\n${comment.body}\n\n---`\n })\n .join(\"\\n\\n\")\n\n response += commentSummaries\n return response\n },\n})\n\n// Initialize and start server\nasync function main() {\n try {\n await setupRedditClient()\n\n // Default to stdio for local/MCP client usage\n // Use HTTP only when explicitly requested (e.g., for Docker)\n const useHttp = process.env.TRANSPORT_TYPE === \"httpStream\" || process.env.TRANSPORT_TYPE === \"http\"\n const port = parseInt(process.env.PORT || \"3000\")\n const host = process.env.HOST || \"127.0.0.1\"\n\n if (useHttp) {\n console.error(`[Setup] Starting HTTP server on ${host}:${port}`)\n await server.start({\n transportType: \"httpStream\",\n httpStream: {\n port,\n host,\n endpoint: \"/mcp\",\n },\n })\n console.error(`[Setup] HTTP server ready at http://${host}:${port}/mcp`)\n console.error(`[Setup] SSE endpoint available at http://${host}:${port}/sse`)\n } else {\n console.error(\"[Setup] Starting in stdio mode\")\n await server.start({\n transportType: \"stdio\",\n })\n }\n } catch (error) {\n console.error(\"[Error] Failed to start server:\", error)\n process.exit(1)\n }\n}\n\n// Handle graceful shutdown\nprocess.on(\"SIGINT\", async () => {\n console.error(\"[Shutdown] Shutting down Reddit MCP Server...\")\n process.exit(0)\n})\n\nprocess.on(\"SIGTERM\", async () => {\n console.error(\"[Shutdown] Shutting down Reddit MCP Server...\")\n process.exit(0)\n})\n\nmain().catch(console.error)\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reddit-mcp-server",
3
- "version": "1.3.2",
3
+ "version": "1.4.0",
4
4
  "description": "A Model Context Protocol (MCP) server for Reddit with full read AND write operations - create posts, reply, edit, and delete content.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -74,14 +74,14 @@
74
74
  "zod": "^4.3.6"
75
75
  },
76
76
  "devDependencies": {
77
- "@types/node": "^22.19.11",
77
+ "@types/node": "^22.19.13",
78
78
  "ajv-cli": "^5.0.0",
79
79
  "cross-env": "^10.1.0",
80
80
  "msw": "^2.12.10",
81
81
  "rimraf": "^6.1.3",
82
- "ts-builds": "^2.4.0",
82
+ "ts-builds": "^2.5.0",
83
83
  "tsdown": "^0.20.3",
84
84
  "tsx": "^4.21.0"
85
85
  },
86
- "packageManager": "pnpm@10.29.3+sha512.498e1fb4cca5aa06c1dcf2611e6fafc50972ffe7189998c409e90de74566444298ffe43e6cd2acdc775ba1aa7cc5e092a8b7054c811ba8c5770f84693d33d2dc"
86
+ "packageManager": "pnpm@10.30.3+sha512.c961d1e0a2d8e354ecaa5166b822516668b7f44cb5bd95122d590dd81922f606f5473b6d23ec4a5be05e7fcd18e8488d47d978bbe981872f1145d06e9a740017"
87
87
  }