reddit-mcp-server 1.1.1 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -11
- package/dist/bin.js +368 -40
- package/dist/index.js +363 -38
- package/package.json +17 -15
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
A Model Context Protocol (MCP) that provides tools for fetching and creating Reddit content.
|
|
4
4
|
|
|
5
|
-
> **Note**: This is a fork of the
|
|
5
|
+
> **Note**: This is a fork of the [reddit-mcp-server](https://github.com/alexandros-lekkas/reddit-mcp-server) by Alexandros Lekkas, updated with pnpm, tsup build system, and npx execution support.
|
|
6
6
|
|
|
7
7
|
## 🔧 Available Tools (Features)
|
|
8
8
|
|
|
@@ -22,6 +22,10 @@ A Model Context Protocol (MCP) that provides tools for fetching and creating Red
|
|
|
22
22
|
|
|
23
23
|
- `create_post(subreddit, title, content, is_self)` - Create a new post in a subreddit
|
|
24
24
|
- `reply_to_post(post_id, content, subreddit?)` - Post a reply to an existing Reddit post
|
|
25
|
+
- `edit_post(thing_id, new_text)` - Edit your own Reddit post (self-text posts only)
|
|
26
|
+
- `edit_comment(thing_id, new_text)` - Edit your own Reddit comment
|
|
27
|
+
- `delete_post(thing_id)` - Delete your own Reddit post
|
|
28
|
+
- `delete_comment(thing_id)` - Delete your own Reddit comment
|
|
25
29
|
|
|
26
30
|
## 🔌 Installation
|
|
27
31
|
|
|
@@ -85,8 +89,12 @@ If you want to write posts you need to include your `REDDIT_USERNAME` and `REDDI
|
|
|
85
89
|
"get_user_posts",
|
|
86
90
|
"get_user_comments",
|
|
87
91
|
"create_post",
|
|
88
|
-
"reply_to_post"
|
|
89
|
-
|
|
92
|
+
"reply_to_post",
|
|
93
|
+
"edit_post",
|
|
94
|
+
"edit_comment",
|
|
95
|
+
"delete_post",
|
|
96
|
+
"delete_comment"
|
|
97
|
+
] // Optional if you do not want to always approve
|
|
90
98
|
}
|
|
91
99
|
}
|
|
92
100
|
```
|
|
@@ -131,19 +139,19 @@ npx reddit-mcp-server --help
|
|
|
131
139
|
npx reddit-mcp-server --generate-token
|
|
132
140
|
```
|
|
133
141
|
|
|
134
|
-
###
|
|
142
|
+
### HTTP MCP Endpoint (FastMCP)
|
|
135
143
|
|
|
136
|
-
In addition to the standard npx execution, this server
|
|
144
|
+
In addition to the standard npx execution, this server supports HTTP transport via FastMCP for direct HTTP integration:
|
|
137
145
|
|
|
138
146
|
```bash
|
|
139
|
-
# Start the
|
|
140
|
-
|
|
147
|
+
# Start the HTTP server on port 3000 (default)
|
|
148
|
+
node dist/index.js
|
|
141
149
|
|
|
142
150
|
# Start with custom port
|
|
143
|
-
PORT=8080
|
|
151
|
+
PORT=8080 node dist/index.js
|
|
144
152
|
|
|
145
|
-
#
|
|
146
|
-
pnpm
|
|
153
|
+
# Or use the npm script
|
|
154
|
+
pnpm start
|
|
147
155
|
```
|
|
148
156
|
|
|
149
157
|
The server will be available at `http://localhost:3000` with the MCP endpoint at `http://localhost:3000/mcp`.
|
|
@@ -380,7 +388,7 @@ OAuth is not applicable when using the traditional npx execution method. Use the
|
|
|
380
388
|
await client.connect(transport);
|
|
381
389
|
```
|
|
382
390
|
|
|
383
|
-
This allows integration with systems that support HTTP-based MCP communication
|
|
391
|
+
This allows integration with systems that support HTTP-based MCP communication via the FastMCP framework.
|
|
384
392
|
|
|
385
393
|
## 🐳 Docker Usage
|
|
386
394
|
|
package/dist/bin.js
CHANGED
|
@@ -26,9 +26,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
26
26
|
mod
|
|
27
27
|
));
|
|
28
28
|
|
|
29
|
-
// node_modules/.pnpm/tsup@8.5.
|
|
29
|
+
// node_modules/.pnpm/tsup@8.5.1_postcss@8.5.6_tsx@4.21.0_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js
|
|
30
30
|
var init_cjs_shims = __esm({
|
|
31
|
-
"node_modules/.pnpm/tsup@8.5.
|
|
31
|
+
"node_modules/.pnpm/tsup@8.5.1_postcss@8.5.6_tsx@4.21.0_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js"() {
|
|
32
32
|
"use strict";
|
|
33
33
|
}
|
|
34
34
|
});
|
|
@@ -98,7 +98,8 @@ var init_reddit_client = __esm({
|
|
|
98
98
|
}
|
|
99
99
|
const authUrl = "https://www.reddit.com/api/v1/access_token";
|
|
100
100
|
const authData = new URLSearchParams();
|
|
101
|
-
|
|
101
|
+
const isUserAuth = !!(this.username && this.password);
|
|
102
|
+
if (isUserAuth) {
|
|
102
103
|
authData.append("grant_type", "password");
|
|
103
104
|
authData.append("username", this.username);
|
|
104
105
|
authData.append("password", this.password);
|
|
@@ -116,13 +117,17 @@ var init_reddit_client = __esm({
|
|
|
116
117
|
body: authData.toString()
|
|
117
118
|
});
|
|
118
119
|
if (!response.ok) {
|
|
119
|
-
|
|
120
|
+
const statusText = response.statusText || "Unknown Error";
|
|
121
|
+
throw new Error(`Authentication failed: ${response.status} ${statusText}`);
|
|
120
122
|
}
|
|
121
123
|
const data = await response.json();
|
|
122
124
|
this.accessToken = data.access_token;
|
|
123
125
|
this.tokenExpiry = now + data.expires_in * 1e3;
|
|
124
126
|
this.authenticated = true;
|
|
125
|
-
} catch {
|
|
127
|
+
} catch (error) {
|
|
128
|
+
if (error instanceof Error) {
|
|
129
|
+
throw error;
|
|
130
|
+
}
|
|
126
131
|
throw new Error("Failed to authenticate with Reddit API");
|
|
127
132
|
}
|
|
128
133
|
}
|
|
@@ -280,6 +285,7 @@ var init_reddit_client = __esm({
|
|
|
280
285
|
}
|
|
281
286
|
}
|
|
282
287
|
async createPost(subreddit, title, content, isSelf = true) {
|
|
288
|
+
var _a, _b, _c, _d, _e, _f;
|
|
283
289
|
await this.authenticate();
|
|
284
290
|
if (!this.username || !this.password) {
|
|
285
291
|
throw new Error("User authentication required for posting");
|
|
@@ -291,6 +297,7 @@ var init_reddit_client = __esm({
|
|
|
291
297
|
params.append("kind", kind);
|
|
292
298
|
params.append("title", title);
|
|
293
299
|
params.append(isSelf ? "text" : "url", content);
|
|
300
|
+
params.append("api_type", "json");
|
|
294
301
|
const response = await this.makeRequest("/api/submit", {
|
|
295
302
|
method: "POST",
|
|
296
303
|
headers: {
|
|
@@ -299,17 +306,33 @@ var init_reddit_client = __esm({
|
|
|
299
306
|
body: params.toString()
|
|
300
307
|
});
|
|
301
308
|
if (!response.ok) {
|
|
302
|
-
|
|
309
|
+
const errorText = await response.text();
|
|
310
|
+
console.error(`[Reddit API] Create post failed: ${response.status} ${response.statusText}`);
|
|
311
|
+
console.error(`[Reddit API] Error response: ${errorText}`);
|
|
312
|
+
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
303
313
|
}
|
|
304
314
|
const json = await response.json();
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
throw new Error(
|
|
315
|
+
console.error(`[Reddit API] Create post response:`, JSON.stringify(json, null, 2));
|
|
316
|
+
if (((_a = json.json) == null ? void 0 : _a.errors) && json.json.errors.length > 0) {
|
|
317
|
+
const errors = json.json.errors.map((e) => e.join(": ")).join(", ");
|
|
318
|
+
console.error(`[Reddit API] Post creation errors: ${errors}`);
|
|
319
|
+
throw new Error(`Reddit API errors: ${errors}`);
|
|
310
320
|
}
|
|
311
|
-
|
|
312
|
-
|
|
321
|
+
const postId = ((_c = (_b = json.json) == null ? void 0 : _b.data) == null ? void 0 : _c.id) || ((_f = (_e = (_d = json.json) == null ? void 0 : _d.data) == null ? void 0 : _e.name) == null ? void 0 : _f.replace("t3_", ""));
|
|
322
|
+
if (!postId) {
|
|
323
|
+
console.error(`[Reddit API] No post ID in response`);
|
|
324
|
+
throw new Error("No post ID returned from Reddit");
|
|
325
|
+
}
|
|
326
|
+
console.error(`[Reddit API] Post created with ID: ${postId}`);
|
|
327
|
+
return await this.getPost(postId, subreddit);
|
|
328
|
+
} catch (error) {
|
|
329
|
+
console.error(`[Reddit API] Create post exception:`, error);
|
|
330
|
+
if (error instanceof Error && error.message.includes("HTTP")) {
|
|
331
|
+
throw error;
|
|
332
|
+
}
|
|
333
|
+
throw new Error(
|
|
334
|
+
`Failed to create post in ${subreddit}: ${error instanceof Error ? error.message : String(error)}`
|
|
335
|
+
);
|
|
313
336
|
}
|
|
314
337
|
}
|
|
315
338
|
async checkPostExists(postId) {
|
|
@@ -337,6 +360,7 @@ var init_reddit_client = __esm({
|
|
|
337
360
|
const params = new URLSearchParams();
|
|
338
361
|
params.append("thing_id", `t3_${postId}`);
|
|
339
362
|
params.append("text", content);
|
|
363
|
+
params.append("api_type", "json");
|
|
340
364
|
const response = await this.makeRequest("/api/comment", {
|
|
341
365
|
method: "POST",
|
|
342
366
|
headers: {
|
|
@@ -345,26 +369,126 @@ var init_reddit_client = __esm({
|
|
|
345
369
|
body: params.toString()
|
|
346
370
|
});
|
|
347
371
|
if (!response.ok) {
|
|
348
|
-
|
|
372
|
+
const errorText = await response.text();
|
|
373
|
+
console.error(`[Reddit API] Reply to post failed: ${response.status} ${response.statusText}`);
|
|
374
|
+
console.error(`[Reddit API] Error response: ${errorText}`);
|
|
375
|
+
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
349
376
|
}
|
|
350
|
-
const
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
377
|
+
const json = await response.json();
|
|
378
|
+
console.error(`[Reddit API] Reply response:`, JSON.stringify(json, null, 2));
|
|
379
|
+
if (json.json && json.json.data && json.json.data.things) {
|
|
380
|
+
const commentData = json.json.data.things[0].data;
|
|
381
|
+
return {
|
|
382
|
+
id: commentData.id,
|
|
383
|
+
author: this.username,
|
|
384
|
+
body: content,
|
|
385
|
+
score: 1,
|
|
386
|
+
controversiality: 0,
|
|
387
|
+
subreddit: commentData.subreddit,
|
|
388
|
+
submissionTitle: commentData.link_title || "",
|
|
389
|
+
createdUtc: Date.now() / 1e3,
|
|
390
|
+
edited: false,
|
|
391
|
+
isSubmitter: false,
|
|
392
|
+
permalink: commentData.permalink
|
|
393
|
+
};
|
|
394
|
+
} else if (json.json && json.json.errors && json.json.errors.length > 0) {
|
|
395
|
+
const errors = json.json.errors.map((e) => e.join(": ")).join(", ");
|
|
396
|
+
console.error(`[Reddit API] Reply errors: ${errors}`);
|
|
397
|
+
throw new Error(`Reddit API errors: ${errors}`);
|
|
398
|
+
} else {
|
|
399
|
+
console.error(`[Reddit API] Unexpected reply response format`);
|
|
400
|
+
throw new Error("Failed to parse reply response");
|
|
401
|
+
}
|
|
402
|
+
} catch (error) {
|
|
403
|
+
console.error(`[Reddit API] Reply to post exception:`, error);
|
|
404
|
+
if (error instanceof Error && error.message.includes("HTTP")) {
|
|
405
|
+
throw error;
|
|
406
|
+
}
|
|
407
|
+
throw new Error(`Failed to reply to post ${postId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
async deletePost(thingId) {
|
|
411
|
+
await this.authenticate();
|
|
412
|
+
if (!this.username || !this.password) {
|
|
413
|
+
throw new Error("User authentication required for deleting content");
|
|
414
|
+
}
|
|
415
|
+
try {
|
|
416
|
+
const fullThingId = thingId.startsWith("t3_") || thingId.startsWith("t1_") ? thingId : `t3_${thingId}`;
|
|
417
|
+
const params = new URLSearchParams();
|
|
418
|
+
params.append("id", fullThingId);
|
|
419
|
+
const response = await this.makeRequest("/api/del", {
|
|
420
|
+
method: "POST",
|
|
421
|
+
headers: {
|
|
422
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
423
|
+
},
|
|
424
|
+
body: params.toString()
|
|
425
|
+
});
|
|
426
|
+
if (!response.ok) {
|
|
427
|
+
const errorText = await response.text();
|
|
428
|
+
console.error(`[Reddit API] Delete failed: ${response.status} ${response.statusText}`);
|
|
429
|
+
console.error(`[Reddit API] Error response: ${errorText}`);
|
|
430
|
+
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
431
|
+
}
|
|
432
|
+
console.error(`[Reddit API] Successfully deleted ${fullThingId}`);
|
|
433
|
+
return true;
|
|
434
|
+
} catch (error) {
|
|
435
|
+
console.error(`[Reddit API] Delete exception:`, error);
|
|
436
|
+
if (error instanceof Error && error.message.includes("HTTP")) {
|
|
437
|
+
throw error;
|
|
438
|
+
}
|
|
439
|
+
throw new Error(`Failed to delete content ${thingId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
async deleteComment(thingId) {
|
|
443
|
+
const fullThingId = thingId.startsWith("t1_") ? thingId : `t1_${thingId}`;
|
|
444
|
+
return this.deletePost(fullThingId);
|
|
445
|
+
}
|
|
446
|
+
async editPost(thingId, newText) {
|
|
447
|
+
var _a;
|
|
448
|
+
await this.authenticate();
|
|
449
|
+
if (!this.username || !this.password) {
|
|
450
|
+
throw new Error("User authentication required for editing content");
|
|
451
|
+
}
|
|
452
|
+
try {
|
|
453
|
+
const fullThingId = thingId.startsWith("t3_") || thingId.startsWith("t1_") ? thingId : `t3_${thingId}`;
|
|
454
|
+
const params = new URLSearchParams();
|
|
455
|
+
params.append("thing_id", fullThingId);
|
|
456
|
+
params.append("text", newText);
|
|
457
|
+
params.append("api_type", "json");
|
|
458
|
+
const response = await this.makeRequest("/api/editusertext", {
|
|
459
|
+
method: "POST",
|
|
460
|
+
headers: {
|
|
461
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
462
|
+
},
|
|
463
|
+
body: params.toString()
|
|
464
|
+
});
|
|
465
|
+
if (!response.ok) {
|
|
466
|
+
const errorText = await response.text();
|
|
467
|
+
console.error(`[Reddit API] Edit failed: ${response.status} ${response.statusText}`);
|
|
468
|
+
console.error(`[Reddit API] Error response: ${errorText}`);
|
|
469
|
+
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
470
|
+
}
|
|
471
|
+
const json = await response.json();
|
|
472
|
+
console.error(`[Reddit API] Edit response:`, JSON.stringify(json, null, 2));
|
|
473
|
+
if (((_a = json.json) == null ? void 0 : _a.errors) && json.json.errors.length > 0) {
|
|
474
|
+
const errors = json.json.errors.map((e) => e.join(": ")).join(", ");
|
|
475
|
+
console.error(`[Reddit API] Edit errors: ${errors}`);
|
|
476
|
+
throw new Error(`Reddit API errors: ${errors}`);
|
|
477
|
+
}
|
|
478
|
+
console.error(`[Reddit API] Successfully edited ${fullThingId}`);
|
|
479
|
+
return true;
|
|
480
|
+
} catch (error) {
|
|
481
|
+
console.error(`[Reddit API] Edit exception:`, error);
|
|
482
|
+
if (error instanceof Error && error.message.includes("HTTP")) {
|
|
483
|
+
throw error;
|
|
484
|
+
}
|
|
485
|
+
throw new Error(`Failed to edit content ${thingId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
366
486
|
}
|
|
367
487
|
}
|
|
488
|
+
async editComment(thingId, newText) {
|
|
489
|
+
const fullThingId = thingId.startsWith("t1_") ? thingId : `t1_${thingId}`;
|
|
490
|
+
return this.editPost(fullThingId, newText);
|
|
491
|
+
}
|
|
368
492
|
async searchReddit(query, options = {}) {
|
|
369
493
|
await this.authenticate();
|
|
370
494
|
try {
|
|
@@ -769,7 +893,7 @@ async function setupRedditClient() {
|
|
|
769
893
|
process.exit(1);
|
|
770
894
|
}
|
|
771
895
|
try {
|
|
772
|
-
initializeRedditClient({
|
|
896
|
+
const client = initializeRedditClient({
|
|
773
897
|
clientId,
|
|
774
898
|
clientSecret,
|
|
775
899
|
userAgent,
|
|
@@ -777,22 +901,51 @@ async function setupRedditClient() {
|
|
|
777
901
|
password
|
|
778
902
|
});
|
|
779
903
|
console.error("[Setup] Reddit client initialized");
|
|
904
|
+
console.error("[Setup] Testing Reddit API connection...");
|
|
905
|
+
const isConnected = await client.checkAuthentication();
|
|
906
|
+
if (!isConnected) {
|
|
907
|
+
console.error("[Error] \u2717 Failed to connect to Reddit API");
|
|
908
|
+
console.error("[Error] Please check your REDDIT_CLIENT_ID and REDDIT_CLIENT_SECRET");
|
|
909
|
+
process.exit(1);
|
|
910
|
+
}
|
|
911
|
+
console.error("[Setup] \u2713 Reddit API connection successful");
|
|
780
912
|
if (username && password) {
|
|
781
|
-
console.error(`[Setup]
|
|
913
|
+
console.error(`[Setup] \u2713 User authenticated as: ${username}`);
|
|
914
|
+
console.error("[Setup] Write operations enabled (posting, replying, editing, deleting)");
|
|
782
915
|
} else {
|
|
783
|
-
console.error("[Setup] Running in read-only mode (
|
|
916
|
+
console.error("[Setup] Running in read-only mode (client credentials only)");
|
|
917
|
+
console.error("[Setup] For write operations, set REDDIT_USERNAME and REDDIT_PASSWORD");
|
|
784
918
|
}
|
|
785
919
|
} catch (error) {
|
|
786
|
-
console.error("[Error]
|
|
920
|
+
console.error("[Error] \u2717 Reddit API connection failed:", error instanceof Error ? error.message : error);
|
|
921
|
+
console.error("[Error] Please verify your Reddit API credentials");
|
|
787
922
|
process.exit(1);
|
|
788
923
|
}
|
|
789
924
|
}
|
|
790
925
|
async function main() {
|
|
791
926
|
try {
|
|
792
927
|
await setupRedditClient();
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
928
|
+
const useStdio = process.env.TRANSPORT_TYPE === "stdio";
|
|
929
|
+
const port = parseInt(process.env.PORT || "3000");
|
|
930
|
+
const host = process.env.HOST || "0.0.0.0";
|
|
931
|
+
if (useStdio) {
|
|
932
|
+
console.error("[Setup] Starting in stdio mode (CLI/npx)");
|
|
933
|
+
await server.start({
|
|
934
|
+
transportType: "stdio"
|
|
935
|
+
});
|
|
936
|
+
} else {
|
|
937
|
+
console.error(`[Setup] Starting HTTP server on ${host}:${port}`);
|
|
938
|
+
await server.start({
|
|
939
|
+
transportType: "httpStream",
|
|
940
|
+
httpStream: {
|
|
941
|
+
port,
|
|
942
|
+
host,
|
|
943
|
+
endpoint: "/mcp"
|
|
944
|
+
}
|
|
945
|
+
});
|
|
946
|
+
console.error(`[Setup] HTTP server ready at http://${host}:${port}/mcp`);
|
|
947
|
+
console.error(`[Setup] SSE endpoint available at http://${host}:${port}/sse`);
|
|
948
|
+
}
|
|
796
949
|
} catch (error) {
|
|
797
950
|
console.error("[Error] Failed to start server:", error);
|
|
798
951
|
process.exit(1);
|
|
@@ -813,15 +966,17 @@ var init_src = __esm({
|
|
|
813
966
|
name: "reddit-mcp-server",
|
|
814
967
|
version: "1.1.0",
|
|
815
968
|
instructions: `A comprehensive Reddit MCP server that provides tools for interacting with Reddit API.
|
|
816
|
-
|
|
969
|
+
|
|
817
970
|
Available capabilities:
|
|
818
971
|
- Fetch Reddit posts, comments, and user information
|
|
819
|
-
- Get subreddit details and statistics
|
|
972
|
+
- Get subreddit details and statistics
|
|
820
973
|
- Search Reddit content across posts and subreddits
|
|
821
974
|
- Create posts and reply to posts/comments (with authentication)
|
|
975
|
+
- Edit your own posts and comments (with authentication)
|
|
976
|
+
- Delete your own posts and comments (with authentication)
|
|
822
977
|
- Analyze engagement metrics and community insights
|
|
823
978
|
|
|
824
|
-
For write operations (posting, replying), ensure REDDIT_USERNAME and REDDIT_PASSWORD are configured.`,
|
|
979
|
+
For write operations (posting, replying, editing, deleting), ensure REDDIT_USERNAME and REDDIT_PASSWORD are configured.`,
|
|
825
980
|
// Optional OAuth configuration for HTTP transport
|
|
826
981
|
...process.env.OAUTH_ENABLED === "true" && {
|
|
827
982
|
authenticate: async (request) => {
|
|
@@ -1168,6 +1323,176 @@ Sorted by: ${args2.sort} | Time: ${args2.time_filter} | Type: ${args2.type}
|
|
|
1168
1323
|
${searchResults}`;
|
|
1169
1324
|
}
|
|
1170
1325
|
});
|
|
1326
|
+
server.addTool({
|
|
1327
|
+
name: "create_post",
|
|
1328
|
+
description: "Create a new post in a subreddit (requires REDDIT_USERNAME and REDDIT_PASSWORD)",
|
|
1329
|
+
parameters: import_zod.z.object({
|
|
1330
|
+
subreddit: import_zod.z.string().describe("The subreddit name (without r/ prefix)"),
|
|
1331
|
+
title: import_zod.z.string().describe("The post title"),
|
|
1332
|
+
content: import_zod.z.string().describe("The post content (text for self posts, URL for link posts)"),
|
|
1333
|
+
is_self: import_zod.z.boolean().default(true).describe("Whether this is a self post (text) or link post")
|
|
1334
|
+
}),
|
|
1335
|
+
execute: async (args2) => {
|
|
1336
|
+
const client = getRedditClient();
|
|
1337
|
+
if (!client) {
|
|
1338
|
+
throw new Error("Reddit client not initialized");
|
|
1339
|
+
}
|
|
1340
|
+
if (!process.env.REDDIT_USERNAME || !process.env.REDDIT_PASSWORD) {
|
|
1341
|
+
throw new Error(
|
|
1342
|
+
"User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables."
|
|
1343
|
+
);
|
|
1344
|
+
}
|
|
1345
|
+
const post = await client.createPost(args2.subreddit, args2.title, args2.content, args2.is_self);
|
|
1346
|
+
const formattedPost = formatPostInfo(post);
|
|
1347
|
+
return `# Post Created Successfully
|
|
1348
|
+
|
|
1349
|
+
## Post Details
|
|
1350
|
+
- Title: ${formattedPost.title}
|
|
1351
|
+
- Subreddit: r/${formattedPost.subreddit}
|
|
1352
|
+
- Type: ${formattedPost.type}
|
|
1353
|
+
- Link: ${formattedPost.links.fullPost}
|
|
1354
|
+
|
|
1355
|
+
Your post has been successfully submitted to r/${formattedPost.subreddit}.`;
|
|
1356
|
+
}
|
|
1357
|
+
});
|
|
1358
|
+
server.addTool({
|
|
1359
|
+
name: "reply_to_post",
|
|
1360
|
+
description: "Post a reply to an existing Reddit post or comment (requires REDDIT_USERNAME and REDDIT_PASSWORD)",
|
|
1361
|
+
parameters: import_zod.z.object({
|
|
1362
|
+
post_id: import_zod.z.string().describe("The Reddit post ID (thing_id, e.g., t3_xxxxx for posts, t1_xxxxx for comments)"),
|
|
1363
|
+
content: import_zod.z.string().describe("The reply content")
|
|
1364
|
+
}),
|
|
1365
|
+
execute: async (args2) => {
|
|
1366
|
+
const client = getRedditClient();
|
|
1367
|
+
if (!client) {
|
|
1368
|
+
throw new Error("Reddit client not initialized");
|
|
1369
|
+
}
|
|
1370
|
+
if (!process.env.REDDIT_USERNAME || !process.env.REDDIT_PASSWORD) {
|
|
1371
|
+
throw new Error(
|
|
1372
|
+
"User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables."
|
|
1373
|
+
);
|
|
1374
|
+
}
|
|
1375
|
+
const comment = await client.replyToPost(args2.post_id, args2.content);
|
|
1376
|
+
return `# Reply Posted Successfully
|
|
1377
|
+
|
|
1378
|
+
## Comment Details
|
|
1379
|
+
- Posted to: ${args2.post_id}
|
|
1380
|
+
- Author: u/${process.env.REDDIT_USERNAME}
|
|
1381
|
+
- Comment ID: ${comment.id}
|
|
1382
|
+
|
|
1383
|
+
Your reply has been successfully posted.`;
|
|
1384
|
+
}
|
|
1385
|
+
});
|
|
1386
|
+
server.addTool({
|
|
1387
|
+
name: "delete_post",
|
|
1388
|
+
description: "Delete your own Reddit post (requires REDDIT_USERNAME and REDDIT_PASSWORD). WARNING: This action is permanent and cannot be undone!",
|
|
1389
|
+
parameters: import_zod.z.object({
|
|
1390
|
+
thing_id: import_zod.z.string().describe(
|
|
1391
|
+
"The full Reddit thing ID (e.g., 't3_abc123' for posts) or just the post ID (e.g., 'abc123'). The 't3_' prefix will be added automatically if missing."
|
|
1392
|
+
)
|
|
1393
|
+
}),
|
|
1394
|
+
execute: async (args2) => {
|
|
1395
|
+
const client = getRedditClient();
|
|
1396
|
+
if (!client) {
|
|
1397
|
+
throw new Error("Reddit client not initialized");
|
|
1398
|
+
}
|
|
1399
|
+
if (!process.env.REDDIT_USERNAME || !process.env.REDDIT_PASSWORD) {
|
|
1400
|
+
throw new Error(
|
|
1401
|
+
"User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables."
|
|
1402
|
+
);
|
|
1403
|
+
}
|
|
1404
|
+
await client.deletePost(args2.thing_id);
|
|
1405
|
+
return `# Post Deleted Successfully
|
|
1406
|
+
|
|
1407
|
+
The post ${args2.thing_id} has been permanently deleted from Reddit.
|
|
1408
|
+
|
|
1409
|
+
**Note**: This action cannot be undone. The post content has been removed and cannot be recovered.`;
|
|
1410
|
+
}
|
|
1411
|
+
});
|
|
1412
|
+
server.addTool({
|
|
1413
|
+
name: "delete_comment",
|
|
1414
|
+
description: "Delete your own Reddit comment (requires REDDIT_USERNAME and REDDIT_PASSWORD). WARNING: This action is permanent and cannot be undone!",
|
|
1415
|
+
parameters: import_zod.z.object({
|
|
1416
|
+
thing_id: import_zod.z.string().describe(
|
|
1417
|
+
"The full Reddit thing ID (e.g., 't1_abc123' for comments) or just the comment ID (e.g., 'abc123'). The 't1_' prefix will be added automatically if missing."
|
|
1418
|
+
)
|
|
1419
|
+
}),
|
|
1420
|
+
execute: async (args2) => {
|
|
1421
|
+
const client = getRedditClient();
|
|
1422
|
+
if (!client) {
|
|
1423
|
+
throw new Error("Reddit client not initialized");
|
|
1424
|
+
}
|
|
1425
|
+
if (!process.env.REDDIT_USERNAME || !process.env.REDDIT_PASSWORD) {
|
|
1426
|
+
throw new Error(
|
|
1427
|
+
"User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables."
|
|
1428
|
+
);
|
|
1429
|
+
}
|
|
1430
|
+
await client.deleteComment(args2.thing_id);
|
|
1431
|
+
return `# Comment Deleted Successfully
|
|
1432
|
+
|
|
1433
|
+
The comment ${args2.thing_id} has been permanently deleted from Reddit.
|
|
1434
|
+
|
|
1435
|
+
**Note**: This action cannot be undone. The comment content has been removed and cannot be recovered.`;
|
|
1436
|
+
}
|
|
1437
|
+
});
|
|
1438
|
+
server.addTool({
|
|
1439
|
+
name: "edit_post",
|
|
1440
|
+
description: "Edit your own Reddit post (self-text posts only, requires REDDIT_USERNAME and REDDIT_PASSWORD). You can only edit the text content of self posts, not titles or link posts.",
|
|
1441
|
+
parameters: import_zod.z.object({
|
|
1442
|
+
thing_id: import_zod.z.string().describe(
|
|
1443
|
+
"The full Reddit thing ID (e.g., 't3_abc123' for posts) or just the post ID (e.g., 'abc123'). The 't3_' prefix will be added automatically if missing."
|
|
1444
|
+
),
|
|
1445
|
+
new_text: import_zod.z.string().describe("The new text content for the post. Supports Reddit markdown formatting.")
|
|
1446
|
+
}),
|
|
1447
|
+
execute: async (args2) => {
|
|
1448
|
+
const client = getRedditClient();
|
|
1449
|
+
if (!client) {
|
|
1450
|
+
throw new Error("Reddit client not initialized");
|
|
1451
|
+
}
|
|
1452
|
+
if (!process.env.REDDIT_USERNAME || !process.env.REDDIT_PASSWORD) {
|
|
1453
|
+
throw new Error(
|
|
1454
|
+
"User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables."
|
|
1455
|
+
);
|
|
1456
|
+
}
|
|
1457
|
+
await client.editPost(args2.thing_id, args2.new_text);
|
|
1458
|
+
return `# Post Edited Successfully
|
|
1459
|
+
|
|
1460
|
+
The post ${args2.thing_id} has been updated with your new content.
|
|
1461
|
+
|
|
1462
|
+
**Note**:
|
|
1463
|
+
- Only self (text) posts can be edited
|
|
1464
|
+
- Post titles cannot be edited
|
|
1465
|
+
- Link posts cannot be edited
|
|
1466
|
+
- An "edited" marker will appear on your post`;
|
|
1467
|
+
}
|
|
1468
|
+
});
|
|
1469
|
+
server.addTool({
|
|
1470
|
+
name: "edit_comment",
|
|
1471
|
+
description: "Edit your own Reddit comment (requires REDDIT_USERNAME and REDDIT_PASSWORD). Update the text content of a comment you previously posted.",
|
|
1472
|
+
parameters: import_zod.z.object({
|
|
1473
|
+
thing_id: import_zod.z.string().describe(
|
|
1474
|
+
"The full Reddit thing ID (e.g., 't1_abc123' for comments) or just the comment ID (e.g., 'abc123'). The 't1_' prefix will be added automatically if missing."
|
|
1475
|
+
),
|
|
1476
|
+
new_text: import_zod.z.string().describe("The new text content for the comment. Supports Reddit markdown formatting.")
|
|
1477
|
+
}),
|
|
1478
|
+
execute: async (args2) => {
|
|
1479
|
+
const client = getRedditClient();
|
|
1480
|
+
if (!client) {
|
|
1481
|
+
throw new Error("Reddit client not initialized");
|
|
1482
|
+
}
|
|
1483
|
+
if (!process.env.REDDIT_USERNAME || !process.env.REDDIT_PASSWORD) {
|
|
1484
|
+
throw new Error(
|
|
1485
|
+
"User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables."
|
|
1486
|
+
);
|
|
1487
|
+
}
|
|
1488
|
+
await client.editComment(args2.thing_id, args2.new_text);
|
|
1489
|
+
return `# Comment Edited Successfully
|
|
1490
|
+
|
|
1491
|
+
The comment ${args2.thing_id} has been updated with your new content.
|
|
1492
|
+
|
|
1493
|
+
**Note**: An "edited" marker will appear on your comment to show it has been modified.`;
|
|
1494
|
+
}
|
|
1495
|
+
});
|
|
1171
1496
|
server.addTool({
|
|
1172
1497
|
name: "get_post_comments",
|
|
1173
1498
|
description: "Get comments from a specific Reddit post",
|
|
@@ -1236,6 +1561,9 @@ var import_fs = __toESM(require("fs"));
|
|
|
1236
1561
|
var import_path = __toESM(require("path"));
|
|
1237
1562
|
var packageJsonPath = import_path.default.join(__dirname, "..", "package.json");
|
|
1238
1563
|
var packageJson = JSON.parse(import_fs.default.readFileSync(packageJsonPath, "utf-8"));
|
|
1564
|
+
if (!process.env.TRANSPORT_TYPE) {
|
|
1565
|
+
process.env.TRANSPORT_TYPE = "stdio";
|
|
1566
|
+
}
|
|
1239
1567
|
var args = process.argv.slice(2);
|
|
1240
1568
|
if (args.includes("--version") || args.includes("-v")) {
|
|
1241
1569
|
console.log(packageJson.version);
|
package/dist/index.js
CHANGED
|
@@ -79,7 +79,8 @@ var RedditClient = class {
|
|
|
79
79
|
}
|
|
80
80
|
const authUrl = "https://www.reddit.com/api/v1/access_token";
|
|
81
81
|
const authData = new URLSearchParams();
|
|
82
|
-
|
|
82
|
+
const isUserAuth = !!(this.username && this.password);
|
|
83
|
+
if (isUserAuth) {
|
|
83
84
|
authData.append("grant_type", "password");
|
|
84
85
|
authData.append("username", this.username);
|
|
85
86
|
authData.append("password", this.password);
|
|
@@ -97,13 +98,17 @@ var RedditClient = class {
|
|
|
97
98
|
body: authData.toString()
|
|
98
99
|
});
|
|
99
100
|
if (!response.ok) {
|
|
100
|
-
|
|
101
|
+
const statusText = response.statusText || "Unknown Error";
|
|
102
|
+
throw new Error(`Authentication failed: ${response.status} ${statusText}`);
|
|
101
103
|
}
|
|
102
104
|
const data = await response.json();
|
|
103
105
|
this.accessToken = data.access_token;
|
|
104
106
|
this.tokenExpiry = now + data.expires_in * 1e3;
|
|
105
107
|
this.authenticated = true;
|
|
106
|
-
} catch {
|
|
108
|
+
} catch (error) {
|
|
109
|
+
if (error instanceof Error) {
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
107
112
|
throw new Error("Failed to authenticate with Reddit API");
|
|
108
113
|
}
|
|
109
114
|
}
|
|
@@ -261,6 +266,7 @@ var RedditClient = class {
|
|
|
261
266
|
}
|
|
262
267
|
}
|
|
263
268
|
async createPost(subreddit, title, content, isSelf = true) {
|
|
269
|
+
var _a, _b, _c, _d, _e, _f;
|
|
264
270
|
await this.authenticate();
|
|
265
271
|
if (!this.username || !this.password) {
|
|
266
272
|
throw new Error("User authentication required for posting");
|
|
@@ -272,6 +278,7 @@ var RedditClient = class {
|
|
|
272
278
|
params.append("kind", kind);
|
|
273
279
|
params.append("title", title);
|
|
274
280
|
params.append(isSelf ? "text" : "url", content);
|
|
281
|
+
params.append("api_type", "json");
|
|
275
282
|
const response = await this.makeRequest("/api/submit", {
|
|
276
283
|
method: "POST",
|
|
277
284
|
headers: {
|
|
@@ -280,17 +287,33 @@ var RedditClient = class {
|
|
|
280
287
|
body: params.toString()
|
|
281
288
|
});
|
|
282
289
|
if (!response.ok) {
|
|
283
|
-
|
|
290
|
+
const errorText = await response.text();
|
|
291
|
+
console.error(`[Reddit API] Create post failed: ${response.status} ${response.statusText}`);
|
|
292
|
+
console.error(`[Reddit API] Error response: ${errorText}`);
|
|
293
|
+
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
284
294
|
}
|
|
285
295
|
const json = await response.json();
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
throw new Error(
|
|
296
|
+
console.error(`[Reddit API] Create post response:`, JSON.stringify(json, null, 2));
|
|
297
|
+
if (((_a = json.json) == null ? void 0 : _a.errors) && json.json.errors.length > 0) {
|
|
298
|
+
const errors = json.json.errors.map((e) => e.join(": ")).join(", ");
|
|
299
|
+
console.error(`[Reddit API] Post creation errors: ${errors}`);
|
|
300
|
+
throw new Error(`Reddit API errors: ${errors}`);
|
|
291
301
|
}
|
|
292
|
-
|
|
293
|
-
|
|
302
|
+
const postId = ((_c = (_b = json.json) == null ? void 0 : _b.data) == null ? void 0 : _c.id) || ((_f = (_e = (_d = json.json) == null ? void 0 : _d.data) == null ? void 0 : _e.name) == null ? void 0 : _f.replace("t3_", ""));
|
|
303
|
+
if (!postId) {
|
|
304
|
+
console.error(`[Reddit API] No post ID in response`);
|
|
305
|
+
throw new Error("No post ID returned from Reddit");
|
|
306
|
+
}
|
|
307
|
+
console.error(`[Reddit API] Post created with ID: ${postId}`);
|
|
308
|
+
return await this.getPost(postId, subreddit);
|
|
309
|
+
} catch (error) {
|
|
310
|
+
console.error(`[Reddit API] Create post exception:`, error);
|
|
311
|
+
if (error instanceof Error && error.message.includes("HTTP")) {
|
|
312
|
+
throw error;
|
|
313
|
+
}
|
|
314
|
+
throw new Error(
|
|
315
|
+
`Failed to create post in ${subreddit}: ${error instanceof Error ? error.message : String(error)}`
|
|
316
|
+
);
|
|
294
317
|
}
|
|
295
318
|
}
|
|
296
319
|
async checkPostExists(postId) {
|
|
@@ -318,6 +341,7 @@ var RedditClient = class {
|
|
|
318
341
|
const params = new URLSearchParams();
|
|
319
342
|
params.append("thing_id", `t3_${postId}`);
|
|
320
343
|
params.append("text", content);
|
|
344
|
+
params.append("api_type", "json");
|
|
321
345
|
const response = await this.makeRequest("/api/comment", {
|
|
322
346
|
method: "POST",
|
|
323
347
|
headers: {
|
|
@@ -326,26 +350,126 @@ var RedditClient = class {
|
|
|
326
350
|
body: params.toString()
|
|
327
351
|
});
|
|
328
352
|
if (!response.ok) {
|
|
329
|
-
|
|
353
|
+
const errorText = await response.text();
|
|
354
|
+
console.error(`[Reddit API] Reply to post failed: ${response.status} ${response.statusText}`);
|
|
355
|
+
console.error(`[Reddit API] Error response: ${errorText}`);
|
|
356
|
+
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
330
357
|
}
|
|
331
|
-
const
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
358
|
+
const json = await response.json();
|
|
359
|
+
console.error(`[Reddit API] Reply response:`, JSON.stringify(json, null, 2));
|
|
360
|
+
if (json.json && json.json.data && json.json.data.things) {
|
|
361
|
+
const commentData = json.json.data.things[0].data;
|
|
362
|
+
return {
|
|
363
|
+
id: commentData.id,
|
|
364
|
+
author: this.username,
|
|
365
|
+
body: content,
|
|
366
|
+
score: 1,
|
|
367
|
+
controversiality: 0,
|
|
368
|
+
subreddit: commentData.subreddit,
|
|
369
|
+
submissionTitle: commentData.link_title || "",
|
|
370
|
+
createdUtc: Date.now() / 1e3,
|
|
371
|
+
edited: false,
|
|
372
|
+
isSubmitter: false,
|
|
373
|
+
permalink: commentData.permalink
|
|
374
|
+
};
|
|
375
|
+
} else if (json.json && json.json.errors && json.json.errors.length > 0) {
|
|
376
|
+
const errors = json.json.errors.map((e) => e.join(": ")).join(", ");
|
|
377
|
+
console.error(`[Reddit API] Reply errors: ${errors}`);
|
|
378
|
+
throw new Error(`Reddit API errors: ${errors}`);
|
|
379
|
+
} else {
|
|
380
|
+
console.error(`[Reddit API] Unexpected reply response format`);
|
|
381
|
+
throw new Error("Failed to parse reply response");
|
|
382
|
+
}
|
|
383
|
+
} catch (error) {
|
|
384
|
+
console.error(`[Reddit API] Reply to post exception:`, error);
|
|
385
|
+
if (error instanceof Error && error.message.includes("HTTP")) {
|
|
386
|
+
throw error;
|
|
387
|
+
}
|
|
388
|
+
throw new Error(`Failed to reply to post ${postId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
async deletePost(thingId) {
|
|
392
|
+
await this.authenticate();
|
|
393
|
+
if (!this.username || !this.password) {
|
|
394
|
+
throw new Error("User authentication required for deleting content");
|
|
395
|
+
}
|
|
396
|
+
try {
|
|
397
|
+
const fullThingId = thingId.startsWith("t3_") || thingId.startsWith("t1_") ? thingId : `t3_${thingId}`;
|
|
398
|
+
const params = new URLSearchParams();
|
|
399
|
+
params.append("id", fullThingId);
|
|
400
|
+
const response = await this.makeRequest("/api/del", {
|
|
401
|
+
method: "POST",
|
|
402
|
+
headers: {
|
|
403
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
404
|
+
},
|
|
405
|
+
body: params.toString()
|
|
406
|
+
});
|
|
407
|
+
if (!response.ok) {
|
|
408
|
+
const errorText = await response.text();
|
|
409
|
+
console.error(`[Reddit API] Delete failed: ${response.status} ${response.statusText}`);
|
|
410
|
+
console.error(`[Reddit API] Error response: ${errorText}`);
|
|
411
|
+
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
412
|
+
}
|
|
413
|
+
console.error(`[Reddit API] Successfully deleted ${fullThingId}`);
|
|
414
|
+
return true;
|
|
415
|
+
} catch (error) {
|
|
416
|
+
console.error(`[Reddit API] Delete exception:`, error);
|
|
417
|
+
if (error instanceof Error && error.message.includes("HTTP")) {
|
|
418
|
+
throw error;
|
|
419
|
+
}
|
|
420
|
+
throw new Error(`Failed to delete content ${thingId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
async deleteComment(thingId) {
|
|
424
|
+
const fullThingId = thingId.startsWith("t1_") ? thingId : `t1_${thingId}`;
|
|
425
|
+
return this.deletePost(fullThingId);
|
|
426
|
+
}
|
|
427
|
+
async editPost(thingId, newText) {
|
|
428
|
+
var _a;
|
|
429
|
+
await this.authenticate();
|
|
430
|
+
if (!this.username || !this.password) {
|
|
431
|
+
throw new Error("User authentication required for editing content");
|
|
432
|
+
}
|
|
433
|
+
try {
|
|
434
|
+
const fullThingId = thingId.startsWith("t3_") || thingId.startsWith("t1_") ? thingId : `t3_${thingId}`;
|
|
435
|
+
const params = new URLSearchParams();
|
|
436
|
+
params.append("thing_id", fullThingId);
|
|
437
|
+
params.append("text", newText);
|
|
438
|
+
params.append("api_type", "json");
|
|
439
|
+
const response = await this.makeRequest("/api/editusertext", {
|
|
440
|
+
method: "POST",
|
|
441
|
+
headers: {
|
|
442
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
443
|
+
},
|
|
444
|
+
body: params.toString()
|
|
445
|
+
});
|
|
446
|
+
if (!response.ok) {
|
|
447
|
+
const errorText = await response.text();
|
|
448
|
+
console.error(`[Reddit API] Edit failed: ${response.status} ${response.statusText}`);
|
|
449
|
+
console.error(`[Reddit API] Error response: ${errorText}`);
|
|
450
|
+
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
451
|
+
}
|
|
452
|
+
const json = await response.json();
|
|
453
|
+
console.error(`[Reddit API] Edit response:`, JSON.stringify(json, null, 2));
|
|
454
|
+
if (((_a = json.json) == null ? void 0 : _a.errors) && json.json.errors.length > 0) {
|
|
455
|
+
const errors = json.json.errors.map((e) => e.join(": ")).join(", ");
|
|
456
|
+
console.error(`[Reddit API] Edit errors: ${errors}`);
|
|
457
|
+
throw new Error(`Reddit API errors: ${errors}`);
|
|
458
|
+
}
|
|
459
|
+
console.error(`[Reddit API] Successfully edited ${fullThingId}`);
|
|
460
|
+
return true;
|
|
461
|
+
} catch (error) {
|
|
462
|
+
console.error(`[Reddit API] Edit exception:`, error);
|
|
463
|
+
if (error instanceof Error && error.message.includes("HTTP")) {
|
|
464
|
+
throw error;
|
|
465
|
+
}
|
|
466
|
+
throw new Error(`Failed to edit content ${thingId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
347
467
|
}
|
|
348
468
|
}
|
|
469
|
+
async editComment(thingId, newText) {
|
|
470
|
+
const fullThingId = thingId.startsWith("t1_") ? thingId : `t1_${thingId}`;
|
|
471
|
+
return this.editPost(fullThingId, newText);
|
|
472
|
+
}
|
|
349
473
|
async searchReddit(query, options = {}) {
|
|
350
474
|
await this.authenticate();
|
|
351
475
|
try {
|
|
@@ -750,7 +874,7 @@ async function setupRedditClient() {
|
|
|
750
874
|
process.exit(1);
|
|
751
875
|
}
|
|
752
876
|
try {
|
|
753
|
-
initializeRedditClient({
|
|
877
|
+
const client = initializeRedditClient({
|
|
754
878
|
clientId,
|
|
755
879
|
clientSecret,
|
|
756
880
|
userAgent,
|
|
@@ -758,13 +882,24 @@ async function setupRedditClient() {
|
|
|
758
882
|
password
|
|
759
883
|
});
|
|
760
884
|
console.error("[Setup] Reddit client initialized");
|
|
885
|
+
console.error("[Setup] Testing Reddit API connection...");
|
|
886
|
+
const isConnected = await client.checkAuthentication();
|
|
887
|
+
if (!isConnected) {
|
|
888
|
+
console.error("[Error] \u2717 Failed to connect to Reddit API");
|
|
889
|
+
console.error("[Error] Please check your REDDIT_CLIENT_ID and REDDIT_CLIENT_SECRET");
|
|
890
|
+
process.exit(1);
|
|
891
|
+
}
|
|
892
|
+
console.error("[Setup] \u2713 Reddit API connection successful");
|
|
761
893
|
if (username && password) {
|
|
762
|
-
console.error(`[Setup]
|
|
894
|
+
console.error(`[Setup] \u2713 User authenticated as: ${username}`);
|
|
895
|
+
console.error("[Setup] Write operations enabled (posting, replying, editing, deleting)");
|
|
763
896
|
} else {
|
|
764
|
-
console.error("[Setup] Running in read-only mode (
|
|
897
|
+
console.error("[Setup] Running in read-only mode (client credentials only)");
|
|
898
|
+
console.error("[Setup] For write operations, set REDDIT_USERNAME and REDDIT_PASSWORD");
|
|
765
899
|
}
|
|
766
900
|
} catch (error) {
|
|
767
|
-
console.error("[Error]
|
|
901
|
+
console.error("[Error] \u2717 Reddit API connection failed:", error instanceof Error ? error.message : error);
|
|
902
|
+
console.error("[Error] Please verify your Reddit API credentials");
|
|
768
903
|
process.exit(1);
|
|
769
904
|
}
|
|
770
905
|
}
|
|
@@ -772,15 +907,17 @@ var server = new import_fastmcp.FastMCP({
|
|
|
772
907
|
name: "reddit-mcp-server",
|
|
773
908
|
version: "1.1.0",
|
|
774
909
|
instructions: `A comprehensive Reddit MCP server that provides tools for interacting with Reddit API.
|
|
775
|
-
|
|
910
|
+
|
|
776
911
|
Available capabilities:
|
|
777
912
|
- Fetch Reddit posts, comments, and user information
|
|
778
|
-
- Get subreddit details and statistics
|
|
913
|
+
- Get subreddit details and statistics
|
|
779
914
|
- Search Reddit content across posts and subreddits
|
|
780
915
|
- Create posts and reply to posts/comments (with authentication)
|
|
916
|
+
- Edit your own posts and comments (with authentication)
|
|
917
|
+
- Delete your own posts and comments (with authentication)
|
|
781
918
|
- Analyze engagement metrics and community insights
|
|
782
919
|
|
|
783
|
-
For write operations (posting, replying), ensure REDDIT_USERNAME and REDDIT_PASSWORD are configured.`,
|
|
920
|
+
For write operations (posting, replying, editing, deleting), ensure REDDIT_USERNAME and REDDIT_PASSWORD are configured.`,
|
|
784
921
|
// Optional OAuth configuration for HTTP transport
|
|
785
922
|
...process.env.OAUTH_ENABLED === "true" && {
|
|
786
923
|
authenticate: async (request) => {
|
|
@@ -1127,6 +1264,176 @@ Sorted by: ${args.sort} | Time: ${args.time_filter} | Type: ${args.type}
|
|
|
1127
1264
|
${searchResults}`;
|
|
1128
1265
|
}
|
|
1129
1266
|
});
|
|
1267
|
+
server.addTool({
|
|
1268
|
+
name: "create_post",
|
|
1269
|
+
description: "Create a new post in a subreddit (requires REDDIT_USERNAME and REDDIT_PASSWORD)",
|
|
1270
|
+
parameters: import_zod.z.object({
|
|
1271
|
+
subreddit: import_zod.z.string().describe("The subreddit name (without r/ prefix)"),
|
|
1272
|
+
title: import_zod.z.string().describe("The post title"),
|
|
1273
|
+
content: import_zod.z.string().describe("The post content (text for self posts, URL for link posts)"),
|
|
1274
|
+
is_self: import_zod.z.boolean().default(true).describe("Whether this is a self post (text) or link post")
|
|
1275
|
+
}),
|
|
1276
|
+
execute: async (args) => {
|
|
1277
|
+
const client = getRedditClient();
|
|
1278
|
+
if (!client) {
|
|
1279
|
+
throw new Error("Reddit client not initialized");
|
|
1280
|
+
}
|
|
1281
|
+
if (!process.env.REDDIT_USERNAME || !process.env.REDDIT_PASSWORD) {
|
|
1282
|
+
throw new Error(
|
|
1283
|
+
"User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables."
|
|
1284
|
+
);
|
|
1285
|
+
}
|
|
1286
|
+
const post = await client.createPost(args.subreddit, args.title, args.content, args.is_self);
|
|
1287
|
+
const formattedPost = formatPostInfo(post);
|
|
1288
|
+
return `# Post Created Successfully
|
|
1289
|
+
|
|
1290
|
+
## Post Details
|
|
1291
|
+
- Title: ${formattedPost.title}
|
|
1292
|
+
- Subreddit: r/${formattedPost.subreddit}
|
|
1293
|
+
- Type: ${formattedPost.type}
|
|
1294
|
+
- Link: ${formattedPost.links.fullPost}
|
|
1295
|
+
|
|
1296
|
+
Your post has been successfully submitted to r/${formattedPost.subreddit}.`;
|
|
1297
|
+
}
|
|
1298
|
+
});
|
|
1299
|
+
server.addTool({
|
|
1300
|
+
name: "reply_to_post",
|
|
1301
|
+
description: "Post a reply to an existing Reddit post or comment (requires REDDIT_USERNAME and REDDIT_PASSWORD)",
|
|
1302
|
+
parameters: import_zod.z.object({
|
|
1303
|
+
post_id: import_zod.z.string().describe("The Reddit post ID (thing_id, e.g., t3_xxxxx for posts, t1_xxxxx for comments)"),
|
|
1304
|
+
content: import_zod.z.string().describe("The reply content")
|
|
1305
|
+
}),
|
|
1306
|
+
execute: async (args) => {
|
|
1307
|
+
const client = getRedditClient();
|
|
1308
|
+
if (!client) {
|
|
1309
|
+
throw new Error("Reddit client not initialized");
|
|
1310
|
+
}
|
|
1311
|
+
if (!process.env.REDDIT_USERNAME || !process.env.REDDIT_PASSWORD) {
|
|
1312
|
+
throw new Error(
|
|
1313
|
+
"User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables."
|
|
1314
|
+
);
|
|
1315
|
+
}
|
|
1316
|
+
const comment = await client.replyToPost(args.post_id, args.content);
|
|
1317
|
+
return `# Reply Posted Successfully
|
|
1318
|
+
|
|
1319
|
+
## Comment Details
|
|
1320
|
+
- Posted to: ${args.post_id}
|
|
1321
|
+
- Author: u/${process.env.REDDIT_USERNAME}
|
|
1322
|
+
- Comment ID: ${comment.id}
|
|
1323
|
+
|
|
1324
|
+
Your reply has been successfully posted.`;
|
|
1325
|
+
}
|
|
1326
|
+
});
|
|
1327
|
+
server.addTool({
|
|
1328
|
+
name: "delete_post",
|
|
1329
|
+
description: "Delete your own Reddit post (requires REDDIT_USERNAME and REDDIT_PASSWORD). WARNING: This action is permanent and cannot be undone!",
|
|
1330
|
+
parameters: import_zod.z.object({
|
|
1331
|
+
thing_id: import_zod.z.string().describe(
|
|
1332
|
+
"The full Reddit thing ID (e.g., 't3_abc123' for posts) or just the post ID (e.g., 'abc123'). The 't3_' prefix will be added automatically if missing."
|
|
1333
|
+
)
|
|
1334
|
+
}),
|
|
1335
|
+
execute: async (args) => {
|
|
1336
|
+
const client = getRedditClient();
|
|
1337
|
+
if (!client) {
|
|
1338
|
+
throw new Error("Reddit client not initialized");
|
|
1339
|
+
}
|
|
1340
|
+
if (!process.env.REDDIT_USERNAME || !process.env.REDDIT_PASSWORD) {
|
|
1341
|
+
throw new Error(
|
|
1342
|
+
"User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables."
|
|
1343
|
+
);
|
|
1344
|
+
}
|
|
1345
|
+
await client.deletePost(args.thing_id);
|
|
1346
|
+
return `# Post Deleted Successfully
|
|
1347
|
+
|
|
1348
|
+
The post ${args.thing_id} has been permanently deleted from Reddit.
|
|
1349
|
+
|
|
1350
|
+
**Note**: This action cannot be undone. The post content has been removed and cannot be recovered.`;
|
|
1351
|
+
}
|
|
1352
|
+
});
|
|
1353
|
+
server.addTool({
|
|
1354
|
+
name: "delete_comment",
|
|
1355
|
+
description: "Delete your own Reddit comment (requires REDDIT_USERNAME and REDDIT_PASSWORD). WARNING: This action is permanent and cannot be undone!",
|
|
1356
|
+
parameters: import_zod.z.object({
|
|
1357
|
+
thing_id: import_zod.z.string().describe(
|
|
1358
|
+
"The full Reddit thing ID (e.g., 't1_abc123' for comments) or just the comment ID (e.g., 'abc123'). The 't1_' prefix will be added automatically if missing."
|
|
1359
|
+
)
|
|
1360
|
+
}),
|
|
1361
|
+
execute: async (args) => {
|
|
1362
|
+
const client = getRedditClient();
|
|
1363
|
+
if (!client) {
|
|
1364
|
+
throw new Error("Reddit client not initialized");
|
|
1365
|
+
}
|
|
1366
|
+
if (!process.env.REDDIT_USERNAME || !process.env.REDDIT_PASSWORD) {
|
|
1367
|
+
throw new Error(
|
|
1368
|
+
"User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables."
|
|
1369
|
+
);
|
|
1370
|
+
}
|
|
1371
|
+
await client.deleteComment(args.thing_id);
|
|
1372
|
+
return `# Comment Deleted Successfully
|
|
1373
|
+
|
|
1374
|
+
The comment ${args.thing_id} has been permanently deleted from Reddit.
|
|
1375
|
+
|
|
1376
|
+
**Note**: This action cannot be undone. The comment content has been removed and cannot be recovered.`;
|
|
1377
|
+
}
|
|
1378
|
+
});
|
|
1379
|
+
server.addTool({
|
|
1380
|
+
name: "edit_post",
|
|
1381
|
+
description: "Edit your own Reddit post (self-text posts only, requires REDDIT_USERNAME and REDDIT_PASSWORD). You can only edit the text content of self posts, not titles or link posts.",
|
|
1382
|
+
parameters: import_zod.z.object({
|
|
1383
|
+
thing_id: import_zod.z.string().describe(
|
|
1384
|
+
"The full Reddit thing ID (e.g., 't3_abc123' for posts) or just the post ID (e.g., 'abc123'). The 't3_' prefix will be added automatically if missing."
|
|
1385
|
+
),
|
|
1386
|
+
new_text: import_zod.z.string().describe("The new text content for the post. Supports Reddit markdown formatting.")
|
|
1387
|
+
}),
|
|
1388
|
+
execute: async (args) => {
|
|
1389
|
+
const client = getRedditClient();
|
|
1390
|
+
if (!client) {
|
|
1391
|
+
throw new Error("Reddit client not initialized");
|
|
1392
|
+
}
|
|
1393
|
+
if (!process.env.REDDIT_USERNAME || !process.env.REDDIT_PASSWORD) {
|
|
1394
|
+
throw new Error(
|
|
1395
|
+
"User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables."
|
|
1396
|
+
);
|
|
1397
|
+
}
|
|
1398
|
+
await client.editPost(args.thing_id, args.new_text);
|
|
1399
|
+
return `# Post Edited Successfully
|
|
1400
|
+
|
|
1401
|
+
The post ${args.thing_id} has been updated with your new content.
|
|
1402
|
+
|
|
1403
|
+
**Note**:
|
|
1404
|
+
- Only self (text) posts can be edited
|
|
1405
|
+
- Post titles cannot be edited
|
|
1406
|
+
- Link posts cannot be edited
|
|
1407
|
+
- An "edited" marker will appear on your post`;
|
|
1408
|
+
}
|
|
1409
|
+
});
|
|
1410
|
+
server.addTool({
|
|
1411
|
+
name: "edit_comment",
|
|
1412
|
+
description: "Edit your own Reddit comment (requires REDDIT_USERNAME and REDDIT_PASSWORD). Update the text content of a comment you previously posted.",
|
|
1413
|
+
parameters: import_zod.z.object({
|
|
1414
|
+
thing_id: import_zod.z.string().describe(
|
|
1415
|
+
"The full Reddit thing ID (e.g., 't1_abc123' for comments) or just the comment ID (e.g., 'abc123'). The 't1_' prefix will be added automatically if missing."
|
|
1416
|
+
),
|
|
1417
|
+
new_text: import_zod.z.string().describe("The new text content for the comment. Supports Reddit markdown formatting.")
|
|
1418
|
+
}),
|
|
1419
|
+
execute: async (args) => {
|
|
1420
|
+
const client = getRedditClient();
|
|
1421
|
+
if (!client) {
|
|
1422
|
+
throw new Error("Reddit client not initialized");
|
|
1423
|
+
}
|
|
1424
|
+
if (!process.env.REDDIT_USERNAME || !process.env.REDDIT_PASSWORD) {
|
|
1425
|
+
throw new Error(
|
|
1426
|
+
"User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables."
|
|
1427
|
+
);
|
|
1428
|
+
}
|
|
1429
|
+
await client.editComment(args.thing_id, args.new_text);
|
|
1430
|
+
return `# Comment Edited Successfully
|
|
1431
|
+
|
|
1432
|
+
The comment ${args.thing_id} has been updated with your new content.
|
|
1433
|
+
|
|
1434
|
+
**Note**: An "edited" marker will appear on your comment to show it has been modified.`;
|
|
1435
|
+
}
|
|
1436
|
+
});
|
|
1130
1437
|
server.addTool({
|
|
1131
1438
|
name: "get_post_comments",
|
|
1132
1439
|
description: "Get comments from a specific Reddit post",
|
|
@@ -1180,9 +1487,27 @@ ${comment.body}
|
|
|
1180
1487
|
async function main() {
|
|
1181
1488
|
try {
|
|
1182
1489
|
await setupRedditClient();
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1490
|
+
const useStdio = process.env.TRANSPORT_TYPE === "stdio";
|
|
1491
|
+
const port = parseInt(process.env.PORT || "3000");
|
|
1492
|
+
const host = process.env.HOST || "0.0.0.0";
|
|
1493
|
+
if (useStdio) {
|
|
1494
|
+
console.error("[Setup] Starting in stdio mode (CLI/npx)");
|
|
1495
|
+
await server.start({
|
|
1496
|
+
transportType: "stdio"
|
|
1497
|
+
});
|
|
1498
|
+
} else {
|
|
1499
|
+
console.error(`[Setup] Starting HTTP server on ${host}:${port}`);
|
|
1500
|
+
await server.start({
|
|
1501
|
+
transportType: "httpStream",
|
|
1502
|
+
httpStream: {
|
|
1503
|
+
port,
|
|
1504
|
+
host,
|
|
1505
|
+
endpoint: "/mcp"
|
|
1506
|
+
}
|
|
1507
|
+
});
|
|
1508
|
+
console.error(`[Setup] HTTP server ready at http://${host}:${port}/mcp`);
|
|
1509
|
+
console.error(`[Setup] SSE endpoint available at http://${host}:${port}/sse`);
|
|
1510
|
+
}
|
|
1186
1511
|
} catch (error) {
|
|
1187
1512
|
console.error("[Error] Failed to start server:", error);
|
|
1188
1513
|
process.exit(1);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "reddit-mcp-server",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "A Model Context Protocol (MCP) that provides tools for fetching and creating Reddit content. Fork of the alexandros-lekkas/reddit-mcp-server.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"api",
|
|
22
22
|
"model-context-protocol"
|
|
23
23
|
],
|
|
24
|
+
"mcpName": "io.github.jordanburke.reddit-mcp-server",
|
|
24
25
|
"author": "Jordan Burke <jordan.burke@gmail.com>",
|
|
25
26
|
"license": "MIT",
|
|
26
27
|
"repository": {
|
|
@@ -33,25 +34,26 @@
|
|
|
33
34
|
"homepage": "https://github.com/jordanburke/reddit-mcp-server#readme",
|
|
34
35
|
"dependencies": {
|
|
35
36
|
"dotenv": "^16.6.1",
|
|
36
|
-
"fastmcp": "^3.
|
|
37
|
-
"zod": "^4.1.
|
|
37
|
+
"fastmcp": "^3.24.0",
|
|
38
|
+
"zod": "^4.1.13"
|
|
38
39
|
},
|
|
39
40
|
"devDependencies": {
|
|
40
|
-
"@eslint/js": "^9.
|
|
41
|
-
"@types/node": "^22.
|
|
42
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
43
|
-
"@typescript-eslint/parser": "^8.
|
|
41
|
+
"@eslint/js": "^9.39.1",
|
|
42
|
+
"@types/node": "^22.19.1",
|
|
43
|
+
"@typescript-eslint/eslint-plugin": "^8.48.1",
|
|
44
|
+
"@typescript-eslint/parser": "^8.48.1",
|
|
44
45
|
"@vitest/coverage-v8": "^3.2.4",
|
|
45
|
-
"
|
|
46
|
-
"
|
|
46
|
+
"ajv-cli": "^5.0.0",
|
|
47
|
+
"cross-env": "^10.1.0",
|
|
48
|
+
"eslint": "^9.39.1",
|
|
47
49
|
"eslint-config-prettier": "^10.1.8",
|
|
48
50
|
"eslint-plugin-prettier": "^5.5.4",
|
|
49
|
-
"msw": "^2.
|
|
50
|
-
"prettier": "^3.
|
|
51
|
-
"rimraf": "^6.
|
|
52
|
-
"tsup": "^8.5.
|
|
53
|
-
"tsx": "^4.
|
|
54
|
-
"typescript": "^5.9.
|
|
51
|
+
"msw": "^2.12.4",
|
|
52
|
+
"prettier": "^3.7.4",
|
|
53
|
+
"rimraf": "^6.1.2",
|
|
54
|
+
"tsup": "^8.5.1",
|
|
55
|
+
"tsx": "^4.21.0",
|
|
56
|
+
"typescript": "^5.9.3",
|
|
55
57
|
"vitest": "^3.2.4"
|
|
56
58
|
},
|
|
57
59
|
"scripts": {
|