reddit-mcp-server 1.2.1 → 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +5 -12
- package/dist/bin.js.map +1 -1
- package/dist/index.js +102 -140
- package/dist/index.js.map +1 -1
- package/package.json +41 -30
- package/dist/chunk-kSYXY2_d.js +0 -34
package/dist/bin.js
CHANGED
|
@@ -1,23 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
const require_chunk = require('./chunk-kSYXY2_d.js');
|
|
3
|
-
let fs = require("fs");
|
|
4
|
-
fs = require_chunk.__toESM(fs);
|
|
5
|
-
let path = require("path");
|
|
6
|
-
path = require_chunk.__toESM(path);
|
|
7
|
-
|
|
8
2
|
//#region src/bin.ts
|
|
9
|
-
const packageJsonPath = path.default.join(__dirname, "..", "package.json");
|
|
10
|
-
const packageJson = JSON.parse(fs.default.readFileSync(packageJsonPath, "utf-8"));
|
|
11
3
|
if (!process.env.TRANSPORT_TYPE) process.env.TRANSPORT_TYPE = "stdio";
|
|
12
4
|
const args = process.argv.slice(2);
|
|
13
5
|
if (args.includes("--version") || args.includes("-v")) {
|
|
14
|
-
console.log(
|
|
6
|
+
console.log("1.3.2");
|
|
15
7
|
process.exit(0);
|
|
16
8
|
}
|
|
17
|
-
main().then();
|
|
18
9
|
if (args.includes("--help") || args.includes("-h")) {
|
|
19
10
|
console.log(`
|
|
20
|
-
Reddit MCP Server
|
|
11
|
+
Reddit MCP Server v1.3.2
|
|
21
12
|
|
|
22
13
|
Usage: reddit-mcp-server [options]
|
|
23
14
|
|
|
@@ -37,8 +28,10 @@ For more information, visit: https://github.com/jordanburke/reddit-mcp-server
|
|
|
37
28
|
process.exit(0);
|
|
38
29
|
}
|
|
39
30
|
async function main() {
|
|
40
|
-
await
|
|
31
|
+
await import("./index.js");
|
|
41
32
|
}
|
|
33
|
+
main().then();
|
|
42
34
|
|
|
43
35
|
//#endregion
|
|
36
|
+
export { };
|
|
44
37
|
//# sourceMappingURL=bin.js.map
|
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\
|
|
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"}
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
crypto = require_chunk.__toESM(crypto);
|
|
6
|
-
let dotenv = require("dotenv");
|
|
7
|
-
dotenv = require_chunk.__toESM(dotenv);
|
|
1
|
+
import crypto from "crypto";
|
|
2
|
+
import dotenv from "dotenv";
|
|
3
|
+
import { FastMCP } from "fastmcp";
|
|
4
|
+
import { z } from "zod";
|
|
8
5
|
|
|
9
6
|
//#region src/client/reddit-client.ts
|
|
10
7
|
var RedditClient = class {
|
|
@@ -87,8 +84,8 @@ var RedditClient = class {
|
|
|
87
84
|
if (this.accessToken && now < this.tokenExpiry) return;
|
|
88
85
|
const authUrl = "https://www.reddit.com/api/v1/access_token";
|
|
89
86
|
const authData = new URLSearchParams();
|
|
90
|
-
const username = this
|
|
91
|
-
const password = this
|
|
87
|
+
const { username } = this;
|
|
88
|
+
const { password } = this;
|
|
92
89
|
if (!!(username && password)) {
|
|
93
90
|
authData.append("grant_type", "password");
|
|
94
91
|
authData.append("username", username);
|
|
@@ -114,7 +111,7 @@ var RedditClient = class {
|
|
|
114
111
|
this.authenticated = true;
|
|
115
112
|
} catch (error) {
|
|
116
113
|
if (error instanceof Error) throw error;
|
|
117
|
-
throw new Error("Failed to authenticate with Reddit API");
|
|
114
|
+
throw new Error("Failed to authenticate with Reddit API", { cause: error });
|
|
118
115
|
}
|
|
119
116
|
}
|
|
120
117
|
async checkAuthentication() {
|
|
@@ -143,7 +140,7 @@ var RedditClient = class {
|
|
|
143
140
|
this.lastWriteTime = Date.now();
|
|
144
141
|
}
|
|
145
142
|
hashContent(content) {
|
|
146
|
-
return crypto.
|
|
143
|
+
return crypto.createHash("sha256").update(content.trim().toLowerCase()).digest("hex");
|
|
147
144
|
}
|
|
148
145
|
checkDuplicateContent(content) {
|
|
149
146
|
if (!this.safeMode.enabled || !this.safeMode.duplicateCheck) return;
|
|
@@ -159,7 +156,7 @@ var RedditClient = class {
|
|
|
159
156
|
try {
|
|
160
157
|
const response = await this.makeRequest(`/user/${username}/about.json`);
|
|
161
158
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
162
|
-
const data =
|
|
159
|
+
const { data } = await response.json();
|
|
163
160
|
return {
|
|
164
161
|
name: data.name,
|
|
165
162
|
id: data.id,
|
|
@@ -180,7 +177,7 @@ var RedditClient = class {
|
|
|
180
177
|
try {
|
|
181
178
|
const response = await this.makeRequest(`/r/${subredditName}/about.json`);
|
|
182
179
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
183
|
-
const data =
|
|
180
|
+
const { data } = await response.json();
|
|
184
181
|
return {
|
|
185
182
|
displayName: data.display_name,
|
|
186
183
|
title: data.title,
|
|
@@ -293,30 +290,18 @@ var RedditClient = class {
|
|
|
293
290
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
294
291
|
body: params.toString()
|
|
295
292
|
});
|
|
296
|
-
if (!response.ok) {
|
|
297
|
-
const errorText = await response.text();
|
|
298
|
-
console.error(`[Reddit API] Create post failed: ${response.status} ${response.statusText}`);
|
|
299
|
-
console.error(`[Reddit API] Error response: ${errorText}`);
|
|
300
|
-
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
301
|
-
}
|
|
293
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
302
294
|
const json = await response.json();
|
|
303
|
-
console.error(`[Reddit API] Create post response:`, JSON.stringify(json, null, 2));
|
|
304
295
|
if (((_json$json = json.json) === null || _json$json === void 0 ? void 0 : _json$json.errors) && json.json.errors.length > 0) {
|
|
305
|
-
const errors = json.json.errors.map((e) => e
|
|
306
|
-
console.error(`[Reddit API] Post creation errors: ${errors}`);
|
|
296
|
+
const errors = json.json.errors.map((e) => e[1] || e[0]).join(", ");
|
|
307
297
|
throw new Error(`Reddit API errors: ${errors}`);
|
|
308
298
|
}
|
|
309
299
|
const postId = ((_json$json2 = json.json) === null || _json$json2 === void 0 || (_json$json2 = _json$json2.data) === null || _json$json2 === void 0 ? void 0 : _json$json2.id) || ((_json$json3 = json.json) === null || _json$json3 === void 0 || (_json$json3 = _json$json3.data) === null || _json$json3 === void 0 || (_json$json3 = _json$json3.name) === null || _json$json3 === void 0 ? void 0 : _json$json3.replace("t3_", ""));
|
|
310
|
-
if (!postId)
|
|
311
|
-
console.error(`[Reddit API] No post ID in response`);
|
|
312
|
-
throw new Error("No post ID returned from Reddit");
|
|
313
|
-
}
|
|
314
|
-
console.error(`[Reddit API] Post created with ID: ${postId}`);
|
|
300
|
+
if (!postId) throw new Error("No post ID returned from Reddit");
|
|
315
301
|
return await this.getPost(postId, subreddit);
|
|
316
302
|
} catch (error) {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
throw new Error(`Failed to create post in ${subreddit}: ${error instanceof Error ? error.message : String(error)}`);
|
|
303
|
+
if (error instanceof Error) throw error;
|
|
304
|
+
throw new Error(`Failed to create post in ${subreddit}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
320
305
|
}
|
|
321
306
|
}
|
|
322
307
|
async checkPostExists(postId) {
|
|
@@ -334,9 +319,10 @@ var RedditClient = class {
|
|
|
334
319
|
this.checkDuplicateContent(content);
|
|
335
320
|
try {
|
|
336
321
|
var _json$json4, _json$json5;
|
|
337
|
-
|
|
322
|
+
const fullThingId = postId.startsWith("t3_") || postId.startsWith("t1_") ? postId : `t3_${postId}`;
|
|
323
|
+
if (!postId.startsWith("t1_") && !await this.checkPostExists(postId.replace(/^t3_/, ""))) throw new Error(`Post with ID ${postId} does not exist or is not accessible`);
|
|
338
324
|
const params = new URLSearchParams();
|
|
339
|
-
params.append("thing_id",
|
|
325
|
+
params.append("thing_id", fullThingId);
|
|
340
326
|
params.append("text", content);
|
|
341
327
|
params.append("api_type", "json");
|
|
342
328
|
const response = await this.makeRequest("/api/comment", {
|
|
@@ -344,14 +330,8 @@ var RedditClient = class {
|
|
|
344
330
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
345
331
|
body: params.toString()
|
|
346
332
|
});
|
|
347
|
-
if (!response.ok) {
|
|
348
|
-
const errorText = await response.text();
|
|
349
|
-
console.error(`[Reddit API] Reply to post failed: ${response.status} ${response.statusText}`);
|
|
350
|
-
console.error(`[Reddit API] Error response: ${errorText}`);
|
|
351
|
-
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
352
|
-
}
|
|
333
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
353
334
|
const json = await response.json();
|
|
354
|
-
console.error(`[Reddit API] Reply response:`, JSON.stringify(json, null, 2));
|
|
355
335
|
if (((_json$json4 = json.json) === null || _json$json4 === void 0 || (_json$json4 = _json$json4.data) === null || _json$json4 === void 0 ? void 0 : _json$json4.things) && json.json.data.things.length > 0) {
|
|
356
336
|
const commentData = json.json.data.things[0].data;
|
|
357
337
|
const author = this.username ?? "[unknown]";
|
|
@@ -369,17 +349,12 @@ var RedditClient = class {
|
|
|
369
349
|
permalink: commentData.permalink
|
|
370
350
|
};
|
|
371
351
|
} else if (((_json$json5 = json.json) === null || _json$json5 === void 0 ? void 0 : _json$json5.errors) && json.json.errors.length > 0) {
|
|
372
|
-
const errors = json.json.errors.map((e) => e
|
|
373
|
-
console.error(`[Reddit API] Reply errors: ${errors}`);
|
|
352
|
+
const errors = json.json.errors.map((e) => e[1] || e[0]).join(", ");
|
|
374
353
|
throw new Error(`Reddit API errors: ${errors}`);
|
|
375
|
-
} else
|
|
376
|
-
console.error(`[Reddit API] Unexpected reply response format`);
|
|
377
|
-
throw new Error("Failed to parse reply response");
|
|
378
|
-
}
|
|
354
|
+
} else throw new Error("Failed to parse reply response");
|
|
379
355
|
} catch (error) {
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
throw new Error(`Failed to reply to post ${postId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
356
|
+
if (error instanceof Error) throw error;
|
|
357
|
+
throw new Error(`Failed to reply to post ${postId}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
383
358
|
}
|
|
384
359
|
}
|
|
385
360
|
async deletePost(thingId) {
|
|
@@ -404,7 +379,7 @@ var RedditClient = class {
|
|
|
404
379
|
} catch (error) {
|
|
405
380
|
console.error(`[Reddit API] Delete exception:`, error);
|
|
406
381
|
if (error instanceof Error && error.message.includes("HTTP")) throw error;
|
|
407
|
-
throw new Error(`Failed to delete content ${thingId}: ${error instanceof Error ? error.message : String(error)}
|
|
382
|
+
throw new Error(`Failed to delete content ${thingId}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
408
383
|
}
|
|
409
384
|
}
|
|
410
385
|
async deleteComment(thingId) {
|
|
@@ -427,25 +402,16 @@ var RedditClient = class {
|
|
|
427
402
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
428
403
|
body: params.toString()
|
|
429
404
|
});
|
|
430
|
-
if (!response.ok) {
|
|
431
|
-
const errorText = await response.text();
|
|
432
|
-
console.error(`[Reddit API] Edit failed: ${response.status} ${response.statusText}`);
|
|
433
|
-
console.error(`[Reddit API] Error response: ${errorText}`);
|
|
434
|
-
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
435
|
-
}
|
|
405
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
436
406
|
const json = await response.json();
|
|
437
|
-
console.error(`[Reddit API] Edit response:`, JSON.stringify(json, null, 2));
|
|
438
407
|
if (((_json$json6 = json.json) === null || _json$json6 === void 0 ? void 0 : _json$json6.errors) && json.json.errors.length > 0) {
|
|
439
|
-
const errors = json.json.errors.map((e) => e
|
|
440
|
-
console.error(`[Reddit API] Edit errors: ${errors}`);
|
|
408
|
+
const errors = json.json.errors.map((e) => e[1] || e[0]).join(", ");
|
|
441
409
|
throw new Error(`Reddit API errors: ${errors}`);
|
|
442
410
|
}
|
|
443
|
-
console.error(`[Reddit API] Successfully edited ${fullThingId}`);
|
|
444
411
|
return true;
|
|
445
412
|
} catch (error) {
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
throw new Error(`Failed to edit content ${thingId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
413
|
+
if (error instanceof Error) throw error;
|
|
414
|
+
throw new Error(`Failed to edit content ${thingId}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
449
415
|
}
|
|
450
416
|
}
|
|
451
417
|
async editComment(thingId, newText) {
|
|
@@ -540,7 +506,7 @@ var RedditClient = class {
|
|
|
540
506
|
depth,
|
|
541
507
|
parentId: item.data.parent_id
|
|
542
508
|
});
|
|
543
|
-
const replies = item.data
|
|
509
|
+
const { replies } = item.data;
|
|
544
510
|
if (replies && typeof replies !== "string" && ((_replies$data = replies.data) === null || _replies$data === void 0 ? void 0 : _replies$data.children)) parseComments(replies.data.children, depth + 1);
|
|
545
511
|
}
|
|
546
512
|
};
|
|
@@ -734,7 +700,7 @@ function formatPostInfo(post) {
|
|
|
734
700
|
return {
|
|
735
701
|
title: post.title,
|
|
736
702
|
type: contentType,
|
|
737
|
-
content: content.length > 300 ? content.substring(0, 297)
|
|
703
|
+
content: content.length > 300 ? `${content.substring(0, 297)}...` : content,
|
|
738
704
|
author: post.author,
|
|
739
705
|
subreddit: post.subreddit,
|
|
740
706
|
stats: {
|
|
@@ -769,7 +735,7 @@ function formatSubredditInfo(subreddit) {
|
|
|
769
735
|
},
|
|
770
736
|
description: {
|
|
771
737
|
short: subreddit.publicDescription,
|
|
772
|
-
full: subreddit.description.length > 300 ? subreddit.description.substring(0, 297)
|
|
738
|
+
full: subreddit.description.length > 300 ? `${subreddit.description.substring(0, 297)}...` : subreddit.description
|
|
773
739
|
},
|
|
774
740
|
metadata: {
|
|
775
741
|
created: formatTimestamp(subreddit.createdUtc),
|
|
@@ -786,8 +752,8 @@ function formatSubredditInfo(subreddit) {
|
|
|
786
752
|
|
|
787
753
|
//#endregion
|
|
788
754
|
//#region src/index.ts
|
|
789
|
-
dotenv.
|
|
790
|
-
const VERSION = "1.2
|
|
755
|
+
dotenv.config();
|
|
756
|
+
const VERSION = "1.3.2";
|
|
791
757
|
function validateUserAgent(userAgent, username) {
|
|
792
758
|
if (!/^[\w-]+:[\w-]+:[\d.]+ \(by \/u\/\w+\)$/.test(userAgent)) {
|
|
793
759
|
console.error("[Warning] User-Agent does not follow Reddit's recommended format");
|
|
@@ -806,8 +772,8 @@ function buildUserAgent(customAgent, username) {
|
|
|
806
772
|
console.error(`[Setup] Auto-generated User-Agent: ${autoAgent}`);
|
|
807
773
|
return autoAgent;
|
|
808
774
|
}
|
|
809
|
-
const fallbackAgent = `
|
|
810
|
-
|
|
775
|
+
const fallbackAgent = `typescript:reddit-mcp-server:${VERSION} (by /u/anonymous)`;
|
|
776
|
+
console.error("[Setup] No REDDIT_USERNAME set — using anonymous User-Agent. Set REDDIT_USERNAME for a personalized agent.");
|
|
811
777
|
return fallbackAgent;
|
|
812
778
|
}
|
|
813
779
|
function buildSafeModeConfig(safeMode) {
|
|
@@ -914,7 +880,9 @@ async function setupRedditClient() {
|
|
|
914
880
|
process.exit(1);
|
|
915
881
|
}
|
|
916
882
|
}
|
|
917
|
-
const
|
|
883
|
+
const oauthToken = process.env.OAUTH_TOKEN || crypto.randomBytes(32).toString("hex");
|
|
884
|
+
if (process.env.OAUTH_ENABLED === "true" && !process.env.OAUTH_TOKEN) console.error(`[Auth] Generated OAuth token: ${oauthToken}`);
|
|
885
|
+
const server = new FastMCP({
|
|
918
886
|
name: "reddit-mcp-server",
|
|
919
887
|
version: VERSION,
|
|
920
888
|
instructions: `A comprehensive Reddit MCP server that provides tools for interacting with Reddit API.
|
|
@@ -931,23 +899,16 @@ Available capabilities:
|
|
|
931
899
|
For write operations (posting, replying, editing, deleting), ensure REDDIT_USERNAME and REDDIT_PASSWORD are configured.`,
|
|
932
900
|
...process.env.OAUTH_ENABLED === "true" && { authenticate: async (request) => {
|
|
933
901
|
const authHeader = request.headers.authorization;
|
|
934
|
-
const expectedToken = process.env.OAUTH_TOKEN;
|
|
935
|
-
if (!expectedToken) {
|
|
936
|
-
const token = Array.from({ length: 32 }, () => "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".charAt(Math.floor(Math.random() * 62))).join("");
|
|
937
|
-
console.log(`[Auth] Generated OAuth token: ${token}`);
|
|
938
|
-
throw new Response(JSON.stringify({
|
|
939
|
-
error: "No OAuth token configured",
|
|
940
|
-
generatedToken: token
|
|
941
|
-
}), {
|
|
942
|
-
status: 401,
|
|
943
|
-
headers: { "Content-Type": "application/json" }
|
|
944
|
-
});
|
|
945
|
-
}
|
|
946
902
|
if (!(authHeader === null || authHeader === void 0 ? void 0 : authHeader.startsWith("Bearer "))) throw new Response(null, {
|
|
947
903
|
status: 401,
|
|
948
904
|
statusText: "Missing or invalid Authorization header"
|
|
949
905
|
});
|
|
950
|
-
|
|
906
|
+
const token = authHeader.slice(7);
|
|
907
|
+
const tokenBuffer = Buffer.from(token);
|
|
908
|
+
const expectedBuffer = Buffer.from(oauthToken);
|
|
909
|
+
const tokenHash = crypto.createHash("sha256").update(tokenBuffer).digest();
|
|
910
|
+
const expectedHash = crypto.createHash("sha256").update(expectedBuffer).digest();
|
|
911
|
+
if (!crypto.timingSafeEqual(tokenHash, expectedHash)) throw new Response(null, {
|
|
951
912
|
status: 403,
|
|
952
913
|
statusText: "Invalid token"
|
|
953
914
|
});
|
|
@@ -957,7 +918,7 @@ For write operations (posting, replying, editing, deleting), ensure REDDIT_USERN
|
|
|
957
918
|
server.addTool({
|
|
958
919
|
name: "test_reddit_mcp_server",
|
|
959
920
|
description: "Test the Reddit MCP Server connection and configuration",
|
|
960
|
-
parameters:
|
|
921
|
+
parameters: z.object({}),
|
|
961
922
|
execute: async () => {
|
|
962
923
|
const client = getRedditClient();
|
|
963
924
|
const hasAuth = client ? "✓" : "✗";
|
|
@@ -974,7 +935,7 @@ Ready to handle Reddit API requests!`;
|
|
|
974
935
|
server.addTool({
|
|
975
936
|
name: "get_user_info",
|
|
976
937
|
description: "Get detailed information about a Reddit user including karma, account status, and activity analysis",
|
|
977
|
-
parameters:
|
|
938
|
+
parameters: z.object({ username: z.string().describe("The Reddit username (without u/ prefix)") }),
|
|
978
939
|
execute: async (args) => {
|
|
979
940
|
const client = getRedditClient();
|
|
980
941
|
if (!client) throw new Error("Reddit client not initialized");
|
|
@@ -992,7 +953,7 @@ server.addTool({
|
|
|
992
953
|
- Profile URL: ${formattedUser.profileUrl}
|
|
993
954
|
|
|
994
955
|
## Activity Analysis
|
|
995
|
-
- ${formattedUser.activityAnalysis.replace(/\n
|
|
956
|
+
- ${formattedUser.activityAnalysis.replace(/\n {2}- /g, "\n- ")}
|
|
996
957
|
|
|
997
958
|
## Recommendations
|
|
998
959
|
- ${formattedUser.recommendations.replace(/\n {2}- /g, "\n- ")}`;
|
|
@@ -1001,14 +962,14 @@ server.addTool({
|
|
|
1001
962
|
server.addTool({
|
|
1002
963
|
name: "get_user_posts",
|
|
1003
964
|
description: "Get recent posts by a Reddit user with sorting and filtering options",
|
|
1004
|
-
parameters:
|
|
1005
|
-
username:
|
|
1006
|
-
sort:
|
|
965
|
+
parameters: z.object({
|
|
966
|
+
username: z.string().describe("The Reddit username (without u/ prefix)"),
|
|
967
|
+
sort: z.enum([
|
|
1007
968
|
"new",
|
|
1008
969
|
"hot",
|
|
1009
970
|
"top"
|
|
1010
971
|
]).default("new").describe("Sort order for posts"),
|
|
1011
|
-
time_filter:
|
|
972
|
+
time_filter: z.enum([
|
|
1012
973
|
"hour",
|
|
1013
974
|
"day",
|
|
1014
975
|
"week",
|
|
@@ -1016,7 +977,7 @@ server.addTool({
|
|
|
1016
977
|
"year",
|
|
1017
978
|
"all"
|
|
1018
979
|
]).default("all").describe("Time filter for top posts"),
|
|
1019
|
-
limit:
|
|
980
|
+
limit: z.number().min(1).max(100).default(10).describe("Number of posts to retrieve")
|
|
1020
981
|
}),
|
|
1021
982
|
execute: async (args) => {
|
|
1022
983
|
const client = getRedditClient();
|
|
@@ -1044,14 +1005,14 @@ ${postSummaries}`;
|
|
|
1044
1005
|
server.addTool({
|
|
1045
1006
|
name: "get_user_comments",
|
|
1046
1007
|
description: "Get recent comments by a Reddit user with sorting and filtering options",
|
|
1047
|
-
parameters:
|
|
1048
|
-
username:
|
|
1049
|
-
sort:
|
|
1008
|
+
parameters: z.object({
|
|
1009
|
+
username: z.string().describe("The Reddit username (without u/ prefix)"),
|
|
1010
|
+
sort: z.enum([
|
|
1050
1011
|
"new",
|
|
1051
1012
|
"hot",
|
|
1052
1013
|
"top"
|
|
1053
1014
|
]).default("new").describe("Sort order for comments"),
|
|
1054
|
-
time_filter:
|
|
1015
|
+
time_filter: z.enum([
|
|
1055
1016
|
"hour",
|
|
1056
1017
|
"day",
|
|
1057
1018
|
"week",
|
|
@@ -1059,7 +1020,7 @@ server.addTool({
|
|
|
1059
1020
|
"year",
|
|
1060
1021
|
"all"
|
|
1061
1022
|
]).default("all").describe("Time filter for top comments"),
|
|
1062
|
-
limit:
|
|
1023
|
+
limit: z.number().min(1).max(100).default(10).describe("Number of comments to retrieve")
|
|
1063
1024
|
}),
|
|
1064
1025
|
execute: async (args) => {
|
|
1065
1026
|
const client = getRedditClient();
|
|
@@ -1071,7 +1032,7 @@ server.addTool({
|
|
|
1071
1032
|
});
|
|
1072
1033
|
if (comments.length === 0) return `No comments found for u/${args.username} with the specified filters.`;
|
|
1073
1034
|
const commentSummaries = comments.map((comment, index) => {
|
|
1074
|
-
const truncatedBody = comment.body.length > 300 ? comment.body.substring(0, 300)
|
|
1035
|
+
const truncatedBody = comment.body.length > 300 ? `${comment.body.substring(0, 300)}...` : comment.body;
|
|
1075
1036
|
const flags = [...comment.edited ? ["*(edited)*"] : [], ...comment.isSubmitter ? ["**OP**"] : []];
|
|
1076
1037
|
return `### ${index + 1}. Comment ${flags.join(" ")}
|
|
1077
1038
|
In r/${comment.subreddit} on "${comment.submissionTitle}"
|
|
@@ -1090,9 +1051,9 @@ ${commentSummaries}`;
|
|
|
1090
1051
|
server.addTool({
|
|
1091
1052
|
name: "get_reddit_post",
|
|
1092
1053
|
description: "Get detailed information about a specific Reddit post including content, stats, and engagement analysis",
|
|
1093
|
-
parameters:
|
|
1094
|
-
subreddit:
|
|
1095
|
-
post_id:
|
|
1054
|
+
parameters: z.object({
|
|
1055
|
+
subreddit: z.string().describe("The subreddit name (without r/ prefix)"),
|
|
1056
|
+
post_id: z.string().describe("The Reddit post ID")
|
|
1096
1057
|
}),
|
|
1097
1058
|
execute: async (args) => {
|
|
1098
1059
|
const client = getRedditClient();
|
|
@@ -1123,7 +1084,7 @@ ${formattedPost.content}
|
|
|
1123
1084
|
- Short Link: ${formattedPost.links.shortLink}
|
|
1124
1085
|
|
|
1125
1086
|
## Engagement Analysis
|
|
1126
|
-
- ${formattedPost.engagementAnalysis.replace(/\n
|
|
1087
|
+
- ${formattedPost.engagementAnalysis.replace(/\n {2}- /g, "\n- ")}
|
|
1127
1088
|
|
|
1128
1089
|
## Best Time to Engage
|
|
1129
1090
|
${formattedPost.bestTimeToEngage}`;
|
|
@@ -1132,9 +1093,9 @@ ${formattedPost.bestTimeToEngage}`;
|
|
|
1132
1093
|
server.addTool({
|
|
1133
1094
|
name: "get_top_posts",
|
|
1134
1095
|
description: "Get top posts from a subreddit or from the Reddit home feed",
|
|
1135
|
-
parameters:
|
|
1136
|
-
subreddit:
|
|
1137
|
-
time_filter:
|
|
1096
|
+
parameters: z.object({
|
|
1097
|
+
subreddit: z.string().optional().describe("The subreddit name (without r/ prefix). Leave empty for home feed"),
|
|
1098
|
+
time_filter: z.enum([
|
|
1138
1099
|
"hour",
|
|
1139
1100
|
"day",
|
|
1140
1101
|
"week",
|
|
@@ -1142,7 +1103,7 @@ server.addTool({
|
|
|
1142
1103
|
"year",
|
|
1143
1104
|
"all"
|
|
1144
1105
|
]).default("week").describe("Time period for top posts"),
|
|
1145
|
-
limit:
|
|
1106
|
+
limit: z.number().min(1).max(100).default(10).describe("Number of posts to retrieve")
|
|
1146
1107
|
}),
|
|
1147
1108
|
execute: async (args) => {
|
|
1148
1109
|
const client = getRedditClient();
|
|
@@ -1163,7 +1124,7 @@ ${postSummaries}`;
|
|
|
1163
1124
|
server.addTool({
|
|
1164
1125
|
name: "get_subreddit_info",
|
|
1165
1126
|
description: "Get detailed information about a subreddit including description, stats, and community analysis",
|
|
1166
|
-
parameters:
|
|
1127
|
+
parameters: z.object({ subreddit_name: z.string().describe("The subreddit name (without r/ prefix)") }),
|
|
1167
1128
|
execute: async (args) => {
|
|
1168
1129
|
const client = getRedditClient();
|
|
1169
1130
|
if (!client) throw new Error("Reddit client not initialized");
|
|
@@ -1191,16 +1152,16 @@ ${formattedSubreddit.description.full}
|
|
|
1191
1152
|
- Wiki: ${formattedSubreddit.links.wiki}
|
|
1192
1153
|
|
|
1193
1154
|
## Community Analysis
|
|
1194
|
-
- ${formattedSubreddit.communityAnalysis.replace(/\n
|
|
1155
|
+
- ${formattedSubreddit.communityAnalysis.replace(/\n {2}- /g, "\n- ")}
|
|
1195
1156
|
|
|
1196
1157
|
## Engagement Tips
|
|
1197
|
-
- ${formattedSubreddit.engagementTips.replace(/\n
|
|
1158
|
+
- ${formattedSubreddit.engagementTips.replace(/\n {2}- /g, "\n- ")}`;
|
|
1198
1159
|
}
|
|
1199
1160
|
});
|
|
1200
1161
|
server.addTool({
|
|
1201
1162
|
name: "get_trending_subreddits",
|
|
1202
1163
|
description: "Get a list of currently trending subreddits",
|
|
1203
|
-
parameters:
|
|
1164
|
+
parameters: z.object({}),
|
|
1204
1165
|
execute: async () => {
|
|
1205
1166
|
const client = getRedditClient();
|
|
1206
1167
|
if (!client) throw new Error("Reddit client not initialized");
|
|
@@ -1212,17 +1173,17 @@ ${(await client.getTrendingSubreddits()).map((subreddit, index) => `${index + 1}
|
|
|
1212
1173
|
server.addTool({
|
|
1213
1174
|
name: "search_reddit",
|
|
1214
1175
|
description: "Search Reddit for posts and content across subreddits",
|
|
1215
|
-
parameters:
|
|
1216
|
-
query:
|
|
1217
|
-
subreddit:
|
|
1218
|
-
sort:
|
|
1176
|
+
parameters: z.object({
|
|
1177
|
+
query: z.string().describe("Search query"),
|
|
1178
|
+
subreddit: z.string().optional().describe("Limit search to specific subreddit (without r/ prefix)"),
|
|
1179
|
+
sort: z.enum([
|
|
1219
1180
|
"relevance",
|
|
1220
1181
|
"hot",
|
|
1221
1182
|
"top",
|
|
1222
1183
|
"new",
|
|
1223
1184
|
"comments"
|
|
1224
1185
|
]).default("relevance").describe("Sort order"),
|
|
1225
|
-
time_filter:
|
|
1186
|
+
time_filter: z.enum([
|
|
1226
1187
|
"hour",
|
|
1227
1188
|
"day",
|
|
1228
1189
|
"week",
|
|
@@ -1230,8 +1191,8 @@ server.addTool({
|
|
|
1230
1191
|
"year",
|
|
1231
1192
|
"all"
|
|
1232
1193
|
]).default("all").describe("Time filter"),
|
|
1233
|
-
limit:
|
|
1234
|
-
type:
|
|
1194
|
+
limit: z.number().min(1).max(100).default(10).describe("Number of results"),
|
|
1195
|
+
type: z.enum([
|
|
1235
1196
|
"link",
|
|
1236
1197
|
"sr",
|
|
1237
1198
|
"user"
|
|
@@ -1273,11 +1234,11 @@ ${searchResults}`;
|
|
|
1273
1234
|
server.addTool({
|
|
1274
1235
|
name: "create_post",
|
|
1275
1236
|
description: "Create a new post in a subreddit (requires REDDIT_USERNAME and REDDIT_PASSWORD). WARNING: Rapid posting or duplicate content may trigger Reddit's spam detection and result in account bans. Consider enabling REDDIT_SAFE_MODE=standard for protection.",
|
|
1276
|
-
parameters:
|
|
1277
|
-
subreddit:
|
|
1278
|
-
title:
|
|
1279
|
-
content:
|
|
1280
|
-
is_self:
|
|
1237
|
+
parameters: z.object({
|
|
1238
|
+
subreddit: z.string().describe("The subreddit name (without r/ prefix)"),
|
|
1239
|
+
title: z.string().describe("The post title"),
|
|
1240
|
+
content: z.string().describe("The post content (text for self posts, URL for link posts)"),
|
|
1241
|
+
is_self: z.boolean().default(true).describe("Whether this is a self post (text) or link post")
|
|
1281
1242
|
}),
|
|
1282
1243
|
execute: async (args) => {
|
|
1283
1244
|
const client = getRedditClient();
|
|
@@ -1298,9 +1259,9 @@ Your post has been successfully submitted to r/${formattedPost.subreddit}.`;
|
|
|
1298
1259
|
server.addTool({
|
|
1299
1260
|
name: "reply_to_post",
|
|
1300
1261
|
description: "Post a reply to an existing Reddit post or comment (requires REDDIT_USERNAME and REDDIT_PASSWORD). WARNING: Rapid commenting or duplicate content may trigger Reddit's spam detection. Enable REDDIT_SAFE_MODE=standard for rate limiting and duplicate detection.",
|
|
1301
|
-
parameters:
|
|
1302
|
-
post_id:
|
|
1303
|
-
content:
|
|
1262
|
+
parameters: z.object({
|
|
1263
|
+
post_id: z.string().describe("The Reddit post ID (thing_id, e.g., t3_xxxxx for posts, t1_xxxxx for comments)"),
|
|
1264
|
+
content: z.string().describe("The reply content")
|
|
1304
1265
|
}),
|
|
1305
1266
|
execute: async (args) => {
|
|
1306
1267
|
const client = getRedditClient();
|
|
@@ -1320,7 +1281,7 @@ Your reply has been successfully posted.`;
|
|
|
1320
1281
|
server.addTool({
|
|
1321
1282
|
name: "delete_post",
|
|
1322
1283
|
description: "Delete your own Reddit post (requires REDDIT_USERNAME and REDDIT_PASSWORD). WARNING: This action is permanent and cannot be undone!",
|
|
1323
|
-
parameters:
|
|
1284
|
+
parameters: z.object({ thing_id: z.string().describe("The full Reddit thing ID (e.g., 't3_abc123' for posts) or just the post ID (e.g., 'abc123'). The 't3_' prefix will be added automatically if missing.") }),
|
|
1324
1285
|
execute: async (args) => {
|
|
1325
1286
|
const client = getRedditClient();
|
|
1326
1287
|
if (!client) throw new Error("Reddit client not initialized");
|
|
@@ -1336,7 +1297,7 @@ The post ${args.thing_id} has been permanently deleted from Reddit.
|
|
|
1336
1297
|
server.addTool({
|
|
1337
1298
|
name: "delete_comment",
|
|
1338
1299
|
description: "Delete your own Reddit comment (requires REDDIT_USERNAME and REDDIT_PASSWORD). WARNING: This action is permanent and cannot be undone!",
|
|
1339
|
-
parameters:
|
|
1300
|
+
parameters: z.object({ thing_id: z.string().describe("The full Reddit thing ID (e.g., 't1_abc123' for comments) or just the comment ID (e.g., 'abc123'). The 't1_' prefix will be added automatically if missing.") }),
|
|
1340
1301
|
execute: async (args) => {
|
|
1341
1302
|
const client = getRedditClient();
|
|
1342
1303
|
if (!client) throw new Error("Reddit client not initialized");
|
|
@@ -1352,9 +1313,9 @@ The comment ${args.thing_id} has been permanently deleted from Reddit.
|
|
|
1352
1313
|
server.addTool({
|
|
1353
1314
|
name: "edit_post",
|
|
1354
1315
|
description: "Edit your own Reddit post (self-text posts only, requires REDDIT_USERNAME and REDDIT_PASSWORD). You can only edit the text content of self posts, not titles or link posts. WARNING: Rapid edits may trigger spam detection. Enable REDDIT_SAFE_MODE for protection.",
|
|
1355
|
-
parameters:
|
|
1356
|
-
thing_id:
|
|
1357
|
-
new_text:
|
|
1316
|
+
parameters: z.object({
|
|
1317
|
+
thing_id: z.string().describe("The full Reddit thing ID (e.g., 't3_abc123' for posts) or just the post ID (e.g., 'abc123'). The 't3_' prefix will be added automatically if missing."),
|
|
1318
|
+
new_text: z.string().describe("The new text content for the post. Supports Reddit markdown formatting.")
|
|
1358
1319
|
}),
|
|
1359
1320
|
execute: async (args) => {
|
|
1360
1321
|
const client = getRedditClient();
|
|
@@ -1375,9 +1336,9 @@ The post ${args.thing_id} has been updated with your new content.
|
|
|
1375
1336
|
server.addTool({
|
|
1376
1337
|
name: "edit_comment",
|
|
1377
1338
|
description: "Edit your own Reddit comment (requires REDDIT_USERNAME and REDDIT_PASSWORD). Update the text content of a comment you previously posted. WARNING: Rapid edits may trigger spam detection. Enable REDDIT_SAFE_MODE for protection.",
|
|
1378
|
-
parameters:
|
|
1379
|
-
thing_id:
|
|
1380
|
-
new_text:
|
|
1339
|
+
parameters: z.object({
|
|
1340
|
+
thing_id: z.string().describe("The full Reddit thing ID (e.g., 't1_abc123' for comments) or just the comment ID (e.g., 'abc123'). The 't1_' prefix will be added automatically if missing."),
|
|
1341
|
+
new_text: z.string().describe("The new text content for the comment. Supports Reddit markdown formatting.")
|
|
1381
1342
|
}),
|
|
1382
1343
|
execute: async (args) => {
|
|
1383
1344
|
const client = getRedditClient();
|
|
@@ -1394,10 +1355,10 @@ The comment ${args.thing_id} has been updated with your new content.
|
|
|
1394
1355
|
server.addTool({
|
|
1395
1356
|
name: "get_post_comments",
|
|
1396
1357
|
description: "Get comments from a specific Reddit post",
|
|
1397
|
-
parameters:
|
|
1398
|
-
post_id:
|
|
1399
|
-
subreddit:
|
|
1400
|
-
sort:
|
|
1358
|
+
parameters: z.object({
|
|
1359
|
+
post_id: z.string().describe("The Reddit post ID"),
|
|
1360
|
+
subreddit: z.string().describe("The subreddit name (without r/ prefix)"),
|
|
1361
|
+
sort: z.enum([
|
|
1401
1362
|
"best",
|
|
1402
1363
|
"top",
|
|
1403
1364
|
"new",
|
|
@@ -1405,7 +1366,7 @@ server.addTool({
|
|
|
1405
1366
|
"old",
|
|
1406
1367
|
"qa"
|
|
1407
1368
|
]).default("best").describe("Comment sort order"),
|
|
1408
|
-
limit:
|
|
1369
|
+
limit: z.number().min(1).max(500).default(100).describe("Maximum number of comments to retrieve")
|
|
1409
1370
|
}),
|
|
1410
1371
|
execute: async (args) => {
|
|
1411
1372
|
const client = getRedditClient();
|
|
@@ -1415,8 +1376,8 @@ server.addTool({
|
|
|
1415
1376
|
sort: args.sort,
|
|
1416
1377
|
limit: args.limit
|
|
1417
1378
|
});
|
|
1418
|
-
const post = data
|
|
1419
|
-
const comments = data
|
|
1379
|
+
const { post } = data;
|
|
1380
|
+
const { comments } = data;
|
|
1420
1381
|
let response = `# Comments for: ${post.title}
|
|
1421
1382
|
|
|
1422
1383
|
**Post by u/${post.author} in r/${post.subreddit}**
|
|
@@ -1449,7 +1410,7 @@ async function main() {
|
|
|
1449
1410
|
await setupRedditClient();
|
|
1450
1411
|
const useHttp = process.env.TRANSPORT_TYPE === "httpStream" || process.env.TRANSPORT_TYPE === "http";
|
|
1451
1412
|
const port = parseInt(process.env.PORT || "3000");
|
|
1452
|
-
const host = process.env.HOST || "
|
|
1413
|
+
const host = process.env.HOST || "127.0.0.1";
|
|
1453
1414
|
if (useHttp) {
|
|
1454
1415
|
console.error(`[Setup] Starting HTTP server on ${host}:${port}`);
|
|
1455
1416
|
await server.start({
|
|
@@ -1482,4 +1443,5 @@ process.on("SIGTERM", async () => {
|
|
|
1482
1443
|
main().catch(console.error);
|
|
1483
1444
|
|
|
1484
1445
|
//#endregion
|
|
1446
|
+
export { };
|
|
1485
1447
|
//# sourceMappingURL=index.js.map
|