feat(reddit): add Reddit health checker and update tool functions

This commit is contained in:
Manas Dutta
2026-02-08 19:26:01 +05:30
parent b71628e211
commit 25331590a7
5 changed files with 139 additions and 74 deletions
@@ -8,6 +8,7 @@ to verify the credential works.
from __future__ import annotations
import json
from dataclasses import dataclass, field
from typing import Any, Protocol
@@ -488,6 +489,97 @@ class ResendHealthChecker:
)
class RedditHealthChecker:
"""Health checker for Reddit OAuth credentials."""
TIMEOUT = 10.0
def check(self, credentials_json: str) -> HealthCheckResult:
"""
Validate Reddit OAuth credentials using PRAW.
Expects credentials_json to be a JSON string with:
- client_id
- client_secret
- refresh_token
- user_agent
"""
try:
# Parse credentials
try:
creds = json.loads(credentials_json)
except json.JSONDecodeError:
return HealthCheckResult(
valid=False,
message="Reddit credentials must be valid JSON",
details={"error": "invalid_json"},
)
required_fields = ["client_id", "client_secret", "refresh_token", "user_agent"]
missing = [f for f in required_fields if f not in creds]
if missing:
return HealthCheckResult(
valid=False,
message=f"Missing required fields: {', '.join(missing)}",
details={"missing_fields": missing},
)
# Import praw here to avoid dependency in health_check.py if not needed
try:
import praw
except ImportError:
return HealthCheckResult(
valid=False,
message="PRAW library not installed (pip install praw)",
details={"error": "missing_dependency"},
)
# Create Reddit instance and validate
try:
reddit = praw.Reddit(
client_id=creds["client_id"],
client_secret=creds["client_secret"],
refresh_token=creds["refresh_token"],
user_agent=creds["user_agent"],
)
# Make a lightweight API call to validate credentials
user = reddit.user.me()
username = str(user)
return HealthCheckResult(
valid=True,
message=f"Reddit credentials valid (authenticated as u/{username})",
details={"username": username},
)
except Exception as e:
error_msg = str(e).lower()
if "401" in error_msg or "unauthorized" in error_msg:
return HealthCheckResult(
valid=False,
message="Reddit credentials are invalid or expired",
details={"error": "unauthorized"},
)
elif "403" in error_msg or "forbidden" in error_msg:
return HealthCheckResult(
valid=False,
message="Reddit credentials lack required scopes",
details={"error": "insufficient_scope"},
)
else:
return HealthCheckResult(
valid=False,
message=f"Reddit API error: {str(e)}",
details={"error": str(e)},
)
except Exception as e:
return HealthCheckResult(
valid=False,
message=f"Failed to validate Reddit credentials: {str(e)}",
details={"error": str(e)},
)
# Registry of health checkers
HEALTH_CHECKERS: dict[str, CredentialHealthChecker] = {
"hubspot": HubSpotHealthChecker(),
@@ -497,6 +589,7 @@ HEALTH_CHECKERS: dict[str, CredentialHealthChecker] = {
"anthropic": AnthropicHealthChecker(),
"github": GitHubHealthChecker(),
"resend": ResendHealthChecker(),
"reddit": RedditHealthChecker(),
}
@@ -12,7 +12,6 @@ REDDIT_CREDENTIALS = {
tools=[
# Search & Monitoring
"reddit_search_posts",
"reddit_search_comments",
"reddit_get_subreddit_new",
"reddit_get_subreddit_hot",
"reddit_get_post",
+5 -6
View File
@@ -213,20 +213,19 @@ def register_all_tools(
"slack_get_team_stats",
# Reddit tools
"reddit_search_posts",
"reddit_search_comments",
"reddit_get_subreddit_posts",
"reddit_get_subreddit_new",
"reddit_get_subreddit_hot",
"reddit_get_post",
"reddit_get_post_comments",
"reddit_get_user_posts",
"reddit_get_comments",
"reddit_submit_post",
"reddit_reply_to_post",
"reddit_reply_to_comment",
"reddit_edit_comment",
"reddit_delete_comment",
"reddit_get_user_profile",
"reddit_vote",
"reddit_upvote",
"reddit_downvote",
"reddit_save_post",
"reddit_unsave_post",
"reddit_remove_post",
"reddit_approve_post",
"reddit_ban_user",
@@ -4,13 +4,12 @@ Community management and content monitoring tool for Reddit. Monitor brand menti
## Features
### Search & Monitoring (6 functions)
### Search & Monitoring (5 functions)
- **reddit_search_posts**: Search for posts matching keywords
- **reddit_search_comments**: Search for comments (via post search + comment retrieval)
- **reddit_get_subreddit_posts**: Get hot/new/top posts from a subreddit
- **reddit_get_subreddit_new**: Get new posts from a subreddit
- **reddit_get_subreddit_hot**: Get hot posts from a subreddit
- **reddit_get_post**: Retrieve specific post details
- **reddit_get_post_comments**: Get all comments from a post
- **reddit_get_user_posts**: Monitor user posting activity
- **reddit_get_comments**: Get all comments from a post
### Content Creation (5 functions)
- **reddit_submit_post**: Create text or link posts
@@ -21,9 +20,9 @@ Community management and content monitoring tool for Reddit. Monitor brand menti
### User Engagement (4 functions)
- **reddit_get_user_profile**: View user profiles and karma
- **reddit_vote**: Upvote/downvote posts and comments
- **reddit_upvote**: Upvote posts and comments
- **reddit_downvote**: Downvote posts and comments
- **reddit_save_post**: Bookmark posts
- **reddit_unsave_post**: Remove bookmarks
### Moderation (3 functions - requires moderator permissions)
- **reddit_remove_post**: Remove posts as a moderator
@@ -96,9 +95,8 @@ for post in result["posts"]:
```python
# Get hot posts from a specific subreddit
result = reddit_get_subreddit_posts(
result = reddit_get_subreddit_hot(
subreddit="python",
feed_type="hot",
limit=25
)
@@ -116,7 +114,7 @@ result = reddit_reply_to_post(
)
# Upvote the post
reddit_vote(item_id="abc123", direction="up")
reddit_upvote(item_id="abc123")
```
### Create Content
@@ -136,7 +134,7 @@ print(f"Post created: {result['permalink']}")
```python
# Get all comments from a post
result = reddit_get_post_comments(
result = reddit_get_comments(
post_id="abc123",
sort="best",
limit=100
@@ -163,19 +161,29 @@ Search for Reddit posts matching a query.
**Returns:** Dict with `query`, `subreddit`, `count`, and `posts` array
### reddit_get_subreddit_posts
### reddit_get_subreddit_new
Get posts from a subreddit feed.
Get new posts from a subreddit.
**Arguments:**
| Name | Type | Default | Description |
|------|------|---------|-------------|
| subreddit | str | Required | Subreddit name (e.g., "python") |
| feed_type | str | "hot" | "hot", "new", "top", "rising", "controversial" |
| time_filter | str | "all" | Time period for "top"/"controversial" |
| limit | int | 25 | Maximum posts to return (1-100) |
**Returns:** Dict with `subreddit`, `feed_type`, `count`, and `posts` array
**Returns:** Dict with `subreddit`, `count`, and `posts` array
### reddit_get_subreddit_hot
Get hot posts from a subreddit.
**Arguments:**
| Name | Type | Default | Description |
|------|------|---------|-------------|
| subreddit | str | Required | Subreddit name (e.g., "python") |
| limit | int | 25 | Maximum posts to return (1-100) |
**Returns:** Dict with `subreddit`, `count`, and `posts` array
### reddit_get_post
@@ -188,7 +196,7 @@ Get a specific Reddit post by ID.
**Returns:** Dict with `success` and `post` object
### reddit_get_post_comments
### reddit_get_comments
Get comments from a Reddit post.
@@ -228,17 +236,27 @@ Reply to a Reddit post.
**Returns:** Dict with `success`, `comment_id`, and `permalink`
### reddit_vote
### reddit_upvote
Vote on a post or comment.
Upvote a post or comment.
**Arguments:**
| Name | Type | Default | Description |
|------|------|---------|-------------|
| item_id | str | Required | Reddit post or comment ID |
| direction | str | Required | "up" (upvote), "down" (downvote), "clear" |
**Returns:** Dict with `success`, `item_id`, `direction`, and `message`
**Returns:** Dict with `success`, `item_id`, and `message`
### reddit_downvote
Downvote a post or comment.
**Arguments:**
| Name | Type | Default | Description |
|------|------|---------|-------------|
| item_id | str | Required | Reddit post or comment ID |
**Returns:** Dict with `success`, `item_id`, and `message`
### reddit_get_user_profile
@@ -5,10 +5,10 @@ Supports:
- OAuth 2.0 authentication via REDDIT_CREDENTIALS
- Search & Monitoring (5 functions)
- Content Creation (5 functions)
- User Engagement (3 functions)
- User Engagement (4 functions)
- Moderation (3 functions)
Total: 18 tools
Total: 17 tools
API Reference: https://www.reddit.com/dev/api/
PRAW Documentation: https://praw.readthedocs.io/
@@ -203,50 +203,6 @@ def register_tools(
except Exception as e:
return {"error": f"Search failed: {str(e)}"}
@mcp.tool()
def reddit_search_comments(
query: str,
subreddit: str = "all",
time_filter: str = "all",
sort: str = "relevance",
limit: int = 10,
) -> dict:
"""
Search for Reddit comments matching a query.
Use this to monitor brand mentions or discussions in comments.
Args:
query: Search query (1-512 characters)
subreddit: Subreddit name or "all" for site-wide search
time_filter: Time period - "hour", "day", "week", "month", "year", "all"
sort: Sort method - "relevance", "new", "top"
limit: Maximum number of comments to return (1-100)
Returns:
Dict with search results or error dict
"""
if not query or len(query) > 512:
return {"error": "Query must be 1-512 characters"}
limit = max(1, min(100, limit))
reddit = _get_reddit_client(credentials)
if isinstance(reddit, dict):
return reddit
try:
# PRAW's search returns submissions, not comments directly
# To get comments, use reddit_search_posts and then reddit_get_comments
return {
"error": "Comment search not directly supported by PRAW",
"help": (
"Use reddit_search_posts and then reddit_get_comments for specific posts"
),
}
except Exception as e:
return {"error": f"Search failed: {str(e)}"}
@mcp.tool()
def reddit_get_subreddit_new(
subreddit: str,