Skip to main content
Glama
GeLi2001

Statsig MCP Server

console_client.py21.8 kB
""" Statsig Console API client for MCP server. """ import logging import os from typing import Any import httpx logger = logging.getLogger(__name__) class StatsigConsoleClient: """Console API client for Statsig.""" def __init__(self) -> None: """Initialize the Console API client.""" self._initialized = False self._console_api_key: str | None = None self._api_version = "20240601" self._base_url = "https://statsigapi.net" self._client: httpx.AsyncClient | None = None async def initialize(self) -> None: """Initialize the Console API client.""" if self._initialized: return # Get Console API key from environment self._console_api_key = os.getenv("STATSIG_CONSOLE_API_KEY") if not self._console_api_key: raise ValueError("STATSIG_CONSOLE_API_KEY environment variable is required") # Create HTTP client with proper headers for Console API console_headers = { "STATSIG-API-KEY": self._console_api_key, "STATSIG-API-VERSION": self._api_version, "Content-Type": "application/json", } self._client = httpx.AsyncClient( base_url=self._base_url, headers=console_headers, timeout=30.0 ) self._initialized = True logger.info("Statsig Console API client initialized successfully") # Gates async def list_gates(self, limit: int | None = None) -> dict[str, Any]: """List all feature gates.""" if not self._initialized or not self._client: raise RuntimeError("Console API client not initialized") try: url = "/console/v1/gates" if limit: url += f"?limit={limit}" response = await self._client.get(url) response.raise_for_status() return response.json() except Exception as e: logger.error(f"Error listing gates: {e}") return {"error": str(e)} async def get_gate(self, gate_id: str) -> dict[str, Any]: """Get details of a specific feature gate.""" if not self._initialized or not self._client: raise RuntimeError("Console API client not initialized") try: response = await self._client.get(f"/console/v1/gates/{gate_id}") if response.status_code == 404: return {"found": False, "error": f"Gate '{gate_id}' not found"} response.raise_for_status() return response.json() except Exception as e: logger.error(f"Error getting gate {gate_id}: {e}") return {"error": str(e)} async def create_gate( self, name: str, description: str = "", is_enabled: bool = False ) -> dict[str, Any]: """Create a new feature gate.""" if not self._initialized or not self._client: raise RuntimeError("Console API client not initialized") try: data = {"name": name, "description": description, "isEnabled": is_enabled} response = await self._client.post("/console/v1/gates", json=data) response.raise_for_status() result = response.json() return {"success": True, "data": result} except Exception as e: logger.error(f"Error creating gate {name}: {e}") return {"success": False, "error": str(e)} async def update_gate( self, gate_id: str, updates: dict[str, Any] ) -> dict[str, Any]: """Update an existing feature gate.""" if not self._initialized or not self._client: raise RuntimeError("Console API client not initialized") try: # Convert updates to API format data = {} if "name" in updates: data["name"] = updates["name"] if "description" in updates: data["description"] = updates["description"] if "is_enabled" in updates: data["isEnabled"] = updates["is_enabled"] response = await self._client.patch( f"/console/v1/gates/{gate_id}", json=data ) response.raise_for_status() return {"success": True} except Exception as e: logger.error(f"Error updating gate {gate_id}: {e}") return {"success": False, "error": str(e)} async def delete_gate(self, gate_id: str) -> dict[str, Any]: """Delete a feature gate.""" if not self._initialized or not self._client: raise RuntimeError("Console API client not initialized") try: response = await self._client.delete(f"/console/v1/gates/{gate_id}") response.raise_for_status() return {"success": True} except Exception as e: logger.error(f"Error deleting gate {gate_id}: {e}") return {"success": False, "error": str(e)} # Experiments async def list_experiments(self, limit: int | None = None) -> dict[str, Any]: """List all experiments.""" if not self._initialized or not self._client: raise RuntimeError("Console API client not initialized") try: url = "/console/v1/experiments" if limit: url += f"?limit={limit}" response = await self._client.get(url) response.raise_for_status() return response.json() except Exception as e: logger.error(f"Error listing experiments: {e}") return {"error": str(e)} async def get_experiment(self, experiment_id: str) -> dict[str, Any]: """Get details of a specific experiment.""" if not self._initialized or not self._client: raise RuntimeError("Console API client not initialized") try: response = await self._client.get( f"/console/v1/experiments/{experiment_id}" ) if response.status_code == 404: return { "found": False, "error": f"Experiment '{experiment_id}' not found", } response.raise_for_status() return response.json() except Exception as e: logger.error(f"Error getting experiment {experiment_id}: {e}") return {"error": str(e)} async def create_experiment( self, name: str, description: str = "", hypothesis: str | None = None ) -> dict[str, Any]: """Create a new experiment.""" if not self._initialized or not self._client: raise RuntimeError("Console API client not initialized") try: data = {"name": name, "description": description} if hypothesis: data["hypothesis"] = hypothesis response = await self._client.post("/console/v1/experiments", json=data) response.raise_for_status() result = response.json() return {"success": True, "data": result} except Exception as e: logger.error(f"Error creating experiment {name}: {e}") return {"success": False, "error": str(e)} async def update_experiment( self, experiment_id: str, updates: dict[str, Any] ) -> dict[str, Any]: """Update an existing experiment.""" if not self._initialized or not self._client: raise RuntimeError("Console API client not initialized") try: response = await self._client.patch( f"/console/v1/experiments/{experiment_id}", json=updates ) response.raise_for_status() return {"success": True} except Exception as e: logger.error(f"Error updating experiment {experiment_id}: {e}") return {"success": False, "error": str(e)} async def delete_experiment(self, experiment_id: str) -> dict[str, Any]: """Delete an experiment.""" if not self._initialized or not self._client: raise RuntimeError("Console API client not initialized") try: response = await self._client.delete( f"/console/v1/experiments/{experiment_id}" ) response.raise_for_status() return {"success": True} except Exception as e: logger.error(f"Error deleting experiment {experiment_id}: {e}") return {"success": False, "error": str(e)} # Dynamic Configs async def list_dynamic_configs(self, limit: int | None = None) -> dict[str, Any]: """List all dynamic configs.""" if not self._initialized or not self._client: raise RuntimeError("Console API client not initialized") try: url = "/console/v1/dynamic_configs" if limit: url += f"?limit={limit}" response = await self._client.get(url) response.raise_for_status() return response.json() except Exception as e: logger.error(f"Error listing dynamic configs: {e}") return {"error": str(e)} async def get_dynamic_config(self, config_id: str) -> dict[str, Any]: """Get details of a specific dynamic config.""" if not self._initialized or not self._client: raise RuntimeError("Console API client not initialized") try: response = await self._client.get( f"/console/v1/dynamic_configs/{config_id}" ) if response.status_code == 404: return { "found": False, "error": f"Dynamic config '{config_id}' not found", } response.raise_for_status() return response.json() except Exception as e: logger.error(f"Error getting dynamic config {config_id}: {e}") return {"error": str(e)} async def create_dynamic_config( self, name: str, description: str = "" ) -> dict[str, Any]: """Create a new dynamic config.""" if not self._initialized or not self._client: raise RuntimeError("Console API client not initialized") try: data = {"name": name, "description": description} response = await self._client.post("/console/v1/dynamic_configs", json=data) response.raise_for_status() result = response.json() return {"success": True, "data": result} except Exception as e: logger.error(f"Error creating dynamic config {name}: {e}") return {"success": False, "error": str(e)} async def update_dynamic_config( self, config_id: str, updates: dict[str, Any] ) -> dict[str, Any]: """Update an existing dynamic config.""" if not self._initialized or not self._client: raise RuntimeError("Console API client not initialized") try: response = await self._client.patch( f"/console/v1/dynamic_configs/{config_id}", json=updates ) response.raise_for_status() return {"success": True} except Exception as e: logger.error(f"Error updating dynamic config {config_id}: {e}") return {"success": False, "error": str(e)} async def delete_dynamic_config(self, config_id: str) -> dict[str, Any]: """Delete a dynamic config.""" if not self._initialized or not self._client: raise RuntimeError("Console API client not initialized") try: response = await self._client.delete( f"/console/v1/dynamic_configs/{config_id}" ) response.raise_for_status() return {"success": True} except Exception as e: logger.error(f"Error deleting dynamic config {config_id}: {e}") return {"success": False, "error": str(e)} # Segments async def list_segments(self, limit: int | None = None) -> dict[str, Any]: """List all segments.""" if not self._initialized or not self._client: raise RuntimeError("Console API client not initialized") try: url = "/console/v1/segments" if limit: url += f"?limit={limit}" response = await self._client.get(url) response.raise_for_status() return response.json() except Exception as e: logger.error(f"Error listing segments: {e}") return {"error": str(e)} async def get_segment(self, segment_id: str) -> dict[str, Any]: """Get details of a specific segment.""" if not self._initialized or not self._client: raise RuntimeError("Console API client not initialized") try: response = await self._client.get(f"/console/v1/segments/{segment_id}") if response.status_code == 404: return {"found": False, "error": f"Segment '{segment_id}' not found"} response.raise_for_status() return response.json() except Exception as e: logger.error(f"Error getting segment {segment_id}: {e}") return {"error": str(e)} async def create_segment(self, name: str, description: str = "") -> dict[str, Any]: """Create a new segment.""" if not self._initialized or not self._client: raise RuntimeError("Console API client not initialized") try: data = {"name": name, "description": description} response = await self._client.post("/console/v1/segments", json=data) response.raise_for_status() result = response.json() return {"success": True, "data": result} except Exception as e: logger.error(f"Error creating segment {name}: {e}") return {"success": False, "error": str(e)} # Metrics async def list_metrics(self, limit: int | None = None) -> dict[str, Any]: """List all metrics.""" if not self._initialized or not self._client: raise RuntimeError("Console API client not initialized") try: url = "/console/v1/metrics" if limit: url += f"?limit={limit}" response = await self._client.get(url) response.raise_for_status() return response.json() except Exception as e: logger.error(f"Error listing metrics: {e}") return {"error": str(e)} async def get_metric(self, metric_id: str) -> dict[str, Any]: """Get details of a specific metric.""" if not self._initialized or not self._client: raise RuntimeError("Console API client not initialized") try: response = await self._client.get(f"/console/v1/metrics/{metric_id}") if response.status_code == 404: return {"found": False, "error": f"Metric '{metric_id}' not found"} response.raise_for_status() return response.json() except Exception as e: logger.error(f"Error getting metric {metric_id}: {e}") return {"error": str(e)} # Audit Logs async def list_audit_logs( self, limit: int = 20, from_date: str | None = None, to_date: str | None = None ) -> dict[str, Any]: """List audit logs.""" if not self._initialized or not self._client: raise RuntimeError("Console API client not initialized") try: params = {"limit": limit} if from_date: params["from"] = from_date if to_date: params["to"] = to_date response = await self._client.get("/console/v1/audit_logs", params=params) response.raise_for_status() return response.json() except Exception as e: logger.error(f"Error listing audit logs: {e}") return {"error": str(e)} # Target Apps async def list_target_apps(self) -> dict[str, Any]: """List all target apps.""" if not self._initialized or not self._client: raise RuntimeError("Console API client not initialized") try: response = await self._client.get("/console/v1/target_apps") response.raise_for_status() return response.json() except Exception as e: logger.error(f"Error listing target apps: {e}") return {"error": str(e)} async def get_target_app(self, app_id: str) -> dict[str, Any]: """Get details of a specific target app.""" if not self._initialized or not self._client: raise RuntimeError("Console API client not initialized") try: response = await self._client.get(f"/console/v1/target_apps/{app_id}") if response.status_code == 404: return {"found": False, "error": f"Target app '{app_id}' not found"} response.raise_for_status() return response.json() except Exception as e: logger.error(f"Error getting target app {app_id}: {e}") return {"error": str(e)} # API Keys async def list_api_keys(self) -> dict[str, Any]: """List all API keys.""" if not self._initialized or not self._client: raise RuntimeError("Console API client not initialized") try: response = await self._client.get("/console/v1/keys") response.raise_for_status() return response.json() except Exception as e: logger.error(f"Error listing API keys: {e}") return {"error": str(e)} # Events (keeping existing functionality) async def query_events( self, event_name: str | None = None, limit: int = 10 ) -> dict[str, Any]: """Query events using Console API - shows event types, not user-specific events.""" if not self._initialized or not self._client: raise RuntimeError("Console API client not initialized") try: if event_name: # Get specific event details response = await self._client.get(f"/console/v1/events/{event_name}") if response.status_code == 404: return { "event_name": event_name, "found": False, "message": f"Event '{event_name}' not found", } response.raise_for_status() data = response.json() return {"event_name": event_name, "found": True, "details": data} else: # List all events response = await self._client.get("/console/v1/events") response.raise_for_status() data = response.json() events = data.get("data", [])[:limit] return { "event_types": events, "total_found": len(events), "note": "This shows event types, not user-specific events. Use Statsig Console for user event history.", } except Exception as e: logger.error(f"Error querying events: {e}") return { "error": str(e), "message": "Failed to query events via Console API", } # Users (keeping existing functionality) async def get_user_by_email(self, email: str) -> dict[str, Any]: """Get user by email using Console API.""" if not self._initialized or not self._client: raise RuntimeError("Console API client not initialized") try: response = await self._client.get(f"/console/v1/users/{email}") if response.status_code == 404: return { "email": email, "found": False, "message": f"User with email '{email}' not found in Statsig team", } response.raise_for_status() data = response.json() return { "email": email, "found": True, "user_data": data, "note": "This shows team member info, not end-user data", } except Exception as e: logger.error(f"Error getting user by email: {e}") return { "email": email, "found": False, "error": str(e), "message": "Failed to get user via Console API", } async def list_team_users(self) -> dict[str, Any]: """List team users using Console API.""" if not self._initialized or not self._client: raise RuntimeError("Console API client not initialized") try: response = await self._client.get("/console/v1/users") response.raise_for_status() data = response.json() users = data.get("data", []) return { "team_users": users, "total_users": len(users), "note": "These are team members, not end-users", } except Exception as e: logger.error(f"Error listing team users: {e}") return { "error": str(e), "message": "Failed to list team users via Console API", } async def shutdown(self) -> None: """Shutdown the Console API client.""" if self._client: await self._client.aclose() self._client = None self._initialized = False logger.info("Statsig Console API client shutdown")

Latest Blog Posts

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/GeLi2001/statsig-mcp'

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