reddit-mcp-server 1.0.3 → 1.0.4
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.d.ts +1 -0
- package/dist/bin.js +1098 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +1107 -0
- package/package.json +1 -1
- package/src/bin.ts +3 -3
- package/src/client/reddit-client.ts +8 -8
- package/src/index.ts +7 -6
- package/src/tools/post-tools.ts +4 -4
- package/src/tools/subreddit-tools.ts +2 -2
- package/src/tools/user-tools.ts +1 -1
package/dist/bin.js
ADDED
|
@@ -0,0 +1,1098 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
18
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
19
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
20
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
21
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
22
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
23
|
+
mod
|
|
24
|
+
));
|
|
25
|
+
|
|
26
|
+
// src/index.ts
|
|
27
|
+
var import_server = require("@modelcontextprotocol/sdk/server/index.js");
|
|
28
|
+
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
29
|
+
var import_types4 = require("@modelcontextprotocol/sdk/types.js");
|
|
30
|
+
|
|
31
|
+
// src/client/reddit-client.ts
|
|
32
|
+
var import_axios = __toESM(require("axios"));
|
|
33
|
+
var RedditClient = class {
|
|
34
|
+
clientId;
|
|
35
|
+
clientSecret;
|
|
36
|
+
userAgent;
|
|
37
|
+
username;
|
|
38
|
+
password;
|
|
39
|
+
accessToken;
|
|
40
|
+
tokenExpiry = 0;
|
|
41
|
+
api;
|
|
42
|
+
authenticated = false;
|
|
43
|
+
constructor(config) {
|
|
44
|
+
this.clientId = config.clientId;
|
|
45
|
+
this.clientSecret = config.clientSecret;
|
|
46
|
+
this.userAgent = config.userAgent;
|
|
47
|
+
this.username = config.username;
|
|
48
|
+
this.password = config.password;
|
|
49
|
+
this.api = import_axios.default.create({
|
|
50
|
+
baseURL: "https://oauth.reddit.com",
|
|
51
|
+
headers: {
|
|
52
|
+
"User-Agent": this.userAgent
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
this.api.interceptors.response.use(
|
|
56
|
+
(response) => response,
|
|
57
|
+
async (error) => {
|
|
58
|
+
if (error.response?.status === 401 && this.authenticated) {
|
|
59
|
+
await this.authenticate();
|
|
60
|
+
const originalRequest = error.config;
|
|
61
|
+
originalRequest.headers["Authorization"] = `Bearer ${this.accessToken}`;
|
|
62
|
+
return this.api(originalRequest);
|
|
63
|
+
}
|
|
64
|
+
return Promise.reject(error);
|
|
65
|
+
}
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
async authenticate() {
|
|
69
|
+
try {
|
|
70
|
+
const now = Date.now();
|
|
71
|
+
if (this.accessToken && now < this.tokenExpiry) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const authUrl = "https://www.reddit.com/api/v1/access_token";
|
|
75
|
+
const authData = new URLSearchParams();
|
|
76
|
+
if (this.username && this.password) {
|
|
77
|
+
authData.append("grant_type", "password");
|
|
78
|
+
authData.append("username", this.username);
|
|
79
|
+
authData.append("password", this.password);
|
|
80
|
+
} else {
|
|
81
|
+
authData.append("grant_type", "client_credentials");
|
|
82
|
+
}
|
|
83
|
+
const response = await import_axios.default.post(authUrl, authData, {
|
|
84
|
+
auth: {
|
|
85
|
+
username: this.clientId,
|
|
86
|
+
password: this.clientSecret
|
|
87
|
+
},
|
|
88
|
+
headers: {
|
|
89
|
+
"User-Agent": this.userAgent,
|
|
90
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
this.accessToken = response.data.access_token;
|
|
94
|
+
this.tokenExpiry = now + response.data.expires_in * 1e3;
|
|
95
|
+
this.authenticated = true;
|
|
96
|
+
this.api.defaults.headers.common["Authorization"] = `Bearer ${this.accessToken}`;
|
|
97
|
+
} catch {
|
|
98
|
+
throw new Error("Failed to authenticate with Reddit API");
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
async checkAuthentication() {
|
|
102
|
+
if (!this.authenticated) {
|
|
103
|
+
try {
|
|
104
|
+
await this.authenticate();
|
|
105
|
+
return true;
|
|
106
|
+
} catch {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
async getUser(username) {
|
|
113
|
+
await this.authenticate();
|
|
114
|
+
try {
|
|
115
|
+
const response = await this.api.get(`/user/${username}/about.json`);
|
|
116
|
+
const data = response.data.data;
|
|
117
|
+
return {
|
|
118
|
+
name: data.name,
|
|
119
|
+
id: data.id,
|
|
120
|
+
commentKarma: data.comment_karma,
|
|
121
|
+
linkKarma: data.link_karma,
|
|
122
|
+
totalKarma: data.total_karma || data.comment_karma + data.link_karma,
|
|
123
|
+
isMod: data.is_mod,
|
|
124
|
+
isGold: data.is_gold,
|
|
125
|
+
isEmployee: data.is_employee,
|
|
126
|
+
createdUtc: data.created_utc,
|
|
127
|
+
profileUrl: `https://reddit.com/user/${data.name}`
|
|
128
|
+
};
|
|
129
|
+
} catch {
|
|
130
|
+
throw new Error(`Failed to get user info for ${username}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
async getSubredditInfo(subredditName) {
|
|
134
|
+
await this.authenticate();
|
|
135
|
+
try {
|
|
136
|
+
const response = await this.api.get(`/r/${subredditName}/about.json`);
|
|
137
|
+
const data = response.data.data;
|
|
138
|
+
return {
|
|
139
|
+
displayName: data.display_name,
|
|
140
|
+
title: data.title,
|
|
141
|
+
description: data.description || "",
|
|
142
|
+
publicDescription: data.public_description || "",
|
|
143
|
+
subscribers: data.subscribers,
|
|
144
|
+
activeUserCount: data.active_user_count,
|
|
145
|
+
createdUtc: data.created_utc,
|
|
146
|
+
over18: data.over18,
|
|
147
|
+
subredditType: data.subreddit_type,
|
|
148
|
+
url: data.url
|
|
149
|
+
};
|
|
150
|
+
} catch {
|
|
151
|
+
throw new Error(`Failed to get subreddit info for ${subredditName}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
async getTopPosts(subreddit, timeFilter = "week", limit = 10) {
|
|
155
|
+
await this.authenticate();
|
|
156
|
+
try {
|
|
157
|
+
const endpoint = subreddit ? `/r/${subreddit}/top.json` : "/top.json";
|
|
158
|
+
const response = await this.api.get(endpoint, {
|
|
159
|
+
params: {
|
|
160
|
+
t: timeFilter,
|
|
161
|
+
limit
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
return response.data.data.children.map((child) => {
|
|
165
|
+
const post = child.data;
|
|
166
|
+
return {
|
|
167
|
+
id: post.id,
|
|
168
|
+
title: post.title,
|
|
169
|
+
author: post.author,
|
|
170
|
+
subreddit: post.subreddit,
|
|
171
|
+
selftext: post.selftext,
|
|
172
|
+
url: post.url,
|
|
173
|
+
score: post.score,
|
|
174
|
+
upvoteRatio: post.upvote_ratio,
|
|
175
|
+
numComments: post.num_comments,
|
|
176
|
+
createdUtc: post.created_utc,
|
|
177
|
+
over18: post.over_18,
|
|
178
|
+
spoiler: post.spoiler,
|
|
179
|
+
edited: !!post.edited,
|
|
180
|
+
isSelf: post.is_self,
|
|
181
|
+
linkFlairText: post.link_flair_text,
|
|
182
|
+
permalink: post.permalink
|
|
183
|
+
};
|
|
184
|
+
});
|
|
185
|
+
} catch {
|
|
186
|
+
throw new Error(`Failed to get top posts for ${subreddit || "home"}`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
async getPost(postId, subreddit) {
|
|
190
|
+
await this.authenticate();
|
|
191
|
+
try {
|
|
192
|
+
const endpoint = subreddit ? `/r/${subreddit}/comments/${postId}.json` : `/api/info.json?id=t3_${postId}`;
|
|
193
|
+
const response = await this.api.get(endpoint);
|
|
194
|
+
let post;
|
|
195
|
+
if (subreddit) {
|
|
196
|
+
post = response.data[0].data.children[0].data;
|
|
197
|
+
} else {
|
|
198
|
+
if (!response.data.data.children.length) {
|
|
199
|
+
throw new Error(`Post with ID ${postId} not found`);
|
|
200
|
+
}
|
|
201
|
+
post = response.data.data.children[0].data;
|
|
202
|
+
}
|
|
203
|
+
return {
|
|
204
|
+
id: post.id,
|
|
205
|
+
title: post.title,
|
|
206
|
+
author: post.author,
|
|
207
|
+
subreddit: post.subreddit,
|
|
208
|
+
selftext: post.selftext,
|
|
209
|
+
url: post.url,
|
|
210
|
+
score: post.score,
|
|
211
|
+
upvoteRatio: post.upvote_ratio,
|
|
212
|
+
numComments: post.num_comments,
|
|
213
|
+
createdUtc: post.created_utc,
|
|
214
|
+
over18: post.over_18,
|
|
215
|
+
spoiler: post.spoiler,
|
|
216
|
+
edited: !!post.edited,
|
|
217
|
+
isSelf: post.is_self,
|
|
218
|
+
linkFlairText: post.link_flair_text,
|
|
219
|
+
permalink: post.permalink
|
|
220
|
+
};
|
|
221
|
+
} catch {
|
|
222
|
+
throw new Error(`Failed to get post with ID ${postId}`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
async getTrendingSubreddits(limit = 5) {
|
|
226
|
+
await this.authenticate();
|
|
227
|
+
try {
|
|
228
|
+
const response = await this.api.get("/subreddits/popular.json", {
|
|
229
|
+
params: { limit }
|
|
230
|
+
});
|
|
231
|
+
return response.data.data.children.map((child) => child.data.display_name);
|
|
232
|
+
} catch {
|
|
233
|
+
throw new Error("Failed to get trending subreddits");
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
async createPost(subreddit, title, content, isSelf = true) {
|
|
237
|
+
await this.authenticate();
|
|
238
|
+
if (!this.username || !this.password) {
|
|
239
|
+
throw new Error("User authentication required for posting");
|
|
240
|
+
}
|
|
241
|
+
try {
|
|
242
|
+
const kind = isSelf ? "self" : "link";
|
|
243
|
+
const params = new URLSearchParams();
|
|
244
|
+
params.append("sr", subreddit);
|
|
245
|
+
params.append("kind", kind);
|
|
246
|
+
params.append("title", title);
|
|
247
|
+
params.append(isSelf ? "text" : "url", content);
|
|
248
|
+
const response = await this.api.post("/api/submit", params, {
|
|
249
|
+
headers: {
|
|
250
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
if (response.data.success) {
|
|
254
|
+
const postId = response.data.data.id;
|
|
255
|
+
return await this.getPost(postId);
|
|
256
|
+
} else {
|
|
257
|
+
throw new Error("Failed to create post");
|
|
258
|
+
}
|
|
259
|
+
} catch {
|
|
260
|
+
throw new Error(`Failed to create post in ${subreddit}`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
async checkPostExists(postId) {
|
|
264
|
+
await this.authenticate();
|
|
265
|
+
try {
|
|
266
|
+
const response = await this.api.get(`/api/info.json?id=t3_${postId}`);
|
|
267
|
+
return response.data.data.children.length > 0;
|
|
268
|
+
} catch {
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
async replyToPost(postId, content) {
|
|
273
|
+
await this.authenticate();
|
|
274
|
+
if (!this.username || !this.password) {
|
|
275
|
+
throw new Error("User authentication required for posting replies");
|
|
276
|
+
}
|
|
277
|
+
try {
|
|
278
|
+
if (!await this.checkPostExists(postId)) {
|
|
279
|
+
throw new Error(`Post with ID ${postId} does not exist or is not accessible`);
|
|
280
|
+
}
|
|
281
|
+
const params = new URLSearchParams();
|
|
282
|
+
params.append("thing_id", `t3_${postId}`);
|
|
283
|
+
params.append("text", content);
|
|
284
|
+
const response = await this.api.post("/api/comment", params, {
|
|
285
|
+
headers: {
|
|
286
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
const commentData = response.data;
|
|
290
|
+
return {
|
|
291
|
+
id: commentData.id,
|
|
292
|
+
author: this.username,
|
|
293
|
+
body: content,
|
|
294
|
+
score: 1,
|
|
295
|
+
controversiality: 0,
|
|
296
|
+
subreddit: commentData.subreddit,
|
|
297
|
+
submissionTitle: commentData.link_title,
|
|
298
|
+
createdUtc: Date.now() / 1e3,
|
|
299
|
+
edited: false,
|
|
300
|
+
isSubmitter: false,
|
|
301
|
+
permalink: commentData.permalink
|
|
302
|
+
};
|
|
303
|
+
} catch {
|
|
304
|
+
throw new Error(`Failed to reply to post ${postId}`);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
var redditClient = null;
|
|
309
|
+
function initializeRedditClient(config) {
|
|
310
|
+
redditClient = new RedditClient(config);
|
|
311
|
+
return redditClient;
|
|
312
|
+
}
|
|
313
|
+
function getRedditClient() {
|
|
314
|
+
return redditClient;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// src/utils/formatters.ts
|
|
318
|
+
function formatTimestamp(timestamp) {
|
|
319
|
+
try {
|
|
320
|
+
const date = new Date(timestamp * 1e3);
|
|
321
|
+
return date.toISOString().replace("T", " ").replace(/\.\d+Z$/, " UTC");
|
|
322
|
+
} catch {
|
|
323
|
+
return String(timestamp);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
function analyzeUserActivity(karmaRatio, isMod, accountAgeDays) {
|
|
327
|
+
const insights = [];
|
|
328
|
+
if (karmaRatio > 5) {
|
|
329
|
+
insights.push("Primarily a commenter, highly engaged in discussions");
|
|
330
|
+
} else if (karmaRatio < 0.2) {
|
|
331
|
+
insights.push("Content creator, focuses on sharing posts");
|
|
332
|
+
} else {
|
|
333
|
+
insights.push("Balanced participation in both posting and commenting");
|
|
334
|
+
}
|
|
335
|
+
if (accountAgeDays < 30) {
|
|
336
|
+
insights.push("New user, still exploring Reddit");
|
|
337
|
+
} else if (accountAgeDays > 365 * 5) {
|
|
338
|
+
insights.push("Long-time Redditor with extensive platform experience");
|
|
339
|
+
}
|
|
340
|
+
if (isMod) {
|
|
341
|
+
insights.push("Community leader who helps maintain subreddit quality");
|
|
342
|
+
}
|
|
343
|
+
return insights.join("\n - ");
|
|
344
|
+
}
|
|
345
|
+
function analyzePostEngagement(score, ratio, numComments) {
|
|
346
|
+
const insights = [];
|
|
347
|
+
if (score > 1e3 && ratio > 0.95) {
|
|
348
|
+
insights.push("Highly successful post with strong community approval");
|
|
349
|
+
} else if (score > 100 && ratio > 0.8) {
|
|
350
|
+
insights.push("Well-received post with good engagement");
|
|
351
|
+
} else if (ratio < 0.5) {
|
|
352
|
+
insights.push("Controversial post that sparked debate");
|
|
353
|
+
}
|
|
354
|
+
if (numComments > 100) {
|
|
355
|
+
insights.push("Generated significant discussion");
|
|
356
|
+
} else if (numComments > score * 0.5) {
|
|
357
|
+
insights.push("Highly discussable content with active comment section");
|
|
358
|
+
} else if (numComments === 0) {
|
|
359
|
+
insights.push("Yet to receive community interaction");
|
|
360
|
+
}
|
|
361
|
+
return insights.join("\n - ");
|
|
362
|
+
}
|
|
363
|
+
function analyzeSubredditHealth(subscribers, activeUsers, ageDays) {
|
|
364
|
+
const insights = [];
|
|
365
|
+
if (subscribers > 1e6) {
|
|
366
|
+
insights.push("Major subreddit with massive following");
|
|
367
|
+
} else if (subscribers > 1e5) {
|
|
368
|
+
insights.push("Well-established community");
|
|
369
|
+
} else if (subscribers < 1e3) {
|
|
370
|
+
insights.push("Niche community, potential for growth");
|
|
371
|
+
}
|
|
372
|
+
if (activeUsers) {
|
|
373
|
+
const activityRatio = activeUsers / subscribers;
|
|
374
|
+
if (activityRatio > 0.1) {
|
|
375
|
+
insights.push("Highly active community with strong engagement");
|
|
376
|
+
} else if (activityRatio < 0.01) {
|
|
377
|
+
insights.push("Could benefit from more community engagement initiatives");
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
if (ageDays > 365 * 5) {
|
|
381
|
+
insights.push("Mature subreddit with established culture");
|
|
382
|
+
} else if (ageDays < 90) {
|
|
383
|
+
insights.push("New subreddit still forming its community");
|
|
384
|
+
}
|
|
385
|
+
return insights.join("\n - ");
|
|
386
|
+
}
|
|
387
|
+
function getUserRecommendations(karmaRatio, isMod, accountAgeDays) {
|
|
388
|
+
const recommendations = [];
|
|
389
|
+
if (karmaRatio > 5) {
|
|
390
|
+
recommendations.push("Consider creating more posts to share your expertise");
|
|
391
|
+
} else if (karmaRatio < 0.2) {
|
|
392
|
+
recommendations.push("Engage more in discussions to build community connections");
|
|
393
|
+
}
|
|
394
|
+
if (accountAgeDays < 30) {
|
|
395
|
+
recommendations.push("Explore popular subreddits in your areas of interest");
|
|
396
|
+
recommendations.push("Read community guidelines before posting");
|
|
397
|
+
}
|
|
398
|
+
if (isMod) {
|
|
399
|
+
recommendations.push("Share moderation insights with other community leaders");
|
|
400
|
+
}
|
|
401
|
+
if (!recommendations.length) {
|
|
402
|
+
recommendations.push("Maintain your balanced engagement across Reddit");
|
|
403
|
+
}
|
|
404
|
+
return recommendations.join("\n - ");
|
|
405
|
+
}
|
|
406
|
+
function getBestEngagementTime(createdUtc) {
|
|
407
|
+
const postHour = new Date(createdUtc * 1e3).getHours();
|
|
408
|
+
if (14 <= postHour && postHour <= 18) {
|
|
409
|
+
return "Posted during peak engagement hours (2 PM - 6 PM), good timing!";
|
|
410
|
+
} else if (23 <= postHour || postHour <= 5) {
|
|
411
|
+
return "Consider posting during more active hours (morning to evening)";
|
|
412
|
+
} else {
|
|
413
|
+
return "Posted during moderate activity hours, timing could be optimized";
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
function getSubredditEngagementTips(subreddit) {
|
|
417
|
+
const tips = [];
|
|
418
|
+
if (subreddit.subscribers > 1e6) {
|
|
419
|
+
tips.push("Post during peak hours for maximum visibility");
|
|
420
|
+
tips.push("Ensure content is highly polished due to high competition");
|
|
421
|
+
} else if (subreddit.subscribers < 1e3) {
|
|
422
|
+
tips.push("Engage actively to help grow the community");
|
|
423
|
+
tips.push("Consider cross-posting to related larger subreddits");
|
|
424
|
+
}
|
|
425
|
+
if (subreddit.activeUserCount) {
|
|
426
|
+
const activityRatio = subreddit.activeUserCount / subreddit.subscribers;
|
|
427
|
+
if (activityRatio > 0.1) {
|
|
428
|
+
tips.push("Quick responses recommended due to high activity");
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
return tips.length ? tips.join("\n - ") : "Regular engagement recommended to maintain community presence";
|
|
432
|
+
}
|
|
433
|
+
function analyzeCommentImpact(score, isEdited, isOp) {
|
|
434
|
+
const insights = [];
|
|
435
|
+
if (score > 100) {
|
|
436
|
+
insights.push("Highly upvoted comment with significant community agreement");
|
|
437
|
+
} else if (score < 0) {
|
|
438
|
+
insights.push("Controversial or contested viewpoint");
|
|
439
|
+
}
|
|
440
|
+
if (isEdited) {
|
|
441
|
+
insights.push("Refined for clarity or accuracy");
|
|
442
|
+
}
|
|
443
|
+
if (isOp) {
|
|
444
|
+
insights.push("Author's perspective adds context to original post");
|
|
445
|
+
}
|
|
446
|
+
return insights.length ? insights.join("\n - ") : "Standard engagement with discussion";
|
|
447
|
+
}
|
|
448
|
+
function formatUserInfo(user) {
|
|
449
|
+
const status = [];
|
|
450
|
+
if (user.isMod) status.push("Moderator");
|
|
451
|
+
if (user.isGold) status.push("Reddit Gold Member");
|
|
452
|
+
if (user.isEmployee) status.push("Reddit Employee");
|
|
453
|
+
const accountAgeDays = (Date.now() / 1e3 - user.createdUtc) / (24 * 3600);
|
|
454
|
+
const karmaRatio = user.commentKarma / (user.linkKarma || 1);
|
|
455
|
+
return {
|
|
456
|
+
username: user.name,
|
|
457
|
+
karma: {
|
|
458
|
+
commentKarma: user.commentKarma,
|
|
459
|
+
postKarma: user.linkKarma,
|
|
460
|
+
totalKarma: user.totalKarma
|
|
461
|
+
},
|
|
462
|
+
accountStatus: status.length ? status : ["Regular User"],
|
|
463
|
+
accountCreated: formatTimestamp(user.createdUtc),
|
|
464
|
+
profileUrl: user.profileUrl,
|
|
465
|
+
activityAnalysis: analyzeUserActivity(karmaRatio, user.isMod, accountAgeDays),
|
|
466
|
+
recommendations: getUserRecommendations(karmaRatio, user.isMod, accountAgeDays)
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
function formatPostInfo(post) {
|
|
470
|
+
const contentType = post.isSelf ? "Text Post" : "Link Post";
|
|
471
|
+
const content = post.isSelf ? post.selftext || "" : post.url || "";
|
|
472
|
+
const flags = [];
|
|
473
|
+
if (post.over18) flags.push("NSFW");
|
|
474
|
+
if (post.spoiler) flags.push("Spoiler");
|
|
475
|
+
if (post.edited) flags.push("Edited");
|
|
476
|
+
return {
|
|
477
|
+
title: post.title,
|
|
478
|
+
type: contentType,
|
|
479
|
+
content: content.length > 300 ? content.substring(0, 297) + "..." : content,
|
|
480
|
+
author: post.author,
|
|
481
|
+
subreddit: post.subreddit,
|
|
482
|
+
stats: {
|
|
483
|
+
score: post.score,
|
|
484
|
+
upvoteRatio: post.upvoteRatio,
|
|
485
|
+
comments: post.numComments
|
|
486
|
+
},
|
|
487
|
+
metadata: {
|
|
488
|
+
posted: formatTimestamp(post.createdUtc),
|
|
489
|
+
flags,
|
|
490
|
+
flair: post.linkFlairText || "None"
|
|
491
|
+
},
|
|
492
|
+
links: {
|
|
493
|
+
fullPost: `https://reddit.com${post.permalink}`,
|
|
494
|
+
shortLink: `https://redd.it/${post.id}`
|
|
495
|
+
},
|
|
496
|
+
engagementAnalysis: analyzePostEngagement(post.score, post.upvoteRatio, post.numComments),
|
|
497
|
+
bestTimeToEngage: getBestEngagementTime(post.createdUtc)
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
function formatSubredditInfo(subreddit) {
|
|
501
|
+
const flags = [];
|
|
502
|
+
if (subreddit.over18) flags.push("NSFW");
|
|
503
|
+
if (subreddit.subredditType) flags.push(`Type: ${subreddit.subredditType}`);
|
|
504
|
+
const ageDays = (Date.now() / 1e3 - subreddit.createdUtc) / (24 * 3600);
|
|
505
|
+
return {
|
|
506
|
+
name: subreddit.displayName,
|
|
507
|
+
title: subreddit.title,
|
|
508
|
+
stats: {
|
|
509
|
+
subscribers: subreddit.subscribers,
|
|
510
|
+
activeUsers: subreddit.activeUserCount !== void 0 ? subreddit.activeUserCount : "Unknown"
|
|
511
|
+
},
|
|
512
|
+
description: {
|
|
513
|
+
short: subreddit.publicDescription,
|
|
514
|
+
full: subreddit.description.length > 300 ? subreddit.description.substring(0, 297) + "..." : subreddit.description
|
|
515
|
+
},
|
|
516
|
+
metadata: {
|
|
517
|
+
created: formatTimestamp(subreddit.createdUtc),
|
|
518
|
+
flags: flags.length ? flags : ["None"]
|
|
519
|
+
},
|
|
520
|
+
links: {
|
|
521
|
+
subreddit: `https://reddit.com${subreddit.url}`,
|
|
522
|
+
wiki: `https://reddit.com/r/${subreddit.displayName}/wiki`
|
|
523
|
+
},
|
|
524
|
+
communityAnalysis: analyzeSubredditHealth(subreddit.subscribers, subreddit.activeUserCount, ageDays),
|
|
525
|
+
engagementTips: getSubredditEngagementTips(subreddit)
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
function formatCommentInfo(comment) {
|
|
529
|
+
const flags = [];
|
|
530
|
+
if (comment.edited) flags.push("Edited");
|
|
531
|
+
if (comment.isSubmitter) flags.push("OP");
|
|
532
|
+
return {
|
|
533
|
+
author: comment.author,
|
|
534
|
+
content: comment.body.length > 300 ? comment.body.substring(0, 297) + "..." : comment.body,
|
|
535
|
+
stats: {
|
|
536
|
+
score: comment.score,
|
|
537
|
+
controversiality: comment.controversiality
|
|
538
|
+
},
|
|
539
|
+
context: {
|
|
540
|
+
subreddit: comment.subreddit,
|
|
541
|
+
thread: comment.submissionTitle
|
|
542
|
+
},
|
|
543
|
+
metadata: {
|
|
544
|
+
posted: formatTimestamp(comment.createdUtc),
|
|
545
|
+
flags: flags.length ? flags : ["None"]
|
|
546
|
+
},
|
|
547
|
+
link: `https://reddit.com${comment.permalink}`,
|
|
548
|
+
commentAnalysis: analyzeCommentImpact(comment.score, comment.edited, comment.isSubmitter)
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// src/tools/user-tools.ts
|
|
553
|
+
var import_types = require("@modelcontextprotocol/sdk/types.js");
|
|
554
|
+
async function getUserInfo(params) {
|
|
555
|
+
const { username } = params;
|
|
556
|
+
const client = getRedditClient();
|
|
557
|
+
if (!client) {
|
|
558
|
+
throw new import_types.McpError(import_types.ErrorCode.InternalError, "Reddit client not initialized");
|
|
559
|
+
}
|
|
560
|
+
try {
|
|
561
|
+
const user = await client.getUser(username);
|
|
562
|
+
const formattedUser = formatUserInfo(user);
|
|
563
|
+
return {
|
|
564
|
+
content: [
|
|
565
|
+
{
|
|
566
|
+
type: "text",
|
|
567
|
+
text: `
|
|
568
|
+
# User Information: u/${formattedUser.username}
|
|
569
|
+
|
|
570
|
+
## Profile Overview
|
|
571
|
+
- Username: u/${formattedUser.username}
|
|
572
|
+
- Karma:
|
|
573
|
+
- Comment Karma: ${formattedUser.karma.commentKarma.toLocaleString()}
|
|
574
|
+
- Post Karma: ${formattedUser.karma.postKarma.toLocaleString()}
|
|
575
|
+
- Total Karma: ${formattedUser.karma.totalKarma.toLocaleString()}
|
|
576
|
+
- Account Status: ${formattedUser.accountStatus.join(", ")}
|
|
577
|
+
- Account Created: ${formattedUser.accountCreated}
|
|
578
|
+
- Profile URL: ${formattedUser.profileUrl}
|
|
579
|
+
|
|
580
|
+
## Activity Analysis
|
|
581
|
+
- ${formattedUser.activityAnalysis.replace(/\n - /g, "\n- ")}
|
|
582
|
+
|
|
583
|
+
## Recommendations
|
|
584
|
+
- ${formattedUser.recommendations.replace(/\n - /g, "\n- ")}
|
|
585
|
+
`
|
|
586
|
+
}
|
|
587
|
+
]
|
|
588
|
+
};
|
|
589
|
+
} catch (error) {
|
|
590
|
+
throw new import_types.McpError(import_types.ErrorCode.InternalError, `Failed to fetch user data: ${String(error)}`);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// src/tools/post-tools.ts
|
|
595
|
+
var import_types2 = require("@modelcontextprotocol/sdk/types.js");
|
|
596
|
+
async function getRedditPost(params) {
|
|
597
|
+
const { subreddit, post_id } = params;
|
|
598
|
+
const client = getRedditClient();
|
|
599
|
+
if (!client) {
|
|
600
|
+
throw new import_types2.McpError(import_types2.ErrorCode.InternalError, "Reddit client not initialized");
|
|
601
|
+
}
|
|
602
|
+
try {
|
|
603
|
+
const post = await client.getPost(post_id, subreddit);
|
|
604
|
+
const formattedPost = formatPostInfo(post);
|
|
605
|
+
return {
|
|
606
|
+
content: [
|
|
607
|
+
{
|
|
608
|
+
type: "text",
|
|
609
|
+
text: `
|
|
610
|
+
# Post from r/${formattedPost.subreddit}
|
|
611
|
+
|
|
612
|
+
## Post Details
|
|
613
|
+
- Title: ${formattedPost.title}
|
|
614
|
+
- Type: ${formattedPost.type}
|
|
615
|
+
- Author: u/${formattedPost.author}
|
|
616
|
+
|
|
617
|
+
## Content
|
|
618
|
+
${formattedPost.content}
|
|
619
|
+
|
|
620
|
+
## Stats
|
|
621
|
+
- Score: ${formattedPost.stats.score.toLocaleString()}
|
|
622
|
+
- Upvote Ratio: ${(formattedPost.stats.upvoteRatio * 100).toFixed(1)}%
|
|
623
|
+
- Comments: ${formattedPost.stats.comments.toLocaleString()}
|
|
624
|
+
|
|
625
|
+
## Metadata
|
|
626
|
+
- Posted: ${formattedPost.metadata.posted}
|
|
627
|
+
- Flags: ${formattedPost.metadata.flags.length ? formattedPost.metadata.flags.join(", ") : "None"}
|
|
628
|
+
- Flair: ${formattedPost.metadata.flair}
|
|
629
|
+
|
|
630
|
+
## Links
|
|
631
|
+
- Full Post: ${formattedPost.links.fullPost}
|
|
632
|
+
- Short Link: ${formattedPost.links.shortLink}
|
|
633
|
+
|
|
634
|
+
## Engagement Analysis
|
|
635
|
+
- ${formattedPost.engagementAnalysis.replace(/\n - /g, "\n- ")}
|
|
636
|
+
|
|
637
|
+
## Best Time to Engage
|
|
638
|
+
${formattedPost.bestTimeToEngage}
|
|
639
|
+
`
|
|
640
|
+
}
|
|
641
|
+
]
|
|
642
|
+
};
|
|
643
|
+
} catch (error) {
|
|
644
|
+
throw new import_types2.McpError(import_types2.ErrorCode.InternalError, `Failed to fetch post data: ${String(error)}`);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
async function getTopPosts(params) {
|
|
648
|
+
const { subreddit, time_filter = "week", limit = 10 } = params;
|
|
649
|
+
const client = getRedditClient();
|
|
650
|
+
if (!client) {
|
|
651
|
+
throw new import_types2.McpError(import_types2.ErrorCode.InternalError, "Reddit client not initialized");
|
|
652
|
+
}
|
|
653
|
+
try {
|
|
654
|
+
const posts = await client.getTopPosts(subreddit, time_filter, limit);
|
|
655
|
+
const formattedPosts = posts.map(formatPostInfo);
|
|
656
|
+
const postSummaries = formattedPosts.map(
|
|
657
|
+
(post, index) => `
|
|
658
|
+
### ${index + 1}. ${post.title}
|
|
659
|
+
- Author: u/${post.author}
|
|
660
|
+
- Score: ${post.stats.score.toLocaleString()} (${(post.stats.upvoteRatio * 100).toFixed(1)}% upvoted)
|
|
661
|
+
- Comments: ${post.stats.comments.toLocaleString()}
|
|
662
|
+
- Posted: ${post.metadata.posted}
|
|
663
|
+
- Link: ${post.links.shortLink}
|
|
664
|
+
`
|
|
665
|
+
).join("\n");
|
|
666
|
+
return {
|
|
667
|
+
content: [
|
|
668
|
+
{
|
|
669
|
+
type: "text",
|
|
670
|
+
text: `
|
|
671
|
+
# Top Posts from r/${subreddit} (${time_filter})
|
|
672
|
+
|
|
673
|
+
${postSummaries}
|
|
674
|
+
`
|
|
675
|
+
}
|
|
676
|
+
]
|
|
677
|
+
};
|
|
678
|
+
} catch (error) {
|
|
679
|
+
throw new import_types2.McpError(import_types2.ErrorCode.InternalError, `Failed to fetch top posts: ${String(error)}`);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
async function createPost(params) {
|
|
683
|
+
const { subreddit, title, content, is_self = true } = params;
|
|
684
|
+
const client = getRedditClient();
|
|
685
|
+
if (!client) {
|
|
686
|
+
throw new import_types2.McpError(import_types2.ErrorCode.InternalError, "Reddit client not initialized");
|
|
687
|
+
}
|
|
688
|
+
try {
|
|
689
|
+
const post = await client.createPost(subreddit, title, content, is_self);
|
|
690
|
+
const formattedPost = formatPostInfo(post);
|
|
691
|
+
return {
|
|
692
|
+
content: [
|
|
693
|
+
{
|
|
694
|
+
type: "text",
|
|
695
|
+
text: `
|
|
696
|
+
# Post Created Successfully
|
|
697
|
+
|
|
698
|
+
## Post Details
|
|
699
|
+
- Title: ${formattedPost.title}
|
|
700
|
+
- Subreddit: r/${formattedPost.subreddit}
|
|
701
|
+
- Type: ${formattedPost.type}
|
|
702
|
+
- Link: ${formattedPost.links.fullPost}
|
|
703
|
+
|
|
704
|
+
Your post has been successfully submitted to r/${formattedPost.subreddit}.
|
|
705
|
+
`
|
|
706
|
+
}
|
|
707
|
+
]
|
|
708
|
+
};
|
|
709
|
+
} catch (error) {
|
|
710
|
+
throw new import_types2.McpError(import_types2.ErrorCode.InternalError, `Failed to create post: ${String(error)}`);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
async function replyToPost(params) {
|
|
714
|
+
const { post_id, content } = params;
|
|
715
|
+
const client = getRedditClient();
|
|
716
|
+
if (!client) {
|
|
717
|
+
throw new import_types2.McpError(import_types2.ErrorCode.InternalError, "Reddit client not initialized");
|
|
718
|
+
}
|
|
719
|
+
try {
|
|
720
|
+
const comment = await client.replyToPost(post_id, content);
|
|
721
|
+
const formattedComment = formatCommentInfo(comment);
|
|
722
|
+
return {
|
|
723
|
+
content: [
|
|
724
|
+
{
|
|
725
|
+
type: "text",
|
|
726
|
+
text: `
|
|
727
|
+
# Reply Posted Successfully
|
|
728
|
+
|
|
729
|
+
## Comment Details
|
|
730
|
+
- Author: u/${formattedComment.author}
|
|
731
|
+
- Subreddit: r/${formattedComment.context.subreddit}
|
|
732
|
+
- Thread: ${formattedComment.context.thread}
|
|
733
|
+
- Link: ${formattedComment.link}
|
|
734
|
+
|
|
735
|
+
Your reply has been successfully posted.
|
|
736
|
+
`
|
|
737
|
+
}
|
|
738
|
+
]
|
|
739
|
+
};
|
|
740
|
+
} catch (error) {
|
|
741
|
+
throw new import_types2.McpError(import_types2.ErrorCode.InternalError, `Failed to reply to post: ${String(error)}`);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// src/tools/subreddit-tools.ts
|
|
746
|
+
var import_types3 = require("@modelcontextprotocol/sdk/types.js");
|
|
747
|
+
async function getSubredditInfo(params) {
|
|
748
|
+
const { subreddit_name } = params;
|
|
749
|
+
const client = getRedditClient();
|
|
750
|
+
if (!client) {
|
|
751
|
+
throw new import_types3.McpError(import_types3.ErrorCode.InternalError, "Reddit client not initialized");
|
|
752
|
+
}
|
|
753
|
+
try {
|
|
754
|
+
const subreddit = await client.getSubredditInfo(subreddit_name);
|
|
755
|
+
const formattedSubreddit = formatSubredditInfo(subreddit);
|
|
756
|
+
return {
|
|
757
|
+
content: [
|
|
758
|
+
{
|
|
759
|
+
type: "text",
|
|
760
|
+
text: `
|
|
761
|
+
# Subreddit Information: r/${formattedSubreddit.name}
|
|
762
|
+
|
|
763
|
+
## Overview
|
|
764
|
+
- Name: r/${formattedSubreddit.name}
|
|
765
|
+
- Title: ${formattedSubreddit.title}
|
|
766
|
+
- Subscribers: ${formattedSubreddit.stats.subscribers.toLocaleString()}
|
|
767
|
+
- Active Users: ${typeof formattedSubreddit.stats.activeUsers === "number" ? formattedSubreddit.stats.activeUsers.toLocaleString() : formattedSubreddit.stats.activeUsers}
|
|
768
|
+
|
|
769
|
+
## Description
|
|
770
|
+
${formattedSubreddit.description.short}
|
|
771
|
+
|
|
772
|
+
## Detailed Description
|
|
773
|
+
${formattedSubreddit.description.full}
|
|
774
|
+
|
|
775
|
+
## Metadata
|
|
776
|
+
- Created: ${formattedSubreddit.metadata.created}
|
|
777
|
+
- Flags: ${formattedSubreddit.metadata.flags.join(", ")}
|
|
778
|
+
|
|
779
|
+
## Links
|
|
780
|
+
- Subreddit: ${formattedSubreddit.links.subreddit}
|
|
781
|
+
- Wiki: ${formattedSubreddit.links.wiki}
|
|
782
|
+
|
|
783
|
+
## Community Analysis
|
|
784
|
+
- ${formattedSubreddit.communityAnalysis.replace(/\n - /g, "\n- ")}
|
|
785
|
+
|
|
786
|
+
## Engagement Tips
|
|
787
|
+
- ${formattedSubreddit.engagementTips.replace(/\n - /g, "\n- ")}
|
|
788
|
+
`
|
|
789
|
+
}
|
|
790
|
+
]
|
|
791
|
+
};
|
|
792
|
+
} catch (error) {
|
|
793
|
+
throw new import_types3.McpError(import_types3.ErrorCode.InternalError, `Failed to fetch subreddit data: ${String(error)}`);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
async function getTrendingSubreddits() {
|
|
797
|
+
const client = getRedditClient();
|
|
798
|
+
if (!client) {
|
|
799
|
+
throw new import_types3.McpError(import_types3.ErrorCode.InternalError, "Reddit client not initialized");
|
|
800
|
+
}
|
|
801
|
+
try {
|
|
802
|
+
const trendingSubreddits = await client.getTrendingSubreddits();
|
|
803
|
+
return {
|
|
804
|
+
content: [
|
|
805
|
+
{
|
|
806
|
+
type: "text",
|
|
807
|
+
text: `
|
|
808
|
+
# Trending Subreddits
|
|
809
|
+
|
|
810
|
+
${trendingSubreddits.map((subreddit, index) => `${index + 1}. r/${subreddit}`).join("\n")}
|
|
811
|
+
`
|
|
812
|
+
}
|
|
813
|
+
]
|
|
814
|
+
};
|
|
815
|
+
} catch (error) {
|
|
816
|
+
throw new import_types3.McpError(import_types3.ErrorCode.InternalError, `Failed to fetch trending subreddits: ${String(error)}`);
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
// src/index.ts
|
|
821
|
+
var import_dotenv = __toESM(require("dotenv"));
|
|
822
|
+
import_dotenv.default.config();
|
|
823
|
+
var RedditServer = class {
|
|
824
|
+
server;
|
|
825
|
+
constructor() {
|
|
826
|
+
this.server = new import_server.Server(
|
|
827
|
+
{
|
|
828
|
+
name: "reddit-mcp-server",
|
|
829
|
+
version: "0.1.0"
|
|
830
|
+
},
|
|
831
|
+
{
|
|
832
|
+
capabilities: {
|
|
833
|
+
tools: {}
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
);
|
|
837
|
+
this.initializeRedditClient();
|
|
838
|
+
this.setupToolHandlers();
|
|
839
|
+
this.server.onerror = async (error) => {
|
|
840
|
+
await this.server.sendLoggingMessage({
|
|
841
|
+
level: "error",
|
|
842
|
+
logger: "reddit-server",
|
|
843
|
+
data: `Server error: ${error}`
|
|
844
|
+
});
|
|
845
|
+
};
|
|
846
|
+
process.on("SIGINT", async () => {
|
|
847
|
+
await this.server.close();
|
|
848
|
+
process.exit(0);
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
initializeRedditClient() {
|
|
852
|
+
const clientId = process.env.REDDIT_CLIENT_ID;
|
|
853
|
+
const clientSecret = process.env.REDDIT_CLIENT_SECRET;
|
|
854
|
+
const userAgent = process.env.REDDIT_USER_AGENT || "RedditMCPServer/0.1.0";
|
|
855
|
+
const username = process.env.REDDIT_USERNAME;
|
|
856
|
+
const password = process.env.REDDIT_PASSWORD;
|
|
857
|
+
if (!clientId || !clientSecret) {
|
|
858
|
+
console.error(
|
|
859
|
+
"[Error] Missing required Reddit API credentials. Please set REDDIT_CLIENT_ID and REDDIT_CLIENT_SECRET environment variables."
|
|
860
|
+
);
|
|
861
|
+
process.exit(1);
|
|
862
|
+
}
|
|
863
|
+
try {
|
|
864
|
+
initializeRedditClient({
|
|
865
|
+
clientId,
|
|
866
|
+
clientSecret,
|
|
867
|
+
userAgent,
|
|
868
|
+
username,
|
|
869
|
+
password
|
|
870
|
+
});
|
|
871
|
+
} catch (error) {
|
|
872
|
+
console.error("[Error] Failed to initialize Reddit client:", error);
|
|
873
|
+
process.exit(1);
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
setupToolHandlers() {
|
|
877
|
+
this.server.setRequestHandler(import_types4.ListToolsRequestSchema, async () => ({
|
|
878
|
+
tools: [
|
|
879
|
+
{
|
|
880
|
+
name: "test_reddit_mcp_server",
|
|
881
|
+
description: "Test the Reddit MCP Server",
|
|
882
|
+
inputSchema: {
|
|
883
|
+
type: "object",
|
|
884
|
+
properties: {
|
|
885
|
+
// No input parameters, this will just return a test message
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
},
|
|
889
|
+
{
|
|
890
|
+
name: "get_reddit_post",
|
|
891
|
+
description: "Get a Reddit post",
|
|
892
|
+
inputSchema: {
|
|
893
|
+
type: "object",
|
|
894
|
+
properties: {
|
|
895
|
+
subreddit: {
|
|
896
|
+
type: "string",
|
|
897
|
+
description: "The subreddit to fetch posts from"
|
|
898
|
+
},
|
|
899
|
+
post_id: {
|
|
900
|
+
type: "string",
|
|
901
|
+
description: "The ID of the post to fetch"
|
|
902
|
+
}
|
|
903
|
+
},
|
|
904
|
+
required: ["subreddit", "post_id"]
|
|
905
|
+
}
|
|
906
|
+
},
|
|
907
|
+
{
|
|
908
|
+
name: "get_top_posts",
|
|
909
|
+
description: "Get top posts from a subreddit",
|
|
910
|
+
inputSchema: {
|
|
911
|
+
type: "object",
|
|
912
|
+
properties: {
|
|
913
|
+
subreddit: {
|
|
914
|
+
type: "string",
|
|
915
|
+
description: "Name of the subreddit"
|
|
916
|
+
},
|
|
917
|
+
time_filter: {
|
|
918
|
+
type: "string",
|
|
919
|
+
description: "Time period to filter posts (e.g. 'day', 'week', 'month', 'year', 'all')",
|
|
920
|
+
enum: ["day", "week", "month", "year", "all"],
|
|
921
|
+
default: "week"
|
|
922
|
+
},
|
|
923
|
+
limit: {
|
|
924
|
+
type: "integer",
|
|
925
|
+
description: "Number of posts to fetch",
|
|
926
|
+
default: 10
|
|
927
|
+
}
|
|
928
|
+
},
|
|
929
|
+
required: ["subreddit"]
|
|
930
|
+
}
|
|
931
|
+
},
|
|
932
|
+
{
|
|
933
|
+
name: "get_user_info",
|
|
934
|
+
description: "Get information about a Reddit user",
|
|
935
|
+
inputSchema: {
|
|
936
|
+
type: "object",
|
|
937
|
+
properties: {
|
|
938
|
+
username: {
|
|
939
|
+
type: "string",
|
|
940
|
+
description: "The username of the Reddit user to get info for"
|
|
941
|
+
}
|
|
942
|
+
},
|
|
943
|
+
required: ["username"]
|
|
944
|
+
}
|
|
945
|
+
},
|
|
946
|
+
{
|
|
947
|
+
name: "get_subreddit_info",
|
|
948
|
+
description: "Get information about a subreddit",
|
|
949
|
+
inputSchema: {
|
|
950
|
+
type: "object",
|
|
951
|
+
properties: {
|
|
952
|
+
subreddit_name: {
|
|
953
|
+
type: "string",
|
|
954
|
+
description: "Name of the subreddit"
|
|
955
|
+
}
|
|
956
|
+
},
|
|
957
|
+
required: ["subreddit_name"]
|
|
958
|
+
}
|
|
959
|
+
},
|
|
960
|
+
{
|
|
961
|
+
name: "get_trending_subreddits",
|
|
962
|
+
description: "Get currently trending subreddits",
|
|
963
|
+
inputSchema: {
|
|
964
|
+
type: "object",
|
|
965
|
+
properties: {}
|
|
966
|
+
}
|
|
967
|
+
},
|
|
968
|
+
{
|
|
969
|
+
name: "create_post",
|
|
970
|
+
description: "Create a new post in a subreddit",
|
|
971
|
+
inputSchema: {
|
|
972
|
+
type: "object",
|
|
973
|
+
properties: {
|
|
974
|
+
subreddit: {
|
|
975
|
+
type: "string",
|
|
976
|
+
description: "Name of the subreddit to post in"
|
|
977
|
+
},
|
|
978
|
+
title: {
|
|
979
|
+
type: "string",
|
|
980
|
+
description: "Title of the post"
|
|
981
|
+
},
|
|
982
|
+
content: {
|
|
983
|
+
type: "string",
|
|
984
|
+
description: "Content of the post (text for self posts, URL for link posts)"
|
|
985
|
+
},
|
|
986
|
+
is_self: {
|
|
987
|
+
type: "boolean",
|
|
988
|
+
description: "Whether this is a self (text) post (true) or link post (false)",
|
|
989
|
+
default: true
|
|
990
|
+
}
|
|
991
|
+
},
|
|
992
|
+
required: ["subreddit", "title", "content"]
|
|
993
|
+
}
|
|
994
|
+
},
|
|
995
|
+
{
|
|
996
|
+
name: "reply_to_post",
|
|
997
|
+
description: "Post a reply to an existing Reddit post",
|
|
998
|
+
inputSchema: {
|
|
999
|
+
type: "object",
|
|
1000
|
+
properties: {
|
|
1001
|
+
post_id: {
|
|
1002
|
+
type: "string",
|
|
1003
|
+
description: "The ID of the post to reply to"
|
|
1004
|
+
},
|
|
1005
|
+
content: {
|
|
1006
|
+
type: "string",
|
|
1007
|
+
description: "The content of the reply"
|
|
1008
|
+
},
|
|
1009
|
+
subreddit: {
|
|
1010
|
+
type: "string",
|
|
1011
|
+
description: "The subreddit name if known (for validation)"
|
|
1012
|
+
}
|
|
1013
|
+
},
|
|
1014
|
+
required: ["post_id", "content"]
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
]
|
|
1018
|
+
}));
|
|
1019
|
+
this.server.setRequestHandler(import_types4.CallToolRequestSchema, async (request) => {
|
|
1020
|
+
try {
|
|
1021
|
+
const toolName = request.params.name;
|
|
1022
|
+
const toolParams = request.params.arguments || {};
|
|
1023
|
+
await this.server.sendLoggingMessage({
|
|
1024
|
+
level: "debug",
|
|
1025
|
+
logger: "reddit-server",
|
|
1026
|
+
data: `Tool call: ${toolName}`
|
|
1027
|
+
});
|
|
1028
|
+
switch (toolName) {
|
|
1029
|
+
case "test_reddit_mcp_server":
|
|
1030
|
+
return {
|
|
1031
|
+
content: [
|
|
1032
|
+
{
|
|
1033
|
+
type: "text",
|
|
1034
|
+
text: "Hello, world! The Reddit MCP Server is working correctly."
|
|
1035
|
+
}
|
|
1036
|
+
]
|
|
1037
|
+
};
|
|
1038
|
+
case "get_reddit_post":
|
|
1039
|
+
return await getRedditPost(toolParams);
|
|
1040
|
+
case "get_top_posts":
|
|
1041
|
+
return await getTopPosts(
|
|
1042
|
+
toolParams
|
|
1043
|
+
);
|
|
1044
|
+
case "get_user_info":
|
|
1045
|
+
return await getUserInfo(toolParams);
|
|
1046
|
+
case "get_subreddit_info":
|
|
1047
|
+
return await getSubredditInfo(toolParams);
|
|
1048
|
+
case "get_trending_subreddits":
|
|
1049
|
+
return await getTrendingSubreddits();
|
|
1050
|
+
case "create_post":
|
|
1051
|
+
return await createPost(
|
|
1052
|
+
toolParams
|
|
1053
|
+
);
|
|
1054
|
+
case "reply_to_post":
|
|
1055
|
+
return await replyToPost(
|
|
1056
|
+
toolParams
|
|
1057
|
+
);
|
|
1058
|
+
default:
|
|
1059
|
+
throw new import_types4.McpError(import_types4.ErrorCode.MethodNotFound, `Tool with name ${toolName} not found`);
|
|
1060
|
+
}
|
|
1061
|
+
} catch (error) {
|
|
1062
|
+
if (error instanceof Error) {
|
|
1063
|
+
await this.server.sendLoggingMessage({
|
|
1064
|
+
level: "error",
|
|
1065
|
+
logger: "reddit-server",
|
|
1066
|
+
data: `Error calling tool: ${error.message}`
|
|
1067
|
+
});
|
|
1068
|
+
throw new import_types4.McpError(import_types4.ErrorCode.InternalError, `Failed to fetch data: ${error.message}`);
|
|
1069
|
+
}
|
|
1070
|
+
throw error;
|
|
1071
|
+
}
|
|
1072
|
+
});
|
|
1073
|
+
}
|
|
1074
|
+
async run() {
|
|
1075
|
+
const transport = new import_stdio.StdioServerTransport();
|
|
1076
|
+
await this.server.connect(transport);
|
|
1077
|
+
await this.server.sendLoggingMessage({
|
|
1078
|
+
level: "info",
|
|
1079
|
+
logger: "reddit-server",
|
|
1080
|
+
data: "Reddit MCP Server is running"
|
|
1081
|
+
});
|
|
1082
|
+
const username = process.env.REDDIT_USERNAME;
|
|
1083
|
+
const password = process.env.REDDIT_PASSWORD;
|
|
1084
|
+
await this.server.sendLoggingMessage({
|
|
1085
|
+
level: "info",
|
|
1086
|
+
logger: "reddit-server",
|
|
1087
|
+
data: username && password ? `Authenticated as user: ${username}` : "Running in read-only mode (no user authentication)"
|
|
1088
|
+
});
|
|
1089
|
+
}
|
|
1090
|
+
};
|
|
1091
|
+
if (require.main === module) {
|
|
1092
|
+
const server2 = new RedditServer();
|
|
1093
|
+
server2.run().catch(console.error);
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
// src/bin.ts
|
|
1097
|
+
var server = new RedditServer();
|
|
1098
|
+
server.run().catch(console.error);
|