index.ts•16.3 kB
#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ErrorCode,
ListToolsRequestSchema,
McpError,
} from "@modelcontextprotocol/sdk/types.js";
import { initializeRedditClient } from "./client/reddit-client";
import * as tools from "./tools";
import dotenv from "dotenv";
// Load environment variables
dotenv.config();
class RedditServer {
private server: Server;
constructor() {
console.log("[Setup] Initializing Reddit Server...");
// Initialize the Reddit client
this.initializeRedditClient();
this.server = new Server(
{
name: "reddit-mcp-server",
version: "0.1.0",
},
{
capabilities: {
tools: {},
},
}
);
this.setupToolHandlers();
this.server.onerror = (error) =>
console.error("[Error] Server error:", error);
process.on("SIGINT", async () => {
await this.server.close();
process.exit(0);
});
}
private initializeRedditClient() {
const clientId = process.env.REDDIT_CLIENT_ID;
const clientSecret = process.env.REDDIT_CLIENT_SECRET;
const userAgent = process.env.REDDIT_USER_AGENT || "RedditMCPServer/0.1.0";
const username = process.env.REDDIT_USERNAME;
const password = process.env.REDDIT_PASSWORD;
if (!clientId || !clientSecret) {
console.error(
"[Error] Missing required Reddit API credentials. Please set REDDIT_CLIENT_ID and REDDIT_CLIENT_SECRET environment variables."
);
process.exit(1);
}
try {
initializeRedditClient({
clientId,
clientSecret,
userAgent,
username,
password,
});
console.log("[Setup] Reddit client initialized");
if (username && password) {
console.log(`[Setup] Authenticated as user: ${username}`);
} else {
console.log(
"[Setup] Running in read-only mode (no user authentication)"
);
}
} catch (error) {
console.error("[Error] Failed to initialize Reddit client:", error);
process.exit(1);
}
}
private setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: "test_reddit_mcp_server",
description: "Test the Reddit MCP Server",
inputSchema: {
type: "object",
properties: {
// No input parameters, this will just return a test message
},
},
},
{
name: "get_reddit_post",
description: "Get a Reddit post",
inputSchema: {
type: "object",
properties: {
subreddit: {
type: "string",
description: "The subreddit to fetch posts from",
},
post_id: {
type: "string",
description: "The ID of the post to fetch",
},
},
required: ["subreddit", "post_id"],
},
},
{
name: "get_top_posts",
description: "Get top posts from a subreddit",
inputSchema: {
type: "object",
properties: {
subreddit: {
type: "string",
description: "Name of the subreddit",
},
time_filter: {
type: "string",
description:
"Time period to filter posts (e.g. 'day', 'week', 'month', 'year', 'all')",
enum: ["day", "week", "month", "year", "all"],
default: "week",
},
limit: {
type: "integer",
description: "Number of posts to fetch",
default: 10,
},
},
required: ["subreddit"],
},
},
{
name: "get_user_info",
description: "Get information about a Reddit user",
inputSchema: {
type: "object",
properties: {
username: {
type: "string",
description: "The username of the Reddit user to get info for",
},
},
required: ["username"],
},
},
{
name: "get_subreddit_info",
description: "Get information about a subreddit",
inputSchema: {
type: "object",
properties: {
subreddit_name: {
type: "string",
description: "Name of the subreddit",
},
},
required: ["subreddit_name"],
},
},
{
name: "get_trending_subreddits",
description: "Get currently trending subreddits",
inputSchema: {
type: "object",
properties: {},
},
},
{
name: "create_post",
description: "Create a new post in a subreddit",
inputSchema: {
type: "object",
properties: {
subreddit: {
type: "string",
description: "Name of the subreddit to post in",
},
title: {
type: "string",
description: "Title of the post",
},
content: {
type: "string",
description:
"Content of the post (text for self posts, URL for link posts)",
},
is_self: {
type: "boolean",
description:
"Whether this is a self (text) post (true) or link post (false)",
default: true,
},
},
required: ["subreddit", "title", "content"],
},
},
{
name: "reply_to_post",
description: "Post a reply to an existing Reddit post",
inputSchema: {
type: "object",
properties: {
post_id: {
type: "string",
description: "The ID of the post to reply to",
},
content: {
type: "string",
description: "The content of the reply",
},
subreddit: {
type: "string",
description: "The subreddit name if known (for validation)",
},
},
required: ["post_id", "content"],
},
},
{
name: "get_comment",
description: "Accéder à un commentaire spécifique",
inputSchema: {
type: "object",
properties: {
comment_id: {
type: "string",
description: "L'ID du commentaire à récupérer",
},
},
required: ["comment_id"],
},
},
{
name: "get_comments_by_submission",
description: "Accéder aux commentaires d'une soumission",
inputSchema: {
type: "object",
properties: {
submission_id: {
type: "string",
description: "L'ID de la soumission",
},
limit: {
type: "integer",
description: "Nombre de commentaires à récupérer",
default: 20,
},
},
required: ["submission_id"],
},
},
{
name: "get_submission",
description: "Accéder à une soumission",
inputSchema: {
type: "object",
properties: {
submission_id: {
type: "string",
description: "L'ID de la soumission à récupérer",
},
},
required: ["submission_id"],
},
},
{
name: "get_subreddit",
description: "Accéder à un subreddit par nom",
inputSchema: {
type: "object",
properties: {
subreddit_name: {
type: "string",
description: "Le nom du subreddit",
},
},
required: ["subreddit_name"],
},
},
{
name: "search_posts",
description: "Rechercher des posts dans un subreddit",
inputSchema: {
type: "object",
properties: {
subreddit: {
type: "string",
description: "Le nom du subreddit dans lequel rechercher",
},
query: {
type: "string",
description: "La requête de recherche",
},
sort: {
type: "string",
description: "Méthode de tri (relevance, new, hot, top)",
enum: ["relevance", "new", "hot", "top"],
default: "relevance",
},
limit: {
type: "integer",
description: "Nombre de résultats à retourner",
default: 25,
},
},
required: ["subreddit", "query"],
},
},
{
name: "search_subreddits",
description: "Rechercher des subreddits par nom ou description",
inputSchema: {
type: "object",
properties: {
query: {
type: "string",
description: "La requête de recherche",
},
limit: {
type: "integer",
description: "Nombre de résultats à retourner",
default: 25,
},
},
required: ["query"],
},
},
{
name: "get_user_posts",
description: "Obtenir l'historique des posts d'un utilisateur",
inputSchema: {
type: "object",
properties: {
username: {
type: "string",
description: "Le nom d'utilisateur Reddit",
},
sort: {
type: "string",
description: "Méthode de tri (new, top, hot)",
enum: ["new", "top", "hot"],
default: "new",
},
limit: {
type: "integer",
description: "Nombre de posts à récupérer",
default: 25,
},
},
required: ["username"],
},
},
{
name: "get_user_comments",
description: "Obtenir l'historique des commentaires d'un utilisateur",
inputSchema: {
type: "object",
properties: {
username: {
type: "string",
description: "Le nom d'utilisateur Reddit",
},
sort: {
type: "string",
description: "Méthode de tri (new, top, hot)",
enum: ["new", "top", "hot"],
default: "new",
},
limit: {
type: "integer",
description: "Nombre de commentaires à récupérer",
default: 25,
},
},
required: ["username"],
},
},
{
name: "get_user_activity",
description: "Obtenir une analyse détaillée de l'activité d'un utilisateur",
inputSchema: {
type: "object",
properties: {
username: {
type: "string",
description: "Le nom d'utilisateur Reddit",
},
limit: {
type: "integer",
description: "Nombre d'éléments d'activité à analyser",
default: 50,
},
},
required: ["username"],
},
},
],
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
try {
const toolName = request.params.name;
const toolParams = request.params.arguments || {};
console.log(`[Request] Tool call: ${toolName}`, toolParams);
switch (toolName) {
case "test_reddit_mcp_server":
return {
content: [
{
type: "text",
text: "Hello, world! The Reddit MCP Server is working correctly.",
},
],
};
case "get_reddit_post":
return await tools.getRedditPost(
toolParams as { subreddit: string; post_id: string }
);
case "get_top_posts":
return await tools.getTopPosts(
toolParams as {
subreddit: string;
time_filter?: string;
limit?: number;
}
);
case "get_user_info":
return await tools.getUserInfo(toolParams as { username: string });
case "get_subreddit_info":
return await tools.getSubredditInfo(
toolParams as { subreddit_name: string }
);
case "get_trending_subreddits":
return await tools.getTrendingSubreddits();
case "create_post":
return await tools.createPost(
toolParams as {
subreddit: string;
title: string;
content: string;
is_self?: boolean;
}
);
case "reply_to_post":
return await tools.replyToPost(
toolParams as {
post_id: string;
content: string;
subreddit?: string;
}
);
case "get_comment":
return await tools.getComment(
toolParams as { comment_id: string }
);
case "get_comments_by_submission":
return await tools.getCommentsBySubmission(
toolParams as { submission_id: string; limit?: number }
);
case "get_submission":
return await tools.getSubmission(
toolParams as { submission_id: string }
);
case "get_subreddit":
return await tools.getSubreddit(
toolParams as { subreddit_name: string }
);
case "search_posts":
return await tools.searchPosts(
toolParams as { subreddit: string; query: string; sort?: string; limit?: number }
);
case "search_subreddits":
return await tools.searchSubreddits(
toolParams as { query: string; limit?: number }
);
case "get_user_posts":
return await tools.getUserPosts(
toolParams as { username: string; sort?: string; limit?: number }
);
case "get_user_comments":
return await tools.getUserComments(
toolParams as { username: string; sort?: string; limit?: number }
);
case "get_user_activity":
return await tools.getUserActivity(
toolParams as { username: string; limit?: number }
);
default:
throw new McpError(
ErrorCode.MethodNotFound,
`Tool with name ${toolName} not found`
);
}
} catch (error: unknown) {
if (error instanceof Error) {
console.error("[Error] Error calling tool:", error.message);
throw new McpError(
ErrorCode.InternalError,
`Failed to fetch data: ${error.message}`
);
}
throw error;
}
});
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.log("[Server] Server is running");
}
}
const server = new RedditServer();
server.run().catch(console.error);