Skip to main content
Glama

Reddit MCP Server

reddit-client.tsβ€’22.3 kB
import axios, { AxiosInstance } from "axios"; import { RedditClientConfig, RedditUser, RedditPost, RedditComment, RedditSubreddit, } from "../types"; export class RedditClient { private clientId: string; private clientSecret: string; private userAgent: string; private username?: string; private password?: string; private accessToken?: string; private tokenExpiry: number = 0; private api: AxiosInstance; private authenticated: boolean = false; constructor(config: RedditClientConfig) { this.clientId = config.clientId; this.clientSecret = config.clientSecret; this.userAgent = config.userAgent; this.username = config.username; this.password = config.password; this.api = axios.create({ baseURL: "https://oauth.reddit.com", headers: { "User-Agent": this.userAgent, }, }); // Add response interceptor to handle token refresh this.api.interceptors.response.use( (response) => response, async (error) => { if (error.response?.status === 401 && this.authenticated) { await this.authenticate(); const originalRequest = error.config; originalRequest.headers[ "Authorization" ] = `Bearer ${this.accessToken}`; return this.api(originalRequest); } return Promise.reject(error); } ); } async authenticate(): Promise<void> { try { const now = Date.now(); if (this.accessToken && now < this.tokenExpiry) { return; } const authUrl = "https://www.reddit.com/api/v1/access_token"; const authData = new URLSearchParams(); if (this.username && this.password) { console.log( `[Auth] Authenticating with user credentials for ${this.username}` ); authData.append("grant_type", "password"); authData.append("username", this.username); authData.append("password", this.password); } else { console.log( "[Auth] Authenticating with client credentials (read-only)" ); authData.append("grant_type", "client_credentials"); } const response = await axios.post(authUrl, authData, { auth: { username: this.clientId, password: this.clientSecret, }, headers: { "User-Agent": this.userAgent, "Content-Type": "application/x-www-form-urlencoded", }, }); this.accessToken = response.data.access_token; this.tokenExpiry = now + response.data.expires_in * 1000; this.authenticated = true; this.api.defaults.headers.common[ "Authorization" ] = `Bearer ${this.accessToken}`; console.log("[Auth] Successfully authenticated with Reddit API"); } catch (error) { console.error("[Auth] Authentication error:", error); throw new Error("Failed to authenticate with Reddit API"); } } async checkAuthentication(): Promise<boolean> { if (!this.authenticated) { try { await this.authenticate(); return true; } catch (error) { return false; } } return true; } async getUser(username: string): Promise<RedditUser> { await this.authenticate(); try { const response = await this.api.get(`/user/${username}/about.json`); const data = response.data.data; return { name: data.name, id: data.id, commentKarma: data.comment_karma, linkKarma: data.link_karma, totalKarma: data.total_karma || data.comment_karma + data.link_karma, isMod: data.is_mod, isGold: data.is_gold, isEmployee: data.is_employee, createdUtc: data.created_utc, profileUrl: `https://reddit.com/user/${data.name}`, }; } catch (error) { console.error(`[Error] Failed to get user info for ${username}:`, error); throw new Error(`Failed to get user info for ${username}`); } } async getSubredditInfo(subredditName: string): Promise<RedditSubreddit> { await this.authenticate(); try { const response = await this.api.get(`/r/${subredditName}/about.json`); const data = response.data.data; return { displayName: data.display_name, title: data.title, description: data.description || "", publicDescription: data.public_description || "", subscribers: data.subscribers, activeUserCount: data.active_user_count, createdUtc: data.created_utc, over18: data.over18, subredditType: data.subreddit_type, url: data.url, }; } catch (error) { console.error( `[Error] Failed to get subreddit info for ${subredditName}:`, error ); throw new Error(`Failed to get subreddit info for ${subredditName}`); } } async getTopPosts( subreddit: string, timeFilter: string = "week", limit: number = 10 ): Promise<RedditPost[]> { await this.authenticate(); try { const endpoint = subreddit ? `/r/${subreddit}/top.json` : "/top.json"; const response = await this.api.get(endpoint, { params: { t: timeFilter, limit, }, }); return response.data.data.children.map((child: any) => { const post = child.data; return { id: post.id, title: post.title, author: post.author, subreddit: post.subreddit, selftext: post.selftext, url: post.url, score: post.score, upvoteRatio: post.upvote_ratio, numComments: post.num_comments, createdUtc: post.created_utc, over18: post.over_18, spoiler: post.spoiler, edited: !!post.edited, isSelf: post.is_self, linkFlairText: post.link_flair_text, permalink: post.permalink, }; }); } catch (error) { console.error( `[Error] Failed to get top posts for ${subreddit || "home"}:`, error ); throw new Error(`Failed to get top posts for ${subreddit || "home"}`); } } async getPost(postId: string, subreddit?: string): Promise<RedditPost> { await this.authenticate(); try { const endpoint = subreddit ? `/r/${subreddit}/comments/${postId}.json` : `/api/info.json?id=t3_${postId}`; const response = await this.api.get(endpoint); let post; if (subreddit) { // When using the comments endpoint post = response.data[0].data.children[0].data; } else { // When using the info endpoint if (!response.data.data.children.length) { throw new Error(`Post with ID ${postId} not found`); } post = response.data.data.children[0].data; } return { id: post.id, title: post.title, author: post.author, subreddit: post.subreddit, selftext: post.selftext, url: post.url, score: post.score, upvoteRatio: post.upvote_ratio, numComments: post.num_comments, createdUtc: post.created_utc, over18: post.over_18, spoiler: post.spoiler, edited: !!post.edited, isSelf: post.is_self, linkFlairText: post.link_flair_text, permalink: post.permalink, }; } catch (error) { console.error(`[Error] Failed to get post with ID ${postId}:`, error); throw new Error(`Failed to get post with ID ${postId}`); } } async getTrendingSubreddits(limit: number = 5): Promise<string[]> { await this.authenticate(); try { const response = await this.api.get("/subreddits/popular.json", { params: { limit }, }); return response.data.data.children.map( (child: any) => child.data.display_name ); } catch (error) { console.error("[Error] Failed to get trending subreddits:", error); throw new Error("Failed to get trending subreddits"); } } async createPost( subreddit: string, title: string, content: string, isSelf: boolean = true ): Promise<RedditPost> { await this.authenticate(); if (!this.username || !this.password) { throw new Error("User authentication required for posting"); } try { console.log(`[Post] Creating post in r/${subreddit}: "${title}"`); const kind = isSelf ? "self" : "link"; const params = new URLSearchParams(); params.append("sr", subreddit); params.append("kind", kind); params.append("title", title); params.append(isSelf ? "text" : "url", content); const response = await this.api.post("/api/submit", params, { headers: { "Content-Type": "application/x-www-form-urlencoded", }, timeout: 30000, // 30 seconds timeout }); console.log(`[Post] Reddit API response:`, response.data); if (response.data.success) { const postId = response.data.data.id; const postName = response.data.data.name; console.log(`[Post] Post created successfully! ID: ${postId}, Name: ${postName}`); try { // Try to get the newly created post const newPost = await this.getPost(postId); console.log(`[Post] Successfully retrieved created post`); return newPost; } catch (getError) { console.log(`[Post] Could not retrieve post details, but post was created successfully`); // Return a basic post object if we can't retrieve the full details return { id: postId, title: title, author: this.username!, subreddit: subreddit, selftext: isSelf ? content : "", url: isSelf ? "" : content, score: 1, upvoteRatio: 1.0, numComments: 0, createdUtc: Math.floor(Date.now() / 1000), over18: false, spoiler: false, edited: false, isSelf: isSelf, linkFlairText: "", permalink: `/r/${subreddit}/comments/${postId}/`, }; } } else { console.error(`[Post] Reddit API returned success=false:`, response.data); const errors = response.data.errors || []; const errorMsg = errors.length > 0 ? errors.join(", ") : "Unknown error"; throw new Error(`Failed to create post: ${errorMsg}`); } } catch (error: any) { console.error(`[Error] Failed to create post in r/${subreddit}:`, error); if (error.response) { console.error(`[Error] Reddit API error response:`, error.response.data); throw new Error(`Failed to create post: ${error.response.data?.message || error.message}`); } else if (error.code === 'ECONNABORTED') { console.error(`[Error] Request timeout when creating post`); throw new Error(`Post creation timed out - check Reddit manually to see if it was created`); } else { throw new Error(`Failed to create post in r/${subreddit}: ${error.message}`); } } } async checkPostExists(postId: string): Promise<boolean> { await this.authenticate(); try { const response = await this.api.get(`/api/info.json?id=t3_${postId}`); return response.data.data.children.length > 0; } catch (error) { return false; } } async replyToPost(postId: string, content: string): Promise<RedditComment> { await this.authenticate(); if (!this.username || !this.password) { throw new Error("User authentication required for posting replies"); } try { if (!(await this.checkPostExists(postId))) { throw new Error( `Post with ID ${postId} does not exist or is not accessible` ); } const params = new URLSearchParams(); params.append("thing_id", `t3_${postId}`); params.append("text", content); const response = await this.api.post("/api/comment", params, { headers: { "Content-Type": "application/x-www-form-urlencoded", }, }); // Extract comment data from response const commentData = response.data; return { id: commentData.id, author: this.username, body: content, score: 1, controversiality: 0, subreddit: commentData.subreddit, submissionTitle: commentData.link_title, createdUtc: Date.now() / 1000, edited: false, isSubmitter: false, permalink: commentData.permalink, }; } catch (error) { console.error(`[Error] Failed to reply to post ${postId}:`, error); throw new Error(`Failed to reply to post ${postId}`); } } async getComment(commentId: string): Promise<RedditComment> { await this.authenticate(); try { const response = await this.api.get(`/api/info.json?id=t1_${commentId}`); if (!response.data.data.children.length) { throw new Error(`Comment with ID ${commentId} not found`); } const comment = response.data.data.children[0].data; return { id: comment.id, author: comment.author, body: comment.body, score: comment.score, controversiality: comment.controversiality, subreddit: comment.subreddit, submissionTitle: comment.link_title || "", createdUtc: comment.created_utc, edited: !!comment.edited, isSubmitter: comment.is_submitter, permalink: comment.permalink, }; } catch (error) { console.error(`[Error] Failed to get comment with ID ${commentId}:`, error); throw new Error(`Failed to get comment with ID ${commentId}`); } } async getCommentsBySubmission(submissionId: string, limit: number = 20): Promise<RedditComment[]> { await this.authenticate(); try { const response = await this.api.get(`/comments/${submissionId}.json`, { params: { limit } }); if (!response.data || response.data.length < 2) { throw new Error(`Submission with ID ${submissionId} not found or has no comments`); } const comments = response.data[1].data.children; return comments.map((child: any) => { const comment = child.data; return { id: comment.id, author: comment.author, body: comment.body, score: comment.score, controversiality: comment.controversiality, subreddit: comment.subreddit, submissionTitle: response.data[0].data.children[0].data.title, createdUtc: comment.created_utc, edited: !!comment.edited, isSubmitter: comment.is_submitter, permalink: comment.permalink, }; }); } catch (error) { console.error(`[Error] Failed to get comments for submission ${submissionId}:`, error); throw new Error(`Failed to get comments for submission ${submissionId}`); } } async getSubmission(submissionId: string): Promise<RedditPost> { // Cette mΓ©thode est similaire Γ  getPost, mais optimisΓ©e pour les submissions return await this.getPost(submissionId); } async getSubreddit(subredditName: string): Promise<RedditSubreddit> { // Cette mΓ©thode est identique Γ  getSubredditInfo return await this.getSubredditInfo(subredditName); } async searchPosts(subreddit: string, query: string, sort: string = "relevance", limit: number = 25): Promise<RedditPost[]> { await this.authenticate(); try { const response = await this.api.get(`/r/${subreddit}/search.json`, { params: { q: query, restrict_sr: true, sort, limit, type: "link" } }); return response.data.data.children.map((child: any) => { const post = child.data; return { id: post.id, title: post.title, author: post.author, subreddit: post.subreddit, selftext: post.selftext, url: post.url, score: post.score, upvoteRatio: post.upvote_ratio, numComments: post.num_comments, createdUtc: post.created_utc, over18: post.over_18, spoiler: post.spoiler, edited: !!post.edited, isSelf: post.is_self, linkFlairText: post.link_flair_text, permalink: post.permalink, }; }); } catch (error) { console.error(`[Error] Failed to search posts in ${subreddit}:`, error); throw new Error(`Failed to search posts in ${subreddit}`); } } async searchSubreddits(query: string, limit: number = 25): Promise<RedditSubreddit[]> { await this.authenticate(); try { const response = await this.api.get("/subreddits/search.json", { params: { q: query, limit, type: "sr", }, }); return response.data.data.children.map((child: any) => { const subreddit = child.data; return { displayName: subreddit.display_name, title: subreddit.title, description: subreddit.description || "", publicDescription: subreddit.public_description || "", subscribers: subreddit.subscribers, activeUserCount: subreddit.active_user_count, createdUtc: subreddit.created_utc, over18: subreddit.over18, subredditType: subreddit.subreddit_type, url: subreddit.url, }; }); } catch (error) { console.error(`[Error] Failed to search subreddits:`, error); throw new Error(`Failed to search subreddits: ${error}`); } } // NEW METHODS FOR USER ACTIVITY async getUserPosts(username: string, sort: string = "new", limit: number = 25): Promise<RedditPost[]> { await this.authenticate(); try { const response = await this.api.get(`/user/${username}/submitted.json`, { params: { sort, limit, }, }); return response.data.data.children.map((child: any) => { const post = child.data; return { id: post.id, title: post.title, author: post.author, subreddit: post.subreddit, selftext: post.selftext, url: post.url, score: post.score, upvoteRatio: post.upvote_ratio, numComments: post.num_comments, createdUtc: post.created_utc, over18: post.over_18, spoiler: post.spoiler, edited: !!post.edited, isSelf: post.is_self, linkFlairText: post.link_flair_text, permalink: post.permalink, }; }); } catch (error) { console.error(`[Error] Failed to get user posts for ${username}:`, error); throw new Error(`Failed to get user posts for ${username}`); } } async getUserComments(username: string, sort: string = "new", limit: number = 25): Promise<RedditComment[]> { await this.authenticate(); try { const response = await this.api.get(`/user/${username}/comments.json`, { params: { sort, limit, }, }); return response.data.data.children.map((child: any) => { const comment = child.data; return { id: comment.id, author: comment.author, body: comment.body, score: comment.score, controversiality: comment.controversiality, subreddit: comment.subreddit, submissionTitle: comment.link_title || "Unknown Post", createdUtc: comment.created_utc, edited: !!comment.edited, isSubmitter: comment.is_submitter, permalink: `https://reddit.com${comment.permalink}`, }; }); } catch (error) { console.error(`[Error] Failed to get user comments for ${username}:`, error); throw new Error(`Failed to get user comments for ${username}`); } } async getUserOverview(username: string, sort: string = "new", limit: number = 25): Promise<{posts: RedditPost[], comments: RedditComment[]}> { await this.authenticate(); try { const response = await this.api.get(`/user/${username}/overview.json`, { params: { sort, limit, }, }); const posts: RedditPost[] = []; const comments: RedditComment[] = []; response.data.data.children.forEach((child: any) => { const item = child.data; if (child.kind === "t3") { // Post posts.push({ id: item.id, title: item.title, author: item.author, subreddit: item.subreddit, selftext: item.selftext, url: item.url, score: item.score, upvoteRatio: item.upvote_ratio, numComments: item.num_comments, createdUtc: item.created_utc, over18: item.over_18, spoiler: item.spoiler, edited: !!item.edited, isSelf: item.is_self, linkFlairText: item.link_flair_text, permalink: item.permalink, }); } else if (child.kind === "t1") { // Comment comments.push({ id: item.id, author: item.author, body: item.body, score: item.score, controversiality: item.controversiality, subreddit: item.subreddit, submissionTitle: item.link_title || "Unknown Post", createdUtc: item.created_utc, edited: !!item.edited, isSubmitter: item.is_submitter, permalink: `https://reddit.com${item.permalink}`, }); } }); return { posts, comments }; } catch (error) { console.error(`[Error] Failed to get user overview for ${username}:`, error); throw new Error(`Failed to get user overview for ${username}`); } } } // Create and export singleton instance let redditClient: RedditClient | null = null; export function initializeRedditClient( config: RedditClientConfig ): RedditClient { redditClient = new RedditClient(config); return redditClient; } export function getRedditClient(): RedditClient | null { return redditClient; }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/samy-clivolt/reddit-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server