reddit-mcp-server 1.0.5 → 1.0.7

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.
@@ -1,91 +0,0 @@
1
- import { getRedditClient } from "../client/reddit-client"
2
- import { formatSubredditInfo } from "../utils/formatters"
3
- import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js"
4
-
5
- export async function getSubredditInfo(params: { subreddit_name: string }) {
6
- const { subreddit_name } = params
7
- const client = getRedditClient()
8
-
9
- if (!client) {
10
- throw new McpError(ErrorCode.InternalError, "Reddit client not initialized")
11
- }
12
-
13
- try {
14
- // Getting subreddit info
15
- const subreddit = await client.getSubredditInfo(subreddit_name)
16
- const formattedSubreddit = formatSubredditInfo(subreddit)
17
-
18
- return {
19
- content: [
20
- {
21
- type: "text",
22
- text: `
23
- # Subreddit Information: r/${formattedSubreddit.name}
24
-
25
- ## Overview
26
- - Name: r/${formattedSubreddit.name}
27
- - Title: ${formattedSubreddit.title}
28
- - Subscribers: ${formattedSubreddit.stats.subscribers.toLocaleString()}
29
- - Active Users: ${
30
- typeof formattedSubreddit.stats.activeUsers === "number"
31
- ? formattedSubreddit.stats.activeUsers.toLocaleString()
32
- : formattedSubreddit.stats.activeUsers
33
- }
34
-
35
- ## Description
36
- ${formattedSubreddit.description.short}
37
-
38
- ## Detailed Description
39
- ${formattedSubreddit.description.full}
40
-
41
- ## Metadata
42
- - Created: ${formattedSubreddit.metadata.created}
43
- - Flags: ${formattedSubreddit.metadata.flags.join(", ")}
44
-
45
- ## Links
46
- - Subreddit: ${formattedSubreddit.links.subreddit}
47
- - Wiki: ${formattedSubreddit.links.wiki}
48
-
49
- ## Community Analysis
50
- - ${formattedSubreddit.communityAnalysis.replace(/\n - /g, "\n- ")}
51
-
52
- ## Engagement Tips
53
- - ${formattedSubreddit.engagementTips.replace(/\n - /g, "\n- ")}
54
- `,
55
- },
56
- ],
57
- }
58
- } catch (error) {
59
- // Error will be logged by the server
60
- throw new McpError(ErrorCode.InternalError, `Failed to fetch subreddit data: ${String(error)}`)
61
- }
62
- }
63
-
64
- export async function getTrendingSubreddits() {
65
- const client = getRedditClient()
66
-
67
- if (!client) {
68
- throw new McpError(ErrorCode.InternalError, "Reddit client not initialized")
69
- }
70
-
71
- try {
72
- // Getting trending subreddits
73
- const trendingSubreddits = await client.getTrendingSubreddits()
74
-
75
- return {
76
- content: [
77
- {
78
- type: "text",
79
- text: `
80
- # Trending Subreddits
81
-
82
- ${trendingSubreddits.map((subreddit, index) => `${index + 1}. r/${subreddit}`).join("\n")}
83
- `,
84
- },
85
- ],
86
- }
87
- } catch (error) {
88
- // Error will be logged by the server
89
- throw new McpError(ErrorCode.InternalError, `Failed to fetch trending subreddits: ${String(error)}`)
90
- }
91
- }
@@ -1,147 +0,0 @@
1
- import { getRedditClient } from "../client/reddit-client"
2
- import { formatUserInfo } from "../utils/formatters"
3
- import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js"
4
-
5
- export async function getUserInfo(params: { username: string }) {
6
- const { username } = params
7
- const client = getRedditClient()
8
-
9
- if (!client) {
10
- throw new McpError(ErrorCode.InternalError, "Reddit client not initialized")
11
- }
12
-
13
- try {
14
- // Getting user info
15
- const user = await client.getUser(username)
16
- const formattedUser = formatUserInfo(user)
17
-
18
- return {
19
- content: [
20
- {
21
- type: "text",
22
- text: `
23
- # User Information: u/${formattedUser.username}
24
-
25
- ## Profile Overview
26
- - Username: u/${formattedUser.username}
27
- - Karma:
28
- - Comment Karma: ${formattedUser.karma.commentKarma.toLocaleString()}
29
- - Post Karma: ${formattedUser.karma.postKarma.toLocaleString()}
30
- - Total Karma: ${formattedUser.karma.totalKarma.toLocaleString()}
31
- - Account Status: ${formattedUser.accountStatus.join(", ")}
32
- - Account Created: ${formattedUser.accountCreated}
33
- - Profile URL: ${formattedUser.profileUrl}
34
-
35
- ## Activity Analysis
36
- - ${formattedUser.activityAnalysis.replace(/\n - /g, "\n- ")}
37
-
38
- ## Recommendations
39
- - ${formattedUser.recommendations.replace(/\n - /g, "\n- ")}
40
- `,
41
- },
42
- ],
43
- }
44
- } catch (error) {
45
- // Error will be logged by the server
46
- throw new McpError(ErrorCode.InternalError, `Failed to fetch user data: ${String(error)}`)
47
- }
48
- }
49
-
50
- export async function getUserPosts(params: { username: string; sort?: string; time_filter?: string; limit?: number }) {
51
- const { username, sort = "new", time_filter = "all", limit = 10 } = params
52
- const client = getRedditClient()
53
-
54
- if (!client) {
55
- throw new McpError(ErrorCode.InternalError, "Reddit client not initialized")
56
- }
57
-
58
- try {
59
- const posts = await client.getUserPosts(username, {
60
- sort,
61
- timeFilter: time_filter,
62
- limit,
63
- })
64
-
65
- return {
66
- content: [
67
- {
68
- type: "text",
69
- text: `# Posts by u/${username}
70
-
71
- ## Sort: ${sort} | Time: ${time_filter} | Count: ${posts.length}
72
-
73
- ${posts
74
- .map((post, index) => {
75
- const date = new Date(post.createdUtc * 1000).toLocaleString()
76
- const selftext = post.selftext
77
- ? `\n${post.selftext.substring(0, 200)}${post.selftext.length > 200 ? "..." : ""}\n`
78
- : ""
79
-
80
- return `### ${index + 1}. ${post.title}
81
- - Subreddit: r/${post.subreddit}
82
- - Score: ${post.score} (${Math.round(post.upvoteRatio * 100)}% upvoted)
83
- - Comments: ${post.numComments}
84
- - Posted: ${date}
85
- ${selftext}
86
- - Link: https://reddit.com${post.permalink}
87
- ${post.over18 ? "- **NSFW**" : ""}
88
- ${post.spoiler ? "- **Spoiler**" : ""}`
89
- })
90
- .join("\n\n---\n\n")}`,
91
- },
92
- ],
93
- }
94
- } catch (error) {
95
- throw new McpError(ErrorCode.InternalError, `Failed to fetch user posts: ${String(error)}`)
96
- }
97
- }
98
-
99
- export async function getUserComments(params: {
100
- username: string
101
- sort?: string
102
- time_filter?: string
103
- limit?: number
104
- }) {
105
- const { username, sort = "new", time_filter = "all", limit = 10 } = params
106
- const client = getRedditClient()
107
-
108
- if (!client) {
109
- throw new McpError(ErrorCode.InternalError, "Reddit client not initialized")
110
- }
111
-
112
- try {
113
- const comments = await client.getUserComments(username, {
114
- sort,
115
- timeFilter: time_filter,
116
- limit,
117
- })
118
-
119
- return {
120
- content: [
121
- {
122
- type: "text",
123
- text: `# Comments by u/${username}
124
-
125
- ## Sort: ${sort} | Time: ${time_filter} | Count: ${comments.length}
126
-
127
- ${comments
128
- .map((comment, index) => {
129
- const date = new Date(comment.createdUtc * 1000).toLocaleString()
130
- const edited = comment.edited ? " *(edited)*" : ""
131
- const body = comment.body.length > 300 ? comment.body.substring(0, 300) + "..." : comment.body
132
-
133
- return `### ${index + 1}. In r/${comment.subreddit} on "${comment.submissionTitle}"
134
- - Score: ${comment.score} points
135
- - Posted: ${date}${edited}
136
- - Link: https://reddit.com${comment.permalink}
137
-
138
- ${body}`
139
- })
140
- .join("\n\n---\n\n")}`,
141
- },
142
- ],
143
- }
144
- } catch (error) {
145
- throw new McpError(ErrorCode.InternalError, `Failed to fetch user comments: ${String(error)}`)
146
- }
147
- }
package/src/types.ts DELETED
@@ -1,148 +0,0 @@
1
- export interface RedditClientConfig {
2
- clientId: string
3
- clientSecret: string
4
- userAgent: string
5
- username?: string
6
- password?: string
7
- }
8
-
9
- export interface RedditUser {
10
- name: string
11
- id: string
12
- commentKarma: number
13
- linkKarma: number
14
- totalKarma: number
15
- isMod: boolean
16
- isGold: boolean
17
- isEmployee: boolean
18
- createdUtc: number
19
- profileUrl: string
20
- }
21
-
22
- export interface RedditPost {
23
- id: string
24
- title: string
25
- author: string
26
- subreddit: string
27
- selftext?: string
28
- url?: string
29
- score: number
30
- upvoteRatio: number
31
- numComments: number
32
- createdUtc: number
33
- over18: boolean
34
- spoiler?: boolean
35
- edited: boolean
36
- isSelf: boolean
37
- linkFlairText?: string
38
- permalink: string
39
- }
40
-
41
- export interface RedditComment {
42
- id: string
43
- author: string
44
- body: string
45
- score: number
46
- controversiality: number
47
- subreddit: string
48
- submissionTitle: string
49
- createdUtc: number
50
- edited: boolean
51
- isSubmitter: boolean
52
- permalink: string
53
- depth?: number
54
- parentId?: string
55
- }
56
-
57
- export interface RedditSubreddit {
58
- displayName: string
59
- title: string
60
- description: string
61
- publicDescription: string
62
- subscribers: number
63
- activeUserCount?: number
64
- createdUtc: number
65
- over18: boolean
66
- subredditType?: string
67
- url: string
68
- }
69
-
70
- export interface FormattedUserInfo {
71
- username: string
72
- karma: {
73
- commentKarma: number
74
- postKarma: number
75
- totalKarma: number
76
- }
77
- accountStatus: string[]
78
- accountCreated: string
79
- profileUrl: string
80
- activityAnalysis: string
81
- recommendations: string
82
- }
83
-
84
- export interface FormattedPostInfo {
85
- title: string
86
- type: string
87
- content: string
88
- author: string
89
- subreddit: string
90
- stats: {
91
- score: number
92
- upvoteRatio: number
93
- comments: number
94
- }
95
- metadata: {
96
- posted: string
97
- flags: string[]
98
- flair: string
99
- }
100
- links: {
101
- fullPost: string
102
- shortLink: string
103
- }
104
- engagementAnalysis: string
105
- bestTimeToEngage: string
106
- }
107
-
108
- export interface FormattedSubredditInfo {
109
- name: string
110
- title: string
111
- stats: {
112
- subscribers: number
113
- activeUsers: number | string
114
- }
115
- description: {
116
- short: string
117
- full: string
118
- }
119
- metadata: {
120
- created: string
121
- flags: string[]
122
- }
123
- links: {
124
- subreddit: string
125
- wiki: string
126
- }
127
- communityAnalysis: string
128
- engagementTips: string
129
- }
130
-
131
- export interface FormattedCommentInfo {
132
- author: string
133
- content: string
134
- stats: {
135
- score: number
136
- controversiality: number | string
137
- }
138
- context: {
139
- subreddit: string
140
- thread: string
141
- }
142
- metadata: {
143
- posted: string
144
- flags: string[]
145
- }
146
- link: string
147
- commentAnalysis: string
148
- }
@@ -1,316 +0,0 @@
1
- import {
2
- RedditUser,
3
- RedditPost,
4
- RedditSubreddit,
5
- RedditComment,
6
- FormattedUserInfo,
7
- FormattedPostInfo,
8
- FormattedSubredditInfo,
9
- FormattedCommentInfo,
10
- } from "../types"
11
-
12
- export function formatTimestamp(timestamp: number): string {
13
- try {
14
- const date = new Date(timestamp * 1000)
15
- return date
16
- .toISOString()
17
- .replace("T", " ")
18
- .replace(/\.\d+Z$/, " UTC")
19
- } catch {
20
- return String(timestamp)
21
- }
22
- }
23
-
24
- export function analyzeUserActivity(karmaRatio: number, isMod: boolean, accountAgeDays: number): string {
25
- const insights: string[] = []
26
-
27
- // Analyze karma ratio
28
- if (karmaRatio > 5) {
29
- insights.push("Primarily a commenter, highly engaged in discussions")
30
- } else if (karmaRatio < 0.2) {
31
- insights.push("Content creator, focuses on sharing posts")
32
- } else {
33
- insights.push("Balanced participation in both posting and commenting")
34
- }
35
-
36
- // Analyze account age and status
37
- if (accountAgeDays < 30) {
38
- insights.push("New user, still exploring Reddit")
39
- } else if (accountAgeDays > 365 * 5) {
40
- insights.push("Long-time Redditor with extensive platform experience")
41
- }
42
-
43
- if (isMod) {
44
- insights.push("Community leader who helps maintain subreddit quality")
45
- }
46
-
47
- return insights.join("\n - ")
48
- }
49
-
50
- export function analyzePostEngagement(score: number, ratio: number, numComments: number): string {
51
- const insights: string[] = []
52
-
53
- // Analyze score and ratio
54
- if (score > 1000 && ratio > 0.95) {
55
- insights.push("Highly successful post with strong community approval")
56
- } else if (score > 100 && ratio > 0.8) {
57
- insights.push("Well-received post with good engagement")
58
- } else if (ratio < 0.5) {
59
- insights.push("Controversial post that sparked debate")
60
- }
61
-
62
- // Analyze comment activity
63
- if (numComments > 100) {
64
- insights.push("Generated significant discussion")
65
- } else if (numComments > score * 0.5) {
66
- insights.push("Highly discussable content with active comment section")
67
- } else if (numComments === 0) {
68
- insights.push("Yet to receive community interaction")
69
- }
70
-
71
- return insights.join("\n - ")
72
- }
73
-
74
- export function analyzeSubredditHealth(subscribers: number, activeUsers: number | undefined, ageDays: number): string {
75
- const insights: string[] = []
76
-
77
- // Analyze size and activity
78
- if (subscribers > 1000000) {
79
- insights.push("Major subreddit with massive following")
80
- } else if (subscribers > 100000) {
81
- insights.push("Well-established community")
82
- } else if (subscribers < 1000) {
83
- insights.push("Niche community, potential for growth")
84
- }
85
-
86
- if (activeUsers) {
87
- // If we have active users data
88
- const activityRatio = activeUsers / subscribers
89
- if (activityRatio > 0.1) {
90
- insights.push("Highly active community with strong engagement")
91
- } else if (activityRatio < 0.01) {
92
- insights.push("Could benefit from more community engagement initiatives")
93
- }
94
- }
95
-
96
- // Analyze age and maturity
97
- if (ageDays > 365 * 5) {
98
- insights.push("Mature subreddit with established culture")
99
- } else if (ageDays < 90) {
100
- insights.push("New subreddit still forming its community")
101
- }
102
-
103
- return insights.join("\n - ")
104
- }
105
-
106
- export function getUserRecommendations(karmaRatio: number, isMod: boolean, accountAgeDays: number): string {
107
- const recommendations: string[] = []
108
-
109
- if (karmaRatio > 5) {
110
- recommendations.push("Consider creating more posts to share your expertise")
111
- } else if (karmaRatio < 0.2) {
112
- recommendations.push("Engage more in discussions to build community connections")
113
- }
114
-
115
- if (accountAgeDays < 30) {
116
- recommendations.push("Explore popular subreddits in your areas of interest")
117
- recommendations.push("Read community guidelines before posting")
118
- }
119
-
120
- if (isMod) {
121
- recommendations.push("Share moderation insights with other community leaders")
122
- }
123
-
124
- if (!recommendations.length) {
125
- recommendations.push("Maintain your balanced engagement across Reddit")
126
- }
127
-
128
- return recommendations.join("\n - ")
129
- }
130
-
131
- export function getBestEngagementTime(createdUtc: number): string {
132
- const postHour = new Date(createdUtc * 1000).getHours()
133
-
134
- // Simple time zone analysis
135
- if (14 <= postHour && postHour <= 18) {
136
- // Peak Reddit hours
137
- return "Posted during peak engagement hours (2 PM - 6 PM), good timing!"
138
- } else if (23 <= postHour || postHour <= 5) {
139
- return "Consider posting during more active hours (morning to evening)"
140
- } else {
141
- return "Posted during moderate activity hours, timing could be optimized"
142
- }
143
- }
144
-
145
- export function getSubredditEngagementTips(subreddit: RedditSubreddit): string {
146
- const tips: string[] = []
147
-
148
- if (subreddit.subscribers > 1000000) {
149
- tips.push("Post during peak hours for maximum visibility")
150
- tips.push("Ensure content is highly polished due to high competition")
151
- } else if (subreddit.subscribers < 1000) {
152
- tips.push("Engage actively to help grow the community")
153
- tips.push("Consider cross-posting to related larger subreddits")
154
- }
155
-
156
- if (subreddit.activeUserCount) {
157
- const activityRatio = subreddit.activeUserCount / subreddit.subscribers
158
- if (activityRatio > 0.1) {
159
- tips.push("Quick responses recommended due to high activity")
160
- }
161
- }
162
-
163
- return tips.length ? tips.join("\n - ") : "Regular engagement recommended to maintain community presence"
164
- }
165
-
166
- export function analyzeCommentImpact(score: number, isEdited: boolean, isOp: boolean): string {
167
- const insights: string[] = []
168
-
169
- if (score > 100) {
170
- insights.push("Highly upvoted comment with significant community agreement")
171
- } else if (score < 0) {
172
- insights.push("Controversial or contested viewpoint")
173
- }
174
-
175
- if (isEdited) {
176
- insights.push("Refined for clarity or accuracy")
177
- }
178
-
179
- if (isOp) {
180
- insights.push("Author's perspective adds context to original post")
181
- }
182
-
183
- return insights.length ? insights.join("\n - ") : "Standard engagement with discussion"
184
- }
185
-
186
- export function formatUserInfo(user: RedditUser): FormattedUserInfo {
187
- const status: string[] = []
188
- if (user.isMod) status.push("Moderator")
189
- if (user.isGold) status.push("Reddit Gold Member")
190
- if (user.isEmployee) status.push("Reddit Employee")
191
-
192
- const accountAgeDays = (Date.now() / 1000 - user.createdUtc) / (24 * 3600) // age in days
193
- const karmaRatio = user.commentKarma / (user.linkKarma || 1)
194
-
195
- return {
196
- username: user.name,
197
- karma: {
198
- commentKarma: user.commentKarma,
199
- postKarma: user.linkKarma,
200
- totalKarma: user.totalKarma,
201
- },
202
- accountStatus: status.length ? status : ["Regular User"],
203
- accountCreated: formatTimestamp(user.createdUtc),
204
- profileUrl: user.profileUrl,
205
- activityAnalysis: analyzeUserActivity(karmaRatio, user.isMod, accountAgeDays),
206
- recommendations: getUserRecommendations(karmaRatio, user.isMod, accountAgeDays),
207
- }
208
- }
209
-
210
- export function formatPostInfo(post: RedditPost): FormattedPostInfo {
211
- const contentType = post.isSelf ? "Text Post" : "Link Post"
212
- const content = post.isSelf ? post.selftext || "" : post.url || ""
213
-
214
- const flags: string[] = []
215
- if (post.over18) flags.push("NSFW")
216
- if (post.spoiler) flags.push("Spoiler")
217
- if (post.edited) flags.push("Edited")
218
-
219
- return {
220
- title: post.title,
221
- type: contentType,
222
- content: content.length > 300 ? content.substring(0, 297) + "..." : content,
223
- author: post.author,
224
- subreddit: post.subreddit,
225
- stats: {
226
- score: post.score,
227
- upvoteRatio: post.upvoteRatio,
228
- comments: post.numComments,
229
- },
230
- metadata: {
231
- posted: formatTimestamp(post.createdUtc),
232
- flags: flags,
233
- flair: post.linkFlairText || "None",
234
- },
235
- links: {
236
- fullPost: `https://reddit.com${post.permalink}`,
237
- shortLink: `https://redd.it/${post.id}`,
238
- },
239
- engagementAnalysis: analyzePostEngagement(post.score, post.upvoteRatio, post.numComments),
240
- bestTimeToEngage: getBestEngagementTime(post.createdUtc),
241
- }
242
- }
243
-
244
- export function formatSubredditInfo(subreddit: RedditSubreddit): FormattedSubredditInfo {
245
- const flags: string[] = []
246
- if (subreddit.over18) flags.push("NSFW")
247
- if (subreddit.subredditType) flags.push(`Type: ${subreddit.subredditType}`)
248
-
249
- const ageDays = (Date.now() / 1000 - subreddit.createdUtc) / (24 * 3600)
250
-
251
- return {
252
- name: subreddit.displayName,
253
- title: subreddit.title,
254
- stats: {
255
- subscribers: subreddit.subscribers,
256
- activeUsers: subreddit.activeUserCount !== undefined ? subreddit.activeUserCount : "Unknown",
257
- },
258
- description: {
259
- short: subreddit.publicDescription,
260
- full:
261
- subreddit.description.length > 300 ? subreddit.description.substring(0, 297) + "..." : subreddit.description,
262
- },
263
- metadata: {
264
- created: formatTimestamp(subreddit.createdUtc),
265
- flags: flags.length ? flags : ["None"],
266
- },
267
- links: {
268
- subreddit: `https://reddit.com${subreddit.url}`,
269
- wiki: `https://reddit.com/r/${subreddit.displayName}/wiki`,
270
- },
271
- communityAnalysis: analyzeSubredditHealth(subreddit.subscribers, subreddit.activeUserCount, ageDays),
272
- engagementTips: getSubredditEngagementTips(subreddit),
273
- }
274
- }
275
-
276
- export function formatCommentInfo(comment: RedditComment): FormattedCommentInfo {
277
- const flags: string[] = []
278
- if (comment.edited) flags.push("Edited")
279
- if (comment.isSubmitter) flags.push("OP")
280
-
281
- return {
282
- author: comment.author,
283
- content: comment.body.length > 300 ? comment.body.substring(0, 297) + "..." : comment.body,
284
- stats: {
285
- score: comment.score,
286
- controversiality: comment.controversiality,
287
- },
288
- context: {
289
- subreddit: comment.subreddit,
290
- thread: comment.submissionTitle,
291
- },
292
- metadata: {
293
- posted: formatTimestamp(comment.createdUtc),
294
- flags: flags.length ? flags : ["None"],
295
- },
296
- link: `https://reddit.com${comment.permalink}`,
297
- commentAnalysis: analyzeCommentImpact(comment.score, comment.edited, comment.isSubmitter),
298
- }
299
- }
300
-
301
- // Simple formatter for posts (used in search and comment tools)
302
- export function formatPost(post: RedditPost) {
303
- return {
304
- title: post.title,
305
- author: post.author,
306
- subreddit: post.subreddit,
307
- score: post.score,
308
- upvoteRatio: Math.round(post.upvoteRatio * 100),
309
- numComments: post.numComments,
310
- createdAt: formatTimestamp(post.createdUtc),
311
- selftext: post.selftext,
312
- permalink: post.permalink,
313
- nsfw: post.over18,
314
- spoiler: post.spoiler,
315
- }
316
- }