reddit-mcp-server 1.1.0 → 1.1.1
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 +8 -2
- package/README.md +72 -51
- package/dist/bin.js +382 -1010
- package/dist/index.d.ts +1 -8
- package/dist/index.js +357 -852
- package/package.json +31 -26
- package/dist/server.d.ts +0 -5
- package/dist/server.js +0 -1754
package/dist/index.js
CHANGED
|
@@ -5,10 +5,6 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __export = (target, all) => {
|
|
9
|
-
for (var name in all)
|
|
10
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
-
};
|
|
12
8
|
var __copyProps = (to, from, except, desc) => {
|
|
13
9
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
10
|
for (let key of __getOwnPropNames(from))
|
|
@@ -25,17 +21,10 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
25
21
|
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
22
|
mod
|
|
27
23
|
));
|
|
28
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
24
|
|
|
30
25
|
// src/index.ts
|
|
31
|
-
var
|
|
32
|
-
|
|
33
|
-
RedditServer: () => RedditServer
|
|
34
|
-
});
|
|
35
|
-
module.exports = __toCommonJS(src_exports);
|
|
36
|
-
var import_server = require("@modelcontextprotocol/sdk/server/index.js");
|
|
37
|
-
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
38
|
-
var import_types6 = require("@modelcontextprotocol/sdk/types.js");
|
|
26
|
+
var import_fastmcp = require("fastmcp");
|
|
27
|
+
var import_zod = require("zod");
|
|
39
28
|
|
|
40
29
|
// src/client/reddit-client.ts
|
|
41
30
|
var RedditClient = class {
|
|
@@ -664,21 +653,6 @@ function getSubredditEngagementTips(subreddit) {
|
|
|
664
653
|
}
|
|
665
654
|
return tips.length ? tips.join("\n - ") : "Regular engagement recommended to maintain community presence";
|
|
666
655
|
}
|
|
667
|
-
function analyzeCommentImpact(score, isEdited, isOp) {
|
|
668
|
-
const insights = [];
|
|
669
|
-
if (score > 100) {
|
|
670
|
-
insights.push("Highly upvoted comment with significant community agreement");
|
|
671
|
-
} else if (score < 0) {
|
|
672
|
-
insights.push("Controversial or contested viewpoint");
|
|
673
|
-
}
|
|
674
|
-
if (isEdited) {
|
|
675
|
-
insights.push("Refined for clarity or accuracy");
|
|
676
|
-
}
|
|
677
|
-
if (isOp) {
|
|
678
|
-
insights.push("Author's perspective adds context to original post");
|
|
679
|
-
}
|
|
680
|
-
return insights.length ? insights.join("\n - ") : "Standard engagement with discussion";
|
|
681
|
-
}
|
|
682
656
|
function formatUserInfo(user) {
|
|
683
657
|
const status = [];
|
|
684
658
|
if (user.isMod) status.push("Moderator");
|
|
@@ -759,62 +733,124 @@ function formatSubredditInfo(subreddit) {
|
|
|
759
733
|
engagementTips: getSubredditEngagementTips(subreddit)
|
|
760
734
|
};
|
|
761
735
|
}
|
|
762
|
-
function formatCommentInfo(comment) {
|
|
763
|
-
const flags = [];
|
|
764
|
-
if (comment.edited) flags.push("Edited");
|
|
765
|
-
if (comment.isSubmitter) flags.push("OP");
|
|
766
|
-
return {
|
|
767
|
-
author: comment.author,
|
|
768
|
-
content: comment.body.length > 300 ? comment.body.substring(0, 297) + "..." : comment.body,
|
|
769
|
-
stats: {
|
|
770
|
-
score: comment.score,
|
|
771
|
-
controversiality: comment.controversiality
|
|
772
|
-
},
|
|
773
|
-
context: {
|
|
774
|
-
subreddit: comment.subreddit,
|
|
775
|
-
thread: comment.submissionTitle
|
|
776
|
-
},
|
|
777
|
-
metadata: {
|
|
778
|
-
posted: formatTimestamp(comment.createdUtc),
|
|
779
|
-
flags: flags.length ? flags : ["None"]
|
|
780
|
-
},
|
|
781
|
-
link: `https://reddit.com${comment.permalink}`,
|
|
782
|
-
commentAnalysis: analyzeCommentImpact(comment.score, comment.edited, comment.isSubmitter)
|
|
783
|
-
};
|
|
784
|
-
}
|
|
785
|
-
function formatPost(post) {
|
|
786
|
-
return {
|
|
787
|
-
title: post.title,
|
|
788
|
-
author: post.author,
|
|
789
|
-
subreddit: post.subreddit,
|
|
790
|
-
score: post.score,
|
|
791
|
-
upvoteRatio: Math.round(post.upvoteRatio * 100),
|
|
792
|
-
numComments: post.numComments,
|
|
793
|
-
createdAt: formatTimestamp(post.createdUtc),
|
|
794
|
-
selftext: post.selftext,
|
|
795
|
-
permalink: post.permalink,
|
|
796
|
-
nsfw: post.over18,
|
|
797
|
-
spoiler: post.spoiler
|
|
798
|
-
};
|
|
799
|
-
}
|
|
800
736
|
|
|
801
|
-
// src/
|
|
802
|
-
var
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
const
|
|
806
|
-
|
|
807
|
-
|
|
737
|
+
// src/index.ts
|
|
738
|
+
var import_dotenv = __toESM(require("dotenv"));
|
|
739
|
+
import_dotenv.default.config();
|
|
740
|
+
async function setupRedditClient() {
|
|
741
|
+
const clientId = process.env.REDDIT_CLIENT_ID;
|
|
742
|
+
const clientSecret = process.env.REDDIT_CLIENT_SECRET;
|
|
743
|
+
const userAgent = process.env.REDDIT_USER_AGENT || "RedditMCPServer/1.1.0";
|
|
744
|
+
const username = process.env.REDDIT_USERNAME;
|
|
745
|
+
const password = process.env.REDDIT_PASSWORD;
|
|
746
|
+
if (!clientId || !clientSecret) {
|
|
747
|
+
console.error(
|
|
748
|
+
"[Error] Missing required Reddit API credentials. Please set REDDIT_CLIENT_ID and REDDIT_CLIENT_SECRET environment variables."
|
|
749
|
+
);
|
|
750
|
+
process.exit(1);
|
|
808
751
|
}
|
|
809
752
|
try {
|
|
810
|
-
|
|
753
|
+
initializeRedditClient({
|
|
754
|
+
clientId,
|
|
755
|
+
clientSecret,
|
|
756
|
+
userAgent,
|
|
757
|
+
username,
|
|
758
|
+
password
|
|
759
|
+
});
|
|
760
|
+
console.error("[Setup] Reddit client initialized");
|
|
761
|
+
if (username && password) {
|
|
762
|
+
console.error(`[Setup] Authenticated as user: ${username}`);
|
|
763
|
+
} else {
|
|
764
|
+
console.error("[Setup] Running in read-only mode (no user authentication)");
|
|
765
|
+
}
|
|
766
|
+
} catch (error) {
|
|
767
|
+
console.error("[Error] Failed to initialize Reddit client:", error);
|
|
768
|
+
process.exit(1);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
var server = new import_fastmcp.FastMCP({
|
|
772
|
+
name: "reddit-mcp-server",
|
|
773
|
+
version: "1.1.0",
|
|
774
|
+
instructions: `A comprehensive Reddit MCP server that provides tools for interacting with Reddit API.
|
|
775
|
+
|
|
776
|
+
Available capabilities:
|
|
777
|
+
- Fetch Reddit posts, comments, and user information
|
|
778
|
+
- Get subreddit details and statistics
|
|
779
|
+
- Search Reddit content across posts and subreddits
|
|
780
|
+
- Create posts and reply to posts/comments (with authentication)
|
|
781
|
+
- Analyze engagement metrics and community insights
|
|
782
|
+
|
|
783
|
+
For write operations (posting, replying), ensure REDDIT_USERNAME and REDDIT_PASSWORD are configured.`,
|
|
784
|
+
// Optional OAuth configuration for HTTP transport
|
|
785
|
+
...process.env.OAUTH_ENABLED === "true" && {
|
|
786
|
+
authenticate: async (request) => {
|
|
787
|
+
const authHeader = request.headers.authorization;
|
|
788
|
+
const expectedToken = process.env.OAUTH_TOKEN;
|
|
789
|
+
if (!expectedToken) {
|
|
790
|
+
const token2 = Array.from(
|
|
791
|
+
{ length: 32 },
|
|
792
|
+
() => "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".charAt(Math.floor(Math.random() * 62))
|
|
793
|
+
).join("");
|
|
794
|
+
console.log(`[Auth] Generated OAuth token: ${token2}`);
|
|
795
|
+
throw new Response(
|
|
796
|
+
JSON.stringify({
|
|
797
|
+
error: "No OAuth token configured",
|
|
798
|
+
generatedToken: token2
|
|
799
|
+
}),
|
|
800
|
+
{
|
|
801
|
+
status: 401,
|
|
802
|
+
headers: { "Content-Type": "application/json" }
|
|
803
|
+
}
|
|
804
|
+
);
|
|
805
|
+
}
|
|
806
|
+
if (!(authHeader == null ? void 0 : authHeader.startsWith("Bearer "))) {
|
|
807
|
+
throw new Response(null, {
|
|
808
|
+
status: 401,
|
|
809
|
+
statusText: "Missing or invalid Authorization header"
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
const token = authHeader.slice(7);
|
|
813
|
+
if (token !== expectedToken) {
|
|
814
|
+
throw new Response(null, {
|
|
815
|
+
status: 403,
|
|
816
|
+
statusText: "Invalid token"
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
return { authenticated: true };
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
});
|
|
823
|
+
server.addTool({
|
|
824
|
+
name: "test_reddit_mcp_server",
|
|
825
|
+
description: "Test the Reddit MCP Server connection and configuration",
|
|
826
|
+
parameters: import_zod.z.object({}),
|
|
827
|
+
execute: async () => {
|
|
828
|
+
const client = getRedditClient();
|
|
829
|
+
const hasAuth = client ? "\u2713" : "\u2717";
|
|
830
|
+
const hasWriteAccess = process.env.REDDIT_USERNAME && process.env.REDDIT_PASSWORD ? "\u2713" : "\u2717";
|
|
831
|
+
return `Reddit MCP Server Status:
|
|
832
|
+
- Server: \u2713 Running
|
|
833
|
+
- Reddit Client: ${hasAuth} ${client ? "Initialized" : "Not initialized"}
|
|
834
|
+
- Write Access: ${hasWriteAccess} ${hasWriteAccess === "\u2713" ? "Available" : "Read-only mode"}
|
|
835
|
+
- Version: 1.1.0
|
|
836
|
+
|
|
837
|
+
Ready to handle Reddit API requests!`;
|
|
838
|
+
}
|
|
839
|
+
});
|
|
840
|
+
server.addTool({
|
|
841
|
+
name: "get_user_info",
|
|
842
|
+
description: "Get detailed information about a Reddit user including karma, account status, and activity analysis",
|
|
843
|
+
parameters: import_zod.z.object({
|
|
844
|
+
username: import_zod.z.string().describe("The Reddit username (without u/ prefix)")
|
|
845
|
+
}),
|
|
846
|
+
execute: async (args) => {
|
|
847
|
+
const client = getRedditClient();
|
|
848
|
+
if (!client) {
|
|
849
|
+
throw new Error("Reddit client not initialized");
|
|
850
|
+
}
|
|
851
|
+
const user = await client.getUser(args.username);
|
|
811
852
|
const formattedUser = formatUserInfo(user);
|
|
812
|
-
return {
|
|
813
|
-
content: [
|
|
814
|
-
{
|
|
815
|
-
type: "text",
|
|
816
|
-
text: `
|
|
817
|
-
# User Information: u/${formattedUser.username}
|
|
853
|
+
return `# User Information: u/${formattedUser.username}
|
|
818
854
|
|
|
819
855
|
## Profile Overview
|
|
820
856
|
- Username: u/${formattedUser.username}
|
|
@@ -830,113 +866,99 @@ async function getUserInfo(params) {
|
|
|
830
866
|
- ${formattedUser.activityAnalysis.replace(/\n - /g, "\n- ")}
|
|
831
867
|
|
|
832
868
|
## Recommendations
|
|
833
|
-
- ${formattedUser.recommendations.replace(/\n
|
|
834
|
-
`
|
|
835
|
-
}
|
|
836
|
-
]
|
|
837
|
-
};
|
|
838
|
-
} catch (error) {
|
|
839
|
-
throw new import_types.McpError(import_types.ErrorCode.InternalError, `Failed to fetch user data: ${String(error)}`);
|
|
840
|
-
}
|
|
841
|
-
}
|
|
842
|
-
async function getUserPosts(params) {
|
|
843
|
-
const { username, sort = "new", time_filter = "all", limit = 10 } = params;
|
|
844
|
-
const client = getRedditClient();
|
|
845
|
-
if (!client) {
|
|
846
|
-
throw new import_types.McpError(import_types.ErrorCode.InternalError, "Reddit client not initialized");
|
|
869
|
+
- ${formattedUser.recommendations.replace(/\n {2}- /g, "\n- ")}`;
|
|
847
870
|
}
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
871
|
+
});
|
|
872
|
+
server.addTool({
|
|
873
|
+
name: "get_user_posts",
|
|
874
|
+
description: "Get recent posts by a Reddit user with sorting and filtering options",
|
|
875
|
+
parameters: import_zod.z.object({
|
|
876
|
+
username: import_zod.z.string().describe("The Reddit username (without u/ prefix)"),
|
|
877
|
+
sort: import_zod.z.enum(["new", "hot", "top"]).default("new").describe("Sort order for posts"),
|
|
878
|
+
time_filter: import_zod.z.enum(["hour", "day", "week", "month", "year", "all"]).default("all").describe("Time filter for top posts"),
|
|
879
|
+
limit: import_zod.z.number().min(1).max(100).default(10).describe("Number of posts to retrieve")
|
|
880
|
+
}),
|
|
881
|
+
execute: async (args) => {
|
|
882
|
+
const client = getRedditClient();
|
|
883
|
+
if (!client) {
|
|
884
|
+
throw new Error("Reddit client not initialized");
|
|
885
|
+
}
|
|
886
|
+
const posts = await client.getUserPosts(args.username, {
|
|
887
|
+
sort: args.sort,
|
|
888
|
+
timeFilter: args.time_filter,
|
|
889
|
+
limit: args.limit
|
|
853
890
|
});
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
## Sort: ${sort} | Time: ${time_filter} | Count: ${posts.length}
|
|
861
|
-
|
|
862
|
-
${posts.map((post, index) => {
|
|
863
|
-
const date = new Date(post.createdUtc * 1e3).toLocaleString();
|
|
864
|
-
const selftext = post.selftext ? `
|
|
865
|
-
${post.selftext.substring(0, 200)}${post.selftext.length > 200 ? "..." : ""}
|
|
866
|
-
` : "";
|
|
867
|
-
return `### ${index + 1}. ${post.title}
|
|
891
|
+
if (posts.length === 0) {
|
|
892
|
+
return `No posts found for u/${args.username} with the specified filters.`;
|
|
893
|
+
}
|
|
894
|
+
const postSummaries = posts.map((post, index) => {
|
|
895
|
+
const flags = [...post.over18 ? ["**NSFW**"] : [], ...post.spoiler ? ["**Spoiler**"] : []];
|
|
896
|
+
return `### ${index + 1}. ${post.title} ${flags.join(" ")}
|
|
868
897
|
- Subreddit: r/${post.subreddit}
|
|
869
|
-
- Score: ${post.score} (${
|
|
870
|
-
- Comments: ${post.numComments}
|
|
871
|
-
- Posted: ${
|
|
872
|
-
${
|
|
873
|
-
|
|
874
|
-
${
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
}
|
|
878
|
-
]
|
|
879
|
-
};
|
|
880
|
-
} catch (error) {
|
|
881
|
-
throw new import_types.McpError(import_types.ErrorCode.InternalError, `Failed to fetch user posts: ${String(error)}`);
|
|
882
|
-
}
|
|
883
|
-
}
|
|
884
|
-
async function getUserComments(params) {
|
|
885
|
-
const { username, sort = "new", time_filter = "all", limit = 10 } = params;
|
|
886
|
-
const client = getRedditClient();
|
|
887
|
-
if (!client) {
|
|
888
|
-
throw new import_types.McpError(import_types.ErrorCode.InternalError, "Reddit client not initialized");
|
|
898
|
+
- Score: ${post.score.toLocaleString()} (${(post.upvoteRatio * 100).toFixed(1)}% upvoted)
|
|
899
|
+
- Comments: ${post.numComments.toLocaleString()}
|
|
900
|
+
- Posted: ${new Date(post.createdUtc * 1e3).toLocaleString()}
|
|
901
|
+
- Link: https://reddit.com${post.permalink}`;
|
|
902
|
+
}).join("\n\n");
|
|
903
|
+
return `# Posts by u/${args.username} (${args.sort} - ${args.time_filter})
|
|
904
|
+
|
|
905
|
+
${postSummaries}`;
|
|
889
906
|
}
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
907
|
+
});
|
|
908
|
+
server.addTool({
|
|
909
|
+
name: "get_user_comments",
|
|
910
|
+
description: "Get recent comments by a Reddit user with sorting and filtering options",
|
|
911
|
+
parameters: import_zod.z.object({
|
|
912
|
+
username: import_zod.z.string().describe("The Reddit username (without u/ prefix)"),
|
|
913
|
+
sort: import_zod.z.enum(["new", "hot", "top"]).default("new").describe("Sort order for comments"),
|
|
914
|
+
time_filter: import_zod.z.enum(["hour", "day", "week", "month", "year", "all"]).default("all").describe("Time filter for top comments"),
|
|
915
|
+
limit: import_zod.z.number().min(1).max(100).default(10).describe("Number of comments to retrieve")
|
|
916
|
+
}),
|
|
917
|
+
execute: async (args) => {
|
|
918
|
+
const client = getRedditClient();
|
|
919
|
+
if (!client) {
|
|
920
|
+
throw new Error("Reddit client not initialized");
|
|
921
|
+
}
|
|
922
|
+
const comments = await client.getUserComments(args.username, {
|
|
923
|
+
sort: args.sort,
|
|
924
|
+
timeFilter: args.time_filter,
|
|
925
|
+
limit: args.limit
|
|
895
926
|
});
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
927
|
+
if (comments.length === 0) {
|
|
928
|
+
return `No comments found for u/${args.username} with the specified filters.`;
|
|
929
|
+
}
|
|
930
|
+
const commentSummaries = comments.map((comment, index) => {
|
|
931
|
+
const truncatedBody = comment.body.length > 300 ? comment.body.substring(0, 300) + "..." : comment.body;
|
|
932
|
+
const flags = [...comment.edited ? ["*(edited)*"] : [], ...comment.isSubmitter ? ["**OP**"] : []];
|
|
933
|
+
return `### ${index + 1}. Comment ${flags.join(" ")}
|
|
934
|
+
In r/${comment.subreddit} on "${comment.submissionTitle}"
|
|
901
935
|
|
|
902
|
-
|
|
936
|
+
> ${truncatedBody}
|
|
903
937
|
|
|
904
|
-
${
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
- Score: ${comment.score} points
|
|
910
|
-
- Posted: ${date}${edited}
|
|
911
|
-
- Link: https://reddit.com${comment.permalink}
|
|
938
|
+
- Score: ${comment.score.toLocaleString()}
|
|
939
|
+
- Posted: ${new Date(comment.createdUtc * 1e3).toLocaleString()}
|
|
940
|
+
- Link: https://reddit.com${comment.permalink}`;
|
|
941
|
+
}).join("\n\n");
|
|
942
|
+
return `# Comments by u/${args.username} (${args.sort} - ${args.time_filter})
|
|
912
943
|
|
|
913
|
-
${
|
|
914
|
-
}).join("\n\n---\n\n")}`
|
|
915
|
-
}
|
|
916
|
-
]
|
|
917
|
-
};
|
|
918
|
-
} catch (error) {
|
|
919
|
-
throw new import_types.McpError(import_types.ErrorCode.InternalError, `Failed to fetch user comments: ${String(error)}`);
|
|
944
|
+
${commentSummaries}`;
|
|
920
945
|
}
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
946
|
+
});
|
|
947
|
+
server.addTool({
|
|
948
|
+
name: "get_reddit_post",
|
|
949
|
+
description: "Get detailed information about a specific Reddit post including content, stats, and engagement analysis",
|
|
950
|
+
parameters: import_zod.z.object({
|
|
951
|
+
subreddit: import_zod.z.string().describe("The subreddit name (without r/ prefix)"),
|
|
952
|
+
post_id: import_zod.z.string().describe("The Reddit post ID")
|
|
953
|
+
}),
|
|
954
|
+
execute: async (args) => {
|
|
955
|
+
const client = getRedditClient();
|
|
956
|
+
if (!client) {
|
|
957
|
+
throw new Error("Reddit client not initialized");
|
|
958
|
+
}
|
|
959
|
+
const post = await client.getPost(args.post_id, args.subreddit);
|
|
933
960
|
const formattedPost = formatPostInfo(post);
|
|
934
|
-
return {
|
|
935
|
-
content: [
|
|
936
|
-
{
|
|
937
|
-
type: "text",
|
|
938
|
-
text: `
|
|
939
|
-
# Post from r/${formattedPost.subreddit}
|
|
961
|
+
return `# Post from r/${formattedPost.subreddit}
|
|
940
962
|
|
|
941
963
|
## Post Details
|
|
942
964
|
- Title: ${formattedPost.title}
|
|
@@ -964,130 +986,56 @@ ${formattedPost.content}
|
|
|
964
986
|
- ${formattedPost.engagementAnalysis.replace(/\n - /g, "\n- ")}
|
|
965
987
|
|
|
966
988
|
## Best Time to Engage
|
|
967
|
-
${formattedPost.bestTimeToEngage}
|
|
968
|
-
`
|
|
969
|
-
}
|
|
970
|
-
]
|
|
971
|
-
};
|
|
972
|
-
} catch (error) {
|
|
973
|
-
throw new import_types2.McpError(import_types2.ErrorCode.InternalError, `Failed to fetch post data: ${String(error)}`);
|
|
974
|
-
}
|
|
975
|
-
}
|
|
976
|
-
async function getTopPosts(params) {
|
|
977
|
-
const { subreddit, time_filter = "week", limit = 10 } = params;
|
|
978
|
-
const client = getRedditClient();
|
|
979
|
-
if (!client) {
|
|
980
|
-
throw new import_types2.McpError(import_types2.ErrorCode.InternalError, "Reddit client not initialized");
|
|
989
|
+
${formattedPost.bestTimeToEngage}`;
|
|
981
990
|
}
|
|
982
|
-
|
|
983
|
-
|
|
991
|
+
});
|
|
992
|
+
server.addTool({
|
|
993
|
+
name: "get_top_posts",
|
|
994
|
+
description: "Get top posts from a subreddit or from the Reddit home feed",
|
|
995
|
+
parameters: import_zod.z.object({
|
|
996
|
+
subreddit: import_zod.z.string().optional().describe("The subreddit name (without r/ prefix). Leave empty for home feed"),
|
|
997
|
+
time_filter: import_zod.z.enum(["hour", "day", "week", "month", "year", "all"]).default("week").describe("Time period for top posts"),
|
|
998
|
+
limit: import_zod.z.number().min(1).max(100).default(10).describe("Number of posts to retrieve")
|
|
999
|
+
}),
|
|
1000
|
+
execute: async (args) => {
|
|
1001
|
+
const client = getRedditClient();
|
|
1002
|
+
if (!client) {
|
|
1003
|
+
throw new Error("Reddit client not initialized");
|
|
1004
|
+
}
|
|
1005
|
+
const posts = await client.getTopPosts(args.subreddit || "", args.time_filter, args.limit);
|
|
1006
|
+
if (posts.length === 0) {
|
|
1007
|
+
const location2 = args.subreddit ? `r/${args.subreddit}` : "home feed";
|
|
1008
|
+
return `No posts found in ${location2} for the specified time period.`;
|
|
1009
|
+
}
|
|
984
1010
|
const formattedPosts = posts.map(formatPostInfo);
|
|
985
1011
|
const postSummaries = formattedPosts.map(
|
|
986
|
-
(post, index) =>
|
|
987
|
-
### ${index + 1}. ${post.title}
|
|
1012
|
+
(post, index) => `### ${index + 1}. ${post.title}
|
|
988
1013
|
- Author: u/${post.author}
|
|
989
1014
|
- Score: ${post.stats.score.toLocaleString()} (${(post.stats.upvoteRatio * 100).toFixed(1)}% upvoted)
|
|
990
1015
|
- Comments: ${post.stats.comments.toLocaleString()}
|
|
991
1016
|
- Posted: ${post.metadata.posted}
|
|
992
|
-
- Link: ${post.links.shortLink}
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
return {
|
|
996
|
-
content: [
|
|
997
|
-
{
|
|
998
|
-
type: "text",
|
|
999
|
-
text: `
|
|
1000
|
-
# Top Posts from r/${subreddit} (${time_filter})
|
|
1001
|
-
|
|
1002
|
-
${postSummaries}
|
|
1003
|
-
`
|
|
1004
|
-
}
|
|
1005
|
-
]
|
|
1006
|
-
};
|
|
1007
|
-
} catch (error) {
|
|
1008
|
-
throw new import_types2.McpError(import_types2.ErrorCode.InternalError, `Failed to fetch top posts: ${String(error)}`);
|
|
1009
|
-
}
|
|
1010
|
-
}
|
|
1011
|
-
async function createPost(params) {
|
|
1012
|
-
const { subreddit, title, content, is_self = true } = params;
|
|
1013
|
-
const client = getRedditClient();
|
|
1014
|
-
if (!client) {
|
|
1015
|
-
throw new import_types2.McpError(import_types2.ErrorCode.InternalError, "Reddit client not initialized");
|
|
1016
|
-
}
|
|
1017
|
-
try {
|
|
1018
|
-
const post = await client.createPost(subreddit, title, content, is_self);
|
|
1019
|
-
const formattedPost = formatPostInfo(post);
|
|
1020
|
-
return {
|
|
1021
|
-
content: [
|
|
1022
|
-
{
|
|
1023
|
-
type: "text",
|
|
1024
|
-
text: `
|
|
1025
|
-
# Post Created Successfully
|
|
1026
|
-
|
|
1027
|
-
## Post Details
|
|
1028
|
-
- Title: ${formattedPost.title}
|
|
1029
|
-
- Subreddit: r/${formattedPost.subreddit}
|
|
1030
|
-
- Type: ${formattedPost.type}
|
|
1031
|
-
- Link: ${formattedPost.links.fullPost}
|
|
1032
|
-
|
|
1033
|
-
Your post has been successfully submitted to r/${formattedPost.subreddit}.
|
|
1034
|
-
`
|
|
1035
|
-
}
|
|
1036
|
-
]
|
|
1037
|
-
};
|
|
1038
|
-
} catch (error) {
|
|
1039
|
-
throw new import_types2.McpError(import_types2.ErrorCode.InternalError, `Failed to create post: ${String(error)}`);
|
|
1040
|
-
}
|
|
1041
|
-
}
|
|
1042
|
-
async function replyToPost(params) {
|
|
1043
|
-
const { post_id, content } = params;
|
|
1044
|
-
const client = getRedditClient();
|
|
1045
|
-
if (!client) {
|
|
1046
|
-
throw new import_types2.McpError(import_types2.ErrorCode.InternalError, "Reddit client not initialized");
|
|
1047
|
-
}
|
|
1048
|
-
try {
|
|
1049
|
-
const comment = await client.replyToPost(post_id, content);
|
|
1050
|
-
const formattedComment = formatCommentInfo(comment);
|
|
1051
|
-
return {
|
|
1052
|
-
content: [
|
|
1053
|
-
{
|
|
1054
|
-
type: "text",
|
|
1055
|
-
text: `
|
|
1056
|
-
# Reply Posted Successfully
|
|
1057
|
-
|
|
1058
|
-
## Comment Details
|
|
1059
|
-
- Author: u/${formattedComment.author}
|
|
1060
|
-
- Subreddit: r/${formattedComment.context.subreddit}
|
|
1061
|
-
- Thread: ${formattedComment.context.thread}
|
|
1062
|
-
- Link: ${formattedComment.link}
|
|
1063
|
-
|
|
1064
|
-
Your reply has been successfully posted.
|
|
1065
|
-
`
|
|
1066
|
-
}
|
|
1067
|
-
]
|
|
1068
|
-
};
|
|
1069
|
-
} catch (error) {
|
|
1070
|
-
throw new import_types2.McpError(import_types2.ErrorCode.InternalError, `Failed to reply to post: ${String(error)}`);
|
|
1071
|
-
}
|
|
1072
|
-
}
|
|
1017
|
+
- Link: ${post.links.shortLink}`
|
|
1018
|
+
).join("\n\n");
|
|
1019
|
+
const location = args.subreddit ? `r/${args.subreddit}` : "Home Feed";
|
|
1020
|
+
return `# Top Posts from ${location} (${args.time_filter})
|
|
1073
1021
|
|
|
1074
|
-
|
|
1075
|
-
var import_types3 = require("@modelcontextprotocol/sdk/types.js");
|
|
1076
|
-
async function getSubredditInfo(params) {
|
|
1077
|
-
const { subreddit_name } = params;
|
|
1078
|
-
const client = getRedditClient();
|
|
1079
|
-
if (!client) {
|
|
1080
|
-
throw new import_types3.McpError(import_types3.ErrorCode.InternalError, "Reddit client not initialized");
|
|
1022
|
+
${postSummaries}`;
|
|
1081
1023
|
}
|
|
1082
|
-
|
|
1083
|
-
|
|
1024
|
+
});
|
|
1025
|
+
server.addTool({
|
|
1026
|
+
name: "get_subreddit_info",
|
|
1027
|
+
description: "Get detailed information about a subreddit including description, stats, and community analysis",
|
|
1028
|
+
parameters: import_zod.z.object({
|
|
1029
|
+
subreddit_name: import_zod.z.string().describe("The subreddit name (without r/ prefix)")
|
|
1030
|
+
}),
|
|
1031
|
+
execute: async (args) => {
|
|
1032
|
+
const client = getRedditClient();
|
|
1033
|
+
if (!client) {
|
|
1034
|
+
throw new Error("Reddit client not initialized");
|
|
1035
|
+
}
|
|
1036
|
+
const subreddit = await client.getSubredditInfo(args.subreddit_name);
|
|
1084
1037
|
const formattedSubreddit = formatSubredditInfo(subreddit);
|
|
1085
|
-
return {
|
|
1086
|
-
content: [
|
|
1087
|
-
{
|
|
1088
|
-
type: "text",
|
|
1089
|
-
text: `
|
|
1090
|
-
# Subreddit Information: r/${formattedSubreddit.name}
|
|
1038
|
+
return `# Subreddit Information: r/${formattedSubreddit.name}
|
|
1091
1039
|
|
|
1092
1040
|
## Overview
|
|
1093
1041
|
- Name: r/${formattedSubreddit.name}
|
|
@@ -1113,582 +1061,139 @@ ${formattedSubreddit.description.full}
|
|
|
1113
1061
|
- ${formattedSubreddit.communityAnalysis.replace(/\n - /g, "\n- ")}
|
|
1114
1062
|
|
|
1115
1063
|
## Engagement Tips
|
|
1116
|
-
- ${formattedSubreddit.engagementTips.replace(/\n - /g, "\n- ")}
|
|
1117
|
-
`
|
|
1118
|
-
}
|
|
1119
|
-
]
|
|
1120
|
-
};
|
|
1121
|
-
} catch (error) {
|
|
1122
|
-
throw new import_types3.McpError(import_types3.ErrorCode.InternalError, `Failed to fetch subreddit data: ${String(error)}`);
|
|
1064
|
+
- ${formattedSubreddit.engagementTips.replace(/\n - /g, "\n- ")}`;
|
|
1123
1065
|
}
|
|
1124
|
-
}
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1066
|
+
});
|
|
1067
|
+
server.addTool({
|
|
1068
|
+
name: "get_trending_subreddits",
|
|
1069
|
+
description: "Get a list of currently trending subreddits",
|
|
1070
|
+
parameters: import_zod.z.object({}),
|
|
1071
|
+
execute: async () => {
|
|
1072
|
+
const client = getRedditClient();
|
|
1073
|
+
if (!client) {
|
|
1074
|
+
throw new Error("Reddit client not initialized");
|
|
1075
|
+
}
|
|
1131
1076
|
const trendingSubreddits = await client.getTrendingSubreddits();
|
|
1132
|
-
return
|
|
1133
|
-
content: [
|
|
1134
|
-
{
|
|
1135
|
-
type: "text",
|
|
1136
|
-
text: `
|
|
1137
|
-
# Trending Subreddits
|
|
1077
|
+
return `# Trending Subreddits
|
|
1138
1078
|
|
|
1139
|
-
${trendingSubreddits.map((subreddit, index) => `${index + 1}. r/${subreddit}`).join("\n")}
|
|
1140
|
-
`
|
|
1141
|
-
}
|
|
1142
|
-
]
|
|
1143
|
-
};
|
|
1144
|
-
} catch (error) {
|
|
1145
|
-
throw new import_types3.McpError(import_types3.ErrorCode.InternalError, `Failed to fetch trending subreddits: ${String(error)}`);
|
|
1079
|
+
${trendingSubreddits.map((subreddit, index) => `${index + 1}. r/${subreddit}`).join("\n")}`;
|
|
1146
1080
|
}
|
|
1147
|
-
}
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1081
|
+
});
|
|
1082
|
+
server.addTool({
|
|
1083
|
+
name: "search_reddit",
|
|
1084
|
+
description: "Search Reddit for posts and content across subreddits",
|
|
1085
|
+
parameters: import_zod.z.object({
|
|
1086
|
+
query: import_zod.z.string().describe("Search query"),
|
|
1087
|
+
subreddit: import_zod.z.string().optional().describe("Limit search to specific subreddit (without r/ prefix)"),
|
|
1088
|
+
sort: import_zod.z.enum(["relevance", "hot", "top", "new", "comments"]).default("relevance").describe("Sort order"),
|
|
1089
|
+
time_filter: import_zod.z.enum(["hour", "day", "week", "month", "year", "all"]).default("all").describe("Time filter"),
|
|
1090
|
+
limit: import_zod.z.number().min(1).max(100).default(10).describe("Number of results"),
|
|
1091
|
+
type: import_zod.z.enum(["link", "sr", "user"]).default("link").describe("Type of content to search")
|
|
1092
|
+
}),
|
|
1093
|
+
execute: async (args) => {
|
|
1094
|
+
const client = getRedditClient();
|
|
1095
|
+
if (!client) {
|
|
1096
|
+
throw new Error("Reddit client not initialized");
|
|
1097
|
+
}
|
|
1098
|
+
if (!args.query || args.query.trim() === "") {
|
|
1099
|
+
throw new Error("Search query cannot be empty");
|
|
1100
|
+
}
|
|
1101
|
+
const posts = await client.searchReddit(args.query, {
|
|
1102
|
+
subreddit: args.subreddit,
|
|
1103
|
+
sort: args.sort,
|
|
1104
|
+
timeFilter: args.time_filter,
|
|
1105
|
+
limit: args.limit,
|
|
1106
|
+
type: args.type
|
|
1167
1107
|
});
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
-
|
|
1176
|
-
-
|
|
1177
|
-
-
|
|
1178
|
-
-
|
|
1108
|
+
if (posts.length === 0) {
|
|
1109
|
+
const searchLocation2 = args.subreddit ? ` in r/${args.subreddit}` : "";
|
|
1110
|
+
return `No results found for "${args.query}"${searchLocation2}.`;
|
|
1111
|
+
}
|
|
1112
|
+
const searchResults = posts.map((post, index) => {
|
|
1113
|
+
const flags = [...post.over18 ? ["**NSFW**"] : [], ...post.spoiler ? ["**Spoiler**"] : []];
|
|
1114
|
+
return `### ${index + 1}. ${post.title} ${flags.join(" ")}
|
|
1115
|
+
- Subreddit: r/${post.subreddit}
|
|
1116
|
+
- Author: u/${post.author}
|
|
1117
|
+
- Score: ${post.score.toLocaleString()} (${(post.upvoteRatio * 100).toFixed(1)}% upvoted)
|
|
1118
|
+
- Comments: ${post.numComments.toLocaleString()}
|
|
1119
|
+
- Posted: ${new Date(post.createdUtc * 1e3).toLocaleString()}
|
|
1120
|
+
- Link: https://reddit.com${post.permalink}`;
|
|
1121
|
+
}).join("\n\n");
|
|
1122
|
+
const searchLocation = args.subreddit ? ` in r/${args.subreddit}` : "";
|
|
1123
|
+
return `# Reddit Search Results for: "${args.query}"${searchLocation}
|
|
1179
1124
|
|
|
1180
|
-
${
|
|
1181
|
-
const formatted = formatPost(post);
|
|
1182
|
-
return `### ${index + 1}. ${formatted.title}
|
|
1183
|
-
- Author: u/${formatted.author}
|
|
1184
|
-
- Subreddit: r/${formatted.subreddit}
|
|
1185
|
-
- Score: ${formatted.score} (${formatted.upvoteRatio}% upvoted)
|
|
1186
|
-
- Comments: ${formatted.numComments}
|
|
1187
|
-
- Posted: ${formatted.createdAt}
|
|
1188
|
-
${formatted.selftext ? `
|
|
1189
|
-
${formatted.selftext.substring(0, 200)}${formatted.selftext.length > 200 ? "..." : ""}
|
|
1190
|
-
` : ""}
|
|
1191
|
-
- Link: https://reddit.com${formatted.permalink}
|
|
1192
|
-
${formatted.nsfw ? "- **NSFW**" : ""}
|
|
1193
|
-
${formatted.spoiler ? "- **Spoiler**" : ""}
|
|
1194
|
-
`;
|
|
1195
|
-
}).join("\n")}`
|
|
1196
|
-
}
|
|
1197
|
-
]
|
|
1198
|
-
};
|
|
1199
|
-
} catch (error) {
|
|
1200
|
-
throw new import_types4.McpError(import_types4.ErrorCode.InternalError, `Failed to search Reddit: ${String(error)}`);
|
|
1201
|
-
}
|
|
1202
|
-
}
|
|
1125
|
+
Sorted by: ${args.sort} | Time: ${args.time_filter} | Type: ${args.type}
|
|
1203
1126
|
|
|
1204
|
-
|
|
1205
|
-
var import_types5 = require("@modelcontextprotocol/sdk/types.js");
|
|
1206
|
-
async function getPostComments(params) {
|
|
1207
|
-
const { post_id, subreddit, sort = "best", limit = 100 } = params;
|
|
1208
|
-
const client = getRedditClient();
|
|
1209
|
-
if (!client) {
|
|
1210
|
-
throw new import_types5.McpError(import_types5.ErrorCode.InternalError, "Reddit client not initialized");
|
|
1211
|
-
}
|
|
1212
|
-
if (!post_id || !subreddit) {
|
|
1213
|
-
throw new import_types5.McpError(import_types5.ErrorCode.InvalidParams, "post_id and subreddit are required");
|
|
1127
|
+
${searchResults}`;
|
|
1214
1128
|
}
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1129
|
+
});
|
|
1130
|
+
server.addTool({
|
|
1131
|
+
name: "get_post_comments",
|
|
1132
|
+
description: "Get comments from a specific Reddit post",
|
|
1133
|
+
parameters: import_zod.z.object({
|
|
1134
|
+
post_id: import_zod.z.string().describe("The Reddit post ID"),
|
|
1135
|
+
subreddit: import_zod.z.string().describe("The subreddit name (without r/ prefix)"),
|
|
1136
|
+
sort: import_zod.z.enum(["best", "top", "new", "controversial", "old", "qa"]).default("best").describe("Comment sort order"),
|
|
1137
|
+
limit: import_zod.z.number().min(1).max(500).default(100).describe("Maximum number of comments to retrieve")
|
|
1138
|
+
}),
|
|
1139
|
+
execute: async (args) => {
|
|
1140
|
+
const client = getRedditClient();
|
|
1141
|
+
if (!client) {
|
|
1142
|
+
throw new Error("Reddit client not initialized");
|
|
1143
|
+
}
|
|
1144
|
+
if (!args.post_id || !args.subreddit) {
|
|
1145
|
+
throw new Error("post_id and subreddit are required");
|
|
1146
|
+
}
|
|
1147
|
+
const data = await client.getPostComments(args.post_id, args.subreddit, {
|
|
1148
|
+
sort: args.sort,
|
|
1149
|
+
limit: args.limit
|
|
1219
1150
|
});
|
|
1220
|
-
const
|
|
1221
|
-
const
|
|
1222
|
-
|
|
1223
|
-
const submitter = comment.isSubmitter ? " **[OP]**" : "";
|
|
1224
|
-
const depth = comment.depth || 0;
|
|
1225
|
-
const prefix = " ".repeat(depth) + (depth > 0 ? "\u2514\u2500 " : "");
|
|
1226
|
-
return `${prefix}**u/${comment.author}**${submitter} \u2022 ${comment.score} points \u2022 ${new Date(comment.createdUtc * 1e3).toLocaleString()}${edited}
|
|
1227
|
-
${prefix}${comment.body.split("\n").join(`
|
|
1228
|
-
${prefix}`)}`;
|
|
1229
|
-
};
|
|
1230
|
-
return {
|
|
1231
|
-
content: [
|
|
1232
|
-
{
|
|
1233
|
-
type: "text",
|
|
1234
|
-
text: `# Comments for: ${formattedPost.title}
|
|
1151
|
+
const post = data.post;
|
|
1152
|
+
const comments = data.comments;
|
|
1153
|
+
let response = `# Comments for: ${post.title}
|
|
1235
1154
|
|
|
1236
|
-
|
|
1237
|
-
-
|
|
1238
|
-
-
|
|
1239
|
-
- Score: ${formattedPost.score} (${formattedPost.upvoteRatio}% upvoted)
|
|
1240
|
-
- Posted: ${formattedPost.createdAt}
|
|
1241
|
-
- Link: https://reddit.com${formattedPost.permalink}
|
|
1155
|
+
**Post by u/${post.author} in r/${post.subreddit}**
|
|
1156
|
+
- Score: ${post.score.toLocaleString()} | Comments: ${post.numComments.toLocaleString()}
|
|
1157
|
+
- Posted: ${new Date(post.createdUtc * 1e3).toLocaleString()}
|
|
1242
1158
|
|
|
1243
|
-
|
|
1244
|
-
${formattedPost.selftext || "[Link post - no text content]"}
|
|
1159
|
+
---
|
|
1245
1160
|
|
|
1246
|
-
|
|
1161
|
+
`;
|
|
1162
|
+
if (comments.length === 0) {
|
|
1163
|
+
response += "No comments found for this post.";
|
|
1164
|
+
return response;
|
|
1165
|
+
}
|
|
1166
|
+
const commentSummaries = comments.map((comment) => {
|
|
1167
|
+
const indent = "\u2514\u2500".repeat(Math.min(comment.depth || 0, 3));
|
|
1168
|
+
const authorBadge = comment.isSubmitter ? " **[OP]**" : "";
|
|
1169
|
+
const editedBadge = comment.edited ? " *(edited)*" : "";
|
|
1170
|
+
return `${indent} **u/${comment.author}**${authorBadge}${editedBadge} (${comment.score.toLocaleString()} points)
|
|
1247
1171
|
|
|
1248
|
-
${
|
|
1249
|
-
}
|
|
1250
|
-
]
|
|
1251
|
-
};
|
|
1252
|
-
} catch (error) {
|
|
1253
|
-
throw new import_types5.McpError(import_types5.ErrorCode.InternalError, `Failed to fetch comments: ${String(error)}`);
|
|
1254
|
-
}
|
|
1255
|
-
}
|
|
1172
|
+
${comment.body}
|
|
1256
1173
|
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
server;
|
|
1262
|
-
constructor() {
|
|
1263
|
-
this.server = new import_server.Server(
|
|
1264
|
-
{
|
|
1265
|
-
name: "reddit-mcp-server",
|
|
1266
|
-
version: "0.1.0"
|
|
1267
|
-
},
|
|
1268
|
-
{
|
|
1269
|
-
capabilities: {
|
|
1270
|
-
tools: {},
|
|
1271
|
-
logging: {}
|
|
1272
|
-
}
|
|
1273
|
-
}
|
|
1274
|
-
);
|
|
1275
|
-
this.initializeRedditClient();
|
|
1276
|
-
this.setupToolHandlers();
|
|
1277
|
-
this.server.onerror = async (error) => {
|
|
1278
|
-
await this.server.sendLoggingMessage({
|
|
1279
|
-
level: "error",
|
|
1280
|
-
logger: "reddit-server",
|
|
1281
|
-
data: `Server error: ${error}`
|
|
1282
|
-
});
|
|
1283
|
-
};
|
|
1284
|
-
process.on("SIGINT", async () => {
|
|
1285
|
-
await this.server.close();
|
|
1286
|
-
process.exit(0);
|
|
1287
|
-
});
|
|
1288
|
-
}
|
|
1289
|
-
initializeRedditClient() {
|
|
1290
|
-
const clientId = process.env.REDDIT_CLIENT_ID;
|
|
1291
|
-
const clientSecret = process.env.REDDIT_CLIENT_SECRET;
|
|
1292
|
-
const userAgent = process.env.REDDIT_USER_AGENT || "RedditMCPServer/0.1.0";
|
|
1293
|
-
const username = process.env.REDDIT_USERNAME;
|
|
1294
|
-
const password = process.env.REDDIT_PASSWORD;
|
|
1295
|
-
if (!clientId || !clientSecret) {
|
|
1296
|
-
console.error(
|
|
1297
|
-
"[Error] Missing required Reddit API credentials. Please set REDDIT_CLIENT_ID and REDDIT_CLIENT_SECRET environment variables."
|
|
1298
|
-
);
|
|
1299
|
-
process.exit(1);
|
|
1300
|
-
}
|
|
1301
|
-
try {
|
|
1302
|
-
initializeRedditClient({
|
|
1303
|
-
clientId,
|
|
1304
|
-
clientSecret,
|
|
1305
|
-
userAgent,
|
|
1306
|
-
username,
|
|
1307
|
-
password
|
|
1308
|
-
});
|
|
1309
|
-
console.error("[Setup] Reddit client initialized");
|
|
1310
|
-
if (username && password) {
|
|
1311
|
-
console.error(`[Setup] Authenticated as user: ${username}`);
|
|
1312
|
-
} else {
|
|
1313
|
-
console.error("[Setup] Running in read-only mode (no user authentication)");
|
|
1314
|
-
}
|
|
1315
|
-
} catch (error) {
|
|
1316
|
-
console.error("[Error] Failed to initialize Reddit client:", error);
|
|
1317
|
-
process.exit(1);
|
|
1318
|
-
}
|
|
1319
|
-
}
|
|
1320
|
-
setupToolHandlers() {
|
|
1321
|
-
this.server.setRequestHandler(import_types6.ListToolsRequestSchema, async () => ({
|
|
1322
|
-
tools: [
|
|
1323
|
-
{
|
|
1324
|
-
name: "test_reddit_mcp_server",
|
|
1325
|
-
description: "Test the Reddit MCP Server",
|
|
1326
|
-
inputSchema: {
|
|
1327
|
-
type: "object",
|
|
1328
|
-
properties: {
|
|
1329
|
-
// No input parameters, this will just return a test message
|
|
1330
|
-
}
|
|
1331
|
-
}
|
|
1332
|
-
},
|
|
1333
|
-
{
|
|
1334
|
-
name: "get_reddit_post",
|
|
1335
|
-
description: "Get a Reddit post",
|
|
1336
|
-
inputSchema: {
|
|
1337
|
-
type: "object",
|
|
1338
|
-
properties: {
|
|
1339
|
-
subreddit: {
|
|
1340
|
-
type: "string",
|
|
1341
|
-
description: "The subreddit to fetch posts from"
|
|
1342
|
-
},
|
|
1343
|
-
post_id: {
|
|
1344
|
-
type: "string",
|
|
1345
|
-
description: "The ID of the post to fetch"
|
|
1346
|
-
}
|
|
1347
|
-
},
|
|
1348
|
-
required: ["subreddit", "post_id"]
|
|
1349
|
-
}
|
|
1350
|
-
},
|
|
1351
|
-
{
|
|
1352
|
-
name: "get_top_posts",
|
|
1353
|
-
description: "Get top posts from a subreddit",
|
|
1354
|
-
inputSchema: {
|
|
1355
|
-
type: "object",
|
|
1356
|
-
properties: {
|
|
1357
|
-
subreddit: {
|
|
1358
|
-
type: "string",
|
|
1359
|
-
description: "Name of the subreddit"
|
|
1360
|
-
},
|
|
1361
|
-
time_filter: {
|
|
1362
|
-
type: "string",
|
|
1363
|
-
description: "Time period to filter posts (e.g. 'day', 'week', 'month', 'year', 'all')",
|
|
1364
|
-
enum: ["day", "week", "month", "year", "all"],
|
|
1365
|
-
default: "week"
|
|
1366
|
-
},
|
|
1367
|
-
limit: {
|
|
1368
|
-
type: "integer",
|
|
1369
|
-
description: "Number of posts to fetch",
|
|
1370
|
-
default: 10
|
|
1371
|
-
}
|
|
1372
|
-
},
|
|
1373
|
-
required: ["subreddit"]
|
|
1374
|
-
}
|
|
1375
|
-
},
|
|
1376
|
-
{
|
|
1377
|
-
name: "get_user_info",
|
|
1378
|
-
description: "Get information about a Reddit user",
|
|
1379
|
-
inputSchema: {
|
|
1380
|
-
type: "object",
|
|
1381
|
-
properties: {
|
|
1382
|
-
username: {
|
|
1383
|
-
type: "string",
|
|
1384
|
-
description: "The username of the Reddit user to get info for"
|
|
1385
|
-
}
|
|
1386
|
-
},
|
|
1387
|
-
required: ["username"]
|
|
1388
|
-
}
|
|
1389
|
-
},
|
|
1390
|
-
{
|
|
1391
|
-
name: "get_subreddit_info",
|
|
1392
|
-
description: "Get information about a subreddit",
|
|
1393
|
-
inputSchema: {
|
|
1394
|
-
type: "object",
|
|
1395
|
-
properties: {
|
|
1396
|
-
subreddit_name: {
|
|
1397
|
-
type: "string",
|
|
1398
|
-
description: "Name of the subreddit"
|
|
1399
|
-
}
|
|
1400
|
-
},
|
|
1401
|
-
required: ["subreddit_name"]
|
|
1402
|
-
}
|
|
1403
|
-
},
|
|
1404
|
-
{
|
|
1405
|
-
name: "get_trending_subreddits",
|
|
1406
|
-
description: "Get currently trending subreddits",
|
|
1407
|
-
inputSchema: {
|
|
1408
|
-
type: "object",
|
|
1409
|
-
properties: {}
|
|
1410
|
-
}
|
|
1411
|
-
},
|
|
1412
|
-
{
|
|
1413
|
-
name: "create_post",
|
|
1414
|
-
description: "Create a new post in a subreddit",
|
|
1415
|
-
inputSchema: {
|
|
1416
|
-
type: "object",
|
|
1417
|
-
properties: {
|
|
1418
|
-
subreddit: {
|
|
1419
|
-
type: "string",
|
|
1420
|
-
description: "Name of the subreddit to post in"
|
|
1421
|
-
},
|
|
1422
|
-
title: {
|
|
1423
|
-
type: "string",
|
|
1424
|
-
description: "Title of the post"
|
|
1425
|
-
},
|
|
1426
|
-
content: {
|
|
1427
|
-
type: "string",
|
|
1428
|
-
description: "Content of the post (text for self posts, URL for link posts)"
|
|
1429
|
-
},
|
|
1430
|
-
is_self: {
|
|
1431
|
-
type: "boolean",
|
|
1432
|
-
description: "Whether this is a self (text) post (true) or link post (false)",
|
|
1433
|
-
default: true
|
|
1434
|
-
}
|
|
1435
|
-
},
|
|
1436
|
-
required: ["subreddit", "title", "content"]
|
|
1437
|
-
}
|
|
1438
|
-
},
|
|
1439
|
-
{
|
|
1440
|
-
name: "reply_to_post",
|
|
1441
|
-
description: "Post a reply to an existing Reddit post",
|
|
1442
|
-
inputSchema: {
|
|
1443
|
-
type: "object",
|
|
1444
|
-
properties: {
|
|
1445
|
-
post_id: {
|
|
1446
|
-
type: "string",
|
|
1447
|
-
description: "The ID of the post to reply to"
|
|
1448
|
-
},
|
|
1449
|
-
content: {
|
|
1450
|
-
type: "string",
|
|
1451
|
-
description: "The content of the reply"
|
|
1452
|
-
},
|
|
1453
|
-
subreddit: {
|
|
1454
|
-
type: "string",
|
|
1455
|
-
description: "The subreddit name if known (for validation)"
|
|
1456
|
-
}
|
|
1457
|
-
},
|
|
1458
|
-
required: ["post_id", "content"]
|
|
1459
|
-
}
|
|
1460
|
-
},
|
|
1461
|
-
{
|
|
1462
|
-
name: "search_reddit",
|
|
1463
|
-
description: "Search for posts on Reddit",
|
|
1464
|
-
inputSchema: {
|
|
1465
|
-
type: "object",
|
|
1466
|
-
properties: {
|
|
1467
|
-
query: {
|
|
1468
|
-
type: "string",
|
|
1469
|
-
description: "The search query"
|
|
1470
|
-
},
|
|
1471
|
-
subreddit: {
|
|
1472
|
-
type: "string",
|
|
1473
|
-
description: "Search within a specific subreddit (optional)"
|
|
1474
|
-
},
|
|
1475
|
-
sort: {
|
|
1476
|
-
type: "string",
|
|
1477
|
-
description: "Sort order: relevance, hot, top, new, comments",
|
|
1478
|
-
enum: ["relevance", "hot", "top", "new", "comments"],
|
|
1479
|
-
default: "relevance"
|
|
1480
|
-
},
|
|
1481
|
-
time_filter: {
|
|
1482
|
-
type: "string",
|
|
1483
|
-
description: "Time filter: hour, day, week, month, year, all",
|
|
1484
|
-
enum: ["hour", "day", "week", "month", "year", "all"],
|
|
1485
|
-
default: "all"
|
|
1486
|
-
},
|
|
1487
|
-
limit: {
|
|
1488
|
-
type: "number",
|
|
1489
|
-
description: "Maximum number of results to return",
|
|
1490
|
-
minimum: 1,
|
|
1491
|
-
maximum: 100,
|
|
1492
|
-
default: 10
|
|
1493
|
-
},
|
|
1494
|
-
type: {
|
|
1495
|
-
type: "string",
|
|
1496
|
-
description: "Type of content: link (posts), sr (subreddits), user (users)",
|
|
1497
|
-
enum: ["link", "sr", "user"],
|
|
1498
|
-
default: "link"
|
|
1499
|
-
}
|
|
1500
|
-
},
|
|
1501
|
-
required: ["query"]
|
|
1502
|
-
}
|
|
1503
|
-
},
|
|
1504
|
-
{
|
|
1505
|
-
name: "get_post_comments",
|
|
1506
|
-
description: "Get comments for a specific Reddit post",
|
|
1507
|
-
inputSchema: {
|
|
1508
|
-
type: "object",
|
|
1509
|
-
properties: {
|
|
1510
|
-
post_id: {
|
|
1511
|
-
type: "string",
|
|
1512
|
-
description: "The ID of the post"
|
|
1513
|
-
},
|
|
1514
|
-
subreddit: {
|
|
1515
|
-
type: "string",
|
|
1516
|
-
description: "The subreddit where the post is located"
|
|
1517
|
-
},
|
|
1518
|
-
sort: {
|
|
1519
|
-
type: "string",
|
|
1520
|
-
description: "Comment sort order: best, top, new, controversial, old, qa",
|
|
1521
|
-
enum: ["best", "top", "new", "controversial", "old", "qa"],
|
|
1522
|
-
default: "best"
|
|
1523
|
-
},
|
|
1524
|
-
limit: {
|
|
1525
|
-
type: "number",
|
|
1526
|
-
description: "Maximum number of comments to load",
|
|
1527
|
-
minimum: 1,
|
|
1528
|
-
maximum: 500,
|
|
1529
|
-
default: 100
|
|
1530
|
-
}
|
|
1531
|
-
},
|
|
1532
|
-
required: ["post_id", "subreddit"]
|
|
1533
|
-
}
|
|
1534
|
-
},
|
|
1535
|
-
{
|
|
1536
|
-
name: "get_user_posts",
|
|
1537
|
-
description: "Get posts submitted by a specific user",
|
|
1538
|
-
inputSchema: {
|
|
1539
|
-
type: "object",
|
|
1540
|
-
properties: {
|
|
1541
|
-
username: {
|
|
1542
|
-
type: "string",
|
|
1543
|
-
description: "The username to get posts for"
|
|
1544
|
-
},
|
|
1545
|
-
sort: {
|
|
1546
|
-
type: "string",
|
|
1547
|
-
description: "Sort order: new, hot, top, controversial",
|
|
1548
|
-
enum: ["new", "hot", "top", "controversial"],
|
|
1549
|
-
default: "new"
|
|
1550
|
-
},
|
|
1551
|
-
time_filter: {
|
|
1552
|
-
type: "string",
|
|
1553
|
-
description: "Time filter for top/controversial: hour, day, week, month, year, all",
|
|
1554
|
-
enum: ["hour", "day", "week", "month", "year", "all"],
|
|
1555
|
-
default: "all"
|
|
1556
|
-
},
|
|
1557
|
-
limit: {
|
|
1558
|
-
type: "number",
|
|
1559
|
-
description: "Maximum number of posts to return",
|
|
1560
|
-
minimum: 1,
|
|
1561
|
-
maximum: 100,
|
|
1562
|
-
default: 10
|
|
1563
|
-
}
|
|
1564
|
-
},
|
|
1565
|
-
required: ["username"]
|
|
1566
|
-
}
|
|
1567
|
-
},
|
|
1568
|
-
{
|
|
1569
|
-
name: "get_user_comments",
|
|
1570
|
-
description: "Get comments made by a specific user",
|
|
1571
|
-
inputSchema: {
|
|
1572
|
-
type: "object",
|
|
1573
|
-
properties: {
|
|
1574
|
-
username: {
|
|
1575
|
-
type: "string",
|
|
1576
|
-
description: "The username to get comments for"
|
|
1577
|
-
},
|
|
1578
|
-
sort: {
|
|
1579
|
-
type: "string",
|
|
1580
|
-
description: "Sort order: new, hot, top, controversial",
|
|
1581
|
-
enum: ["new", "hot", "top", "controversial"],
|
|
1582
|
-
default: "new"
|
|
1583
|
-
},
|
|
1584
|
-
time_filter: {
|
|
1585
|
-
type: "string",
|
|
1586
|
-
description: "Time filter for top/controversial: hour, day, week, month, year, all",
|
|
1587
|
-
enum: ["hour", "day", "week", "month", "year", "all"],
|
|
1588
|
-
default: "all"
|
|
1589
|
-
},
|
|
1590
|
-
limit: {
|
|
1591
|
-
type: "number",
|
|
1592
|
-
description: "Maximum number of comments to return",
|
|
1593
|
-
minimum: 1,
|
|
1594
|
-
maximum: 100,
|
|
1595
|
-
default: 10
|
|
1596
|
-
}
|
|
1597
|
-
},
|
|
1598
|
-
required: ["username"]
|
|
1599
|
-
}
|
|
1600
|
-
}
|
|
1601
|
-
]
|
|
1602
|
-
}));
|
|
1603
|
-
this.server.setRequestHandler(import_types6.CallToolRequestSchema, async (request) => {
|
|
1604
|
-
try {
|
|
1605
|
-
const toolName = request.params.name;
|
|
1606
|
-
const toolParams = request.params.arguments || {};
|
|
1607
|
-
await this.server.sendLoggingMessage({
|
|
1608
|
-
level: "debug",
|
|
1609
|
-
logger: "reddit-server",
|
|
1610
|
-
data: `Tool call: ${toolName}`
|
|
1611
|
-
});
|
|
1612
|
-
switch (toolName) {
|
|
1613
|
-
case "test_reddit_mcp_server":
|
|
1614
|
-
return {
|
|
1615
|
-
content: [
|
|
1616
|
-
{
|
|
1617
|
-
type: "text",
|
|
1618
|
-
text: "Hello, world! The Reddit MCP Server is working correctly."
|
|
1619
|
-
}
|
|
1620
|
-
]
|
|
1621
|
-
};
|
|
1622
|
-
case "get_reddit_post":
|
|
1623
|
-
return await getRedditPost(toolParams);
|
|
1624
|
-
case "get_top_posts":
|
|
1625
|
-
return await getTopPosts(
|
|
1626
|
-
toolParams
|
|
1627
|
-
);
|
|
1628
|
-
case "get_user_info":
|
|
1629
|
-
return await getUserInfo(toolParams);
|
|
1630
|
-
case "get_subreddit_info":
|
|
1631
|
-
return await getSubredditInfo(toolParams);
|
|
1632
|
-
case "get_trending_subreddits":
|
|
1633
|
-
return await getTrendingSubreddits();
|
|
1634
|
-
case "create_post":
|
|
1635
|
-
return await createPost(
|
|
1636
|
-
toolParams
|
|
1637
|
-
);
|
|
1638
|
-
case "reply_to_post":
|
|
1639
|
-
return await replyToPost(
|
|
1640
|
-
toolParams
|
|
1641
|
-
);
|
|
1642
|
-
case "search_reddit":
|
|
1643
|
-
return await searchReddit(
|
|
1644
|
-
toolParams
|
|
1645
|
-
);
|
|
1646
|
-
case "get_post_comments":
|
|
1647
|
-
return await getPostComments(
|
|
1648
|
-
toolParams
|
|
1649
|
-
);
|
|
1650
|
-
case "get_user_posts":
|
|
1651
|
-
return await getUserPosts(
|
|
1652
|
-
toolParams
|
|
1653
|
-
);
|
|
1654
|
-
case "get_user_comments":
|
|
1655
|
-
return await getUserComments(
|
|
1656
|
-
toolParams
|
|
1657
|
-
);
|
|
1658
|
-
default:
|
|
1659
|
-
throw new import_types6.McpError(import_types6.ErrorCode.MethodNotFound, `Tool with name ${toolName} not found`);
|
|
1660
|
-
}
|
|
1661
|
-
} catch (error) {
|
|
1662
|
-
if (error instanceof Error) {
|
|
1663
|
-
await this.server.sendLoggingMessage({
|
|
1664
|
-
level: "error",
|
|
1665
|
-
logger: "reddit-server",
|
|
1666
|
-
data: `Error calling tool: ${error.message}`
|
|
1667
|
-
});
|
|
1668
|
-
throw new import_types6.McpError(import_types6.ErrorCode.InternalError, `Failed to fetch data: ${error.message}`);
|
|
1669
|
-
}
|
|
1670
|
-
throw error;
|
|
1671
|
-
}
|
|
1672
|
-
});
|
|
1174
|
+
---`;
|
|
1175
|
+
}).join("\n\n");
|
|
1176
|
+
response += commentSummaries;
|
|
1177
|
+
return response;
|
|
1673
1178
|
}
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
await
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
data: "Reddit MCP Server is running"
|
|
1681
|
-
});
|
|
1682
|
-
const username = process.env.REDDIT_USERNAME;
|
|
1683
|
-
const password = process.env.REDDIT_PASSWORD;
|
|
1684
|
-
await this.server.sendLoggingMessage({
|
|
1685
|
-
level: "info",
|
|
1686
|
-
logger: "reddit-server",
|
|
1687
|
-
data: username && password ? `Authenticated as user: ${username}` : "Running in read-only mode (no user authentication)"
|
|
1179
|
+
});
|
|
1180
|
+
async function main() {
|
|
1181
|
+
try {
|
|
1182
|
+
await setupRedditClient();
|
|
1183
|
+
await server.start({
|
|
1184
|
+
transportType: "stdio"
|
|
1688
1185
|
});
|
|
1186
|
+
} catch (error) {
|
|
1187
|
+
console.error("[Error] Failed to start server:", error);
|
|
1188
|
+
process.exit(1);
|
|
1689
1189
|
}
|
|
1690
|
-
}
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1190
|
+
}
|
|
1191
|
+
process.on("SIGINT", async () => {
|
|
1192
|
+
console.error("[Shutdown] Shutting down Reddit MCP Server...");
|
|
1193
|
+
process.exit(0);
|
|
1194
|
+
});
|
|
1195
|
+
process.on("SIGTERM", async () => {
|
|
1196
|
+
console.error("[Shutdown] Shutting down Reddit MCP Server...");
|
|
1197
|
+
process.exit(0);
|
|
1694
1198
|
});
|
|
1199
|
+
main().catch(console.error);
|