reddit-mcp-server 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.ts ADDED
@@ -0,0 +1,303 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js"
4
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
5
+ import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError } from "@modelcontextprotocol/sdk/types.js"
6
+ import { initializeRedditClient } from "./client/reddit-client"
7
+ import * as tools from "./tools"
8
+ import dotenv from "dotenv"
9
+
10
+ // Load environment variables
11
+ dotenv.config()
12
+
13
+ class RedditServer {
14
+ private server: Server
15
+
16
+ constructor() {
17
+ console.log("[Setup] Initializing Reddit Server...")
18
+
19
+ // Initialize the Reddit client
20
+ this.initializeRedditClient()
21
+
22
+ this.server = new Server(
23
+ {
24
+ name: "reddit-mcp-server",
25
+ version: "0.1.0",
26
+ },
27
+ {
28
+ capabilities: {
29
+ tools: {},
30
+ },
31
+ },
32
+ )
33
+
34
+ this.setupToolHandlers()
35
+
36
+ this.server.onerror = (error) => console.error("[Error] Server error:", error)
37
+ process.on("SIGINT", async () => {
38
+ await this.server.close()
39
+ process.exit(0)
40
+ })
41
+ }
42
+
43
+ private initializeRedditClient() {
44
+ const clientId = process.env.REDDIT_CLIENT_ID
45
+ const clientSecret = process.env.REDDIT_CLIENT_SECRET
46
+ const userAgent = process.env.REDDIT_USER_AGENT || "RedditMCPServer/0.1.0"
47
+ const username = process.env.REDDIT_USERNAME
48
+ const password = process.env.REDDIT_PASSWORD
49
+
50
+ if (!clientId || !clientSecret) {
51
+ console.error(
52
+ "[Error] Missing required Reddit API credentials. Please set REDDIT_CLIENT_ID and REDDIT_CLIENT_SECRET environment variables.",
53
+ )
54
+ process.exit(1)
55
+ }
56
+
57
+ try {
58
+ initializeRedditClient({
59
+ clientId,
60
+ clientSecret,
61
+ userAgent,
62
+ username,
63
+ password,
64
+ })
65
+
66
+ console.log("[Setup] Reddit client initialized")
67
+ if (username && password) {
68
+ console.log(`[Setup] Authenticated as user: ${username}`)
69
+ } else {
70
+ console.log("[Setup] Running in read-only mode (no user authentication)")
71
+ }
72
+ } catch (error) {
73
+ console.error("[Error] Failed to initialize Reddit client:", error)
74
+ process.exit(1)
75
+ }
76
+ }
77
+
78
+ private setupToolHandlers() {
79
+ this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
80
+ tools: [
81
+ {
82
+ name: "test_reddit_mcp_server",
83
+ description: "Test the Reddit MCP Server",
84
+ inputSchema: {
85
+ type: "object",
86
+ properties: {
87
+ // No input parameters, this will just return a test message
88
+ },
89
+ },
90
+ },
91
+ {
92
+ name: "get_reddit_post",
93
+ description: "Get a Reddit post",
94
+ inputSchema: {
95
+ type: "object",
96
+ properties: {
97
+ subreddit: {
98
+ type: "string",
99
+ description: "The subreddit to fetch posts from",
100
+ },
101
+ post_id: {
102
+ type: "string",
103
+ description: "The ID of the post to fetch",
104
+ },
105
+ },
106
+ required: ["subreddit", "post_id"],
107
+ },
108
+ },
109
+ {
110
+ name: "get_top_posts",
111
+ description: "Get top posts from a subreddit",
112
+ inputSchema: {
113
+ type: "object",
114
+ properties: {
115
+ subreddit: {
116
+ type: "string",
117
+ description: "Name of the subreddit",
118
+ },
119
+ time_filter: {
120
+ type: "string",
121
+ description: "Time period to filter posts (e.g. 'day', 'week', 'month', 'year', 'all')",
122
+ enum: ["day", "week", "month", "year", "all"],
123
+ default: "week",
124
+ },
125
+ limit: {
126
+ type: "integer",
127
+ description: "Number of posts to fetch",
128
+ default: 10,
129
+ },
130
+ },
131
+ required: ["subreddit"],
132
+ },
133
+ },
134
+ {
135
+ name: "get_user_info",
136
+ description: "Get information about a Reddit user",
137
+ inputSchema: {
138
+ type: "object",
139
+ properties: {
140
+ username: {
141
+ type: "string",
142
+ description: "The username of the Reddit user to get info for",
143
+ },
144
+ },
145
+ required: ["username"],
146
+ },
147
+ },
148
+ {
149
+ name: "get_subreddit_info",
150
+ description: "Get information about a subreddit",
151
+ inputSchema: {
152
+ type: "object",
153
+ properties: {
154
+ subreddit_name: {
155
+ type: "string",
156
+ description: "Name of the subreddit",
157
+ },
158
+ },
159
+ required: ["subreddit_name"],
160
+ },
161
+ },
162
+ {
163
+ name: "get_trending_subreddits",
164
+ description: "Get currently trending subreddits",
165
+ inputSchema: {
166
+ type: "object",
167
+ properties: {},
168
+ },
169
+ },
170
+ {
171
+ name: "create_post",
172
+ description: "Create a new post in a subreddit",
173
+ inputSchema: {
174
+ type: "object",
175
+ properties: {
176
+ subreddit: {
177
+ type: "string",
178
+ description: "Name of the subreddit to post in",
179
+ },
180
+ title: {
181
+ type: "string",
182
+ description: "Title of the post",
183
+ },
184
+ content: {
185
+ type: "string",
186
+ description: "Content of the post (text for self posts, URL for link posts)",
187
+ },
188
+ is_self: {
189
+ type: "boolean",
190
+ description: "Whether this is a self (text) post (true) or link post (false)",
191
+ default: true,
192
+ },
193
+ },
194
+ required: ["subreddit", "title", "content"],
195
+ },
196
+ },
197
+ {
198
+ name: "reply_to_post",
199
+ description: "Post a reply to an existing Reddit post",
200
+ inputSchema: {
201
+ type: "object",
202
+ properties: {
203
+ post_id: {
204
+ type: "string",
205
+ description: "The ID of the post to reply to",
206
+ },
207
+ content: {
208
+ type: "string",
209
+ description: "The content of the reply",
210
+ },
211
+ subreddit: {
212
+ type: "string",
213
+ description: "The subreddit name if known (for validation)",
214
+ },
215
+ },
216
+ required: ["post_id", "content"],
217
+ },
218
+ },
219
+ ],
220
+ }))
221
+
222
+ this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
223
+ try {
224
+ const toolName = request.params.name
225
+ const toolParams = request.params.arguments || {}
226
+
227
+ console.log(`[Request] Tool call: ${toolName}`, toolParams)
228
+
229
+ switch (toolName) {
230
+ case "test_reddit_mcp_server":
231
+ return {
232
+ content: [
233
+ {
234
+ type: "text",
235
+ text: "Hello, world! The Reddit MCP Server is working correctly.",
236
+ },
237
+ ],
238
+ }
239
+
240
+ case "get_reddit_post":
241
+ return await tools.getRedditPost(toolParams as { subreddit: string; post_id: string })
242
+
243
+ case "get_top_posts":
244
+ return await tools.getTopPosts(
245
+ toolParams as {
246
+ subreddit: string
247
+ time_filter?: string
248
+ limit?: number
249
+ },
250
+ )
251
+
252
+ case "get_user_info":
253
+ return await tools.getUserInfo(toolParams as { username: string })
254
+
255
+ case "get_subreddit_info":
256
+ return await tools.getSubredditInfo(toolParams as { subreddit_name: string })
257
+
258
+ case "get_trending_subreddits":
259
+ return await tools.getTrendingSubreddits()
260
+
261
+ case "create_post":
262
+ return await tools.createPost(
263
+ toolParams as {
264
+ subreddit: string
265
+ title: string
266
+ content: string
267
+ is_self?: boolean
268
+ },
269
+ )
270
+
271
+ case "reply_to_post":
272
+ return await tools.replyToPost(
273
+ toolParams as {
274
+ post_id: string
275
+ content: string
276
+ subreddit?: string
277
+ },
278
+ )
279
+
280
+ default:
281
+ throw new McpError(ErrorCode.MethodNotFound, `Tool with name ${toolName} not found`)
282
+ }
283
+ } catch (error: unknown) {
284
+ if (error instanceof Error) {
285
+ console.error("[Error] Error calling tool:", error.message)
286
+
287
+ throw new McpError(ErrorCode.InternalError, `Failed to fetch data: ${error.message}`)
288
+ }
289
+
290
+ throw error
291
+ }
292
+ })
293
+ }
294
+
295
+ async run() {
296
+ const transport = new StdioServerTransport()
297
+ await this.server.connect(transport)
298
+ console.log("[Server] Server is running")
299
+ }
300
+ }
301
+
302
+ const server = new RedditServer()
303
+ server.run().catch(console.error)
@@ -0,0 +1,3 @@
1
+ export * from "./user-tools"
2
+ export * from "./post-tools"
3
+ export * from "./subreddit-tools"
@@ -0,0 +1,178 @@
1
+ import { getRedditClient } from "../client/reddit-client"
2
+ import { formatPostInfo, formatCommentInfo } from "../utils/formatters"
3
+ import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js"
4
+
5
+ export async function getRedditPost(params: { subreddit: string; post_id: string }) {
6
+ const { subreddit, post_id } = params
7
+ const client = getRedditClient()
8
+
9
+ if (!client) {
10
+ throw new McpError(ErrorCode.InternalError, "Reddit client not initialized")
11
+ }
12
+
13
+ try {
14
+ console.log(`[Tool] Getting post ${post_id} from r/${subreddit}`)
15
+ const post = await client.getPost(post_id, subreddit)
16
+ const formattedPost = formatPostInfo(post)
17
+
18
+ return {
19
+ content: [
20
+ {
21
+ type: "text",
22
+ text: `
23
+ # Post from r/${formattedPost.subreddit}
24
+
25
+ ## Post Details
26
+ - Title: ${formattedPost.title}
27
+ - Type: ${formattedPost.type}
28
+ - Author: u/${formattedPost.author}
29
+
30
+ ## Content
31
+ ${formattedPost.content}
32
+
33
+ ## Stats
34
+ - Score: ${formattedPost.stats.score.toLocaleString()}
35
+ - Upvote Ratio: ${(formattedPost.stats.upvoteRatio * 100).toFixed(1)}%
36
+ - Comments: ${formattedPost.stats.comments.toLocaleString()}
37
+
38
+ ## Metadata
39
+ - Posted: ${formattedPost.metadata.posted}
40
+ - Flags: ${formattedPost.metadata.flags.length ? formattedPost.metadata.flags.join(", ") : "None"}
41
+ - Flair: ${formattedPost.metadata.flair}
42
+
43
+ ## Links
44
+ - Full Post: ${formattedPost.links.fullPost}
45
+ - Short Link: ${formattedPost.links.shortLink}
46
+
47
+ ## Engagement Analysis
48
+ - ${formattedPost.engagementAnalysis.replace(/\n - /g, "\n- ")}
49
+
50
+ ## Best Time to Engage
51
+ ${formattedPost.bestTimeToEngage}
52
+ `,
53
+ },
54
+ ],
55
+ }
56
+ } catch (error) {
57
+ console.error(`[Error] Error getting post: ${error}`)
58
+ throw new McpError(ErrorCode.InternalError, `Failed to fetch post data: ${error}`)
59
+ }
60
+ }
61
+
62
+ export async function getTopPosts(params: { subreddit: string; time_filter?: string; limit?: number }) {
63
+ const { subreddit, time_filter = "week", limit = 10 } = params
64
+ const client = getRedditClient()
65
+
66
+ if (!client) {
67
+ throw new McpError(ErrorCode.InternalError, "Reddit client not initialized")
68
+ }
69
+
70
+ try {
71
+ console.log(`[Tool] Getting top posts from r/${subreddit}`)
72
+ const posts = await client.getTopPosts(subreddit, time_filter, limit)
73
+ const formattedPosts = posts.map(formatPostInfo)
74
+
75
+ const postSummaries = formattedPosts
76
+ .map(
77
+ (post, index) => `
78
+ ### ${index + 1}. ${post.title}
79
+ - Author: u/${post.author}
80
+ - Score: ${post.stats.score.toLocaleString()} (${(post.stats.upvoteRatio * 100).toFixed(1)}% upvoted)
81
+ - Comments: ${post.stats.comments.toLocaleString()}
82
+ - Posted: ${post.metadata.posted}
83
+ - Link: ${post.links.shortLink}
84
+ `,
85
+ )
86
+ .join("\n")
87
+
88
+ return {
89
+ content: [
90
+ {
91
+ type: "text",
92
+ text: `
93
+ # Top Posts from r/${subreddit} (${time_filter})
94
+
95
+ ${postSummaries}
96
+ `,
97
+ },
98
+ ],
99
+ }
100
+ } catch (error) {
101
+ console.error(`[Error] Error getting top posts: ${error}`)
102
+ throw new McpError(ErrorCode.InternalError, `Failed to fetch top posts: ${error}`)
103
+ }
104
+ }
105
+
106
+ export async function createPost(params: { subreddit: string; title: string; content: string; is_self?: boolean }) {
107
+ const { subreddit, title, content, is_self = true } = params
108
+ const client = getRedditClient()
109
+
110
+ if (!client) {
111
+ throw new McpError(ErrorCode.InternalError, "Reddit client not initialized")
112
+ }
113
+
114
+ try {
115
+ console.log(`[Tool] Creating ${is_self ? "text" : "link"} post in r/${subreddit}`)
116
+ const post = await client.createPost(subreddit, title, content, is_self)
117
+ const formattedPost = formatPostInfo(post)
118
+
119
+ return {
120
+ content: [
121
+ {
122
+ type: "text",
123
+ text: `
124
+ # Post Created Successfully
125
+
126
+ ## Post Details
127
+ - Title: ${formattedPost.title}
128
+ - Subreddit: r/${formattedPost.subreddit}
129
+ - Type: ${formattedPost.type}
130
+ - Link: ${formattedPost.links.fullPost}
131
+
132
+ Your post has been successfully submitted to r/${formattedPost.subreddit}.
133
+ `,
134
+ },
135
+ ],
136
+ }
137
+ } catch (error) {
138
+ console.error(`[Error] Error creating post: ${error}`)
139
+ throw new McpError(ErrorCode.InternalError, `Failed to create post: ${error}`)
140
+ }
141
+ }
142
+
143
+ export async function replyToPost(params: { post_id: string; content: string; subreddit?: string }) {
144
+ const { post_id, content } = params
145
+ const client = getRedditClient()
146
+
147
+ if (!client) {
148
+ throw new McpError(ErrorCode.InternalError, "Reddit client not initialized")
149
+ }
150
+
151
+ try {
152
+ console.log(`[Tool] Replying to post ${post_id}`)
153
+ const comment = await client.replyToPost(post_id, content)
154
+ const formattedComment = formatCommentInfo(comment)
155
+
156
+ return {
157
+ content: [
158
+ {
159
+ type: "text",
160
+ text: `
161
+ # Reply Posted Successfully
162
+
163
+ ## Comment Details
164
+ - Author: u/${formattedComment.author}
165
+ - Subreddit: r/${formattedComment.context.subreddit}
166
+ - Thread: ${formattedComment.context.thread}
167
+ - Link: ${formattedComment.link}
168
+
169
+ Your reply has been successfully posted.
170
+ `,
171
+ },
172
+ ],
173
+ }
174
+ } catch (error) {
175
+ console.error(`[Error] Error replying to post: ${error}`)
176
+ throw new McpError(ErrorCode.InternalError, `Failed to reply to post: ${error}`)
177
+ }
178
+ }
@@ -0,0 +1,91 @@
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
+ console.log(`[Tool] Getting info for r/${subreddit_name}`)
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
+ console.error(`[Error] Error getting subreddit info: ${error}`)
60
+ throw new McpError(ErrorCode.InternalError, `Failed to fetch subreddit data: ${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
+ console.log("[Tool] 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
+ console.error(`[Error] Error getting trending subreddits: ${error}`)
89
+ throw new McpError(ErrorCode.InternalError, `Failed to fetch trending subreddits: ${error}`)
90
+ }
91
+ }
@@ -0,0 +1,48 @@
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
+ console.log(`[Tool] Getting info for u/${username}`)
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
+ console.error(`[Error] Error getting user info: ${error}`)
46
+ throw new McpError(ErrorCode.InternalError, `Failed to fetch user data: ${error}`)
47
+ }
48
+ }