Skip to main content
Glama

product-hunt-mcp

by jaipandya
client.py•8.32 kB
""" GraphQL API Client for Product Hunt This module handles GraphQL queries and API execution. """ import logging import time from typing import Any, Dict, Optional, Tuple import requests from product_hunt_mcp.utils.rate_limit import RateLimitManager from product_hunt_mcp.utils.token import get_token, check_token logger = logging.getLogger("ph_mcp") # API endpoints API_BASE_URL = "https://api.producthunt.com/v2" GRAPHQL_URL = f"{API_BASE_URL}/api/graphql" def execute_graphql_query( query: str, variables: Dict[str, Any] = None, operation_name: str = None ) -> Tuple[Optional[Dict[str, Any]], Dict[str, Any], Optional[Dict[str, Any]]]: """ Execute a GraphQL query against Product Hunt API with rate limit handling Args: query: The GraphQL query string variables: Dictionary of query variables operation_name: Optional operation name Returns: tuple: (result, rate_limit_info, error) - only one of result or error will be populated """ # Get developer token token = get_token() if not token: return ( None, None, { "code": "NO_TOKEN", "message": "Developer token not found. Please add PRODUCT_HUNT_TOKEN to your environment variables.", }, ) # Check rate limits current_time = int(time.time()) rate_limits = RateLimitManager.get_rate_limit_info() if ( current_time < RateLimitManager.rate_limit_reset and RateLimitManager.rate_limit_remaining <= 0 ): wait_time = RateLimitManager.rate_limit_reset - current_time logger.warning(f"Rate limit reached. Need to wait for {wait_time} seconds.") return ( None, rate_limits, { "code": "RATE_LIMIT_EXCEEDED", "message": ( f"Rate limit exceeded. Reset in {wait_time} seconds at " f"{time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(RateLimitManager.rate_limit_reset))}." ), "retry_after": wait_time, }, ) # Set up headers with auth headers = { "Authorization": f"Bearer {token}", "Content-Type": "application/json", "User-Agent": "curl/7.64.1", } payload = {"query": query} if variables: payload["variables"] = variables if operation_name: payload["operationName"] = operation_name try: response = requests.post(GRAPHQL_URL, headers=headers, json=payload) # Always update rate limits if headers are present (regardless of case) # This is now handled in the RateLimitManager.update_from_headers method RateLimitManager.update_from_headers(response.headers) rate_limits = RateLimitManager.get_rate_limit_info() if response.status_code == 200: result = response.json() # Check for GraphQL-level errors if "errors" in result: error_message = ( result["errors"][0]["message"] if result["errors"] else "Unknown GraphQL error" ) error_path = result["errors"][0].get("path", []) if result["errors"] else [] logger.error(f"GraphQL error: {error_message}") # Map known GraphQL errors to more user-friendly messages friendly_message = error_message error_code = "GRAPHQL_ERROR" # Detect common GraphQL error patterns and provide friendly messages if "Variable $" in error_message and "was provided invalid value" in error_message: if "DateTime" in error_message: error_code = "INVALID_DATE_FORMAT" friendly_message = "Invalid date format. Dates must be in ISO 8601 format (e.g., 2023-01-01T00:00:00Z)." elif "Int" in error_message: error_code = "INVALID_PARAMETER" friendly_message = "One of the parameters has an invalid value type. Please check numeric parameters." else: friendly_message = ( "One of the parameters has an invalid value. Please check your input." ) elif ( "Not found" in error_message or "not found" in error_message.lower() or "doesn't exist" in error_message.lower() ): error_code = "NOT_FOUND" if error_path and len(error_path) > 0: friendly_message = f"Resource not found: {error_path[-1]}" else: friendly_message = "The requested resource could not be found." return ( None, rate_limits, {"code": error_code, "message": friendly_message, "details": result["errors"]}, ) return result, rate_limits, None elif response.status_code == 429: # Rate limited # Find the reset header regardless of case reset_header = next( (h for h in response.headers if h.lower() == "x-rate-limit-reset"), None ) wait_time = int(response.headers.get(reset_header, 900)) if reset_header else 900 logger.warning(f"Rate limited by API. Reset in {wait_time} seconds.") return ( None, rate_limits, { "code": "RATE_LIMIT_EXCEEDED", "message": f"Rate limit exceeded. Reset in {wait_time} seconds.", "retry_after": wait_time, }, ) else: error_msg = f"API request failed: {response.status_code} - {response.text}" logger.error(error_msg) # Provide more user-friendly messages based on status code if response.status_code == 401: friendly_message = "Authentication failed. Please check your API token." error_code = "AUTHENTICATION_ERROR" elif response.status_code == 403: friendly_message = "You don't have permission to access this resource." error_code = "PERMISSION_DENIED" elif response.status_code == 404: friendly_message = "The requested resource was not found." error_code = "NOT_FOUND" elif response.status_code >= 500: friendly_message = ( "The Product Hunt API is currently unavailable. Please try again later." ) error_code = "SERVER_ERROR" else: friendly_message = ( "An error occurred while communicating with the Product Hunt API." ) error_code = "API_ERROR" return ( None, rate_limits, { "code": error_code, "message": friendly_message, "status_code": response.status_code, }, ) except requests.RequestException as e: error_msg = f"Request error: {str(e)}" logger.error(error_msg) # Provide more user-friendly network error messages if "ConnectionError" in str(e.__class__): friendly_message = ( "Could not connect to the Product Hunt API. Please check your internet connection." ) elif "Timeout" in str(e.__class__): friendly_message = "The request to Product Hunt API timed out. Please try again later." elif "TooManyRedirects" in str(e.__class__): friendly_message = ( "Too many redirects occurred when connecting to the Product Hunt API." ) else: friendly_message = "A network error occurred while connecting to the Product Hunt API." return ( None, RateLimitManager.get_rate_limit_info(), {"code": "NETWORK_ERROR", "message": friendly_message, "details": str(e)}, )

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jaipandya/producthunt-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server