reddit-mcp-server 1.4.7 → 1.5.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/CHANGELOG.md +21 -0
- package/README.md +26 -22
- package/dist/bin.js +2 -2
- package/dist/bin.js.map +1 -1
- package/dist/index.js +714 -177
- package/dist/index.js.map +1 -1
- package/package.json +8 -8
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/client/reddit-client.ts","../src/utils/formatters.ts","../src/index.ts"],"sourcesContent":["/* eslint-disable functype/prefer-either --\n * This module is the imperative-to-functional boundary for the Reddit HTTP client.\n * Each method already returns Either<Error, T>; the try/catch blocks are the mechanism\n * that captures thrown errors from fetch, JSON parsing, and orThrow() into Either.left.\n * Wrapping every block in Try.fromPromise would add indirection without changing the\n * effective contract. Throws inside this file are internal validation helpers that\n * the surrounding try/catch converts into Either.left at the method boundary.\n */\nimport crypto from \"crypto\"\nimport type { Either } from \"functype\"\nimport { Left, Option, Right } from \"functype\"\n\nimport type {\n BotDisclosureConfig,\n ContentRecord,\n RedditApiCommentResponse,\n RedditApiCommentTreeData,\n RedditApiEditResponse,\n RedditApiInfoResponse,\n RedditApiListingResponse,\n RedditApiPopularSubredditsResponse,\n RedditApiPostCommentsResponse,\n RedditApiPostData,\n RedditApiSubmitResponse,\n RedditApiSubredditResponse,\n RedditApiUserResponse,\n RedditAuthMode,\n RedditClientConfig,\n RedditComment,\n RedditPost,\n RedditSubreddit,\n RedditUser,\n SafeModeConfig,\n} from \"../types\"\n\nfunction toError(error: unknown): Error {\n return error instanceof Error ? error : new Error(String(error))\n}\n\nfunction parsePostData(post: RedditApiPostData): RedditPost {\n return {\n id: post.id,\n title: post.title,\n author: post.author,\n subreddit: post.subreddit,\n selftext: post.selftext,\n url: post.url,\n score: post.score,\n upvoteRatio: post.upvote_ratio,\n numComments: post.num_comments,\n createdUtc: post.created_utc,\n over18: post.over_18,\n spoiler: post.spoiler,\n edited: Boolean(post.edited),\n isSelf: post.is_self,\n linkFlairText: post.link_flair_text ?? undefined,\n permalink: post.permalink,\n }\n}\n\nexport class RedditClient {\n private readonly clientId: string\n private readonly clientSecret: string\n private readonly userAgent: string\n private readonly username?: string\n private readonly password?: string\n private readonly baseUrl: string\n private readonly authMode: RedditAuthMode\n private readonly hasCredentials: boolean\n private readonly safeMode: SafeModeConfig\n private readonly botDisclosure: BotDisclosureConfig\n\n // Mutable state — inherent to a stateful HTTP client with token refresh\n\n private accessToken?: string\n\n private tokenExpiry: number = 0\n\n private authenticated: boolean = false\n\n private lastWriteTime: number = 0\n\n private recentContentRecords: ContentRecord[] = []\n\n constructor(config: RedditClientConfig) {\n this.clientId = config.clientId\n this.clientSecret = config.clientSecret\n this.userAgent = config.userAgent\n this.username = config.username\n this.password = config.password\n this.authMode = config.authMode ?? \"auto\"\n this.hasCredentials = Boolean(this.clientId && this.clientSecret)\n this.baseUrl = this.determineBaseUrl()\n\n this.safeMode = config.safeMode ?? {\n enabled: false,\n mode: \"off\",\n writeDelayMs: 0,\n duplicateCheck: false,\n maxRecentHashes: 10,\n }\n\n this.botDisclosure = config.botDisclosure ?? { enabled: false, footer: \"\" }\n }\n\n private determineBaseUrl(): string {\n switch (this.authMode) {\n case \"authenticated\":\n return \"https://oauth.reddit.com\"\n case \"anonymous\":\n return \"https://www.reddit.com\"\n case \"auto\":\n return this.hasCredentials ? \"https://oauth.reddit.com\" : \"https://www.reddit.com\"\n }\n }\n\n private async makeRequest(path: string, options: RequestInit = {}): Promise<Either<Error, Response>> {\n try {\n const requiresAuth = this.authMode === \"authenticated\" || (this.authMode === \"auto\" && this.hasCredentials)\n\n if (requiresAuth && (Date.now() >= this.tokenExpiry || !this.authenticated)) {\n const authResult = await this.authenticate()\n authResult.orThrow()\n }\n\n const url = `${this.baseUrl}${path}`\n const headers: Record<string, string> = {\n \"User-Agent\": this.userAgent,\n // eslint-disable-next-line functype/prefer-option -- RequestInit.headers is external fetch typing; used in spread\n ...(options.headers as Record<string, string> | undefined),\n }\n\n if (requiresAuth && this.accessToken !== undefined) {\n headers[\"Authorization\"] = `Bearer ${this.accessToken}`\n }\n\n const response = await fetch(url, {\n ...options,\n headers,\n })\n\n if (response.status === 401 && this.authenticated) {\n const reAuthResult = await this.authenticate()\n reAuthResult.orThrow()\n const retryHeaders = {\n ...headers,\n Authorization: `Bearer ${this.accessToken}`,\n }\n return Right(\n await fetch(url, {\n ...options,\n headers: retryHeaders,\n }),\n )\n }\n\n return Right(response)\n } catch (error) {\n return Left(toError(error))\n }\n }\n\n async authenticate(): Promise<Either<Error, void>> {\n if (this.authMode === \"anonymous\") {\n this.authenticated = false\n return Right(undefined as void)\n }\n\n if (this.authMode === \"authenticated\" && !this.hasCredentials) {\n return Left(new Error(\"Authenticated mode requires REDDIT_CLIENT_ID and REDDIT_CLIENT_SECRET\"))\n }\n\n if (this.authMode === \"auto\" && !this.hasCredentials) {\n this.authenticated = false\n return Right(undefined as void)\n }\n\n try {\n const now = Date.now()\n if (this.accessToken !== undefined && now < this.tokenExpiry) {\n return Right(undefined as void)\n }\n\n const authUrl = \"https://www.reddit.com/api/v1/access_token\"\n const authData = new URLSearchParams()\n\n const { username } = this\n const { password } = this\n const isUserAuth = Boolean(username && password)\n if (isUserAuth && username !== undefined && password !== undefined) {\n authData.append(\"grant_type\", \"password\")\n authData.append(\"username\", username)\n authData.append(\"password\", password)\n } else {\n authData.append(\"grant_type\", \"client_credentials\")\n }\n\n const credentials = Buffer.from(`${this.clientId}:${this.clientSecret}`).toString(\"base64\")\n const response = await fetch(authUrl, {\n method: \"POST\",\n headers: {\n \"User-Agent\": this.userAgent,\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n Authorization: `Basic ${credentials}`,\n },\n body: authData.toString(),\n })\n\n if (!response.ok) {\n const statusText = response.statusText !== \"\" ? response.statusText : \"Unknown Error\"\n return Left(new Error(`Authentication failed: ${response.status} ${statusText}`))\n }\n\n const data = (await response.json()) as { access_token: string; expires_in: number }\n this.accessToken = data.access_token\n this.tokenExpiry = now + data.expires_in * 1000\n this.authenticated = true\n return Right(undefined as void)\n } catch (error) {\n return Left(toError(error))\n }\n }\n\n async checkAuthentication(): Promise<boolean> {\n if (!this.authenticated) {\n const result = await this.authenticate()\n return result.isRight()\n }\n return true\n }\n\n private validateWriteAccess(): void {\n if (this.username === undefined || this.password === undefined) {\n if (this.authMode === \"anonymous\") {\n throw new Error(\n \"Write operations not available in anonymous mode. \" +\n \"Set REDDIT_USERNAME, REDDIT_PASSWORD and use 'auto' or 'authenticated' mode.\",\n )\n }\n throw new Error(\"Write operations require REDDIT_USERNAME and REDDIT_PASSWORD\")\n }\n }\n\n private async enforceWriteRateLimit(): Promise<void> {\n if (!this.safeMode.enabled || this.safeMode.writeDelayMs <= 0) {\n return\n }\n\n const now = Date.now()\n const elapsed = now - this.lastWriteTime\n if (elapsed < this.safeMode.writeDelayMs) {\n const waitTime = this.safeMode.writeDelayMs - elapsed\n console.error(`[SafeMode] Rate limit: waiting ${waitTime}ms before write operation`)\n await new Promise((resolve) => setTimeout(resolve, waitTime))\n }\n this.lastWriteTime = Date.now()\n }\n\n private hashContent(content: string): string {\n return crypto.createHash(\"sha256\").update(content.trim().toLowerCase()).digest(\"hex\")\n }\n\n private checkDuplicateContent(content: string, subreddit?: string): void {\n if (!this.safeMode.enabled || !this.safeMode.duplicateCheck) {\n return\n }\n\n const hash = this.hashContent(content)\n\n const duplicate = this.recentContentRecords.find((record) => record.hash === hash)\n if (duplicate !== undefined) {\n if (subreddit !== undefined && duplicate.subreddit !== \"\" && subreddit !== duplicate.subreddit) {\n throw new Error(\n \"Cross-subreddit duplicate detected. Reddit's Responsible Builder Policy prohibits \" +\n \"posting identical or substantially similar content across multiple subreddits. \" +\n \"Please create unique content for each subreddit.\",\n )\n }\n throw new Error(\n \"Duplicate content detected. Reddit's spam filter may ban your account for posting identical content. \" +\n \"Please modify your content and try again.\",\n )\n }\n\n this.recentContentRecords.push({\n hash,\n subreddit: subreddit ?? \"\",\n timestamp: Date.now(),\n })\n\n this.recentContentRecords = this.recentContentRecords.slice(-this.safeMode.maxRecentHashes)\n }\n\n private appendBotDisclosure(content: string): string {\n if (!this.botDisclosure.enabled || this.botDisclosure.footer === \"\") {\n return content\n }\n return `${content}${this.botDisclosure.footer}`\n }\n\n async getUser(username: string): Promise<Either<Error, RedditUser>> {\n try {\n const response = (await this.makeRequest(`/user/${username}/about.json`)).orThrow()\n if (!response.ok) {\n return Left(new Error(`Failed to get user info for ${username}: HTTP ${response.status}`))\n }\n\n const json = (await response.json()) as RedditApiUserResponse\n const { data } = json\n\n return Right({\n name: data.name,\n id: data.id,\n commentKarma: data.comment_karma,\n linkKarma: data.link_karma,\n totalKarma: data.total_karma ?? data.comment_karma + data.link_karma,\n isMod: data.is_mod,\n isGold: data.is_gold,\n isEmployee: data.is_employee,\n createdUtc: data.created_utc,\n profileUrl: `https://reddit.com/user/${data.name}`,\n })\n } catch (error) {\n return Left(new Error(`Failed to get user info for ${username}: ${toError(error).message}`))\n }\n }\n\n async getSubredditInfo(subredditName: string): Promise<Either<Error, RedditSubreddit>> {\n try {\n const response = (await this.makeRequest(`/r/${subredditName}/about.json`)).orThrow()\n if (!response.ok) {\n return Left(new Error(`Failed to get subreddit info for ${subredditName}: HTTP ${response.status}`))\n }\n\n const json = (await response.json()) as RedditApiSubredditResponse\n const { data } = json\n\n const subreddit: RedditSubreddit = {\n displayName: data.display_name,\n title: data.title,\n description: data.description,\n publicDescription: data.public_description,\n subscribers: data.subscribers,\n activeUserCount: data.active_user_count ?? undefined,\n createdUtc: data.created_utc,\n over18: data.over18,\n subredditType: data.subreddit_type,\n url: data.url,\n }\n\n return Right(subreddit)\n } catch (error) {\n return Left(new Error(`Failed to get subreddit info for ${subredditName}: ${toError(error).message}`))\n }\n }\n\n async getTopPosts(\n subreddit: string,\n timeFilter: string = \"week\",\n limit: number = 10,\n ): Promise<Either<Error, readonly RedditPost[]>> {\n const endpoint = subreddit !== \"\" ? `/r/${subreddit}/top.json` : \"/top.json\"\n const params = new URLSearchParams({\n t: timeFilter,\n limit: limit.toString(),\n })\n\n try {\n const response = (await this.makeRequest(`${endpoint}?${params}`)).orThrow()\n if (!response.ok) {\n return Left(new Error(`Failed to get top posts: HTTP ${response.status}`))\n }\n\n const json = (await response.json()) as RedditApiListingResponse<RedditApiPostData>\n const posts: readonly RedditPost[] = json.data.children.map((child) => parsePostData(child.data))\n return Right(posts)\n } catch (error) {\n return Left(\n new Error(`Failed to get top posts for ${subreddit !== \"\" ? subreddit : \"home\"}: ${toError(error).message}`),\n )\n }\n }\n\n async getPost(postId: string, subreddit?: string): Promise<Either<Error, RedditPost>> {\n const endpoint = Option(subreddit).fold(\n () => `/api/info.json?id=t3_${postId}`,\n (sr) => `/r/${sr}/comments/${postId}.json`,\n )\n\n try {\n const response = (await this.makeRequest(endpoint)).orThrow()\n if (!response.ok) {\n return Left(new Error(`Failed to get post with ID ${postId}: HTTP ${response.status}`))\n }\n\n if (subreddit !== undefined) {\n const json = (await response.json()) as [RedditApiListingResponse<RedditApiPostData>, unknown]\n return Right(parsePostData(json[0].data.children[0].data))\n }\n\n const json = (await response.json()) as RedditApiInfoResponse\n if (json.data.children.length === 0) {\n return Left(new Error(`Post with ID ${postId} not found`))\n }\n return Right(parsePostData(json.data.children[0].data))\n } catch (error) {\n return Left(new Error(`Failed to get post with ID ${postId}: ${toError(error).message}`))\n }\n }\n\n async getTrendingSubreddits(limit: number = 5): Promise<Either<Error, readonly string[]>> {\n const params = new URLSearchParams({ limit: limit.toString() })\n\n try {\n const response = (await this.makeRequest(`/subreddits/popular.json?${params}`)).orThrow()\n if (!response.ok) {\n return Left(new Error(`Failed to get trending subreddits: HTTP ${response.status}`))\n }\n\n const json = (await response.json()) as RedditApiPopularSubredditsResponse\n const names: readonly string[] = json.data.children.map((child) => child.data.display_name)\n return Right(names)\n } catch (error) {\n return Left(new Error(`Failed to get trending subreddits: ${toError(error).message}`))\n }\n }\n\n async createPost(\n subreddit: string,\n title: string,\n content: string,\n isSelf: boolean = true,\n ): Promise<Either<Error, RedditPost>> {\n try {\n this.validateWriteAccess()\n await this.enforceWriteRateLimit()\n this.checkDuplicateContent(title + content, subreddit)\n\n const finalContent = isSelf ? this.appendBotDisclosure(content) : content\n const kind = isSelf ? \"self\" : \"link\"\n const params = new URLSearchParams()\n params.append(\"sr\", subreddit)\n params.append(\"kind\", kind)\n params.append(\"title\", title)\n params.append(isSelf ? \"text\" : \"url\", finalContent)\n params.append(\"api_type\", \"json\")\n\n const response = (\n await this.makeRequest(\"/api/submit\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n },\n body: params.toString(),\n })\n ).orThrow()\n\n if (!response.ok) {\n return Left(new Error(`Failed to create post: HTTP ${response.status}`))\n }\n\n const json = (await response.json()) as RedditApiSubmitResponse\n\n if (json.json.errors !== undefined && json.json.errors.length > 0) {\n const errors = json.json.errors.map((e) => e[1]).join(\", \")\n return Left(new Error(`Reddit API errors: ${errors}`))\n }\n\n const postId = json.json.data?.id ?? json.json.data?.name?.replace(\"t3_\", \"\")\n\n if (postId === undefined) {\n return Left(new Error(\"No post ID returned from Reddit\"))\n }\n\n return this.getPost(postId, subreddit)\n } catch (error) {\n return Left(toError(error))\n }\n }\n\n async checkPostExists(postId: string): Promise<boolean> {\n try {\n const response = (await this.makeRequest(`/api/info.json?id=t3_${postId}`)).orThrow()\n if (!response.ok) {\n return false\n }\n\n const json = (await response.json()) as RedditApiInfoResponse\n return json.data.children.length > 0\n } catch {\n return false\n }\n }\n\n async replyToPost(postId: string, content: string): Promise<Either<Error, RedditComment>> {\n try {\n this.validateWriteAccess()\n await this.enforceWriteRateLimit()\n this.checkDuplicateContent(content)\n\n const finalContent = this.appendBotDisclosure(content)\n const fullThingId = postId.startsWith(\"t3_\") || postId.startsWith(\"t1_\") ? postId : `t3_${postId}`\n\n if (!postId.startsWith(\"t1_\")) {\n const exists = await this.checkPostExists(postId.replace(/^t3_/, \"\"))\n if (!exists) {\n return Left(new Error(`Post with ID ${postId} does not exist or is not accessible`))\n }\n }\n\n const params = new URLSearchParams()\n params.append(\"thing_id\", fullThingId)\n params.append(\"text\", finalContent)\n params.append(\"api_type\", \"json\")\n\n const response = (\n await this.makeRequest(\"/api/comment\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n },\n body: params.toString(),\n })\n ).orThrow()\n\n if (!response.ok) {\n return Left(new Error(`Failed to reply: HTTP ${response.status}`))\n }\n\n const json = (await response.json()) as RedditApiCommentResponse\n\n if (json.json.data?.things !== undefined && json.json.data.things.length > 0) {\n const commentData = json.json.data.things[0].data\n const author = this.username ?? \"[unknown]\"\n const comment: RedditComment = {\n id: commentData.id,\n author,\n body: content,\n score: 1,\n controversiality: 0,\n subreddit: commentData.subreddit,\n submissionTitle: commentData.link_title ?? \"\",\n createdUtc: Date.now() / 1000,\n edited: false,\n isSubmitter: false,\n permalink: commentData.permalink,\n }\n return Right(comment)\n } else if (json.json.errors !== undefined && json.json.errors.length > 0) {\n const errors = json.json.errors.map((e) => e[1]).join(\", \")\n return Left(new Error(`Reddit API errors: ${errors}`))\n } else {\n return Left(new Error(\"Failed to parse reply response\"))\n }\n } catch (error) {\n return Left(toError(error))\n }\n }\n\n async deletePost(thingId: string): Promise<Either<Error, boolean>> {\n try {\n this.validateWriteAccess()\n\n const fullThingId = thingId.startsWith(\"t3_\") || thingId.startsWith(\"t1_\") ? thingId : `t3_${thingId}`\n\n const params = new URLSearchParams()\n params.append(\"id\", fullThingId)\n\n const response = (\n await this.makeRequest(\"/api/del\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n },\n body: params.toString(),\n })\n ).orThrow()\n\n if (!response.ok) {\n const errorText = await response.text()\n console.error(`[Reddit API] Delete failed: ${response.status} ${response.statusText}`)\n console.error(`[Reddit API] Error response: ${errorText}`)\n return Left(new Error(`HTTP ${response.status}: ${errorText}`))\n }\n\n console.error(`[Reddit API] Successfully deleted ${fullThingId}`)\n return Right(true)\n } catch (error) {\n console.error(`[Reddit API] Delete exception:`, error)\n return Left(toError(error))\n }\n }\n\n async deleteComment(thingId: string): Promise<Either<Error, boolean>> {\n const fullThingId = thingId.startsWith(\"t1_\") ? thingId : `t1_${thingId}`\n return this.deletePost(fullThingId)\n }\n\n async editPost(thingId: string, newText: string): Promise<Either<Error, boolean>> {\n try {\n this.validateWriteAccess()\n await this.enforceWriteRateLimit()\n this.checkDuplicateContent(newText)\n\n const finalText = this.appendBotDisclosure(newText)\n const fullThingId = thingId.startsWith(\"t3_\") || thingId.startsWith(\"t1_\") ? thingId : `t3_${thingId}`\n\n const params = new URLSearchParams()\n params.append(\"thing_id\", fullThingId)\n params.append(\"text\", finalText)\n params.append(\"api_type\", \"json\")\n\n const response = (\n await this.makeRequest(\"/api/editusertext\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n },\n body: params.toString(),\n })\n ).orThrow()\n\n if (!response.ok) {\n return Left(new Error(`Failed to edit: HTTP ${response.status}`))\n }\n\n const json = (await response.json()) as RedditApiEditResponse\n\n if (json.json.errors !== undefined && json.json.errors.length > 0) {\n const errors = json.json.errors.map((e) => e[1]).join(\", \")\n return Left(new Error(`Reddit API errors: ${errors}`))\n }\n\n return Right(true)\n } catch (error) {\n return Left(toError(error))\n }\n }\n\n async editComment(thingId: string, newText: string): Promise<Either<Error, boolean>> {\n const fullThingId = thingId.startsWith(\"t1_\") ? thingId : `t1_${thingId}`\n return this.editPost(fullThingId, newText)\n }\n\n async searchReddit(\n query: string,\n options: {\n readonly subreddit?: string\n readonly sort?: string\n readonly timeFilter?: string\n readonly limit?: number\n readonly type?: string\n } = {},\n ): Promise<Either<Error, readonly RedditPost[]>> {\n const { subreddit, sort = \"relevance\", timeFilter = \"all\", limit = 25, type = \"link\" } = options\n const endpoint = Option(subreddit).fold(\n () => \"/search.json\",\n (sr) => `/r/${sr}/search.json`,\n )\n\n const params = new URLSearchParams({\n q: query,\n sort,\n t: timeFilter,\n limit: limit.toString(),\n type,\n // eslint-disable-next-line functype/prefer-fold -- conditional spread of native string | undefined into URLSearchParams init\n ...(subreddit !== undefined ? { restrict_sr: \"true\" } : {}),\n })\n\n try {\n const response = (await this.makeRequest(`${endpoint}?${params}`)).orThrow()\n if (!response.ok) {\n return Left(new Error(`Failed to search Reddit: HTTP ${response.status}`))\n }\n\n const json = (await response.json()) as RedditApiListingResponse<RedditApiPostData>\n\n const posts: readonly RedditPost[] = json.data.children\n .filter((child) => child.kind === \"t3\")\n .map((child) => parsePostData(child.data))\n return Right(posts)\n } catch (error) {\n return Left(new Error(`Failed to search Reddit for: ${query}: ${toError(error).message}`))\n }\n }\n\n async getPostComments(\n postId: string,\n subreddit: string,\n options: {\n readonly sort?: string\n readonly limit?: number\n } = {},\n ): Promise<Either<Error, { readonly post: RedditPost; readonly comments: readonly RedditComment[] }>> {\n const { sort = \"best\", limit = 100 } = options\n const params = new URLSearchParams({\n sort,\n limit: limit.toString(),\n })\n\n try {\n const response = (await this.makeRequest(`/r/${subreddit}/comments/${postId}.json?${params}`)).orThrow()\n if (!response.ok) {\n return Left(new Error(`Failed to get comments: HTTP ${response.status}`))\n }\n\n const json = (await response.json()) as RedditApiPostCommentsResponse\n\n const postData = json[0].data.children[0].data\n const post = parsePostData(postData)\n\n const parseComments = (\n commentData: ReadonlyArray<{ readonly kind: string; readonly data: RedditApiCommentTreeData }>,\n depth: number = 0,\n ): readonly RedditComment[] =>\n commentData.flatMap((item) => {\n if (item.kind !== \"t1\" || item.data.body === undefined) return []\n\n const comment: RedditComment = {\n id: item.data.id,\n author: item.data.author,\n body: item.data.body,\n score: item.data.score,\n controversiality: item.data.controversiality,\n subreddit: item.data.subreddit,\n submissionTitle: post.title,\n createdUtc: item.data.created_utc,\n edited: Boolean(item.data.edited),\n isSubmitter: item.data.is_submitter,\n permalink: item.data.permalink,\n depth,\n parentId: item.data.parent_id,\n }\n\n const { replies } = item.data\n const childComments =\n replies !== undefined && typeof replies !== \"string\" ? parseComments(replies.data.children, depth + 1) : []\n\n return [comment, ...childComments]\n })\n\n const comments: readonly RedditComment[] = parseComments(json[1].data.children)\n\n return Right({ post, comments })\n } catch (error) {\n return Left(new Error(`Failed to get comments for post ${postId}: ${toError(error).message}`))\n }\n }\n\n async getUserPosts(\n username: string,\n options: {\n readonly sort?: string\n readonly timeFilter?: string\n readonly limit?: number\n } = {},\n ): Promise<Either<Error, readonly RedditPost[]>> {\n const { sort = \"new\", timeFilter = \"all\", limit = 25 } = options\n const params = new URLSearchParams({\n sort,\n t: timeFilter,\n limit: limit.toString(),\n })\n\n try {\n const response = (await this.makeRequest(`/user/${username}/submitted.json?${params}`)).orThrow()\n if (!response.ok) {\n return Left(new Error(`Failed to get posts for user ${username}: HTTP ${response.status}`))\n }\n\n const json = (await response.json()) as RedditApiListingResponse<RedditApiPostData>\n\n const posts: readonly RedditPost[] = json.data.children\n .filter((child) => child.kind === \"t3\")\n .map((child) => parsePostData(child.data))\n return Right(posts)\n } catch (error) {\n return Left(new Error(`Failed to get posts for user ${username}: ${toError(error).message}`))\n }\n }\n\n async getUserComments(\n username: string,\n options: {\n readonly sort?: string\n readonly timeFilter?: string\n readonly limit?: number\n } = {},\n ): Promise<Either<Error, readonly RedditComment[]>> {\n const { sort = \"new\", timeFilter = \"all\", limit = 25 } = options\n const params = new URLSearchParams({\n sort,\n t: timeFilter,\n limit: limit.toString(),\n })\n\n try {\n const response = (await this.makeRequest(`/user/${username}/comments.json?${params}`)).orThrow()\n if (!response.ok) {\n return Left(new Error(`Failed to get comments for user ${username}: HTTP ${response.status}`))\n }\n\n const json = (await response.json()) as RedditApiListingResponse<RedditApiCommentTreeData>\n\n const comments: readonly RedditComment[] = json.data.children\n .filter((child) => child.kind === \"t1\")\n .map((child) => {\n const comment = child.data\n return {\n id: comment.id,\n author: comment.author,\n body: comment.body ?? \"\",\n score: comment.score,\n controversiality: comment.controversiality,\n subreddit: comment.subreddit,\n submissionTitle: comment.link_title ?? \"\",\n createdUtc: comment.created_utc,\n edited: Boolean(comment.edited),\n isSubmitter: comment.is_submitter,\n permalink: comment.permalink,\n }\n })\n\n return Right(comments)\n } catch (error) {\n return Left(new Error(`Failed to get comments for user ${username}: ${toError(error).message}`))\n }\n }\n}\n\n// Create and export singleton instance\nconst clientHolder: { instance: Option<RedditClient> } = { instance: Option.none() }\n\nexport function initializeRedditClient(config: RedditClientConfig): RedditClient {\n const client = new RedditClient(config)\n\n clientHolder.instance = Option(client)\n return client\n}\n\nexport function getRedditClient(): Option<RedditClient> {\n return clientHolder.instance\n}\n","import { Option, Try } from \"functype\"\n\nimport type {\n FormattedCommentInfo,\n FormattedPostInfo,\n FormattedSubredditInfo,\n FormattedUserInfo,\n RedditComment,\n RedditPost,\n RedditSubreddit,\n RedditUser,\n} from \"../types\"\n\nexport function formatTimestamp(timestamp: number): string {\n return Try(() => {\n const date = new Date(timestamp * 1000)\n return date\n .toISOString()\n .replace(\"T\", \" \")\n .replace(/\\.\\d+Z$/, \" UTC\")\n }).orElse(String(timestamp))\n}\n\nexport function analyzeUserActivity(karmaRatio: number, isMod: boolean, accountAgeDays: number): string {\n const insights: readonly string[] = [\n ...(karmaRatio > 5\n ? [\"Primarily a commenter, highly engaged in discussions\"]\n : karmaRatio < 0.2\n ? [\"Content creator, focuses on sharing posts\"]\n : [\"Balanced participation in both posting and commenting\"]),\n ...(accountAgeDays < 30\n ? [\"New user, still exploring Reddit\"]\n : accountAgeDays > 365 * 5\n ? [\"Long-time Redditor with extensive platform experience\"]\n : []),\n ...(isMod ? [\"Community leader who helps maintain subreddit quality\"] : []),\n ]\n\n return insights.join(\"\\n - \")\n}\n\nexport function analyzePostEngagement(score: number, ratio: number, numComments: number): string {\n const insights: readonly string[] = [\n ...(score > 1000 && ratio > 0.95\n ? [\"Highly successful post with strong community approval\"]\n : score > 100 && ratio > 0.8\n ? [\"Well-received post with good engagement\"]\n : ratio < 0.5\n ? [\"Controversial post that sparked debate\"]\n : []),\n ...(numComments > 100\n ? [\"Generated significant discussion\"]\n : numComments > score * 0.5\n ? [\"Highly discussable content with active comment section\"]\n : numComments === 0\n ? [\"Yet to receive community interaction\"]\n : []),\n ]\n\n return insights.join(\"\\n - \")\n}\n\nexport function analyzeSubredditHealth(subscribers: number, activeUsers: Option<number>, ageDays: number): string {\n const sizeInsights: readonly string[] =\n subscribers > 1000000\n ? [\"Major subreddit with massive following\"]\n : subscribers > 100000\n ? [\"Well-established community\"]\n : subscribers < 1000\n ? [\"Niche community, potential for growth\"]\n : []\n\n const activityInsights: readonly string[] = activeUsers\n .map((active) => active / subscribers)\n .fold(\n () => [] as readonly string[],\n (activityRatio) =>\n activityRatio > 0.1\n ? [\"Highly active community with strong engagement\"]\n : activityRatio < 0.01\n ? [\"Could benefit from more community engagement initiatives\"]\n : [],\n )\n\n const ageInsights: readonly string[] =\n ageDays > 365 * 5\n ? [\"Mature subreddit with established culture\"]\n : ageDays < 90\n ? [\"New subreddit still forming its community\"]\n : []\n\n return [...sizeInsights, ...activityInsights, ...ageInsights].join(\"\\n - \")\n}\n\nexport function getUserRecommendations(karmaRatio: number, isMod: boolean, accountAgeDays: number): string {\n const recommendations: readonly string[] = [\n ...(karmaRatio > 5\n ? [\"Consider creating more posts to share your expertise\"]\n : karmaRatio < 0.2\n ? [\"Engage more in discussions to build community connections\"]\n : []),\n ...(accountAgeDays < 30\n ? [\"Explore popular subreddits in your areas of interest\", \"Read community guidelines before posting\"]\n : []),\n ...(isMod ? [\"Share moderation insights with other community leaders\"] : []),\n ]\n\n return recommendations.length > 0 ? recommendations.join(\"\\n - \") : \"Maintain your balanced engagement across Reddit\"\n}\n\nexport function getBestEngagementTime(createdUtc: number): string {\n const postHour = new Date(createdUtc * 1000).getHours()\n\n if (14 <= postHour && postHour <= 18) {\n return \"Posted during peak engagement hours (2 PM - 6 PM), good timing!\"\n } else if (23 <= postHour || postHour <= 5) {\n return \"Consider posting during more active hours (morning to evening)\"\n } else {\n return \"Posted during moderate activity hours, timing could be optimized\"\n }\n}\n\nexport function getSubredditEngagementTips(subreddit: RedditSubreddit): string {\n const sizeTips: readonly string[] =\n subreddit.subscribers > 1000000\n ? [\"Post during peak hours for maximum visibility\", \"Ensure content is highly polished due to high competition\"]\n : subreddit.subscribers < 1000\n ? [\"Engage actively to help grow the community\", \"Consider cross-posting to related larger subreddits\"]\n : []\n\n const activityTips: readonly string[] = Option(subreddit.activeUserCount)\n .map((active) => active / subreddit.subscribers)\n .fold(\n () => [] as readonly string[],\n (activityRatio) =>\n activityRatio > 0.1 ? [\"Quick responses recommended due to high activity\"] : ([] as readonly string[]),\n )\n\n const allTips = [...sizeTips, ...activityTips]\n return allTips.length > 0 ? allTips.join(\"\\n - \") : \"Regular engagement recommended to maintain community presence\"\n}\n\nexport function analyzeCommentImpact(score: number, isEdited: boolean, isOp: boolean): string {\n const insights: readonly string[] = [\n ...(score > 100\n ? [\"Highly upvoted comment with significant community agreement\"]\n : score < 0\n ? [\"Controversial or contested viewpoint\"]\n : []),\n ...(isEdited ? [\"Refined for clarity or accuracy\"] : []),\n ...(isOp ? [\"Author's perspective adds context to original post\"] : []),\n ]\n\n return insights.length > 0 ? insights.join(\"\\n - \") : \"Standard engagement with discussion\"\n}\n\nexport function formatUserInfo(user: RedditUser): FormattedUserInfo {\n const accountAgeDays = (Date.now() / 1000 - user.createdUtc) / (24 * 3600)\n const karmaRatio = user.commentKarma / (user.linkKarma === 0 ? 1 : user.linkKarma)\n\n const status: readonly string[] = [\n ...(user.isMod ? [\"Moderator\"] : []),\n ...(user.isGold ? [\"Reddit Gold Member\"] : []),\n ...(user.isEmployee ? [\"Reddit Employee\"] : []),\n ]\n\n return {\n username: user.name,\n karma: {\n commentKarma: user.commentKarma,\n postKarma: user.linkKarma,\n totalKarma: user.totalKarma,\n },\n accountStatus: status.length > 0 ? status : [\"Regular User\"],\n accountCreated: formatTimestamp(user.createdUtc),\n profileUrl: user.profileUrl,\n activityAnalysis: analyzeUserActivity(karmaRatio, user.isMod, accountAgeDays),\n recommendations: getUserRecommendations(karmaRatio, user.isMod, accountAgeDays),\n }\n}\n\nexport function formatPostInfo(post: RedditPost): FormattedPostInfo {\n const contentType = post.isSelf ? \"Text Post\" : \"Link Post\"\n const content = post.isSelf ? (post.selftext ?? \"\") : (post.url ?? \"\")\n\n const flags: readonly string[] = [\n ...(post.over18 ? [\"NSFW\"] : []),\n ...(post.spoiler === true ? [\"Spoiler\"] : []),\n ...(post.edited ? [\"Edited\"] : []),\n ]\n\n return {\n title: post.title,\n type: contentType,\n content: content.length > 300 ? `${content.substring(0, 297)}...` : content,\n author: post.author,\n subreddit: post.subreddit,\n stats: {\n score: post.score,\n upvoteRatio: post.upvoteRatio,\n comments: post.numComments,\n },\n metadata: {\n posted: formatTimestamp(post.createdUtc),\n flags,\n flair: post.linkFlairText ?? \"None\",\n },\n links: {\n fullPost: `https://reddit.com${post.permalink}`,\n shortLink: `https://redd.it/${post.id}`,\n },\n engagementAnalysis: analyzePostEngagement(post.score, post.upvoteRatio, post.numComments),\n bestTimeToEngage: getBestEngagementTime(post.createdUtc),\n }\n}\n\nexport function formatSubredditInfo(subreddit: RedditSubreddit): FormattedSubredditInfo {\n const flags: readonly string[] = [\n ...(subreddit.over18 ? [\"NSFW\"] : []),\n ...Option(subreddit.subredditType).fold(\n () => [] as readonly string[],\n (type) => [`Type: ${type}`] as readonly string[],\n ),\n ]\n\n const ageDays = (Date.now() / 1000 - subreddit.createdUtc) / (24 * 3600)\n\n return {\n name: subreddit.displayName,\n title: subreddit.title,\n stats: {\n subscribers: subreddit.subscribers,\n activeUsers: Option(subreddit.activeUserCount).fold(\n () => \"Unknown\" as number | string,\n (count) => count as number | string,\n ),\n },\n description: {\n short: subreddit.publicDescription,\n full:\n subreddit.description.length > 300 ? `${subreddit.description.substring(0, 297)}...` : subreddit.description,\n },\n metadata: {\n created: formatTimestamp(subreddit.createdUtc),\n flags: flags.length > 0 ? flags : [\"None\"],\n },\n links: {\n subreddit: `https://reddit.com${subreddit.url}`,\n wiki: `https://reddit.com/r/${subreddit.displayName}/wiki`,\n },\n communityAnalysis: analyzeSubredditHealth(subreddit.subscribers, Option(subreddit.activeUserCount), ageDays),\n engagementTips: getSubredditEngagementTips(subreddit),\n }\n}\n\nexport function formatCommentInfo(comment: RedditComment): FormattedCommentInfo {\n const flags: readonly string[] = [...(comment.edited ? [\"Edited\"] : []), ...(comment.isSubmitter ? [\"OP\"] : [])]\n\n return {\n author: comment.author,\n content: comment.body.length > 300 ? `${comment.body.substring(0, 297)}...` : comment.body,\n stats: {\n score: comment.score,\n controversiality: comment.controversiality,\n },\n context: {\n subreddit: comment.subreddit,\n thread: comment.submissionTitle,\n },\n metadata: {\n posted: formatTimestamp(comment.createdUtc),\n flags: flags.length > 0 ? flags : [\"None\"],\n },\n link: `https://reddit.com${comment.permalink}`,\n commentAnalysis: analyzeCommentImpact(comment.score, comment.edited, comment.isSubmitter),\n }\n}\n\n// Simple formatter for posts (used in search and comment tools)\nexport function formatPost(post: RedditPost) {\n return {\n title: post.title,\n author: post.author,\n subreddit: post.subreddit,\n score: post.score,\n upvoteRatio: Math.round(post.upvoteRatio * 100),\n numComments: post.numComments,\n createdAt: formatTimestamp(post.createdUtc),\n selftext: post.selftext,\n permalink: post.permalink,\n nsfw: post.over18,\n spoiler: post.spoiler,\n }\n}\n","import crypto from \"crypto\"\nimport dotenv from \"dotenv\"\nimport { FastMCP } from \"fastmcp\"\nimport { Option } from \"functype\"\nimport { z } from \"zod\"\n\nimport { getRedditClient, initializeRedditClient } from \"./client/reddit-client\"\nimport type { BotDisclosureConfig, RedditAuthMode, RedditSafeMode, SafeModeConfig } from \"./types\"\nimport { formatPostInfo, formatSubredditInfo, formatUserInfo } from \"./utils/formatters\"\n\n// Load environment variables\ndotenv.config({ quiet: true })\n\n// Version injected at build time by tsdown\ndeclare const __VERSION__: string\nconst VERSION = (typeof __VERSION__ !== \"undefined\" ? __VERSION__ : \"0.0.0-dev\") as `${number}.${number}.${number}`\n\n// User-Agent validation and building\nfunction validateUserAgent(userAgent: string, username?: string): void {\n const recommendedPattern = /^[\\w-]+:[\\w-]+:[\\d.]+ \\(by \\/u\\/\\w+\\)$/\n if (!recommendedPattern.test(userAgent)) {\n console.error(\"[Warning] User-Agent does not follow Reddit's recommended format\")\n console.error(\"[Warning] Recommended: 'platform:app_id:version (by /u/username)'\")\n console.error(\"[Warning] Non-standard User-Agents may increase ban risk\")\n if (username !== undefined) {\n console.error(`[Warning] Consider using: 'typescript:reddit-mcp-server:${VERSION} (by /u/${username})'`)\n }\n }\n}\n\nfunction buildUserAgent(customAgent?: string, username?: string): string {\n if (customAgent !== undefined) {\n validateUserAgent(customAgent, username)\n return customAgent\n }\n\n if (username !== undefined) {\n const autoAgent = `typescript:reddit-mcp-server:${VERSION} (by /u/${username})`\n console.error(`[Setup] Auto-generated User-Agent: ${autoAgent}`)\n return autoAgent\n }\n\n const fallbackAgent = `typescript:reddit-mcp-server:${VERSION} (by /u/anonymous)`\n console.error(\n \"[Setup] No REDDIT_USERNAME set — using anonymous User-Agent. Set REDDIT_USERNAME for a personalized agent.\",\n )\n return fallbackAgent\n}\n\n// Safe mode configuration\nfunction buildSafeModeConfig(safeMode: RedditSafeMode): SafeModeConfig {\n switch (safeMode) {\n case \"off\":\n return {\n enabled: false,\n mode: \"off\",\n writeDelayMs: 0,\n duplicateCheck: false,\n maxRecentHashes: 10,\n }\n case \"standard\":\n return {\n enabled: true,\n mode: \"standard\",\n writeDelayMs: 2000,\n duplicateCheck: true,\n maxRecentHashes: 10,\n }\n case \"strict\":\n return {\n enabled: true,\n mode: \"strict\",\n writeDelayMs: 5000,\n duplicateCheck: true,\n maxRecentHashes: 20,\n }\n }\n}\n\nfunction unwrapClient() {\n return getRedditClient().orThrow(new Error(\"Reddit client not initialized\"))\n}\n\n// Initialize Reddit client\nasync function setupRedditClient() {\n const clientId = process.env.REDDIT_CLIENT_ID\n const clientSecret = process.env.REDDIT_CLIENT_SECRET\n const customUserAgent = process.env.REDDIT_USER_AGENT\n const username = process.env.REDDIT_USERNAME\n const password = process.env.REDDIT_PASSWORD\n const authMode = (process.env.REDDIT_AUTH_MODE ?? \"auto\") as RedditAuthMode\n const safeMode = (process.env.REDDIT_SAFE_MODE ?? \"standard\") as RedditSafeMode\n\n // Validate auth mode\n if (![\"auto\", \"authenticated\", \"anonymous\"].includes(authMode)) {\n console.error(`[Error] Invalid REDDIT_AUTH_MODE: ${authMode}`)\n console.error(\"[Error] Valid options are: auto, authenticated, anonymous\")\n process.exit(1)\n }\n\n // Validate safe mode\n if (![\"off\", \"standard\", \"strict\"].includes(safeMode)) {\n console.error(`[Error] Invalid REDDIT_SAFE_MODE: ${safeMode}`)\n console.error(\"[Error] Valid options are: off, standard, strict\")\n process.exit(1)\n }\n\n // In authenticated mode, require credentials\n if (authMode === \"authenticated\" && (clientId === undefined || clientSecret === undefined)) {\n console.error(\"[Error] Authenticated mode requires REDDIT_CLIENT_ID and REDDIT_CLIENT_SECRET\")\n process.exit(1)\n }\n\n // For auto/anonymous, credentials are optional\n const hasCredentials = Boolean(clientId && clientSecret)\n\n // Build user-agent (auto-format with username if available)\n const userAgent = buildUserAgent(customUserAgent, username)\n\n // Build safe mode config\n const safeModeConfig = buildSafeModeConfig(safeMode)\n\n // Build bot disclosure config\n const botDisclosureMode = process.env.REDDIT_BOT_DISCLOSURE ?? \"off\"\n const defaultFooter =\n \"\\n\\n---\\n^(🤖 I am a bot | Built with) [^reddit-mcp-server](https://github.com/jordanburke/reddit-mcp-server)\"\n const botDisclosureConfig: BotDisclosureConfig = {\n enabled: botDisclosureMode === \"auto\",\n footer: botDisclosureMode === \"auto\" ? (process.env.REDDIT_BOT_FOOTER ?? defaultFooter) : \"\",\n }\n\n const client = initializeRedditClient({\n clientId: clientId ?? \"\",\n clientSecret: clientSecret ?? \"\",\n userAgent,\n username,\n password,\n authMode,\n safeMode: safeModeConfig,\n botDisclosure: botDisclosureConfig,\n })\n\n console.error(\"[Setup] Reddit client initialized\")\n console.error(`[Setup] Authentication mode: ${authMode}`)\n\n if (authMode === \"anonymous\" || !hasCredentials) {\n console.error(\"[Setup] Using anonymous Reddit API (~10 req/min)\")\n console.error(\"[Setup] No authentication required - ready to use!\")\n } else {\n console.error(\"[Setup] Testing Reddit API connection...\")\n const isConnected = await client.checkAuthentication()\n\n if (!isConnected) {\n console.error(\"[Error] ✗ Failed to connect to Reddit API\")\n console.error(\"[Error] Please check your REDDIT_CLIENT_ID and REDDIT_CLIENT_SECRET\")\n process.exit(1)\n }\n\n console.error(\"[Setup] ✓ Reddit API connection successful\")\n console.error(\"[Setup] Using OAuth Reddit API (60-100 req/min)\")\n }\n\n if (username !== undefined && password !== undefined) {\n console.error(`[Setup] ✓ User authenticated as: ${username}`)\n console.error(\"[Setup] Write operations enabled (posting, replying, editing, deleting)\")\n } else {\n console.error(\"[Setup] Read-only mode (no user credentials)\")\n console.error(\"[Setup] For write operations, set REDDIT_USERNAME and REDDIT_PASSWORD\")\n }\n\n // Log safe mode status\n if (safeModeConfig.enabled) {\n console.error(`[Setup] ✓ Safe mode enabled: ${safeModeConfig.mode}`)\n console.error(`[Setup] - Write delay: ${safeModeConfig.writeDelayMs}ms between operations`)\n console.error(`[Setup] - Duplicate detection: enabled (tracking last ${safeModeConfig.maxRecentHashes} items)`)\n } else {\n console.error(\n \"[Setup] Safe mode: off (explicitly disabled — ensure compliance with Reddit's Responsible Builder Policy)\",\n )\n }\n\n // Log bot disclosure status\n if (botDisclosureConfig.enabled) {\n console.error(\"[Setup] ✓ Bot disclosure: enabled (automated content will include bot footer)\")\n } else {\n console.error(\"[Setup] Bot disclosure: off\")\n console.error(\"[Setup] For Reddit policy compliance, consider REDDIT_BOT_DISCLOSURE=auto\")\n }\n}\n\n// OAuth token: generate once at startup, never expose in responses\nconst oauthToken = process.env.OAUTH_TOKEN ?? crypto.randomBytes(32).toString(\"hex\")\nif (process.env.OAUTH_ENABLED === \"true\" && process.env.OAUTH_TOKEN === undefined) {\n console.error(`[Auth] Generated OAuth token: ${oauthToken}`)\n}\n\n// Create FastMCP server\nconst server = new FastMCP({\n name: \"reddit-mcp-server\",\n version: VERSION,\n instructions: `A comprehensive Reddit MCP server that provides tools for interacting with Reddit API.\n\nAvailable capabilities:\n- Fetch Reddit posts, comments, and user information\n- Get subreddit details and statistics\n- Search Reddit content across posts and subreddits\n- Create posts and reply to posts/comments (with authentication)\n- Edit your own posts and comments (with authentication)\n- Delete your own posts and comments (with authentication)\n- Analyze engagement metrics and community insights\n\nFor write operations (posting, replying, editing, deleting), ensure REDDIT_USERNAME and REDDIT_PASSWORD are configured.\n\nIMPORTANT - Reddit Responsible Builder Policy compliance:\n- Data retrieved via these tools must NOT be used for AI model training without Reddit's written approval\n- Data must NOT be sold, licensed, or commercially redistributed\n- Do NOT attempt to de-anonymize or re-identify Reddit users\n- Do NOT post identical or substantially similar content across multiple subreddits\n- Do NOT use these tools to manipulate votes, karma, or circumvent Reddit safety mechanisms\n- All bot-generated content must clearly disclose its automated nature\n- Bots must NOT send private/direct messages without explicit user consent\nFor details: https://support.reddithelp.com/hc/en-us/articles/42728983564564-Responsible-Builder-Policy`,\n\n // Optional OAuth configuration for HTTP transport\n ...(process.env.OAUTH_ENABLED === \"true\" && {\n authenticate: (request: { readonly headers: { readonly authorization?: string } }) => {\n const authHeader = request.headers.authorization\n if (!authHeader?.startsWith(\"Bearer \")) {\n // eslint-disable-next-line functype/prefer-either\n throw new Response(null, {\n status: 401,\n statusText: \"Missing or invalid Authorization header\",\n })\n }\n\n const token = authHeader.slice(7)\n const tokenBuffer = Buffer.from(token)\n const expectedBuffer = Buffer.from(oauthToken)\n const tokenHash = crypto.createHash(\"sha256\").update(tokenBuffer).digest()\n const expectedHash = crypto.createHash(\"sha256\").update(expectedBuffer).digest()\n if (!crypto.timingSafeEqual(tokenHash, expectedHash)) {\n // eslint-disable-next-line functype/prefer-either\n throw new Response(null, {\n status: 403,\n statusText: \"Invalid token\",\n })\n }\n\n return Promise.resolve({ authenticated: true })\n },\n }),\n})\n\n// Test tool\nserver.addTool({\n name: \"test_reddit_mcp_server\",\n description: \"Test the Reddit MCP Server connection and configuration\",\n parameters: z.object({}),\n execute: () => {\n const client = getRedditClient()\n const hasAuth = client.fold(\n () => \"✗\",\n () => \"✓\",\n )\n const hasWriteAccess =\n process.env.REDDIT_USERNAME !== undefined && process.env.REDDIT_PASSWORD !== undefined ? \"✓\" : \"✗\"\n\n return Promise.resolve(`Reddit MCP Server Status:\n- Server: ✓ Running\n- Reddit Client: ${hasAuth} ${client.fold(\n () => \"Not initialized\",\n () => \"Initialized\",\n )}\n- Write Access: ${hasWriteAccess} ${hasWriteAccess === \"✓\" ? \"Available\" : \"Read-only mode\"}\n- Version: ${VERSION}\n\nReady to handle Reddit API requests!`)\n },\n})\n\n// User tools\nserver.addTool({\n name: \"get_user_info\",\n description: \"Get detailed information about a Reddit user including karma, account status, and activity analysis\",\n parameters: z.object({\n username: z.string().describe(\"The Reddit username (without u/ prefix)\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n const result = await client.getUser(args.username)\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to get user info: ${err.message}`)\n },\n (user) => {\n const formattedUser = formatUserInfo(user)\n\n return `# User Information: u/${formattedUser.username}\n\n## Profile Overview\n- Username: u/${formattedUser.username}\n- Karma:\n - Comment Karma: ${formattedUser.karma.commentKarma.toLocaleString()}\n - Post Karma: ${formattedUser.karma.postKarma.toLocaleString()}\n - Total Karma: ${formattedUser.karma.totalKarma.toLocaleString()}\n- Account Status: ${formattedUser.accountStatus.join(\", \")}\n- Account Created: ${formattedUser.accountCreated}\n- Profile URL: ${formattedUser.profileUrl}\n\n## Activity Analysis\n- ${formattedUser.activityAnalysis.replace(/\\n {2}- /g, \"\\n- \")}\n\n## Recommendations\n- ${formattedUser.recommendations.replace(/\\n {2}- /g, \"\\n- \")}`\n },\n )\n },\n})\n\nserver.addTool({\n name: \"get_user_posts\",\n description: \"Get recent posts by a Reddit user with sorting and filtering options\",\n parameters: z.object({\n username: z.string().describe(\"The Reddit username (without u/ prefix)\"),\n sort: z.enum([\"new\", \"hot\", \"top\"]).default(\"new\").describe(\"Sort order for posts\"),\n time_filter: z\n .enum([\"hour\", \"day\", \"week\", \"month\", \"year\", \"all\"])\n .default(\"all\")\n .describe(\"Time filter for top posts\"),\n limit: z.number().min(1).max(100).default(10).describe(\"Number of posts to retrieve\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n const result = await client.getUserPosts(args.username, {\n sort: args.sort,\n timeFilter: args.time_filter,\n limit: args.limit,\n })\n\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to get user posts: ${err.message}`)\n },\n (posts) => {\n if (posts.length === 0) {\n return `No posts found for u/${args.username} with the specified filters.`\n }\n\n const postSummaries = posts\n .map((post, index) => {\n const flags = [...(post.over18 ? [\"**NSFW**\"] : []), ...(post.spoiler === true ? [\"**Spoiler**\"] : [])]\n\n return `### ${index + 1}. ${post.title} ${flags.join(\" \")}\n- Subreddit: r/${post.subreddit}\n- Score: ${post.score.toLocaleString()} (${(post.upvoteRatio * 100).toFixed(1)}% upvoted)\n- Comments: ${post.numComments.toLocaleString()}\n- Posted: ${new Date(post.createdUtc * 1000).toLocaleString()}\n- Link: https://reddit.com${post.permalink}`\n })\n .join(\"\\n\\n\")\n\n return `# Posts by u/${args.username} (${args.sort} - ${args.time_filter})\n\n${postSummaries}`\n },\n )\n },\n})\n\nserver.addTool({\n name: \"get_user_comments\",\n description: \"Get recent comments by a Reddit user with sorting and filtering options\",\n parameters: z.object({\n username: z.string().describe(\"The Reddit username (without u/ prefix)\"),\n sort: z.enum([\"new\", \"hot\", \"top\"]).default(\"new\").describe(\"Sort order for comments\"),\n time_filter: z\n .enum([\"hour\", \"day\", \"week\", \"month\", \"year\", \"all\"])\n .default(\"all\")\n .describe(\"Time filter for top comments\"),\n limit: z.number().min(1).max(100).default(10).describe(\"Number of comments to retrieve\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n const result = await client.getUserComments(args.username, {\n sort: args.sort,\n timeFilter: args.time_filter,\n limit: args.limit,\n })\n\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to get user comments: ${err.message}`)\n },\n (comments) => {\n if (comments.length === 0) {\n return `No comments found for u/${args.username} with the specified filters.`\n }\n\n const commentSummaries = comments\n .map((comment, index) => {\n const truncatedBody = comment.body.length > 300 ? `${comment.body.substring(0, 300)}...` : comment.body\n\n const flags = [...(comment.edited ? [\"*(edited)*\"] : []), ...(comment.isSubmitter ? [\"**OP**\"] : [])]\n\n return `### ${index + 1}. Comment ${flags.join(\" \")}\nIn r/${comment.subreddit} on \"${comment.submissionTitle}\"\n\n> ${truncatedBody}\n\n- Score: ${comment.score.toLocaleString()}\n- Posted: ${new Date(comment.createdUtc * 1000).toLocaleString()}\n- Link: https://reddit.com${comment.permalink}`\n })\n .join(\"\\n\\n\")\n\n return `# Comments by u/${args.username} (${args.sort} - ${args.time_filter})\n\n${commentSummaries}`\n },\n )\n },\n})\n\n// Post tools\nserver.addTool({\n name: \"get_reddit_post\",\n description:\n \"Get detailed information about a specific Reddit post including content, stats, and engagement analysis\",\n parameters: z.object({\n subreddit: z.string().describe(\"The subreddit name (without r/ prefix)\"),\n post_id: z.string().describe(\"The Reddit post ID\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n const result = await client.getPost(args.post_id, args.subreddit)\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to get post: ${err.message}`)\n },\n (post) => {\n const formattedPost = formatPostInfo(post)\n\n return `# Post from r/${formattedPost.subreddit}\n\n## Post Details\n- Title: ${formattedPost.title}\n- Type: ${formattedPost.type}\n- Author: u/${formattedPost.author}\n\n## Content\n${formattedPost.content}\n\n## Stats\n- Score: ${formattedPost.stats.score.toLocaleString()}\n- Upvote Ratio: ${(formattedPost.stats.upvoteRatio * 100).toFixed(1)}%\n- Comments: ${formattedPost.stats.comments.toLocaleString()}\n\n## Metadata\n- Posted: ${formattedPost.metadata.posted}\n- Flags: ${formattedPost.metadata.flags.length > 0 ? formattedPost.metadata.flags.join(\", \") : \"None\"}\n- Flair: ${formattedPost.metadata.flair}\n\n## Links\n- Full Post: ${formattedPost.links.fullPost}\n- Short Link: ${formattedPost.links.shortLink}\n\n## Engagement Analysis\n- ${formattedPost.engagementAnalysis.replace(/\\n {2}- /g, \"\\n- \")}\n\n## Best Time to Engage\n${formattedPost.bestTimeToEngage}`\n },\n )\n },\n})\n\nserver.addTool({\n name: \"get_top_posts\",\n description: \"Get top posts from a subreddit or from the Reddit home feed\",\n parameters: z.object({\n subreddit: z.string().optional().describe(\"The subreddit name (without r/ prefix). Leave empty for home feed\"),\n time_filter: z\n .enum([\"hour\", \"day\", \"week\", \"month\", \"year\", \"all\"])\n .default(\"week\")\n .describe(\"Time period for top posts\"),\n limit: z.number().min(1).max(100).default(10).describe(\"Number of posts to retrieve\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n const result = await client.getTopPosts(args.subreddit ?? \"\", args.time_filter, args.limit)\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to get top posts: ${err.message}`)\n },\n (posts) => {\n if (posts.length === 0) {\n const location = Option(args.subreddit).fold(\n () => \"home feed\",\n (sr) => `r/${sr}`,\n )\n return `No posts found in ${location} for the specified time period.`\n }\n\n const formattedPosts = posts.map(formatPostInfo)\n const postSummaries = formattedPosts\n .map(\n (post, index) => `### ${index + 1}. ${post.title}\n- Author: u/${post.author}\n- Score: ${post.stats.score.toLocaleString()} (${(post.stats.upvoteRatio * 100).toFixed(1)}% upvoted)\n- Comments: ${post.stats.comments.toLocaleString()}\n- Posted: ${post.metadata.posted}\n- Link: ${post.links.shortLink}`,\n )\n .join(\"\\n\\n\")\n\n const location = Option(args.subreddit).fold(\n () => \"Home Feed\",\n (sr) => `r/${sr}`,\n )\n return `# Top Posts from ${location} (${args.time_filter})\n\n${postSummaries}`\n },\n )\n },\n})\n\n// Subreddit tools\nserver.addTool({\n name: \"get_subreddit_info\",\n description: \"Get detailed information about a subreddit including description, stats, and community analysis\",\n parameters: z.object({\n subreddit_name: z.string().describe(\"The subreddit name (without r/ prefix)\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n const result = await client.getSubredditInfo(args.subreddit_name)\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to get subreddit info: ${err.message}`)\n },\n (subreddit) => {\n const formattedSubreddit = formatSubredditInfo(subreddit)\n\n return `# Subreddit Information: r/${formattedSubreddit.name}\n\n## Overview\n- Name: r/${formattedSubreddit.name}\n- Title: ${formattedSubreddit.title}\n- Subscribers: ${formattedSubreddit.stats.subscribers.toLocaleString()}\n- Active Users: ${\n typeof formattedSubreddit.stats.activeUsers === \"number\"\n ? formattedSubreddit.stats.activeUsers.toLocaleString()\n : formattedSubreddit.stats.activeUsers\n }\n\n## Description\n${formattedSubreddit.description.short}\n\n## Detailed Description\n${formattedSubreddit.description.full}\n\n## Metadata\n- Created: ${formattedSubreddit.metadata.created}\n- Flags: ${formattedSubreddit.metadata.flags.join(\", \")}\n\n## Links\n- Subreddit: ${formattedSubreddit.links.subreddit}\n- Wiki: ${formattedSubreddit.links.wiki}\n\n## Community Analysis\n- ${formattedSubreddit.communityAnalysis.replace(/\\n {2}- /g, \"\\n- \")}\n\n## Engagement Tips\n- ${formattedSubreddit.engagementTips.replace(/\\n {2}- /g, \"\\n- \")}`\n },\n )\n },\n})\n\nserver.addTool({\n name: \"get_trending_subreddits\",\n description: \"Get a list of currently trending subreddits\",\n parameters: z.object({}),\n execute: async () => {\n const client = unwrapClient()\n\n const result = await client.getTrendingSubreddits()\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to get trending subreddits: ${err.message}`)\n },\n (trendingSubreddits) => `# Trending Subreddits\n\n${trendingSubreddits.map((subreddit, index) => `${index + 1}. r/${subreddit}`).join(\"\\n\")}`,\n )\n },\n})\n\n// Search tools\nserver.addTool({\n name: \"search_reddit\",\n description: \"Search Reddit for posts and content across subreddits\",\n parameters: z.object({\n query: z.string().describe(\"Search query\"),\n subreddit: z.string().optional().describe(\"Limit search to specific subreddit (without r/ prefix)\"),\n sort: z.enum([\"relevance\", \"hot\", \"top\", \"new\", \"comments\"]).default(\"relevance\").describe(\"Sort order\"),\n time_filter: z.enum([\"hour\", \"day\", \"week\", \"month\", \"year\", \"all\"]).default(\"all\").describe(\"Time filter\"),\n limit: z.number().min(1).max(100).default(10).describe(\"Number of results\"),\n type: z.enum([\"link\", \"sr\", \"user\"]).default(\"link\").describe(\"Type of content to search\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n if (args.query.trim() === \"\") {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(\"Search query cannot be empty\")\n }\n\n const result = await client.searchReddit(args.query, {\n subreddit: args.subreddit,\n sort: args.sort,\n timeFilter: args.time_filter,\n limit: args.limit,\n type: args.type,\n })\n\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to search: ${err.message}`)\n },\n (posts) => {\n if (posts.length === 0) {\n const searchLocation = Option(args.subreddit).fold(\n () => \"\",\n (sr) => ` in r/${sr}`,\n )\n return `No results found for \"${args.query}\"${searchLocation}.`\n }\n\n const searchResults = posts\n .map((post, index) => {\n const flags = [...(post.over18 ? [\"**NSFW**\"] : []), ...(post.spoiler === true ? [\"**Spoiler**\"] : [])]\n\n return `### ${index + 1}. ${post.title} ${flags.join(\" \")}\n- Subreddit: r/${post.subreddit}\n- Author: u/${post.author}\n- Score: ${post.score.toLocaleString()} (${(post.upvoteRatio * 100).toFixed(1)}% upvoted)\n- Comments: ${post.numComments.toLocaleString()}\n- Posted: ${new Date(post.createdUtc * 1000).toLocaleString()}\n- Link: https://reddit.com${post.permalink}`\n })\n .join(\"\\n\\n\")\n\n const searchLocation = Option(args.subreddit).fold(\n () => \"\",\n (sr) => ` in r/${sr}`,\n )\n return `# Reddit Search Results for: \"${args.query}\"${searchLocation}\n\nSorted by: ${args.sort} | Time: ${args.time_filter} | Type: ${args.type}\n\n${searchResults}`\n },\n )\n },\n})\n\n// Write tools (require user authentication)\nserver.addTool({\n name: \"create_post\",\n description:\n \"Create a new post in a subreddit (requires REDDIT_USERNAME and REDDIT_PASSWORD). \" +\n \"WARNING: Rapid posting or duplicate content may trigger Reddit's spam detection and result in account bans. \" +\n \"Consider enabling REDDIT_SAFE_MODE=standard for protection.\",\n parameters: z.object({\n subreddit: z.string().describe(\"The subreddit name (without r/ prefix)\"),\n title: z.string().describe(\"The post title\"),\n content: z.string().describe(\"The post content (text for self posts, URL for link posts)\"),\n is_self: z.boolean().default(true).describe(\"Whether this is a self post (text) or link post\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n if (process.env.REDDIT_USERNAME === undefined || process.env.REDDIT_PASSWORD === undefined) {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(\n \"User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables.\",\n )\n }\n\n const result = await client.createPost(args.subreddit, args.title, args.content, args.is_self)\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to create post: ${err.message}`)\n },\n (post) => {\n const formattedPost = formatPostInfo(post)\n\n return `# Post Created Successfully\n\n## Post Details\n- Title: ${formattedPost.title}\n- Subreddit: r/${formattedPost.subreddit}\n- Type: ${formattedPost.type}\n- Link: ${formattedPost.links.fullPost}\n\nYour post has been successfully submitted to r/${formattedPost.subreddit}.`\n },\n )\n },\n})\n\nserver.addTool({\n name: \"reply_to_post\",\n description:\n \"Post a reply to an existing Reddit post or comment (requires REDDIT_USERNAME and REDDIT_PASSWORD). \" +\n \"WARNING: Rapid commenting or duplicate content may trigger Reddit's spam detection. \" +\n \"Enable REDDIT_SAFE_MODE=standard for rate limiting and duplicate detection.\",\n parameters: z.object({\n post_id: z.string().describe(\"The Reddit post ID (thing_id, e.g., t3_xxxxx for posts, t1_xxxxx for comments)\"),\n content: z.string().describe(\"The reply content\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n if (process.env.REDDIT_USERNAME === undefined || process.env.REDDIT_PASSWORD === undefined) {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(\n \"User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables.\",\n )\n }\n\n const result = await client.replyToPost(args.post_id, args.content)\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to reply: ${err.message}`)\n },\n (comment) => `# Reply Posted Successfully\n\n## Comment Details\n- Posted to: ${args.post_id}\n- Author: u/${process.env.REDDIT_USERNAME}\n- Comment ID: ${comment.id}\n\nYour reply has been successfully posted.`,\n )\n },\n})\n\nserver.addTool({\n name: \"delete_post\",\n description:\n \"Delete your own Reddit post (requires REDDIT_USERNAME and REDDIT_PASSWORD). WARNING: This action is permanent and cannot be undone!\",\n parameters: z.object({\n thing_id: z\n .string()\n .describe(\n \"The full Reddit thing ID (e.g., 't3_abc123' for posts) or just the post ID (e.g., 'abc123'). The 't3_' prefix will be added automatically if missing.\",\n ),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n if (process.env.REDDIT_USERNAME === undefined || process.env.REDDIT_PASSWORD === undefined) {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(\n \"User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables.\",\n )\n }\n\n const result = await client.deletePost(args.thing_id)\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to delete post: ${err.message}`)\n },\n () => `# Post Deleted Successfully\n\nThe post ${args.thing_id} has been permanently deleted from Reddit.\n\n**Note**: This action cannot be undone. The post content has been removed and cannot be recovered.`,\n )\n },\n})\n\nserver.addTool({\n name: \"delete_comment\",\n description:\n \"Delete your own Reddit comment (requires REDDIT_USERNAME and REDDIT_PASSWORD). WARNING: This action is permanent and cannot be undone!\",\n parameters: z.object({\n thing_id: z\n .string()\n .describe(\n \"The full Reddit thing ID (e.g., 't1_abc123' for comments) or just the comment ID (e.g., 'abc123'). The 't1_' prefix will be added automatically if missing.\",\n ),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n if (process.env.REDDIT_USERNAME === undefined || process.env.REDDIT_PASSWORD === undefined) {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(\n \"User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables.\",\n )\n }\n\n const result = await client.deleteComment(args.thing_id)\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to delete comment: ${err.message}`)\n },\n () => `# Comment Deleted Successfully\n\nThe comment ${args.thing_id} has been permanently deleted from Reddit.\n\n**Note**: This action cannot be undone. The comment content has been removed and cannot be recovered.`,\n )\n },\n})\n\nserver.addTool({\n name: \"edit_post\",\n description:\n \"Edit your own Reddit post (self-text posts only, requires REDDIT_USERNAME and REDDIT_PASSWORD). \" +\n \"You can only edit the text content of self posts, not titles or link posts. \" +\n \"WARNING: Rapid edits may trigger spam detection. Enable REDDIT_SAFE_MODE for protection.\",\n parameters: z.object({\n thing_id: z\n .string()\n .describe(\n \"The full Reddit thing ID (e.g., 't3_abc123' for posts) or just the post ID (e.g., 'abc123'). The 't3_' prefix will be added automatically if missing.\",\n ),\n new_text: z.string().describe(\"The new text content for the post. Supports Reddit markdown formatting.\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n if (process.env.REDDIT_USERNAME === undefined || process.env.REDDIT_PASSWORD === undefined) {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(\n \"User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables.\",\n )\n }\n\n const result = await client.editPost(args.thing_id, args.new_text)\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to edit post: ${err.message}`)\n },\n () => `# Post Edited Successfully\n\nThe post ${args.thing_id} has been updated with your new content.\n\n**Note**:\n- Only self (text) posts can be edited\n- Post titles cannot be edited\n- Link posts cannot be edited\n- An \"edited\" marker will appear on your post`,\n )\n },\n})\n\nserver.addTool({\n name: \"edit_comment\",\n description:\n \"Edit your own Reddit comment (requires REDDIT_USERNAME and REDDIT_PASSWORD). \" +\n \"Update the text content of a comment you previously posted. \" +\n \"WARNING: Rapid edits may trigger spam detection. Enable REDDIT_SAFE_MODE for protection.\",\n parameters: z.object({\n thing_id: z\n .string()\n .describe(\n \"The full Reddit thing ID (e.g., 't1_abc123' for comments) or just the comment ID (e.g., 'abc123'). The 't1_' prefix will be added automatically if missing.\",\n ),\n new_text: z.string().describe(\"The new text content for the comment. Supports Reddit markdown formatting.\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n if (process.env.REDDIT_USERNAME === undefined || process.env.REDDIT_PASSWORD === undefined) {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(\n \"User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables.\",\n )\n }\n\n const result = await client.editComment(args.thing_id, args.new_text)\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to edit comment: ${err.message}`)\n },\n () => `# Comment Edited Successfully\n\nThe comment ${args.thing_id} has been updated with your new content.\n\n**Note**: An \"edited\" marker will appear on your comment to show it has been modified.`,\n )\n },\n})\n\n// Comment tools\nserver.addTool({\n name: \"get_post_comments\",\n description: \"Get comments from a specific Reddit post\",\n parameters: z.object({\n post_id: z.string().describe(\"The Reddit post ID\"),\n subreddit: z.string().describe(\"The subreddit name (without r/ prefix)\"),\n sort: z.enum([\"best\", \"top\", \"new\", \"controversial\", \"old\", \"qa\"]).default(\"best\").describe(\"Comment sort order\"),\n limit: z.number().min(1).max(500).default(100).describe(\"Maximum number of comments to retrieve\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n if (args.post_id === \"\" || args.subreddit === \"\") {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(\"post_id and subreddit are required\")\n }\n\n const result = await client.getPostComments(args.post_id, args.subreddit, {\n sort: args.sort,\n limit: args.limit,\n })\n\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to get comments: ${err.message}`)\n },\n ({ post, comments }) => {\n const header = `# Comments for: ${post.title}\n\n**Post by u/${post.author} in r/${post.subreddit}**\n- Score: ${post.score.toLocaleString()} | Comments: ${post.numComments.toLocaleString()}\n- Posted: ${new Date(post.createdUtc * 1000).toLocaleString()}\n\n---\n\n`\n\n if (comments.length === 0) {\n return `${header}No comments found for this post.`\n }\n\n const commentSummaries = comments\n .map((comment) => {\n const indent = \"└─\".repeat(Math.min(comment.depth ?? 0, 3))\n const authorBadge = comment.isSubmitter ? \" **[OP]**\" : \"\"\n const editedBadge = comment.edited ? \" *(edited)*\" : \"\"\n\n return `${indent} **u/${comment.author}**${authorBadge}${editedBadge} (${comment.score.toLocaleString()} points)\n\n${comment.body}\n\n---`\n })\n .join(\"\\n\\n\")\n\n return header + commentSummaries\n },\n )\n },\n})\n\n// Initialize and start server\nasync function main() {\n await setupRedditClient()\n\n const useHttp = process.env.TRANSPORT_TYPE === \"httpStream\" || process.env.TRANSPORT_TYPE === \"http\"\n const port = parseInt(process.env.PORT ?? \"3000\")\n const host = process.env.HOST ?? \"127.0.0.1\"\n\n if (useHttp) {\n console.error(`[Setup] Starting HTTP server on ${host}:${port}`)\n await server.start({\n transportType: \"httpStream\",\n httpStream: {\n port,\n host,\n endpoint: \"/mcp\",\n },\n })\n console.error(`[Setup] HTTP server ready at http://${host}:${port}/mcp`)\n console.error(`[Setup] SSE endpoint available at http://${host}:${port}/sse`)\n } else {\n console.error(\"[Setup] Starting in stdio mode\")\n await server.start({\n transportType: \"stdio\",\n })\n }\n}\n\n// Handle graceful shutdown\nprocess.on(\"SIGINT\", () => {\n console.error(\"[Shutdown] Shutting down Reddit MCP Server...\")\n process.exit(0)\n})\n\nprocess.on(\"SIGTERM\", () => {\n console.error(\"[Shutdown] Shutting down Reddit MCP Server...\")\n process.exit(0)\n})\n\nvoid main().catch(console.error)\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/client/errors.ts","../src/client/response-cache.ts","../src/client/reddit-client.ts","../src/utils/formatters.ts","../src/index.ts"],"sourcesContent":["import { Option } from \"functype\"\n\n/**\n * Typed error channel for the Reddit client.\n *\n * The client's methods are the imperative-to-functional boundary: each captures throws\n * (network, HTTP, JSON parsing, validation, deliberate domain errors) inside a `Try` and\n * converts to a typed `Either<RedditError, T>`. Modelling the error as a discriminated ADT\n * — rather than a bare `Error` — makes the failure contract explicit at the type level, so\n * callers can reason about (and branch on) what actually went wrong without string-matching.\n *\n * `classifyRedditError` is TOTAL and never re-throws — every captured `Error` maps to a\n * variant — which is what makes the migration behavior-preserving: an error that was a\n * graceful `Left` before is still a graceful `Left` after, just with a richer type.\n *\n * Two-tier behavior, to preserve the exact messages of the original try/catch code:\n * - A *deliberate* typed throw (HttpError, NotFoundError, …) already carries its final\n * message, so it passes through unchanged.\n * - An *unexpected* generic error (fetch/JSON/orThrow) is wrapped as UnknownError, with the\n * optional `context` prefix — present for read methods (which prefixed in their catch),\n * absent for write methods (which returned the raw message).\n */\n\nabstract class RedditErrorBase extends Error {}\n\n/** A non-ok HTTP response from the Reddit API. Carries the status for caller branching. */\nexport class HttpError extends RedditErrorBase {\n readonly _tag = \"HttpError\" as const\n constructor(\n readonly status: number,\n message: string,\n ) {\n super(message)\n this.name = \"HttpError\"\n }\n}\n\n/** A write operation was attempted without the required user credentials / in a wrong mode. */\nexport class NotAuthenticatedError extends RedditErrorBase {\n readonly _tag = \"NotAuthenticatedError\" as const\n constructor(message: string) {\n super(message)\n this.name = \"NotAuthenticatedError\"\n }\n}\n\n/** Reddit accepted the request but returned errors in its JSON envelope (or an unusable body). */\nexport class ApiError extends RedditErrorBase {\n readonly _tag = \"ApiError\" as const\n constructor(message: string) {\n super(message)\n this.name = \"ApiError\"\n }\n}\n\n/** The requested post/entity does not exist or is not accessible. */\nexport class NotFoundError extends RedditErrorBase {\n readonly _tag = \"NotFoundError\" as const\n constructor(message: string) {\n super(message)\n this.name = \"NotFoundError\"\n }\n}\n\n/** Client-side input or safety-policy rejection (invalid sort, duplicate-content guard). */\nexport class ValidationError extends RedditErrorBase {\n readonly _tag = \"ValidationError\" as const\n constructor(message: string) {\n super(message)\n this.name = \"ValidationError\"\n }\n}\n\n/** Any failure that is not a recognized category: network, JSON parsing, unexpected throws. */\nexport class UnknownError extends RedditErrorBase {\n readonly _tag = \"UnknownError\" as const\n constructor(\n message: string,\n readonly cause?: unknown,\n ) {\n super(message)\n this.name = \"UnknownError\"\n }\n}\n\nexport type RedditError = HttpError | NotAuthenticatedError | ApiError | NotFoundError | ValidationError | UnknownError\n\nexport function isRedditError(error: unknown): error is RedditError {\n return error instanceof RedditErrorBase\n}\n\n/**\n * Total classifier from a captured `Error` to a `RedditError`. Deliberate typed throws pass\n * through unchanged; everything else becomes an `UnknownError`, prefixed with `context` when\n * provided so the observable message text matches the previous try/catch-based wrapping.\n */\nexport function classifyRedditError(error: Error, context?: string): RedditError {\n if (isRedditError(error)) {\n return error\n }\n const message = Option(context).fold(\n () => error.message,\n (ctx) => `${ctx}: ${error.message}`,\n )\n return new UnknownError(message, error)\n}\n","/* eslint-disable functype/prefer-functype-map, functype/prefer-option, functype/no-imperative-loops --\n * This module is a deliberately imperative performance primitive: a mutable, byte-bounded\n * LRU cache. A functype immutable Map cannot express LRU access-order reordering or running\n * byte accounting without rebuilding the whole structure on every operation, and the eviction\n * loop and undefined \"miss\" sentinel are the clearest expression of that stateful contract.\n * Mirrors the imperative-boundary convention used in reddit-client.ts.\n */\n\n/**\n * In-memory cache for read-only Reddit GET responses.\n *\n * Reddit's rate limits are tight (~10 req/min anonymous, 60-100 authenticated),\n * so caching identical reads for a short window meaningfully reduces request\n * pressure. TTLs are adaptive: volatile listings expire quickly while relatively\n * stable resources (top/controversial, search, user/subreddit \"about\") live longer.\n *\n * Eviction is LRU bounded by a byte budget, so the cache can never grow unbounded.\n */\n\ntype CacheEntry = {\n readonly body: string\n readonly status: number\n readonly expiresAt: number\n readonly bytes: number\n}\n\nexport type CachedResponse = {\n readonly body: string\n readonly status: number\n}\n\nconst SECOND = 1_000\n\nexport class ResponseCache {\n private readonly maxBytes: number\n private readonly now: () => number\n // Map iteration order is insertion order, which we use as the LRU ordering:\n // the first key is the least-recently-used entry.\n private readonly entries = new Map<string, CacheEntry>()\n private currentBytes = 0\n\n constructor(options: { readonly maxBytes: number; readonly now?: () => number }) {\n this.maxBytes = options.maxBytes\n this.now = options.now ?? Date.now\n }\n\n /** Adaptive TTL (in milliseconds) for a given request URL. */\n ttlFor(url: string): number {\n if (/\\/(hot|new|rising)\\.json/.test(url)) {\n return 60 * SECOND\n }\n if (/\\/(top|controversial)\\.json/.test(url) || /\\/search\\.json/.test(url) || /\\/about\\.json/.test(url)) {\n return 300 * SECOND\n }\n if (/\\/comments\\//.test(url)) {\n return 60 * SECOND\n }\n return 120 * SECOND\n }\n\n get(url: string): CachedResponse | undefined {\n const entry = this.entries.get(url)\n if (entry === undefined) {\n return undefined\n }\n if (this.now() >= entry.expiresAt) {\n this.entries.delete(url)\n this.currentBytes -= entry.bytes\n return undefined\n }\n // Mark as most-recently-used by reinserting at the end.\n this.entries.delete(url)\n this.entries.set(url, entry)\n return { body: entry.body, status: entry.status }\n }\n\n set(url: string, body: string, status: number): void {\n const bytes = Buffer.byteLength(body, \"utf8\")\n // A single oversized body is simply not cached.\n if (bytes > this.maxBytes) {\n return\n }\n\n const existing = this.entries.get(url)\n if (existing !== undefined) {\n this.entries.delete(url)\n this.currentBytes -= existing.bytes\n }\n\n this.entries.set(url, {\n body,\n status,\n expiresAt: this.now() + this.ttlFor(url),\n bytes,\n })\n this.currentBytes += bytes\n\n this.evictUntilWithinBudget()\n }\n\n private evictUntilWithinBudget(): void {\n while (this.currentBytes > this.maxBytes) {\n const oldestKey = this.entries.keys().next().value\n if (oldestKey === undefined) {\n return\n }\n const oldest = this.entries.get(oldestKey)\n this.entries.delete(oldestKey)\n if (oldest !== undefined) {\n this.currentBytes -= oldest.bytes\n }\n }\n }\n}\n","/* eslint-disable functype/prefer-either --\n * This module is the imperative-to-functional boundary for the Reddit HTTP client.\n * Each public method runs its failure-producing region inside a `Try` and converts the\n * result to `Either<RedditError, T>` via the total `classifyRedditError`. Because the body\n * of `Try.async(() => Promise<T>)` can only signal failure by throwing, the `throw`s here\n * (HTTP/validation/domain errors, and the validateWriteAccess/checkDuplicateContent helpers\n * they call) are local control-flow captured by that `Try` — they never escape the method\n * boundary. prefer-either's \"return Either.left\" suggestion does not apply inside a Try body.\n */\nimport crypto from \"crypto\"\nimport type { Either } from \"functype\"\nimport { Left, Option, Right, Try } from \"functype\"\n\nimport type {\n BotDisclosureConfig,\n ContentRecord,\n Page,\n RedditApiCommentResponse,\n RedditApiCommentTreeData,\n RedditApiEditResponse,\n RedditApiInfoResponse,\n RedditApiLinkFlairResponse,\n RedditApiListingResponse,\n RedditApiMeResponse,\n RedditApiMoreChildrenResponse,\n RedditApiPopularSubredditsResponse,\n RedditApiPostCommentsResponse,\n RedditApiPostData,\n RedditApiRulesResponse,\n RedditApiSubmitResponse,\n RedditApiSubredditResponse,\n RedditApiUserResponse,\n RedditAuthMode,\n RedditClientConfig,\n RedditComment,\n RedditFlair,\n RedditPost,\n RedditRule,\n RedditSubreddit,\n RedditUser,\n RetryConfig,\n SafeModeConfig,\n UserContent,\n} from \"../types\"\nimport type { RedditError } from \"./errors\"\nimport {\n ApiError,\n classifyRedditError,\n HttpError,\n isRedditError,\n NotAuthenticatedError,\n NotFoundError,\n ValidationError,\n} from \"./errors\"\nimport { ResponseCache } from \"./response-cache\"\n\n// Extract Reddit's pagination cursors from a listing's `data`. Reddit returns `after`/`before`\n// as a fullname string or null; we surface only present string cursors (no undefined keys, so\n// this is safe under exactOptionalPropertyTypes).\nfunction listingCursor(data: { readonly [key: string]: unknown }): Pick<Page<unknown>, \"after\" | \"before\"> {\n const after = typeof data.after === \"string\" ? { after: data.after } : {}\n const before = typeof data.before === \"string\" ? { before: data.before } : {}\n return { ...after, ...before }\n}\n\nfunction parsePostData(post: RedditApiPostData): RedditPost {\n return {\n id: post.id,\n title: post.title,\n author: post.author,\n subreddit: post.subreddit,\n selftext: post.selftext,\n url: post.url,\n score: post.score,\n upvoteRatio: post.upvote_ratio,\n numComments: post.num_comments,\n createdUtc: post.created_utc,\n over18: post.over_18,\n spoiler: post.spoiler,\n edited: Boolean(post.edited),\n isSelf: post.is_self,\n linkFlairText: post.link_flair_text ?? undefined,\n permalink: post.permalink,\n }\n}\n\nexport class RedditClient {\n private readonly clientId: string\n private readonly clientSecret: string\n private readonly userAgent: string\n private readonly username?: string\n private readonly password?: string\n private readonly baseUrl: string\n private readonly authMode: RedditAuthMode\n private readonly hasCredentials: boolean\n private readonly safeMode: SafeModeConfig\n private readonly botDisclosure: BotDisclosureConfig\n private readonly cache?: ResponseCache\n private readonly retry: RetryConfig\n\n // Mutable state — inherent to a stateful HTTP client with token refresh\n\n private accessToken?: string\n\n private tokenExpiry: number = 0\n\n private authenticated: boolean = false\n\n private lastWriteTime: number = 0\n\n private recentContentRecords: ContentRecord[] = []\n\n constructor(config: RedditClientConfig) {\n this.clientId = config.clientId\n this.clientSecret = config.clientSecret\n this.userAgent = config.userAgent\n this.username = config.username\n this.password = config.password\n this.authMode = config.authMode ?? \"auto\"\n this.hasCredentials = Boolean(this.clientId && this.clientSecret)\n this.baseUrl = this.determineBaseUrl()\n\n this.safeMode = config.safeMode ?? {\n enabled: false,\n mode: \"off\",\n writeDelayMs: 0,\n duplicateCheck: false,\n maxRecentHashes: 10,\n }\n\n this.botDisclosure = config.botDisclosure ?? { enabled: false, footer: \"\" }\n\n this.cache = config.cache?.enabled === true ? new ResponseCache({ maxBytes: config.cache.maxBytes }) : undefined\n\n this.retry = config.retry ?? { maxRetries: 3, baseDelayMs: 1000, maxDelayMs: 60000 }\n }\n\n private determineBaseUrl(): string {\n switch (this.authMode) {\n case \"authenticated\":\n return \"https://oauth.reddit.com\"\n case \"anonymous\":\n return \"https://www.reddit.com\"\n case \"auto\":\n return this.hasCredentials ? \"https://oauth.reddit.com\" : \"https://www.reddit.com\"\n }\n }\n\n // Low-level HTTP boundary. Returns Either<Error, Response>: a Right even for non-ok HTTP\n // statuses (callers inspect response.ok); only thrown failures (network, auth) become Left.\n private async makeRequest(path: string, options: RequestInit = {}): Promise<Either<Error, Response>> {\n const attempt = await Try.async(async (): Promise<Response> => {\n const url = `${this.baseUrl}${path}`\n const method = (options.method ?? \"GET\").toUpperCase()\n const cacheable = this.cache !== undefined && method === \"GET\"\n\n if (cacheable) {\n const cached = this.cache!.get(url)\n if (cached !== undefined) {\n return new Response(cached.body, { status: cached.status })\n }\n }\n\n const requiresAuth = this.authMode === \"authenticated\" || (this.authMode === \"auto\" && this.hasCredentials)\n\n if (requiresAuth && (Date.now() >= this.tokenExpiry || !this.authenticated)) {\n const authResult = await this.authenticate()\n authResult.orThrow()\n }\n\n const headers: Record<string, string> = {\n \"User-Agent\": this.userAgent,\n\n ...(options.headers as Record<string, string> | undefined),\n }\n\n if (requiresAuth && this.accessToken !== undefined) {\n headers[\"Authorization\"] = `Bearer ${this.accessToken}`\n }\n\n const first = await this.fetchWithRetry(url, options, headers, path, 0)\n\n // 401 once-off re-auth, then retry the request (which itself honors 429 backoff).\n const response =\n first.status === 401 && this.authenticated\n ? await this.fetchWithRetry(url, options, { ...headers, Authorization: await this.reauthorize() }, path, 0)\n : first\n\n // Cache successful read responses and return a fresh, readable Response.\n // (A fetch Response body can only be consumed once, so we re-wrap the text.)\n if (cacheable && response.ok) {\n const text = await response.text()\n this.cache!.set(url, text, response.status)\n return new Response(text, { status: response.status })\n }\n\n return response\n })\n\n return attempt.toEither((error) => error)\n }\n\n async authenticate(): Promise<Either<Error, void>> {\n if (this.authMode === \"anonymous\") {\n this.authenticated = false\n return Right(undefined as void)\n }\n\n if (this.authMode === \"authenticated\" && !this.hasCredentials) {\n return Left(new Error(\"Authenticated mode requires REDDIT_CLIENT_ID and REDDIT_CLIENT_SECRET\"))\n }\n\n if (this.authMode === \"auto\" && !this.hasCredentials) {\n this.authenticated = false\n return Right(undefined as void)\n }\n\n const attempt = await Try.async(async (): Promise<void> => {\n const now = Date.now()\n if (this.accessToken !== undefined && now < this.tokenExpiry) {\n return\n }\n\n const authUrl = \"https://www.reddit.com/api/v1/access_token\"\n const authData = new URLSearchParams()\n\n const { username } = this\n const { password } = this\n const isUserAuth = Boolean(username && password)\n if (isUserAuth && username !== undefined && password !== undefined) {\n authData.append(\"grant_type\", \"password\")\n authData.append(\"username\", username)\n authData.append(\"password\", password)\n } else {\n authData.append(\"grant_type\", \"client_credentials\")\n }\n\n const credentials = Buffer.from(`${this.clientId}:${this.clientSecret}`).toString(\"base64\")\n const response = await fetch(authUrl, {\n method: \"POST\",\n headers: {\n \"User-Agent\": this.userAgent,\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n Authorization: `Basic ${credentials}`,\n },\n body: authData.toString(),\n })\n\n if (!response.ok) {\n const statusText = response.statusText !== \"\" ? response.statusText : \"Unknown Error\"\n throw new Error(`Authentication failed: ${response.status} ${statusText}`)\n }\n\n const data = (await response.json()) as { access_token: string; expires_in: number }\n this.accessToken = data.access_token\n this.tokenExpiry = now + data.expires_in * 1000\n this.authenticated = true\n })\n\n return attempt.toEither((error) => error)\n }\n\n async checkAuthentication(): Promise<boolean> {\n if (!this.authenticated) {\n const result = await this.authenticate()\n return result.isRight()\n }\n return true\n }\n\n private validateWriteAccess(): void {\n if (this.username === undefined || this.password === undefined) {\n if (this.authMode === \"anonymous\") {\n throw new NotAuthenticatedError(\n \"Write operations not available in anonymous mode. \" +\n \"Set REDDIT_USERNAME, REDDIT_PASSWORD and use 'auto' or 'authenticated' mode.\",\n )\n }\n throw new NotAuthenticatedError(\"Write operations require REDDIT_USERNAME and REDDIT_PASSWORD\")\n }\n }\n\n private async enforceWriteRateLimit(): Promise<void> {\n if (!this.safeMode.enabled || this.safeMode.writeDelayMs <= 0) {\n return\n }\n\n const now = Date.now()\n const elapsed = now - this.lastWriteTime\n if (elapsed < this.safeMode.writeDelayMs) {\n const waitTime = this.safeMode.writeDelayMs - elapsed\n console.error(`[SafeMode] Rate limit: waiting ${waitTime}ms before write operation`)\n await new Promise((resolve) => setTimeout(resolve, waitTime))\n }\n this.lastWriteTime = Date.now()\n }\n\n private hashContent(content: string): string {\n return crypto.createHash(\"sha256\").update(content.trim().toLowerCase()).digest(\"hex\")\n }\n\n private checkDuplicateContent(content: string, subreddit?: string): void {\n if (!this.safeMode.enabled || !this.safeMode.duplicateCheck) {\n return\n }\n\n const hash = this.hashContent(content)\n\n const duplicate = this.recentContentRecords.find((record) => record.hash === hash)\n if (duplicate !== undefined) {\n if (subreddit !== undefined && duplicate.subreddit !== \"\" && subreddit !== duplicate.subreddit) {\n throw new ValidationError(\n \"Cross-subreddit duplicate detected. Reddit's Responsible Builder Policy prohibits \" +\n \"posting identical or substantially similar content across multiple subreddits. \" +\n \"Please create unique content for each subreddit.\",\n )\n }\n throw new ValidationError(\n \"Duplicate content detected. Reddit's spam filter may ban your account for posting identical content. \" +\n \"Please modify your content and try again.\",\n )\n }\n\n this.recentContentRecords.push({\n hash,\n subreddit: subreddit ?? \"\",\n timestamp: Date.now(),\n })\n\n this.recentContentRecords = this.recentContentRecords.slice(-this.safeMode.maxRecentHashes)\n }\n\n // Re-authenticate and return a fresh Bearer header value (throws via orThrow on failure).\n private async reauthorize(): Promise<string> {\n const result = await this.authenticate()\n result.orThrow()\n return `Bearer ${this.accessToken}`\n }\n\n // Fetch with transparent retry on HTTP 429. Honors Retry-After / x-ratelimit-reset, else\n // exponential backoff; surfaces the 429 once retries are exhausted or the required wait\n // exceeds the cap. Recursive (not a loop) to satisfy the functional style.\n private async fetchWithRetry(\n url: string,\n options: RequestInit,\n headers: Record<string, string>,\n path: string,\n attempt: number,\n ): Promise<Response> {\n const response = await fetch(url, { ...options, headers })\n if (response.status !== 429 || attempt >= this.retry.maxRetries) {\n return response\n }\n\n const wait = this.retryAfterMs(response).fold(\n () => Math.min(this.retry.baseDelayMs * 2 ** attempt, this.retry.maxDelayMs),\n (ms) => ms,\n )\n if (wait > this.retry.maxDelayMs) {\n return response\n }\n\n console.error(`[RateLimit] 429 from ${path} — retry ${attempt + 1}/${this.retry.maxRetries} in ${wait}ms`)\n await new Promise((resolve) => setTimeout(resolve, wait))\n return this.fetchWithRetry(url, options, headers, path, attempt + 1)\n }\n\n // Parse a retry delay (ms) from a 429 response: prefer Retry-After (delta-seconds or\n // HTTP-date), then x-ratelimit-reset (seconds). None when no usable header is present,\n // signalling the caller to fall back to exponential backoff.\n private retryAfterMs(response: Response): Option<number> {\n const { headers } = response\n\n const retryAfter = headers.get(\"retry-after\")\n if (retryAfter !== null && retryAfter !== \"\") {\n const seconds = Number(retryAfter)\n if (!Number.isNaN(seconds)) {\n return Option(seconds * 1000)\n }\n const when = Date.parse(retryAfter)\n if (!Number.isNaN(when)) {\n return Option(Math.max(0, when - Date.now()))\n }\n }\n\n const reset = headers.get(\"x-ratelimit-reset\")\n if (reset !== null && reset !== \"\") {\n const seconds = Number(reset)\n if (!Number.isNaN(seconds)) {\n return Option(seconds * 1000)\n }\n }\n\n return Option.none()\n }\n\n private appendBotDisclosure(content: string): string {\n if (!this.botDisclosure.enabled || this.botDisclosure.footer === \"\") {\n return content\n }\n return `${content}${this.botDisclosure.footer}`\n }\n\n async getUser(username: string): Promise<Either<RedditError, RedditUser>> {\n const context = `Failed to get user info for ${username}`\n const attempt = await Try.async(async (): Promise<RedditUser> => {\n const response = (await this.makeRequest(`/user/${username}/about.json`)).orThrow()\n if (!response.ok) {\n throw new HttpError(response.status, `${context}: HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiUserResponse\n const { data } = json\n\n return {\n name: data.name,\n id: data.id,\n commentKarma: data.comment_karma,\n linkKarma: data.link_karma,\n totalKarma: data.total_karma ?? data.comment_karma + data.link_karma,\n isMod: data.is_mod,\n isGold: data.is_gold,\n isEmployee: data.is_employee,\n createdUtc: data.created_utc,\n profileUrl: `https://reddit.com/user/${data.name}`,\n }\n })\n\n return attempt.toEither((error) => classifyRedditError(error, context))\n }\n\n // Fetch + split a mixed user listing (saved/overview) into posts (t3) and comments (t1).\n private async getUserContent(path: string, context: string): Promise<Either<RedditError, UserContent>> {\n const attempt = await Try.async(async (): Promise<UserContent> => {\n const response = (await this.makeRequest(path)).orThrow()\n if (!response.ok) {\n throw new HttpError(response.status, `${context}: HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiListingResponse<RedditApiPostData | RedditApiCommentTreeData>\n const posts = json.data.children\n .filter((child) => child.kind === \"t3\")\n .map((child) => parsePostData(child.data as RedditApiPostData))\n const comments = json.data.children\n .filter((child) => child.kind === \"t1\")\n .map((child) => {\n const comment = child.data as RedditApiCommentTreeData\n return {\n id: comment.id,\n author: comment.author,\n body: comment.body ?? \"\",\n score: comment.score,\n controversiality: comment.controversiality,\n subreddit: comment.subreddit,\n submissionTitle: comment.link_title ?? \"\",\n createdUtc: comment.created_utc,\n edited: Boolean(comment.edited),\n isSubmitter: comment.is_submitter,\n permalink: comment.permalink,\n parentId: comment.parent_id,\n }\n })\n return { posts, comments, ...listingCursor(json.data) }\n })\n\n return attempt.toEither((error) => classifyRedditError(error, context))\n }\n\n async getMyOverview(\n options: { readonly limit?: number; readonly after?: string } = {},\n ): Promise<Either<RedditError, UserContent>> {\n if (this.username === undefined) {\n return Left(new NotAuthenticatedError(\"Fetching your overview requires REDDIT_USERNAME\"))\n }\n const { limit = 25, after } = options\n const params = new URLSearchParams({ limit: limit.toString() })\n if (after !== undefined) {\n params.set(\"after\", after)\n }\n return this.getUserContent(`/user/${this.username}/overview.json?${params}`, \"Failed to get your overview\")\n }\n\n async getMySaved(\n options: { readonly limit?: number; readonly after?: string } = {},\n ): Promise<Either<RedditError, UserContent>> {\n if (this.username === undefined) {\n return Left(new NotAuthenticatedError(\"Fetching saved content requires REDDIT_USERNAME\"))\n }\n const { limit = 25, after } = options\n const params = new URLSearchParams({ limit: limit.toString() })\n if (after !== undefined) {\n params.set(\"after\", after)\n }\n return this.getUserContent(`/user/${this.username}/saved.json?${params}`, \"Failed to get saved content\")\n }\n\n // The authenticated user's own account (requires user credentials — /api/v1/me needs identity).\n async getMe(): Promise<Either<RedditError, RedditUser>> {\n if (this.username === undefined) {\n return Left(new NotAuthenticatedError(\"Fetching your account requires REDDIT_USERNAME\"))\n }\n const context = \"Failed to get authenticated user info\"\n const attempt = await Try.async(async (): Promise<RedditUser> => {\n const response = (await this.makeRequest(\"/api/v1/me\")).orThrow()\n if (!response.ok) {\n throw new HttpError(response.status, `${context}: HTTP ${response.status}`)\n }\n\n const data = (await response.json()) as RedditApiMeResponse\n return {\n name: data.name,\n id: data.id,\n commentKarma: data.comment_karma,\n linkKarma: data.link_karma,\n totalKarma: data.total_karma ?? data.comment_karma + data.link_karma,\n isMod: data.is_mod,\n isGold: data.is_gold,\n isEmployee: data.is_employee,\n createdUtc: data.created_utc,\n profileUrl: `https://reddit.com/user/${data.name}`,\n }\n })\n\n return attempt.toEither((error) => classifyRedditError(error, context))\n }\n\n async getSubredditInfo(subredditName: string): Promise<Either<RedditError, RedditSubreddit>> {\n const context = `Failed to get subreddit info for ${subredditName}`\n const attempt = await Try.async(async (): Promise<RedditSubreddit> => {\n const response = (await this.makeRequest(`/r/${subredditName}/about.json`)).orThrow()\n if (!response.ok) {\n throw new HttpError(response.status, `${context}: HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiSubredditResponse\n const { data } = json\n\n return {\n displayName: data.display_name,\n title: data.title,\n description: data.description,\n publicDescription: data.public_description,\n subscribers: data.subscribers,\n activeUserCount: data.active_user_count ?? undefined,\n createdUtc: data.created_utc,\n over18: data.over18,\n subredditType: data.subreddit_type,\n url: data.url,\n }\n })\n\n return attempt.toEither((error) => classifyRedditError(error, context))\n }\n\n async getSubredditRules(subreddit: string): Promise<Either<RedditError, readonly RedditRule[]>> {\n const context = `Failed to get rules for r/${subreddit}`\n const attempt = await Try.async(async (): Promise<readonly RedditRule[]> => {\n const response = (await this.makeRequest(`/r/${subreddit}/about/rules.json`)).orThrow()\n if (!response.ok) {\n throw new HttpError(response.status, `${context}: HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiRulesResponse\n return json.rules.map((rule) => ({\n shortName: rule.short_name,\n description: rule.description,\n kind: rule.kind,\n violationReason: rule.violation_reason,\n priority: rule.priority,\n createdUtc: rule.created_utc,\n }))\n })\n\n return attempt.toEither((error) => classifyRedditError(error, context))\n }\n\n async getPostFlairs(subreddit: string): Promise<Either<RedditError, readonly RedditFlair[]>> {\n const context = `Failed to get post flairs for r/${subreddit}`\n const attempt = await Try.async(async (): Promise<readonly RedditFlair[]> => {\n const response = (await this.makeRequest(`/r/${subreddit}/api/link_flair_v2.json`)).orThrow()\n if (!response.ok) {\n throw new HttpError(response.status, `${context}: HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiLinkFlairResponse\n return json.map((flair) => ({\n id: flair.id,\n text: flair.text,\n type: flair.type,\n textEditable: flair.text_editable,\n }))\n })\n\n return attempt.toEither((error) => classifyRedditError(error, context))\n }\n\n async getTopPosts(\n subreddit: string,\n timeFilter: string = \"week\",\n limit: number = 10,\n after?: string,\n ): Promise<Either<RedditError, Page<RedditPost>>> {\n const endpoint = subreddit !== \"\" ? `/r/${subreddit}/top.json` : \"/top.json\"\n const params = new URLSearchParams({\n t: timeFilter,\n limit: limit.toString(),\n })\n if (after !== undefined) {\n params.set(\"after\", after)\n }\n const context = `Failed to get top posts for ${subreddit !== \"\" ? subreddit : \"home\"}`\n\n const attempt = await Try.async(async (): Promise<Page<RedditPost>> => {\n const response = (await this.makeRequest(`${endpoint}?${params}`)).orThrow()\n if (!response.ok) {\n throw new HttpError(response.status, `Failed to get top posts: HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiListingResponse<RedditApiPostData>\n const items = json.data.children.map((child) => parsePostData(child.data))\n return { items, ...listingCursor(json.data) }\n })\n\n return attempt.toEither((error) => classifyRedditError(error, context))\n }\n\n async browseSubreddit(\n subreddit: string,\n sort: string = \"hot\",\n timeFilter: string = \"week\",\n limit: number = 10,\n after?: string,\n ): Promise<Either<RedditError, Page<RedditPost>>> {\n const validSorts = [\"hot\", \"new\", \"top\", \"rising\", \"controversial\"]\n if (!validSorts.includes(sort)) {\n return Left(new ValidationError(`Invalid sort \"${sort}\". Valid options are: ${validSorts.join(\", \")}`))\n }\n\n const endpoint = subreddit !== \"\" ? `/r/${subreddit}/${sort}.json` : `/${sort}.json`\n const params = new URLSearchParams({ limit: limit.toString() })\n // The time filter only applies to top/controversial listings.\n if (sort === \"top\" || sort === \"controversial\") {\n params.set(\"t\", timeFilter)\n }\n if (after !== undefined) {\n params.set(\"after\", after)\n }\n const home = subreddit !== \"\" ? subreddit : \"home\"\n const context = `Failed to browse r/${home} (${sort})`\n\n const attempt = await Try.async(async (): Promise<Page<RedditPost>> => {\n const response = (await this.makeRequest(`${endpoint}?${params}`)).orThrow()\n if (!response.ok) {\n throw new HttpError(response.status, `Failed to browse r/${home}: HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiListingResponse<RedditApiPostData>\n const items = json.data.children.map((child) => parsePostData(child.data))\n return { items, ...listingCursor(json.data) }\n })\n\n return attempt.toEither((error) => classifyRedditError(error, context))\n }\n\n async getPost(postId: string, subreddit?: string): Promise<Either<RedditError, RedditPost>> {\n const endpoint = Option(subreddit).fold(\n () => `/api/info.json?id=t3_${postId}`,\n (sr) => `/r/${sr}/comments/${postId}.json`,\n )\n const context = `Failed to get post with ID ${postId}`\n\n const attempt = await Try.async(async (): Promise<RedditPost> => {\n const response = (await this.makeRequest(endpoint)).orThrow()\n if (!response.ok) {\n throw new HttpError(response.status, `${context}: HTTP ${response.status}`)\n }\n\n if (subreddit !== undefined) {\n const json = (await response.json()) as [RedditApiListingResponse<RedditApiPostData>, unknown]\n return parsePostData(json[0].data.children[0].data)\n }\n\n const json = (await response.json()) as RedditApiInfoResponse\n if (json.data.children.length === 0) {\n throw new NotFoundError(`Post with ID ${postId} not found`)\n }\n return parsePostData(json.data.children[0].data)\n })\n\n return attempt.toEither((error) => classifyRedditError(error, context))\n }\n\n async getTrendingSubreddits(limit: number = 5): Promise<Either<RedditError, readonly string[]>> {\n const params = new URLSearchParams({ limit: limit.toString() })\n const context = `Failed to get trending subreddits`\n\n const attempt = await Try.async(async (): Promise<readonly string[]> => {\n const response = (await this.makeRequest(`/subreddits/popular.json?${params}`)).orThrow()\n if (!response.ok) {\n throw new HttpError(response.status, `${context}: HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiPopularSubredditsResponse\n return json.data.children.map((child) => child.data.display_name)\n })\n\n return attempt.toEither((error) => classifyRedditError(error, context))\n }\n\n async createPost(\n subreddit: string,\n title: string,\n content: string,\n isSelf: boolean = true,\n flairId?: string,\n flairText?: string,\n ): Promise<Either<RedditError, RedditPost>> {\n const attempt = await Try.async(async (): Promise<RedditPost> => {\n this.validateWriteAccess()\n await this.enforceWriteRateLimit()\n this.checkDuplicateContent(title + content, subreddit)\n\n const finalContent = isSelf ? this.appendBotDisclosure(content) : content\n const kind = isSelf ? \"self\" : \"link\"\n const params = new URLSearchParams()\n params.append(\"sr\", subreddit)\n params.append(\"kind\", kind)\n params.append(\"title\", title)\n params.append(isSelf ? \"text\" : \"url\", finalContent)\n params.append(\"api_type\", \"json\")\n if (flairId !== undefined) {\n params.append(\"flair_id\", flairId)\n }\n if (flairText !== undefined) {\n params.append(\"flair_text\", flairText)\n }\n\n const response = (\n await this.makeRequest(\"/api/submit\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n },\n body: params.toString(),\n })\n ).orThrow()\n\n if (!response.ok) {\n throw new HttpError(response.status, `Failed to create post: HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiSubmitResponse\n\n if (json.json.errors !== undefined && json.json.errors.length > 0) {\n const errors = json.json.errors.map((e) => e[1]).join(\", \")\n throw new ApiError(`Reddit API errors: ${errors}`)\n }\n\n const postId = json.json.data?.id ?? json.json.data?.name?.replace(\"t3_\", \"\")\n\n if (postId === undefined) {\n throw new ApiError(\"No post ID returned from Reddit\")\n }\n\n return (await this.getPost(postId, subreddit)).orThrow()\n })\n\n return attempt.toEither((error) => classifyRedditError(error))\n }\n\n async checkPostExists(postId: string): Promise<boolean> {\n const attempt = await Try.async(async (): Promise<boolean> => {\n const response = (await this.makeRequest(`/api/info.json?id=t3_${postId}`)).orThrow()\n if (!response.ok) {\n return false\n }\n\n const json = (await response.json()) as RedditApiInfoResponse\n return json.data.children.length > 0\n })\n\n return attempt.orElse(false)\n }\n\n async replyToPost(postId: string, content: string): Promise<Either<RedditError, RedditComment>> {\n const attempt = await Try.async(async (): Promise<RedditComment> => {\n this.validateWriteAccess()\n await this.enforceWriteRateLimit()\n this.checkDuplicateContent(content)\n\n const finalContent = this.appendBotDisclosure(content)\n const fullThingId = postId.startsWith(\"t3_\") || postId.startsWith(\"t1_\") ? postId : `t3_${postId}`\n\n if (!postId.startsWith(\"t1_\")) {\n const exists = await this.checkPostExists(postId.replace(/^t3_/, \"\"))\n if (!exists) {\n throw new NotFoundError(`Post with ID ${postId} does not exist or is not accessible`)\n }\n }\n\n const params = new URLSearchParams()\n params.append(\"thing_id\", fullThingId)\n params.append(\"text\", finalContent)\n params.append(\"api_type\", \"json\")\n\n const response = (\n await this.makeRequest(\"/api/comment\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n },\n body: params.toString(),\n })\n ).orThrow()\n\n if (!response.ok) {\n throw new HttpError(response.status, `Failed to reply: HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiCommentResponse\n\n if (json.json.data?.things !== undefined && json.json.data.things.length > 0) {\n const commentData = json.json.data.things[0].data\n const author = this.username ?? \"[unknown]\"\n return {\n id: commentData.id,\n author,\n body: content,\n score: 1,\n controversiality: 0,\n subreddit: commentData.subreddit,\n submissionTitle: commentData.link_title ?? \"\",\n createdUtc: Date.now() / 1000,\n edited: false,\n isSubmitter: false,\n permalink: commentData.permalink,\n }\n } else if (json.json.errors !== undefined && json.json.errors.length > 0) {\n const errors = json.json.errors.map((e) => e[1]).join(\", \")\n throw new ApiError(`Reddit API errors: ${errors}`)\n } else {\n throw new ApiError(\"Failed to parse reply response\")\n }\n })\n\n return attempt.toEither((error) => classifyRedditError(error))\n }\n\n async deletePost(thingId: string): Promise<Either<RedditError, boolean>> {\n const attempt = await Try.async(async (): Promise<boolean> => {\n this.validateWriteAccess()\n\n const fullThingId = thingId.startsWith(\"t3_\") || thingId.startsWith(\"t1_\") ? thingId : `t3_${thingId}`\n\n const params = new URLSearchParams()\n params.append(\"id\", fullThingId)\n\n const response = (\n await this.makeRequest(\"/api/del\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n },\n body: params.toString(),\n })\n ).orThrow()\n\n if (!response.ok) {\n const errorText = await response.text()\n console.error(`[Reddit API] Delete failed: ${response.status} ${response.statusText}`)\n console.error(`[Reddit API] Error response: ${errorText}`)\n throw new HttpError(response.status, `HTTP ${response.status}: ${errorText}`)\n }\n\n console.error(`[Reddit API] Successfully deleted ${fullThingId}`)\n return true\n })\n\n return attempt.toEither((error) => {\n if (!isRedditError(error)) {\n console.error(`[Reddit API] Delete exception:`, error)\n }\n return classifyRedditError(error)\n })\n }\n\n async deleteComment(thingId: string): Promise<Either<RedditError, boolean>> {\n const fullThingId = thingId.startsWith(\"t1_\") ? thingId : `t1_${thingId}`\n return this.deletePost(fullThingId)\n }\n\n async editPost(thingId: string, newText: string): Promise<Either<RedditError, boolean>> {\n const attempt = await Try.async(async (): Promise<boolean> => {\n this.validateWriteAccess()\n await this.enforceWriteRateLimit()\n this.checkDuplicateContent(newText)\n\n const finalText = this.appendBotDisclosure(newText)\n const fullThingId = thingId.startsWith(\"t3_\") || thingId.startsWith(\"t1_\") ? thingId : `t3_${thingId}`\n\n const params = new URLSearchParams()\n params.append(\"thing_id\", fullThingId)\n params.append(\"text\", finalText)\n params.append(\"api_type\", \"json\")\n\n const response = (\n await this.makeRequest(\"/api/editusertext\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n },\n body: params.toString(),\n })\n ).orThrow()\n\n if (!response.ok) {\n throw new HttpError(response.status, `Failed to edit: HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiEditResponse\n\n if (json.json.errors !== undefined && json.json.errors.length > 0) {\n const errors = json.json.errors.map((e) => e[1]).join(\", \")\n throw new ApiError(`Reddit API errors: ${errors}`)\n }\n\n return true\n })\n\n return attempt.toEither((error) => classifyRedditError(error))\n }\n\n async editComment(thingId: string, newText: string): Promise<Either<RedditError, boolean>> {\n const fullThingId = thingId.startsWith(\"t1_\") ? thingId : `t1_${thingId}`\n return this.editPost(fullThingId, newText)\n }\n\n async searchReddit(\n query: string,\n options: {\n readonly subreddit?: string\n readonly sort?: string\n readonly timeFilter?: string\n readonly limit?: number\n readonly type?: string\n readonly after?: string\n readonly before?: string\n } = {},\n ): Promise<Either<RedditError, Page<RedditPost>>> {\n const { subreddit, sort = \"relevance\", timeFilter = \"all\", limit = 25, type = \"link\", after, before } = options\n const endpoint = Option(subreddit).fold(\n () => \"/search.json\",\n (sr) => `/r/${sr}/search.json`,\n )\n\n const params = new URLSearchParams({\n q: query,\n sort,\n t: timeFilter,\n limit: limit.toString(),\n type,\n // eslint-disable-next-line functype/prefer-fold -- conditional spread of native string | undefined into URLSearchParams init\n ...(subreddit !== undefined ? { restrict_sr: \"true\" } : {}),\n // eslint-disable-next-line functype/prefer-fold -- conditional spread of cursors into URLSearchParams init\n ...(after !== undefined ? { after } : {}),\n // eslint-disable-next-line functype/prefer-fold -- conditional spread of cursors into URLSearchParams init\n ...(before !== undefined ? { before } : {}),\n })\n const context = `Failed to search Reddit for: ${query}`\n\n const attempt = await Try.async(async (): Promise<Page<RedditPost>> => {\n const response = (await this.makeRequest(`${endpoint}?${params}`)).orThrow()\n if (!response.ok) {\n throw new HttpError(response.status, `Failed to search Reddit: HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiListingResponse<RedditApiPostData>\n\n const items = json.data.children.filter((child) => child.kind === \"t3\").map((child) => parsePostData(child.data))\n return { items, ...listingCursor(json.data) }\n })\n\n return attempt.toEither((error) => classifyRedditError(error, context))\n }\n\n async getPostComments(\n postId: string,\n subreddit: string,\n options: {\n readonly sort?: string\n readonly limit?: number\n } = {},\n ): Promise<Either<RedditError, { readonly post: RedditPost; readonly comments: readonly RedditComment[] }>> {\n const { sort = \"best\", limit = 100 } = options\n const params = new URLSearchParams({\n sort,\n limit: limit.toString(),\n })\n const context = `Failed to get comments for post ${postId}`\n\n const attempt = await Try.async(\n async (): Promise<{ readonly post: RedditPost; readonly comments: readonly RedditComment[] }> => {\n const response = (await this.makeRequest(`/r/${subreddit}/comments/${postId}.json?${params}`)).orThrow()\n if (!response.ok) {\n throw new HttpError(response.status, `Failed to get comments: HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiPostCommentsResponse\n\n const postData = json[0].data.children[0].data\n const post = parsePostData(postData)\n\n const parseComments = (\n commentData: ReadonlyArray<{ readonly kind: string; readonly data: RedditApiCommentTreeData }>,\n depth: number = 0,\n ): readonly RedditComment[] =>\n commentData.flatMap((item) => {\n if (item.kind !== \"t1\" || item.data.body === undefined) return []\n\n const comment: RedditComment = {\n id: item.data.id,\n author: item.data.author,\n body: item.data.body,\n score: item.data.score,\n controversiality: item.data.controversiality,\n subreddit: item.data.subreddit,\n submissionTitle: post.title,\n createdUtc: item.data.created_utc,\n edited: Boolean(item.data.edited),\n isSubmitter: item.data.is_submitter,\n permalink: item.data.permalink,\n depth,\n parentId: item.data.parent_id,\n }\n\n const { replies } = item.data\n const childComments =\n replies !== undefined && typeof replies !== \"string\"\n ? parseComments(replies.data.children, depth + 1)\n : []\n\n return [comment, ...childComments]\n })\n\n const comments: readonly RedditComment[] = parseComments(json[1].data.children)\n\n return { post, comments }\n },\n )\n\n return attempt.toEither((error) => classifyRedditError(error, context))\n }\n\n // Expand \"load more\" comment stubs via /api/morechildren. `commentIds` are the ids from a\n // `more` node returned by getPostComments. Returns a flat list of the expanded comments.\n async getMoreComments(\n linkId: string,\n commentIds: readonly string[],\n ): Promise<Either<RedditError, readonly RedditComment[]>> {\n const fullLinkId = linkId.startsWith(\"t3_\") ? linkId : `t3_${linkId}`\n const context = `Failed to expand comments for ${fullLinkId}`\n const params = new URLSearchParams({\n api_type: \"json\",\n link_id: fullLinkId,\n children: commentIds.join(\",\"),\n })\n\n const attempt = await Try.async(async (): Promise<readonly RedditComment[]> => {\n const response = (await this.makeRequest(`/api/morechildren?${params}`)).orThrow()\n if (!response.ok) {\n throw new HttpError(response.status, `${context}: HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiMoreChildrenResponse\n const things = json.json.data?.things ?? []\n return things\n .filter((thing) => thing.kind === \"t1\" && thing.data.body !== undefined)\n .map((thing) => {\n const comment = thing.data\n return {\n id: comment.id,\n author: comment.author,\n body: comment.body ?? \"\",\n score: comment.score,\n controversiality: comment.controversiality,\n subreddit: comment.subreddit,\n submissionTitle: comment.link_title ?? \"\",\n createdUtc: comment.created_utc,\n edited: Boolean(comment.edited),\n isSubmitter: comment.is_submitter,\n permalink: comment.permalink,\n parentId: comment.parent_id,\n }\n })\n })\n\n return attempt.toEither((error) => classifyRedditError(error, context))\n }\n\n async getUserPosts(\n username: string,\n options: {\n readonly sort?: string\n readonly timeFilter?: string\n readonly limit?: number\n readonly after?: string\n } = {},\n ): Promise<Either<RedditError, Page<RedditPost>>> {\n const { sort = \"new\", timeFilter = \"all\", limit = 25, after } = options\n const params = new URLSearchParams({\n sort,\n t: timeFilter,\n limit: limit.toString(),\n })\n if (after !== undefined) {\n params.set(\"after\", after)\n }\n const context = `Failed to get posts for user ${username}`\n\n const attempt = await Try.async(async (): Promise<Page<RedditPost>> => {\n const response = (await this.makeRequest(`/user/${username}/submitted.json?${params}`)).orThrow()\n if (!response.ok) {\n throw new HttpError(response.status, `${context}: HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiListingResponse<RedditApiPostData>\n\n const items = json.data.children.filter((child) => child.kind === \"t3\").map((child) => parsePostData(child.data))\n return { items, ...listingCursor(json.data) }\n })\n\n return attempt.toEither((error) => classifyRedditError(error, context))\n }\n\n async getUserComments(\n username: string,\n options: {\n readonly sort?: string\n readonly timeFilter?: string\n readonly limit?: number\n readonly after?: string\n } = {},\n ): Promise<Either<RedditError, Page<RedditComment>>> {\n const { sort = \"new\", timeFilter = \"all\", limit = 25, after } = options\n const params = new URLSearchParams({\n sort,\n t: timeFilter,\n limit: limit.toString(),\n })\n if (after !== undefined) {\n params.set(\"after\", after)\n }\n const context = `Failed to get comments for user ${username}`\n\n const attempt = await Try.async(async (): Promise<Page<RedditComment>> => {\n const response = (await this.makeRequest(`/user/${username}/comments.json?${params}`)).orThrow()\n if (!response.ok) {\n throw new HttpError(response.status, `${context}: HTTP ${response.status}`)\n }\n\n const json = (await response.json()) as RedditApiListingResponse<RedditApiCommentTreeData>\n\n const items = json.data.children\n .filter((child) => child.kind === \"t1\")\n .map((child) => {\n const comment = child.data\n return {\n id: comment.id,\n author: comment.author,\n body: comment.body ?? \"\",\n score: comment.score,\n controversiality: comment.controversiality,\n subreddit: comment.subreddit,\n submissionTitle: comment.link_title ?? \"\",\n createdUtc: comment.created_utc,\n edited: Boolean(comment.edited),\n isSubmitter: comment.is_submitter,\n permalink: comment.permalink,\n }\n })\n return { items, ...listingCursor(json.data) }\n })\n\n return attempt.toEither((error) => classifyRedditError(error, context))\n }\n}\n\n// Create and export singleton instance\nconst clientHolder: { instance: Option<RedditClient> } = { instance: Option.none() }\n\nexport function initializeRedditClient(config: RedditClientConfig): RedditClient {\n const client = new RedditClient(config)\n\n clientHolder.instance = Option(client)\n return client\n}\n\nexport function getRedditClient(): Option<RedditClient> {\n return clientHolder.instance\n}\n","import { Option, Try } from \"functype\"\n\nimport type {\n FormattedCommentInfo,\n FormattedPostInfo,\n FormattedSubredditInfo,\n FormattedUserInfo,\n RedditComment,\n RedditPost,\n RedditSubreddit,\n RedditUser,\n} from \"../types\"\n\nexport function formatTimestamp(timestamp: number): string {\n return Try(() => {\n const date = new Date(timestamp * 1000)\n return date\n .toISOString()\n .replace(\"T\", \" \")\n .replace(/\\.\\d+Z$/, \" UTC\")\n }).orElse(String(timestamp))\n}\n\nexport function analyzeUserActivity(karmaRatio: number, isMod: boolean, accountAgeDays: number): string {\n const insights: readonly string[] = [\n ...(karmaRatio > 5\n ? [\"Primarily a commenter, highly engaged in discussions\"]\n : karmaRatio < 0.2\n ? [\"Content creator, focuses on sharing posts\"]\n : [\"Balanced participation in both posting and commenting\"]),\n ...(accountAgeDays < 30\n ? [\"New user, still exploring Reddit\"]\n : accountAgeDays > 365 * 5\n ? [\"Long-time Redditor with extensive platform experience\"]\n : []),\n ...(isMod ? [\"Community leader who helps maintain subreddit quality\"] : []),\n ]\n\n return insights.join(\"\\n - \")\n}\n\nexport function analyzePostEngagement(score: number, ratio: number, numComments: number): string {\n const insights: readonly string[] = [\n ...(score > 1000 && ratio > 0.95\n ? [\"Highly successful post with strong community approval\"]\n : score > 100 && ratio > 0.8\n ? [\"Well-received post with good engagement\"]\n : ratio < 0.5\n ? [\"Controversial post that sparked debate\"]\n : []),\n ...(numComments > 100\n ? [\"Generated significant discussion\"]\n : numComments > score * 0.5\n ? [\"Highly discussable content with active comment section\"]\n : numComments === 0\n ? [\"Yet to receive community interaction\"]\n : []),\n ]\n\n return insights.join(\"\\n - \")\n}\n\nexport function analyzeSubredditHealth(subscribers: number, activeUsers: Option<number>, ageDays: number): string {\n const sizeInsights: readonly string[] =\n subscribers > 1000000\n ? [\"Major subreddit with massive following\"]\n : subscribers > 100000\n ? [\"Well-established community\"]\n : subscribers < 1000\n ? [\"Niche community, potential for growth\"]\n : []\n\n const activityInsights: readonly string[] = activeUsers\n .map((active) => active / subscribers)\n .fold(\n () => [] as readonly string[],\n (activityRatio) =>\n activityRatio > 0.1\n ? [\"Highly active community with strong engagement\"]\n : activityRatio < 0.01\n ? [\"Could benefit from more community engagement initiatives\"]\n : [],\n )\n\n const ageInsights: readonly string[] =\n ageDays > 365 * 5\n ? [\"Mature subreddit with established culture\"]\n : ageDays < 90\n ? [\"New subreddit still forming its community\"]\n : []\n\n return [...sizeInsights, ...activityInsights, ...ageInsights].join(\"\\n - \")\n}\n\nexport function getUserRecommendations(karmaRatio: number, isMod: boolean, accountAgeDays: number): string {\n const recommendations: readonly string[] = [\n ...(karmaRatio > 5\n ? [\"Consider creating more posts to share your expertise\"]\n : karmaRatio < 0.2\n ? [\"Engage more in discussions to build community connections\"]\n : []),\n ...(accountAgeDays < 30\n ? [\"Explore popular subreddits in your areas of interest\", \"Read community guidelines before posting\"]\n : []),\n ...(isMod ? [\"Share moderation insights with other community leaders\"] : []),\n ]\n\n return recommendations.length > 0 ? recommendations.join(\"\\n - \") : \"Maintain your balanced engagement across Reddit\"\n}\n\nexport function getBestEngagementTime(createdUtc: number): string {\n const postHour = new Date(createdUtc * 1000).getHours()\n\n if (14 <= postHour && postHour <= 18) {\n return \"Posted during peak engagement hours (2 PM - 6 PM), good timing!\"\n } else if (23 <= postHour || postHour <= 5) {\n return \"Consider posting during more active hours (morning to evening)\"\n } else {\n return \"Posted during moderate activity hours, timing could be optimized\"\n }\n}\n\nexport function getSubredditEngagementTips(subreddit: RedditSubreddit): string {\n const sizeTips: readonly string[] =\n subreddit.subscribers > 1000000\n ? [\"Post during peak hours for maximum visibility\", \"Ensure content is highly polished due to high competition\"]\n : subreddit.subscribers < 1000\n ? [\"Engage actively to help grow the community\", \"Consider cross-posting to related larger subreddits\"]\n : []\n\n const activityTips: readonly string[] = Option(subreddit.activeUserCount)\n .map((active) => active / subreddit.subscribers)\n .fold(\n () => [] as readonly string[],\n (activityRatio) =>\n activityRatio > 0.1 ? [\"Quick responses recommended due to high activity\"] : ([] as readonly string[]),\n )\n\n const allTips = [...sizeTips, ...activityTips]\n return allTips.length > 0 ? allTips.join(\"\\n - \") : \"Regular engagement recommended to maintain community presence\"\n}\n\nexport function analyzeCommentImpact(score: number, isEdited: boolean, isOp: boolean): string {\n const insights: readonly string[] = [\n ...(score > 100\n ? [\"Highly upvoted comment with significant community agreement\"]\n : score < 0\n ? [\"Controversial or contested viewpoint\"]\n : []),\n ...(isEdited ? [\"Refined for clarity or accuracy\"] : []),\n ...(isOp ? [\"Author's perspective adds context to original post\"] : []),\n ]\n\n return insights.length > 0 ? insights.join(\"\\n - \") : \"Standard engagement with discussion\"\n}\n\nexport function formatUserInfo(user: RedditUser): FormattedUserInfo {\n const accountAgeDays = (Date.now() / 1000 - user.createdUtc) / (24 * 3600)\n const karmaRatio = user.commentKarma / (user.linkKarma === 0 ? 1 : user.linkKarma)\n\n const status: readonly string[] = [\n ...(user.isMod ? [\"Moderator\"] : []),\n ...(user.isGold ? [\"Reddit Gold Member\"] : []),\n ...(user.isEmployee ? [\"Reddit Employee\"] : []),\n ]\n\n return {\n username: user.name,\n karma: {\n commentKarma: user.commentKarma,\n postKarma: user.linkKarma,\n totalKarma: user.totalKarma,\n },\n accountStatus: status.length > 0 ? status : [\"Regular User\"],\n accountCreated: formatTimestamp(user.createdUtc),\n profileUrl: user.profileUrl,\n activityAnalysis: analyzeUserActivity(karmaRatio, user.isMod, accountAgeDays),\n recommendations: getUserRecommendations(karmaRatio, user.isMod, accountAgeDays),\n }\n}\n\nexport function formatPostInfo(post: RedditPost): FormattedPostInfo {\n const contentType = post.isSelf ? \"Text Post\" : \"Link Post\"\n const content = post.isSelf ? (post.selftext ?? \"\") : (post.url ?? \"\")\n\n const flags: readonly string[] = [\n ...(post.over18 ? [\"NSFW\"] : []),\n ...(post.spoiler === true ? [\"Spoiler\"] : []),\n ...(post.edited ? [\"Edited\"] : []),\n ]\n\n return {\n title: post.title,\n type: contentType,\n content: content.length > 300 ? `${content.substring(0, 297)}...` : content,\n author: post.author,\n subreddit: post.subreddit,\n stats: {\n score: post.score,\n upvoteRatio: post.upvoteRatio,\n comments: post.numComments,\n },\n metadata: {\n posted: formatTimestamp(post.createdUtc),\n flags,\n flair: post.linkFlairText ?? \"None\",\n },\n links: {\n fullPost: `https://reddit.com${post.permalink}`,\n shortLink: `https://redd.it/${post.id}`,\n },\n engagementAnalysis: analyzePostEngagement(post.score, post.upvoteRatio, post.numComments),\n bestTimeToEngage: getBestEngagementTime(post.createdUtc),\n }\n}\n\nexport function formatSubredditInfo(subreddit: RedditSubreddit): FormattedSubredditInfo {\n const flags: readonly string[] = [\n ...(subreddit.over18 ? [\"NSFW\"] : []),\n ...Option(subreddit.subredditType).fold(\n () => [] as readonly string[],\n (type) => [`Type: ${type}`] as readonly string[],\n ),\n ]\n\n const ageDays = (Date.now() / 1000 - subreddit.createdUtc) / (24 * 3600)\n\n return {\n name: subreddit.displayName,\n title: subreddit.title,\n stats: {\n subscribers: subreddit.subscribers,\n activeUsers: Option(subreddit.activeUserCount).fold(\n () => \"Unknown\" as number | string,\n (count) => count as number | string,\n ),\n },\n description: {\n short: subreddit.publicDescription,\n full:\n subreddit.description.length > 300 ? `${subreddit.description.substring(0, 297)}...` : subreddit.description,\n },\n metadata: {\n created: formatTimestamp(subreddit.createdUtc),\n flags: flags.length > 0 ? flags : [\"None\"],\n },\n links: {\n subreddit: `https://reddit.com${subreddit.url}`,\n wiki: `https://reddit.com/r/${subreddit.displayName}/wiki`,\n },\n communityAnalysis: analyzeSubredditHealth(subreddit.subscribers, Option(subreddit.activeUserCount), ageDays),\n engagementTips: getSubredditEngagementTips(subreddit),\n }\n}\n\nexport function formatCommentInfo(comment: RedditComment): FormattedCommentInfo {\n const flags: readonly string[] = [...(comment.edited ? [\"Edited\"] : []), ...(comment.isSubmitter ? [\"OP\"] : [])]\n\n return {\n author: comment.author,\n content: comment.body.length > 300 ? `${comment.body.substring(0, 297)}...` : comment.body,\n stats: {\n score: comment.score,\n controversiality: comment.controversiality,\n },\n context: {\n subreddit: comment.subreddit,\n thread: comment.submissionTitle,\n },\n metadata: {\n posted: formatTimestamp(comment.createdUtc),\n flags: flags.length > 0 ? flags : [\"None\"],\n },\n link: `https://reddit.com${comment.permalink}`,\n commentAnalysis: analyzeCommentImpact(comment.score, comment.edited, comment.isSubmitter),\n }\n}\n\n// Simple formatter for posts (used in search and comment tools)\nexport function formatPost(post: RedditPost) {\n return {\n title: post.title,\n author: post.author,\n subreddit: post.subreddit,\n score: post.score,\n upvoteRatio: Math.round(post.upvoteRatio * 100),\n numComments: post.numComments,\n createdAt: formatTimestamp(post.createdUtc),\n selftext: post.selftext,\n permalink: post.permalink,\n nsfw: post.over18,\n spoiler: post.spoiler,\n }\n}\n","import crypto from \"crypto\"\nimport dotenv from \"dotenv\"\nimport { FastMCP } from \"fastmcp\"\nimport { Option } from \"functype\"\nimport { z } from \"zod\"\n\nimport { getRedditClient, initializeRedditClient } from \"./client/reddit-client\"\nimport type {\n BotDisclosureConfig,\n CacheConfig,\n RedditAuthMode,\n RedditSafeMode,\n RetryConfig,\n SafeModeConfig,\n UserContent,\n} from \"./types\"\nimport { formatPostInfo, formatSubredditInfo, formatUserInfo } from \"./utils/formatters\"\n\n// Load environment variables\ndotenv.config({ quiet: true })\n\n// Version injected at build time by tsdown\ndeclare const __VERSION__: string\nconst VERSION = (typeof __VERSION__ !== \"undefined\" ? __VERSION__ : \"0.0.0-dev\") as `${number}.${number}.${number}`\n\n// User-Agent validation and building\nfunction validateUserAgent(userAgent: string, username?: string): void {\n const recommendedPattern = /^[\\w-]+:[\\w-]+:[\\d.]+ \\(by \\/u\\/\\w+\\)$/\n if (!recommendedPattern.test(userAgent)) {\n console.error(\"[Warning] User-Agent does not follow Reddit's recommended format\")\n console.error(\"[Warning] Recommended: 'platform:app_id:version (by /u/username)'\")\n console.error(\"[Warning] Non-standard User-Agents may increase ban risk\")\n if (username !== undefined) {\n console.error(`[Warning] Consider using: 'typescript:reddit-mcp-server:${VERSION} (by /u/${username})'`)\n }\n }\n}\n\nfunction buildUserAgent(customAgent?: string, username?: string): string {\n if (customAgent !== undefined) {\n validateUserAgent(customAgent, username)\n return customAgent\n }\n\n if (username !== undefined) {\n const autoAgent = `typescript:reddit-mcp-server:${VERSION} (by /u/${username})`\n console.error(`[Setup] Auto-generated User-Agent: ${autoAgent}`)\n return autoAgent\n }\n\n const fallbackAgent = `typescript:reddit-mcp-server:${VERSION} (by /u/anonymous)`\n console.error(\n \"[Setup] No REDDIT_USERNAME set — using anonymous User-Agent. Set REDDIT_USERNAME for a personalized agent.\",\n )\n return fallbackAgent\n}\n\n// Safe mode configuration\nfunction buildSafeModeConfig(safeMode: RedditSafeMode): SafeModeConfig {\n switch (safeMode) {\n case \"off\":\n return {\n enabled: false,\n mode: \"off\",\n writeDelayMs: 0,\n duplicateCheck: false,\n maxRecentHashes: 10,\n }\n case \"standard\":\n return {\n enabled: true,\n mode: \"standard\",\n writeDelayMs: 2000,\n duplicateCheck: true,\n maxRecentHashes: 10,\n }\n case \"strict\":\n return {\n enabled: true,\n mode: \"strict\",\n writeDelayMs: 5000,\n duplicateCheck: true,\n maxRecentHashes: 20,\n }\n }\n}\n\nfunction unwrapClient() {\n return getRedditClient().orThrow(new Error(\"Reddit client not initialized\"))\n}\n\n// Footer appended to paginated listings when more results are available.\nfunction nextPageHint(after?: string): string {\n return Option(after).fold(\n () => \"\",\n (cursor) => `\\n\\n---\\nMore results available — call again with after=\"${cursor}\" for the next page.`,\n )\n}\n\n// Render a mixed posts+comments listing (saved / overview).\nfunction formatUserContent(heading: string, content: UserContent): string {\n const postsSection =\n content.posts.length === 0\n ? \"\"\n : `## Posts (${content.posts.length})\\n${content.posts\n .map(\n (post, index) =>\n `${index + 1}. ${post.title} — r/${post.subreddit}, score ${post.score.toLocaleString()} — https://reddit.com${post.permalink}`,\n )\n .join(\"\\n\")}\\n\\n`\n\n const commentsSection =\n content.comments.length === 0\n ? \"\"\n : `## Comments (${content.comments.length})\\n${content.comments\n .map((comment, index) => {\n const body = comment.body.length > 200 ? `${comment.body.substring(0, 200)}...` : comment.body\n return `${index + 1}. in r/${comment.subreddit}: ${body} — https://reddit.com${comment.permalink}`\n })\n .join(\"\\n\")}\\n\\n`\n\n const empty = content.posts.length === 0 && content.comments.length === 0 ? \"No items found.\\n\\n\" : \"\"\n\n return `# ${heading}\\n\\n${postsSection}${commentsSection}${empty}`.trimEnd() + nextPageHint(content.after)\n}\n\n// Initialize Reddit client\nasync function setupRedditClient() {\n const clientId = process.env.REDDIT_CLIENT_ID\n const clientSecret = process.env.REDDIT_CLIENT_SECRET\n const customUserAgent = process.env.REDDIT_USER_AGENT\n const username = process.env.REDDIT_USERNAME\n const password = process.env.REDDIT_PASSWORD\n const authMode = (process.env.REDDIT_AUTH_MODE ?? \"auto\") as RedditAuthMode\n const safeMode = (process.env.REDDIT_SAFE_MODE ?? \"standard\") as RedditSafeMode\n\n // Validate auth mode\n if (![\"auto\", \"authenticated\", \"anonymous\"].includes(authMode)) {\n console.error(`[Error] Invalid REDDIT_AUTH_MODE: ${authMode}`)\n console.error(\"[Error] Valid options are: auto, authenticated, anonymous\")\n process.exit(1)\n }\n\n // Validate safe mode\n if (![\"off\", \"standard\", \"strict\"].includes(safeMode)) {\n console.error(`[Error] Invalid REDDIT_SAFE_MODE: ${safeMode}`)\n console.error(\"[Error] Valid options are: off, standard, strict\")\n process.exit(1)\n }\n\n // In authenticated mode, require credentials\n if (authMode === \"authenticated\" && (clientId === undefined || clientSecret === undefined)) {\n console.error(\"[Error] Authenticated mode requires REDDIT_CLIENT_ID and REDDIT_CLIENT_SECRET\")\n process.exit(1)\n }\n\n // For auto/anonymous, credentials are optional\n const hasCredentials = Boolean(clientId && clientSecret)\n\n // Build user-agent (auto-format with username if available)\n const userAgent = buildUserAgent(customUserAgent, username)\n\n // Build safe mode config\n const safeModeConfig = buildSafeModeConfig(safeMode)\n\n // Build bot disclosure config\n const botDisclosureMode = process.env.REDDIT_BOT_DISCLOSURE ?? \"off\"\n const defaultFooter =\n \"\\n\\n---\\n^(🤖 I am a bot | Built with) [^reddit-mcp-server](https://github.com/jordanburke/reddit-mcp-server)\"\n const botDisclosureConfig: BotDisclosureConfig = {\n enabled: botDisclosureMode === \"auto\",\n footer: botDisclosureMode === \"auto\" ? (process.env.REDDIT_BOT_FOOTER ?? defaultFooter) : \"\",\n }\n\n // Build cache config (enabled by default to ease Reddit rate limits; opt out with REDDIT_CACHE=off)\n const cacheEnabled = (process.env.REDDIT_CACHE ?? \"on\") !== \"off\"\n const cacheMaxMb = Number(process.env.REDDIT_CACHE_MAX_MB ?? \"50\")\n const cacheConfig: CacheConfig = {\n enabled: cacheEnabled,\n maxBytes: (Number.isFinite(cacheMaxMb) && cacheMaxMb > 0 ? cacheMaxMb : 50) * 1024 * 1024,\n }\n\n // Retry on HTTP 429 with Retry-After backoff (opt out with REDDIT_MAX_RETRIES=0)\n const maxRetriesRaw = Number(process.env.REDDIT_MAX_RETRIES ?? \"3\")\n const retryConfig: RetryConfig = {\n maxRetries: Number.isFinite(maxRetriesRaw) && maxRetriesRaw >= 0 ? Math.floor(maxRetriesRaw) : 3,\n baseDelayMs: 1000,\n maxDelayMs: 60_000,\n }\n\n const client = initializeRedditClient({\n clientId: clientId ?? \"\",\n clientSecret: clientSecret ?? \"\",\n userAgent,\n username,\n password,\n authMode,\n safeMode: safeModeConfig,\n botDisclosure: botDisclosureConfig,\n cache: cacheConfig,\n retry: retryConfig,\n })\n\n console.error(\"[Setup] Reddit client initialized\")\n console.error(`[Setup] Authentication mode: ${authMode}`)\n\n if (authMode === \"anonymous\" || !hasCredentials) {\n console.error(\"[Setup] Using anonymous Reddit API (~10 req/min)\")\n console.error(\"[Setup] No authentication required - ready to use!\")\n } else {\n console.error(\"[Setup] Testing Reddit API connection...\")\n const isConnected = await client.checkAuthentication()\n\n if (!isConnected) {\n console.error(\"[Error] ✗ Failed to connect to Reddit API\")\n console.error(\"[Error] Please check your REDDIT_CLIENT_ID and REDDIT_CLIENT_SECRET\")\n process.exit(1)\n }\n\n console.error(\"[Setup] ✓ Reddit API connection successful\")\n console.error(\"[Setup] Using OAuth Reddit API (60-100 req/min)\")\n }\n\n if (username !== undefined && password !== undefined) {\n console.error(`[Setup] ✓ User authenticated as: ${username}`)\n console.error(\"[Setup] Write operations enabled (posting, replying, editing, deleting)\")\n } else {\n console.error(\"[Setup] Read-only mode (no user credentials)\")\n console.error(\"[Setup] For write operations, set REDDIT_USERNAME and REDDIT_PASSWORD\")\n }\n\n // Log safe mode status\n if (safeModeConfig.enabled) {\n console.error(`[Setup] ✓ Safe mode enabled: ${safeModeConfig.mode}`)\n console.error(`[Setup] - Write delay: ${safeModeConfig.writeDelayMs}ms between operations`)\n console.error(`[Setup] - Duplicate detection: enabled (tracking last ${safeModeConfig.maxRecentHashes} items)`)\n } else {\n console.error(\n \"[Setup] Safe mode: off (explicitly disabled — ensure compliance with Reddit's Responsible Builder Policy)\",\n )\n }\n\n // Log bot disclosure status\n if (botDisclosureConfig.enabled) {\n console.error(\"[Setup] ✓ Bot disclosure: enabled (automated content will include bot footer)\")\n } else {\n console.error(\"[Setup] Bot disclosure: off\")\n console.error(\"[Setup] For Reddit policy compliance, consider REDDIT_BOT_DISCLOSURE=auto\")\n }\n}\n\n// OAuth token: generate once at startup, never expose in responses\nconst oauthToken = process.env.OAUTH_TOKEN ?? crypto.randomBytes(32).toString(\"hex\")\nif (process.env.OAUTH_ENABLED === \"true\" && process.env.OAUTH_TOKEN === undefined) {\n console.error(`[Auth] Generated OAuth token: ${oauthToken}`)\n}\n\n// Create FastMCP server\nconst server = new FastMCP({\n name: \"reddit-mcp-server\",\n version: VERSION,\n instructions: `A comprehensive Reddit MCP server that provides tools for interacting with Reddit API.\n\nAvailable capabilities:\n- Fetch Reddit posts, comments, and user information\n- Get subreddit details and statistics\n- Search Reddit content across posts and subreddits\n- Create posts and reply to posts/comments (with authentication)\n- Edit your own posts and comments (with authentication)\n- Delete your own posts and comments (with authentication)\n- Analyze engagement metrics and community insights\n\nFor write operations (posting, replying, editing, deleting), ensure REDDIT_USERNAME and REDDIT_PASSWORD are configured.\n\nIMPORTANT - Reddit Responsible Builder Policy compliance:\n- Data retrieved via these tools must NOT be used for AI model training without Reddit's written approval\n- Data must NOT be sold, licensed, or commercially redistributed\n- Do NOT attempt to de-anonymize or re-identify Reddit users\n- Do NOT post identical or substantially similar content across multiple subreddits\n- Do NOT use these tools to manipulate votes, karma, or circumvent Reddit safety mechanisms\n- All bot-generated content must clearly disclose its automated nature\n- Bots must NOT send private/direct messages without explicit user consent\nFor details: https://support.reddithelp.com/hc/en-us/articles/42728983564564-Responsible-Builder-Policy`,\n\n // Optional OAuth configuration for HTTP transport\n ...(process.env.OAUTH_ENABLED === \"true\" && {\n authenticate: (request: { readonly headers: { readonly authorization?: string } }) => {\n const authHeader = request.headers.authorization\n if (!authHeader?.startsWith(\"Bearer \")) {\n // eslint-disable-next-line functype/prefer-either\n throw new Response(null, {\n status: 401,\n statusText: \"Missing or invalid Authorization header\",\n })\n }\n\n const token = authHeader.slice(7)\n const tokenBuffer = Buffer.from(token)\n const expectedBuffer = Buffer.from(oauthToken)\n const tokenHash = crypto.createHash(\"sha256\").update(tokenBuffer).digest()\n const expectedHash = crypto.createHash(\"sha256\").update(expectedBuffer).digest()\n if (!crypto.timingSafeEqual(tokenHash, expectedHash)) {\n // eslint-disable-next-line functype/prefer-either\n throw new Response(null, {\n status: 403,\n statusText: \"Invalid token\",\n })\n }\n\n return Promise.resolve({ authenticated: true })\n },\n }),\n})\n\n// Test tool\nserver.addTool({\n name: \"test_reddit_mcp_server\",\n description: \"Test the Reddit MCP Server connection and configuration\",\n parameters: z.object({}),\n execute: () => {\n const client = getRedditClient()\n const hasAuth = client.fold(\n () => \"✗\",\n () => \"✓\",\n )\n const hasWriteAccess =\n process.env.REDDIT_USERNAME !== undefined && process.env.REDDIT_PASSWORD !== undefined ? \"✓\" : \"✗\"\n\n return Promise.resolve(`Reddit MCP Server Status:\n- Server: ✓ Running\n- Reddit Client: ${hasAuth} ${client.fold(\n () => \"Not initialized\",\n () => \"Initialized\",\n )}\n- Write Access: ${hasWriteAccess} ${hasWriteAccess === \"✓\" ? \"Available\" : \"Read-only mode\"}\n- Version: ${VERSION}\n\nReady to handle Reddit API requests!`)\n },\n})\n\n// User tools\nserver.addTool({\n name: \"get_user_info\",\n description: \"Get detailed information about a Reddit user including karma, account status, and activity analysis\",\n parameters: z.object({\n username: z.string().describe(\"The Reddit username (without u/ prefix)\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n const result = await client.getUser(args.username)\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to get user info: ${err.message}`)\n },\n (user) => {\n const formattedUser = formatUserInfo(user)\n\n return `# User Information: u/${formattedUser.username}\n\n## Profile Overview\n- Username: u/${formattedUser.username}\n- Karma:\n - Comment Karma: ${formattedUser.karma.commentKarma.toLocaleString()}\n - Post Karma: ${formattedUser.karma.postKarma.toLocaleString()}\n - Total Karma: ${formattedUser.karma.totalKarma.toLocaleString()}\n- Account Status: ${formattedUser.accountStatus.join(\", \")}\n- Account Created: ${formattedUser.accountCreated}\n- Profile URL: ${formattedUser.profileUrl}\n\n## Activity Analysis\n- ${formattedUser.activityAnalysis.replace(/\\n {2}- /g, \"\\n- \")}\n\n## Recommendations\n- ${formattedUser.recommendations.replace(/\\n {2}- /g, \"\\n- \")}`\n },\n )\n },\n})\n\nserver.addTool({\n name: \"get_me\",\n description:\n \"Get the authenticated user's own account info (karma, account status). Requires user credentials (REDDIT_USERNAME/REDDIT_PASSWORD); fails in anonymous mode.\",\n parameters: z.object({}),\n execute: async () => {\n const client = unwrapClient()\n\n const result = await client.getMe()\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to get authenticated user: ${err.message}`)\n },\n (user) => {\n const formattedUser = formatUserInfo(user)\n\n return `# Your Account: u/${formattedUser.username}\n\n## Profile Overview\n- Username: u/${formattedUser.username}\n- Karma:\n - Comment Karma: ${formattedUser.karma.commentKarma.toLocaleString()}\n - Post Karma: ${formattedUser.karma.postKarma.toLocaleString()}\n - Total Karma: ${formattedUser.karma.totalKarma.toLocaleString()}\n- Account Status: ${formattedUser.accountStatus.join(\", \")}\n- Account Created: ${formattedUser.accountCreated}\n- Profile URL: ${formattedUser.profileUrl}`\n },\n )\n },\n})\n\nserver.addTool({\n name: \"get_my_overview\",\n description:\n \"Get your own recent activity (posts and comments combined). Requires user credentials (REDDIT_USERNAME/REDDIT_PASSWORD).\",\n parameters: z.object({\n limit: z.number().min(1).max(100).default(25).describe(\"Number of items to retrieve\"),\n after: z.string().optional().describe(\"Pagination cursor: pass the `after` value from a previous page\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n const result = await client.getMyOverview({ limit: args.limit, after: args.after })\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to get your overview: ${err.message}`)\n },\n (content) => formatUserContent(\"Your Overview\", content),\n )\n },\n})\n\nserver.addTool({\n name: \"get_my_saved\",\n description:\n \"Get your saved posts and comments. Requires user credentials (REDDIT_USERNAME/REDDIT_PASSWORD); saved content is private.\",\n parameters: z.object({\n limit: z.number().min(1).max(100).default(25).describe(\"Number of items to retrieve\"),\n after: z.string().optional().describe(\"Pagination cursor: pass the `after` value from a previous page\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n const result = await client.getMySaved({ limit: args.limit, after: args.after })\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to get saved content: ${err.message}`)\n },\n (content) => formatUserContent(\"Your Saved Content\", content),\n )\n },\n})\n\nserver.addTool({\n name: \"get_user_posts\",\n description: \"Get recent posts by a Reddit user with sorting and filtering options\",\n parameters: z.object({\n username: z.string().describe(\"The Reddit username (without u/ prefix)\"),\n sort: z.enum([\"new\", \"hot\", \"top\"]).default(\"new\").describe(\"Sort order for posts\"),\n time_filter: z\n .enum([\"hour\", \"day\", \"week\", \"month\", \"year\", \"all\"])\n .default(\"all\")\n .describe(\"Time filter for top posts\"),\n limit: z.number().min(1).max(100).default(10).describe(\"Number of posts to retrieve\"),\n after: z\n .string()\n .optional()\n .describe(\"Pagination cursor: pass the `after` value from a previous page to fetch the next page\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n const result = await client.getUserPosts(args.username, {\n sort: args.sort,\n timeFilter: args.time_filter,\n limit: args.limit,\n after: args.after,\n })\n\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to get user posts: ${err.message}`)\n },\n (page) => {\n const posts = page.items\n if (posts.length === 0) {\n return `No posts found for u/${args.username} with the specified filters.`\n }\n\n const postSummaries = posts\n .map((post, index) => {\n const flags = [...(post.over18 ? [\"**NSFW**\"] : []), ...(post.spoiler === true ? [\"**Spoiler**\"] : [])]\n\n return `### ${index + 1}. ${post.title} ${flags.join(\" \")}\n- Subreddit: r/${post.subreddit}\n- Score: ${post.score.toLocaleString()} (${(post.upvoteRatio * 100).toFixed(1)}% upvoted)\n- Comments: ${post.numComments.toLocaleString()}\n- Posted: ${new Date(post.createdUtc * 1000).toLocaleString()}\n- Link: https://reddit.com${post.permalink}`\n })\n .join(\"\\n\\n\")\n\n return `# Posts by u/${args.username} (${args.sort} - ${args.time_filter})\n\n${postSummaries}${nextPageHint(page.after)}`\n },\n )\n },\n})\n\nserver.addTool({\n name: \"get_user_comments\",\n description: \"Get recent comments by a Reddit user with sorting and filtering options\",\n parameters: z.object({\n username: z.string().describe(\"The Reddit username (without u/ prefix)\"),\n sort: z.enum([\"new\", \"hot\", \"top\"]).default(\"new\").describe(\"Sort order for comments\"),\n time_filter: z\n .enum([\"hour\", \"day\", \"week\", \"month\", \"year\", \"all\"])\n .default(\"all\")\n .describe(\"Time filter for top comments\"),\n limit: z.number().min(1).max(100).default(10).describe(\"Number of comments to retrieve\"),\n after: z\n .string()\n .optional()\n .describe(\"Pagination cursor: pass the `after` value from a previous page to fetch the next page\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n const result = await client.getUserComments(args.username, {\n sort: args.sort,\n timeFilter: args.time_filter,\n limit: args.limit,\n after: args.after,\n })\n\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to get user comments: ${err.message}`)\n },\n (page) => {\n const comments = page.items\n if (comments.length === 0) {\n return `No comments found for u/${args.username} with the specified filters.`\n }\n\n const commentSummaries = comments\n .map((comment, index) => {\n const truncatedBody = comment.body.length > 300 ? `${comment.body.substring(0, 300)}...` : comment.body\n\n const flags = [...(comment.edited ? [\"*(edited)*\"] : []), ...(comment.isSubmitter ? [\"**OP**\"] : [])]\n\n return `### ${index + 1}. Comment ${flags.join(\" \")}\nIn r/${comment.subreddit} on \"${comment.submissionTitle}\"\n\n> ${truncatedBody}\n\n- Score: ${comment.score.toLocaleString()}\n- Posted: ${new Date(comment.createdUtc * 1000).toLocaleString()}\n- Link: https://reddit.com${comment.permalink}`\n })\n .join(\"\\n\\n\")\n\n return `# Comments by u/${args.username} (${args.sort} - ${args.time_filter})\n\n${commentSummaries}${nextPageHint(page.after)}`\n },\n )\n },\n})\n\n// Post tools\nserver.addTool({\n name: \"get_reddit_post\",\n description:\n \"Get detailed information about a specific Reddit post including content, stats, and engagement analysis\",\n parameters: z.object({\n subreddit: z.string().describe(\"The subreddit name (without r/ prefix)\"),\n post_id: z.string().describe(\"The Reddit post ID\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n const result = await client.getPost(args.post_id, args.subreddit)\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to get post: ${err.message}`)\n },\n (post) => {\n const formattedPost = formatPostInfo(post)\n\n return `# Post from r/${formattedPost.subreddit}\n\n## Post Details\n- Title: ${formattedPost.title}\n- Type: ${formattedPost.type}\n- Author: u/${formattedPost.author}\n\n## Content\n${formattedPost.content}\n\n## Stats\n- Score: ${formattedPost.stats.score.toLocaleString()}\n- Upvote Ratio: ${(formattedPost.stats.upvoteRatio * 100).toFixed(1)}%\n- Comments: ${formattedPost.stats.comments.toLocaleString()}\n\n## Metadata\n- Posted: ${formattedPost.metadata.posted}\n- Flags: ${formattedPost.metadata.flags.length > 0 ? formattedPost.metadata.flags.join(\", \") : \"None\"}\n- Flair: ${formattedPost.metadata.flair}\n\n## Links\n- Full Post: ${formattedPost.links.fullPost}\n- Short Link: ${formattedPost.links.shortLink}\n\n## Engagement Analysis\n- ${formattedPost.engagementAnalysis.replace(/\\n {2}- /g, \"\\n- \")}\n\n## Best Time to Engage\n${formattedPost.bestTimeToEngage}`\n },\n )\n },\n})\n\nserver.addTool({\n name: \"get_top_posts\",\n description: \"Get top posts from a subreddit or from the Reddit home feed\",\n parameters: z.object({\n subreddit: z.string().optional().describe(\"The subreddit name (without r/ prefix). Leave empty for home feed\"),\n time_filter: z\n .enum([\"hour\", \"day\", \"week\", \"month\", \"year\", \"all\"])\n .default(\"week\")\n .describe(\"Time period for top posts\"),\n limit: z.number().min(1).max(100).default(10).describe(\"Number of posts to retrieve\"),\n after: z\n .string()\n .optional()\n .describe(\"Pagination cursor: pass the `after` value from a previous page to fetch the next page\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n const result = await client.getTopPosts(args.subreddit ?? \"\", args.time_filter, args.limit, args.after)\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to get top posts: ${err.message}`)\n },\n (page) => {\n const posts = page.items\n if (posts.length === 0) {\n const location = Option(args.subreddit).fold(\n () => \"home feed\",\n (sr) => `r/${sr}`,\n )\n return `No posts found in ${location} for the specified time period.`\n }\n\n const formattedPosts = posts.map(formatPostInfo)\n const postSummaries = formattedPosts\n .map(\n (post, index) => `### ${index + 1}. ${post.title}\n- Author: u/${post.author}\n- Score: ${post.stats.score.toLocaleString()} (${(post.stats.upvoteRatio * 100).toFixed(1)}% upvoted)\n- Comments: ${post.stats.comments.toLocaleString()}\n- Posted: ${post.metadata.posted}\n- Link: ${post.links.shortLink}`,\n )\n .join(\"\\n\\n\")\n\n const location = Option(args.subreddit).fold(\n () => \"Home Feed\",\n (sr) => `r/${sr}`,\n )\n return `# Top Posts from ${location} (${args.time_filter})\n\n${postSummaries}${nextPageHint(page.after)}`\n },\n )\n },\n})\n\nserver.addTool({\n name: \"browse_subreddit\",\n description:\n \"Browse posts from a subreddit or the Reddit home feed with a sort order (hot, new, top, rising, controversial). \" +\n \"The time_filter only applies to top and controversial sorts.\",\n parameters: z.object({\n subreddit: z.string().optional().describe(\"The subreddit name (without r/ prefix). Leave empty for home feed\"),\n sort: z.enum([\"hot\", \"new\", \"top\", \"rising\", \"controversial\"]).default(\"hot\").describe(\"Sort order for posts\"),\n time_filter: z\n .enum([\"hour\", \"day\", \"week\", \"month\", \"year\", \"all\"])\n .default(\"week\")\n .describe(\"Time period (only applies to top and controversial sorts)\"),\n limit: z.number().min(1).max(100).default(10).describe(\"Number of posts to retrieve\"),\n after: z\n .string()\n .optional()\n .describe(\"Pagination cursor: pass the `after` value from a previous page to fetch the next page\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n const result = await client.browseSubreddit(\n args.subreddit ?? \"\",\n args.sort,\n args.time_filter,\n args.limit,\n args.after,\n )\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to browse subreddit: ${err.message}`)\n },\n (page) => {\n const posts = page.items\n const location = Option(args.subreddit).fold(\n () => \"home feed\",\n (sr) => `r/${sr}`,\n )\n if (posts.length === 0) {\n return `No posts found in ${location}.`\n }\n\n const formattedPosts = posts.map(formatPostInfo)\n const postSummaries = formattedPosts\n .map(\n (post, index) => `### ${index + 1}. ${post.title}\n- Author: u/${post.author}\n- Score: ${post.stats.score.toLocaleString()} (${(post.stats.upvoteRatio * 100).toFixed(1)}% upvoted)\n- Comments: ${post.stats.comments.toLocaleString()}\n- Posted: ${post.metadata.posted}\n- Link: ${post.links.shortLink}`,\n )\n .join(\"\\n\\n\")\n\n const timeSuffix = args.sort === \"top\" || args.sort === \"controversial\" ? `, ${args.time_filter}` : \"\"\n const heading = location === \"home feed\" ? \"Home Feed\" : location\n return `# ${args.sort} posts from ${heading} (${args.sort}${timeSuffix})\n\n${postSummaries}${nextPageHint(page.after)}`\n },\n )\n },\n})\n\n// Subreddit tools\nserver.addTool({\n name: \"get_subreddit_info\",\n description: \"Get detailed information about a subreddit including description, stats, and community analysis\",\n parameters: z.object({\n subreddit_name: z.string().describe(\"The subreddit name (without r/ prefix)\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n const result = await client.getSubredditInfo(args.subreddit_name)\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to get subreddit info: ${err.message}`)\n },\n (subreddit) => {\n const formattedSubreddit = formatSubredditInfo(subreddit)\n\n return `# Subreddit Information: r/${formattedSubreddit.name}\n\n## Overview\n- Name: r/${formattedSubreddit.name}\n- Title: ${formattedSubreddit.title}\n- Subscribers: ${formattedSubreddit.stats.subscribers.toLocaleString()}\n- Active Users: ${\n typeof formattedSubreddit.stats.activeUsers === \"number\"\n ? formattedSubreddit.stats.activeUsers.toLocaleString()\n : formattedSubreddit.stats.activeUsers\n }\n\n## Description\n${formattedSubreddit.description.short}\n\n## Detailed Description\n${formattedSubreddit.description.full}\n\n## Metadata\n- Created: ${formattedSubreddit.metadata.created}\n- Flags: ${formattedSubreddit.metadata.flags.join(\", \")}\n\n## Links\n- Subreddit: ${formattedSubreddit.links.subreddit}\n- Wiki: ${formattedSubreddit.links.wiki}\n\n## Community Analysis\n- ${formattedSubreddit.communityAnalysis.replace(/\\n {2}- /g, \"\\n- \")}\n\n## Engagement Tips\n- ${formattedSubreddit.engagementTips.replace(/\\n {2}- /g, \"\\n- \")}`\n },\n )\n },\n})\n\nserver.addTool({\n name: \"get_subreddit_rules\",\n description:\n \"Get a subreddit's posting rules. Useful to check requirements before creating a post to avoid auto-removal.\",\n parameters: z.object({\n subreddit_name: z.string().describe(\"The subreddit name (without r/ prefix)\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n const result = await client.getSubredditRules(args.subreddit_name)\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to get subreddit rules: ${err.message}`)\n },\n (rules) => {\n if (rules.length === 0) {\n return `r/${args.subreddit_name} has no listed subreddit-specific rules.`\n }\n\n const ruleList = rules\n .map((rule, index) => {\n const applies = rule.kind === \"all\" ? \"posts & comments\" : `${rule.kind}s`\n const detail = rule.description.trim() === \"\" ? \"\" : `\\n${rule.description.trim()}`\n return `### ${index + 1}. ${rule.shortName} _(applies to ${applies})_${detail}`\n })\n .join(\"\\n\\n\")\n\n return `# Posting Rules for r/${args.subreddit_name}\n\n${ruleList}`\n },\n )\n },\n})\n\nserver.addTool({\n name: \"get_post_flairs\",\n description:\n \"List the available link flairs for a subreddit (use a flair_id with create_post). Requires user credentials; many subreddits only expose flairs to members, so this may fail in anonymous mode.\",\n parameters: z.object({\n subreddit_name: z.string().describe(\"The subreddit name (without r/ prefix)\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n const result = await client.getPostFlairs(args.subreddit_name)\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to get post flairs: ${err.message}`)\n },\n (flairs) => {\n if (flairs.length === 0) {\n return `r/${args.subreddit_name} has no selectable link flairs (or none are visible to this account).`\n }\n\n const flairList = flairs\n .map((flair) => {\n const editable = flair.textEditable === true ? \" _(text editable)_\" : \"\"\n return `- ${flair.text}${editable} — \\`flair_id: ${flair.id}\\``\n })\n .join(\"\\n\")\n\n return `# Available Link Flairs for r/${args.subreddit_name}\n\n${flairList}\n\nPass the desired \\`flair_id\\` to \\`create_post\\`.`\n },\n )\n },\n})\n\nserver.addTool({\n name: \"get_trending_subreddits\",\n description: \"Get a list of currently trending subreddits\",\n parameters: z.object({}),\n execute: async () => {\n const client = unwrapClient()\n\n const result = await client.getTrendingSubreddits()\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to get trending subreddits: ${err.message}`)\n },\n (trendingSubreddits) => `# Trending Subreddits\n\n${trendingSubreddits.map((subreddit, index) => `${index + 1}. r/${subreddit}`).join(\"\\n\")}`,\n )\n },\n})\n\n// Search tools\nserver.addTool({\n name: \"search_reddit\",\n description: \"Search Reddit for posts and content across subreddits\",\n parameters: z.object({\n query: z.string().describe(\"Search query\"),\n subreddit: z.string().optional().describe(\"Limit search to specific subreddit (without r/ prefix)\"),\n sort: z\n .enum([\"relevance\", \"hot\", \"top\", \"new\", \"comments\"])\n .default(\"relevance\")\n .describe(\n \"Sort order. Prefer 'relevance' (default) for finding posts about a topic. Use 'top'/'hot' only for what's currently popular and 'new' for the latest — these rank by karma/recency and, especially combined with a narrow time_filter, can surface loosely-matching posts over the best topical results.\",\n ),\n time_filter: z.enum([\"hour\", \"day\", \"week\", \"month\", \"year\", \"all\"]).default(\"all\").describe(\"Time filter\"),\n limit: z.number().min(1).max(100).default(10).describe(\"Number of results\"),\n type: z.enum([\"link\", \"sr\", \"user\"]).default(\"link\").describe(\"Type of content to search\"),\n after: z\n .string()\n .optional()\n .describe(\"Pagination cursor: pass the `after` value from a previous page to fetch the next page\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n if (args.query.trim() === \"\") {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(\"Search query cannot be empty\")\n }\n\n const result = await client.searchReddit(args.query, {\n subreddit: args.subreddit,\n sort: args.sort,\n timeFilter: args.time_filter,\n limit: args.limit,\n type: args.type,\n after: args.after,\n })\n\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to search: ${err.message}`)\n },\n (page) => {\n const posts = page.items\n if (posts.length === 0) {\n const searchLocation = Option(args.subreddit).fold(\n () => \"\",\n (sr) => ` in r/${sr}`,\n )\n return `No results found for \"${args.query}\"${searchLocation}.`\n }\n\n const searchResults = posts\n .map((post, index) => {\n const flags = [...(post.over18 ? [\"**NSFW**\"] : []), ...(post.spoiler === true ? [\"**Spoiler**\"] : [])]\n\n return `### ${index + 1}. ${post.title} ${flags.join(\" \")}\n- Subreddit: r/${post.subreddit}\n- Author: u/${post.author}\n- Score: ${post.score.toLocaleString()} (${(post.upvoteRatio * 100).toFixed(1)}% upvoted)\n- Comments: ${post.numComments.toLocaleString()}\n- Posted: ${new Date(post.createdUtc * 1000).toLocaleString()}\n- Link: https://reddit.com${post.permalink}`\n })\n .join(\"\\n\\n\")\n\n const searchLocation = Option(args.subreddit).fold(\n () => \"\",\n (sr) => ` in r/${sr}`,\n )\n return `# Reddit Search Results for: \"${args.query}\"${searchLocation}\n\nSorted by: ${args.sort} | Time: ${args.time_filter} | Type: ${args.type}\n\n${searchResults}${nextPageHint(page.after)}`\n },\n )\n },\n})\n\n// Write tools (require user authentication)\nserver.addTool({\n name: \"create_post\",\n description:\n \"Create a new post in a subreddit (requires REDDIT_USERNAME and REDDIT_PASSWORD). \" +\n \"WARNING: Rapid posting or duplicate content may trigger Reddit's spam detection and result in account bans. \" +\n \"Consider enabling REDDIT_SAFE_MODE=standard for protection.\",\n parameters: z.object({\n subreddit: z.string().describe(\"The subreddit name (without r/ prefix)\"),\n title: z.string().describe(\"The post title\"),\n content: z.string().describe(\"The post content (text for self posts, URL for link posts)\"),\n is_self: z.boolean().default(true).describe(\"Whether this is a self post (text) or link post\"),\n flair_id: z\n .string()\n .optional()\n .describe(\"Link flair template id (from get_post_flairs); many subreddits require a flair\"),\n flair_text: z.string().optional().describe(\"Custom flair text, only for flairs whose template is text-editable\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n if (process.env.REDDIT_USERNAME === undefined || process.env.REDDIT_PASSWORD === undefined) {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(\n \"User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables.\",\n )\n }\n\n const result = await client.createPost(\n args.subreddit,\n args.title,\n args.content,\n args.is_self,\n args.flair_id,\n args.flair_text,\n )\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to create post: ${err.message}`)\n },\n (post) => {\n const formattedPost = formatPostInfo(post)\n\n return `# Post Created Successfully\n\n## Post Details\n- Title: ${formattedPost.title}\n- Subreddit: r/${formattedPost.subreddit}\n- Type: ${formattedPost.type}\n- Link: ${formattedPost.links.fullPost}\n\nYour post has been successfully submitted to r/${formattedPost.subreddit}.`\n },\n )\n },\n})\n\nserver.addTool({\n name: \"reply_to_post\",\n description:\n \"Post a reply to an existing Reddit post or comment (requires REDDIT_USERNAME and REDDIT_PASSWORD). \" +\n \"WARNING: Rapid commenting or duplicate content may trigger Reddit's spam detection. \" +\n \"Enable REDDIT_SAFE_MODE=standard for rate limiting and duplicate detection.\",\n parameters: z.object({\n post_id: z.string().describe(\"The Reddit post ID (thing_id, e.g., t3_xxxxx for posts, t1_xxxxx for comments)\"),\n content: z.string().describe(\"The reply content\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n if (process.env.REDDIT_USERNAME === undefined || process.env.REDDIT_PASSWORD === undefined) {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(\n \"User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables.\",\n )\n }\n\n const result = await client.replyToPost(args.post_id, args.content)\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to reply: ${err.message}`)\n },\n (comment) => `# Reply Posted Successfully\n\n## Comment Details\n- Posted to: ${args.post_id}\n- Author: u/${process.env.REDDIT_USERNAME}\n- Comment ID: ${comment.id}\n\nYour reply has been successfully posted.`,\n )\n },\n})\n\nserver.addTool({\n name: \"delete_post\",\n description:\n \"Delete your own Reddit post (requires REDDIT_USERNAME and REDDIT_PASSWORD). WARNING: This action is permanent and cannot be undone!\",\n parameters: z.object({\n thing_id: z\n .string()\n .describe(\n \"The full Reddit thing ID (e.g., 't3_abc123' for posts) or just the post ID (e.g., 'abc123'). The 't3_' prefix will be added automatically if missing.\",\n ),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n if (process.env.REDDIT_USERNAME === undefined || process.env.REDDIT_PASSWORD === undefined) {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(\n \"User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables.\",\n )\n }\n\n const result = await client.deletePost(args.thing_id)\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to delete post: ${err.message}`)\n },\n () => `# Post Deleted Successfully\n\nThe post ${args.thing_id} has been permanently deleted from Reddit.\n\n**Note**: This action cannot be undone. The post content has been removed and cannot be recovered.`,\n )\n },\n})\n\nserver.addTool({\n name: \"delete_comment\",\n description:\n \"Delete your own Reddit comment (requires REDDIT_USERNAME and REDDIT_PASSWORD). WARNING: This action is permanent and cannot be undone!\",\n parameters: z.object({\n thing_id: z\n .string()\n .describe(\n \"The full Reddit thing ID (e.g., 't1_abc123' for comments) or just the comment ID (e.g., 'abc123'). The 't1_' prefix will be added automatically if missing.\",\n ),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n if (process.env.REDDIT_USERNAME === undefined || process.env.REDDIT_PASSWORD === undefined) {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(\n \"User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables.\",\n )\n }\n\n const result = await client.deleteComment(args.thing_id)\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to delete comment: ${err.message}`)\n },\n () => `# Comment Deleted Successfully\n\nThe comment ${args.thing_id} has been permanently deleted from Reddit.\n\n**Note**: This action cannot be undone. The comment content has been removed and cannot be recovered.`,\n )\n },\n})\n\nserver.addTool({\n name: \"edit_post\",\n description:\n \"Edit your own Reddit post (self-text posts only, requires REDDIT_USERNAME and REDDIT_PASSWORD). \" +\n \"You can only edit the text content of self posts, not titles or link posts. \" +\n \"WARNING: Rapid edits may trigger spam detection. Enable REDDIT_SAFE_MODE for protection.\",\n parameters: z.object({\n thing_id: z\n .string()\n .describe(\n \"The full Reddit thing ID (e.g., 't3_abc123' for posts) or just the post ID (e.g., 'abc123'). The 't3_' prefix will be added automatically if missing.\",\n ),\n new_text: z.string().describe(\"The new text content for the post. Supports Reddit markdown formatting.\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n if (process.env.REDDIT_USERNAME === undefined || process.env.REDDIT_PASSWORD === undefined) {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(\n \"User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables.\",\n )\n }\n\n const result = await client.editPost(args.thing_id, args.new_text)\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to edit post: ${err.message}`)\n },\n () => `# Post Edited Successfully\n\nThe post ${args.thing_id} has been updated with your new content.\n\n**Note**:\n- Only self (text) posts can be edited\n- Post titles cannot be edited\n- Link posts cannot be edited\n- An \"edited\" marker will appear on your post`,\n )\n },\n})\n\nserver.addTool({\n name: \"edit_comment\",\n description:\n \"Edit your own Reddit comment (requires REDDIT_USERNAME and REDDIT_PASSWORD). \" +\n \"Update the text content of a comment you previously posted. \" +\n \"WARNING: Rapid edits may trigger spam detection. Enable REDDIT_SAFE_MODE for protection.\",\n parameters: z.object({\n thing_id: z\n .string()\n .describe(\n \"The full Reddit thing ID (e.g., 't1_abc123' for comments) or just the comment ID (e.g., 'abc123'). The 't1_' prefix will be added automatically if missing.\",\n ),\n new_text: z.string().describe(\"The new text content for the comment. Supports Reddit markdown formatting.\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n if (process.env.REDDIT_USERNAME === undefined || process.env.REDDIT_PASSWORD === undefined) {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(\n \"User authentication required. Please set REDDIT_USERNAME and REDDIT_PASSWORD environment variables.\",\n )\n }\n\n const result = await client.editComment(args.thing_id, args.new_text)\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to edit comment: ${err.message}`)\n },\n () => `# Comment Edited Successfully\n\nThe comment ${args.thing_id} has been updated with your new content.\n\n**Note**: An \"edited\" marker will appear on your comment to show it has been modified.`,\n )\n },\n})\n\n// Comment tools\nserver.addTool({\n name: \"get_post_comments\",\n description: \"Get comments from a specific Reddit post\",\n parameters: z.object({\n post_id: z.string().describe(\"The Reddit post ID\"),\n subreddit: z.string().describe(\"The subreddit name (without r/ prefix)\"),\n sort: z.enum([\"best\", \"top\", \"new\", \"controversial\", \"old\", \"qa\"]).default(\"best\").describe(\"Comment sort order\"),\n limit: z.number().min(1).max(500).default(100).describe(\"Maximum number of comments to retrieve\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n if (args.post_id === \"\" || args.subreddit === \"\") {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(\"post_id and subreddit are required\")\n }\n\n const result = await client.getPostComments(args.post_id, args.subreddit, {\n sort: args.sort,\n limit: args.limit,\n })\n\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to get comments: ${err.message}`)\n },\n ({ post, comments }) => {\n const header = `# Comments for: ${post.title}\n\n**Post by u/${post.author} in r/${post.subreddit}**\n- Score: ${post.score.toLocaleString()} | Comments: ${post.numComments.toLocaleString()}\n- Posted: ${new Date(post.createdUtc * 1000).toLocaleString()}\n\n---\n\n`\n\n if (comments.length === 0) {\n return `${header}No comments found for this post.`\n }\n\n const commentSummaries = comments\n .map((comment) => {\n const indent = \"└─\".repeat(Math.min(comment.depth ?? 0, 3))\n const authorBadge = comment.isSubmitter ? \" **[OP]**\" : \"\"\n const editedBadge = comment.edited ? \" *(edited)*\" : \"\"\n\n return `${indent} **u/${comment.author}**${authorBadge}${editedBadge} (${comment.score.toLocaleString()} points)\n\n${comment.body}\n\n---`\n })\n .join(\"\\n\\n\")\n\n return header + commentSummaries\n },\n )\n },\n})\n\nserver.addTool({\n name: \"get_more_comments\",\n description:\n \"Expand truncated 'load more comments' stubs in a thread. Pass the post's link id and the comment ids from a 'more' node (returned by get_post_comments) to fetch those comments.\",\n parameters: z.object({\n link_id: z.string().describe(\"The post (link) id, with or without the t3_ prefix\"),\n comment_ids: z.array(z.string()).min(1).describe(\"Comment ids to expand (from a 'more' node)\"),\n }),\n execute: async (args) => {\n const client = unwrapClient()\n\n const result = await client.getMoreComments(args.link_id, args.comment_ids)\n return result.fold(\n (err) => {\n // eslint-disable-next-line functype/prefer-either\n throw new Error(`Failed to expand comments: ${err.message}`)\n },\n (comments) => {\n if (comments.length === 0) {\n return \"No additional comments were returned for those ids.\"\n }\n\n const commentList = comments\n .map((comment, index) => {\n const truncated = comment.body.length > 300 ? `${comment.body.substring(0, 300)}...` : comment.body\n const flags = [...(comment.edited ? [\"*(edited)*\"] : []), ...(comment.isSubmitter ? [\"**OP**\"] : [])]\n return `### ${index + 1}. u/${comment.author} ${flags.join(\" \")}\n> ${truncated}\n\n- Score: ${comment.score.toLocaleString()}\n- Link: https://reddit.com${comment.permalink}`\n })\n .join(\"\\n\\n\")\n\n return `# Expanded Comments (${comments.length})\n\n${commentList}`\n },\n )\n },\n})\n\n// Initialize and start server\nasync function main() {\n await setupRedditClient()\n\n const useHttp = process.env.TRANSPORT_TYPE === \"httpStream\" || process.env.TRANSPORT_TYPE === \"http\"\n const port = parseInt(process.env.PORT ?? \"3000\")\n const host = process.env.HOST ?? \"127.0.0.1\"\n\n if (useHttp) {\n console.error(`[Setup] Starting HTTP server on ${host}:${port}`)\n await server.start({\n transportType: \"httpStream\",\n httpStream: {\n port,\n host,\n endpoint: \"/mcp\",\n },\n })\n console.error(`[Setup] HTTP server ready at http://${host}:${port}/mcp`)\n console.error(`[Setup] SSE endpoint available at http://${host}:${port}/sse`)\n } else {\n console.error(\"[Setup] Starting in stdio mode\")\n await server.start({\n transportType: \"stdio\",\n })\n }\n}\n\n// Handle graceful shutdown\nprocess.on(\"SIGINT\", () => {\n console.error(\"[Shutdown] Shutting down Reddit MCP Server...\")\n process.exit(0)\n})\n\nprocess.on(\"SIGTERM\", () => {\n console.error(\"[Shutdown] Shutting down Reddit MCP Server...\")\n process.exit(0)\n})\n\nvoid main().catch(console.error)\n"],"mappings":"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"}
|