"""Reddit MCP Server implementation using FastMCP."""
import logging
from typing import Optional
from fastmcp import FastMCP
from .config import RedditConfig
from .reddit_client import RedditClient
# Set up logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Initialize FastMCP
mcp = FastMCP("reddit-mcp-tool")
# Global client instance
reddit_client: Optional[RedditClient] = None
def initialize_reddit_client():
"""Initialize the Reddit client with configuration."""
global reddit_client
try:
config = RedditConfig.from_env()
reddit_client = RedditClient(config)
logger.info("Reddit client initialized successfully in read-only mode")
except Exception as e:
logger.error(f"Failed to initialize Reddit client: {str(e)}")
logger.error("Please ensure you have created a .env file with your Reddit API credentials.")
logger.error("Copy env.example to .env and fill in your Reddit API details.")
reddit_client = None
@mcp.tool()
async def search_reddit_posts(
subreddit: str,
query: str,
limit: int = 10,
sort: str = "relevance",
time_filter: str = "all"
) -> str:
"""
Search for posts in a specific subreddit
Args:
subreddit: The name of the subreddit to search in (without r/)
query: The search query
limit: Number of posts to return (default: 10, max: 100)
sort: Sort method - "relevance", "hot", "top", "new", "comments" (default: "relevance")
time_filter: Time filter - "all", "day", "week", "month", "year" (default: "all")
Returns:
Human readable string containing search results
"""
if reddit_client is None:
return """Error: Reddit client not initialized.
To fix this:
1. Copy env.example to .env: cp env.example .env
2. Edit .env with your Reddit API credentials:
- Get credentials from https://old.reddit.com/prefs/apps/
- Create a 'script' type app
- Fill in REDDIT_CLIENT_ID, REDDIT_CLIENT_SECRET, and REDDIT_USER_AGENT
3. Restart the MCP server
Example .env content:
REDDIT_CLIENT_ID=your_14_char_client_id
REDDIT_CLIENT_SECRET=your_27_char_client_secret
REDDIT_USER_AGENT=reddit-mcp-tool:v0.2.0 (by /u/yourusername)"""
try:
posts = await reddit_client.search_posts(
subreddit_name=subreddit,
query=query,
limit=min(limit, 100),
sort=sort,
time_filter=time_filter
)
if not posts:
return f"No posts found in r/{subreddit} for query: '{query}'"
result = f"Found {len(posts)} posts in r/{subreddit} for query: '{query}'\n\n"
for i, post in enumerate(posts, 1):
result += (
f"{i}. **{post['title']}**\n"
f" Author: {post['author']}\n"
f" Score: {post['score']} (upvote ratio: {post['upvote_ratio']:.0%})\n"
f" Comments: {post['num_comments']}\n"
f" Link: {post['permalink']}\n"
f" Subreddit: r/{post['subreddit']}\n"
)
if post['selftext'] and len(post['selftext']) > 0:
preview = post['selftext'][:200] + "..." if len(post['selftext']) > 200 else post['selftext']
result += f" Content: {preview}\n"
result += "\n"
return result
except Exception as e:
logger.error(f"Error searching posts in r/{subreddit}: {str(e)}")
return f"Error searching posts in r/{subreddit}: {str(e)}"
@mcp.tool()
async def search_reddit_all(
query: str,
limit: int = 10,
sort: str = "relevance",
time_filter: str = "all"
) -> str:
"""
Search for posts across all of Reddit (site-wide search)
Args:
query: The search query to search across all Reddit
limit: Number of posts to return (default: 10, max: 100)
sort: Sort method - "relevance", "hot", "top", "new", "comments" (default: "relevance")
time_filter: Time filter - "all", "day", "week", "month", "year" (default: "all")
Returns:
Human readable string containing search results from across Reddit
"""
if reddit_client is None:
return """Error: Reddit client not initialized.
To fix this:
1. Copy env.example to .env: cp env.example .env
2. Edit .env with your Reddit API credentials:
- Get credentials from https://old.reddit.com/prefs/apps/
- Create a 'script' type app
- Fill in REDDIT_CLIENT_ID, REDDIT_CLIENT_SECRET, and REDDIT_USER_AGENT
3. Restart the MCP server
Example .env content:
REDDIT_CLIENT_ID=your_14_char_client_id
REDDIT_CLIENT_SECRET=your_27_char_client_secret
REDDIT_USER_AGENT=reddit-mcp-tool:v0.2.0 (by /u/yourusername)"""
try:
posts = await reddit_client.search_all_reddit(
query=query,
limit=min(limit, 100),
sort=sort,
time_filter=time_filter
)
if not posts:
return f"No posts found across Reddit for query: '{query}'"
result = f"Found {len(posts)} posts across all Reddit for query: '{query}'\n\n"
for i, post in enumerate(posts, 1):
result += (
f"{i}. **{post['title']}**\n"
f" Author: {post['author']}\n"
f" Score: {post['score']} (upvote ratio: {post['upvote_ratio']:.0%})\n"
f" Comments: {post['num_comments']}\n"
f" Link: {post['permalink']}\n"
f" Subreddit: r/{post['subreddit']}\n"
)
if post['selftext'] and len(post['selftext']) > 0:
preview = post['selftext'][:200] + "..." if len(post['selftext']) > 200 else post['selftext']
result += f" Content: {preview}\n"
result += "\n"
return result
except Exception as e:
logger.error(f"Error searching all Reddit for query '{query}': {str(e)}")
return f"Error searching all Reddit for query '{query}': {str(e)}"
@mcp.tool()
async def get_reddit_post_details(post_id: str) -> str:
"""
Get detailed information about a specific Reddit post
Args:
post_id: The Reddit post ID
Returns:
Human readable string containing detailed post information
"""
if reddit_client is None:
return """Error: Reddit client not initialized.
To fix this:
1. Copy env.example to .env: cp env.example .env
2. Edit .env with your Reddit API credentials:
- Get credentials from https://old.reddit.com/prefs/apps/
- Create a 'script' type app
- Fill in REDDIT_CLIENT_ID, REDDIT_CLIENT_SECRET, and REDDIT_USER_AGENT
3. Restart the MCP server
Example .env content:
REDDIT_CLIENT_ID=your_14_char_client_id
REDDIT_CLIENT_SECRET=your_27_char_client_secret
REDDIT_USER_AGENT=reddit-mcp-tool:v0.2.0 (by /u/yourusername)"""
try:
post_details = await reddit_client.get_post_details(post_id)
result = (
f"**{post_details['title']}**\n\n"
f"Author: {post_details['author']}\n"
f"Score: {post_details['score']} (upvote ratio: {post_details['upvote_ratio']:.0%})\n"
f"Comments: {post_details['num_comments']}\n"
f"Link: {post_details['permalink']}\n"
f"Subreddit: r/{post_details['subreddit']}\n"
f"Domain: {post_details['domain']}\n"
f"Locked: {post_details['locked']}\n"
f"Stickied: {post_details['stickied']}\n"
)
if post_details.get('flair_text'):
result += f"Flair: {post_details['flair_text']}\n"
if post_details['selftext']:
result += f"\nContent:\n{post_details['selftext']}\n"
return result
except Exception as e:
logger.error(f"Error getting post details for {post_id}: {str(e)}")
return f"Error getting post details for {post_id}: {str(e)}"
@mcp.tool()
async def get_subreddit_info(subreddit: str) -> str:
"""
Get information about a subreddit
Args:
subreddit: The name of the subreddit (without r/)
Returns:
Human readable string containing subreddit information
"""
if reddit_client is None:
return """Error: Reddit client not initialized.
To fix this:
1. Copy env.example to .env: cp env.example .env
2. Edit .env with your Reddit API credentials:
- Get credentials from https://old.reddit.com/prefs/apps/
- Create a 'script' type app
- Fill in REDDIT_CLIENT_ID, REDDIT_CLIENT_SECRET, and REDDIT_USER_AGENT
3. Restart the MCP server
Example .env content:
REDDIT_CLIENT_ID=your_14_char_client_id
REDDIT_CLIENT_SECRET=your_27_char_client_secret
REDDIT_USER_AGENT=reddit-mcp-tool:v0.2.0 (by /u/yourusername)"""
try:
subreddit_info = await reddit_client.get_subreddit_info(subreddit)
result = (
f"**r/{subreddit_info['name']}**\n\n"
f"Title: {subreddit_info['title']}\n"
f"Subscribers: {subreddit_info['subscribers']:,}\n"
f"Active Users: {subreddit_info['active_user_count'] or 'N/A'}\n"
f"NSFW: {subreddit_info['over18']}\n"
f"URL: {subreddit_info['url']}\n\n"
f"Description:\n{subreddit_info['public_description']}\n"
)
if subreddit_info['description'] and subreddit_info['description'] != subreddit_info['public_description']:
result += f"\nFull Description:\n{subreddit_info['description']}\n"
return result
except Exception as e:
logger.error(f"Error getting subreddit info for r/{subreddit}: {str(e)}")
return f"Error getting subreddit info for r/{subreddit}: {str(e)}"
@mcp.tool()
async def get_hot_reddit_posts(subreddit: str, limit: int = 10) -> str:
"""
Get hot posts from a subreddit
Args:
subreddit: The name of the subreddit (without r/)
limit: Number of posts to return (default: 10, max: 100)
Returns:
Human readable string containing hot posts
"""
if reddit_client is None:
return """Error: Reddit client not initialized.
To fix this:
1. Copy env.example to .env: cp env.example .env
2. Edit .env with your Reddit API credentials:
- Get credentials from https://old.reddit.com/prefs/apps/
- Create a 'script' type app
- Fill in REDDIT_CLIENT_ID, REDDIT_CLIENT_SECRET, and REDDIT_USER_AGENT
3. Restart the MCP server
Example .env content:
REDDIT_CLIENT_ID=your_14_char_client_id
REDDIT_CLIENT_SECRET=your_27_char_client_secret
REDDIT_USER_AGENT=reddit-mcp-tool:v0.2.0 (by /u/yourusername)"""
try:
posts = await reddit_client.get_hot_posts(subreddit, min(limit, 100))
if not posts:
return f"No hot posts found in r/{subreddit}"
result = f"Hot posts from r/{subreddit}:\n\n"
for i, post in enumerate(posts, 1):
result += (
f"{i}. **{post['title']}**\n"
f" Author: {post['author']}\n"
f" Score: {post['score']} (upvote ratio: {post['upvote_ratio']:.0%})\n"
f" Comments: {post['num_comments']}\n"
f" Link: {post['permalink']}\n"
)
if post['selftext'] and len(post['selftext']) > 0:
preview = post['selftext'][:150] + "..." if len(post['selftext']) > 150 else post['selftext']
result += f" Content: {preview}\n"
result += "\n"
return result
except Exception as e:
logger.error(f"Error getting hot posts from r/{subreddit}: {str(e)}")
return f"Error getting hot posts from r/{subreddit}: {str(e)}"
def run_server():
"""Entry point for the CLI command."""
# Initialize Reddit client
initialize_reddit_client()
# Run the FastMCP server
mcp.run()
if __name__ == "__main__":
run_server()