Skip to main content
Glama
api.py34.3 kB
""" API endpoints for admin interface """ import secrets import json import uuid from typing import Optional, List, Dict, Any from datetime import datetime, timedelta from fastapi import APIRouter, Request, HTTPException, Depends from pydantic import BaseModel from src.database import DatabaseService from src.tools.registry import tool_registry from src.resources.registry import resource_registry from src.server.factory import clear_config_cache from src.utils.prompt_generator import generate_system_prompt from src.admin.auth import require_admin_auth, authenticate_admin, verify_admin_session, logout_admin, get_current_admin_username # Request/Response models class LoginRequest(BaseModel): username: str password: str class CreateClientRequest(BaseModel): name: str description: Optional[str] = None class CreateApiKeyRequest(BaseModel): name: str expires_days: Optional[int] = None class AuthResponse(BaseModel): authenticated: bool message: Optional[str] = None class ClientSummary(BaseModel): id: str name: str description: Optional[str] api_key_count: int tool_count: int created_at: datetime is_active: bool class DashboardResponse(BaseModel): clients: List[ClientSummary] total_api_keys: int total_tools: int # Total configured tools across all clients available_tools: int # Total available tools in the system class ApiKeySummary(BaseModel): id: int key_value: str name: str created_at: datetime expires_at: Optional[datetime] is_active: bool class ToolInfo(BaseModel): name: str description: Optional[str] requires_config: bool config_schema: Optional[Dict[str, Any]] is_configured: bool configuration: Optional[Dict[str, Any]] class ResourceInfo(BaseModel): name: str requires_config: bool config_schema: Optional[Dict[str, Any]] is_configured: bool configuration: Optional[Dict[str, Any]] class ClientDetailResponse(BaseModel): client: ClientSummary api_keys: List[ApiKeySummary] tools: List[ToolInfo] resources: List[ResourceInfo] class SystemPromptSummary(BaseModel): id: int version: int user_requirements: str is_active: bool created_at: datetime updated_at: datetime class GeneratePromptRequest(BaseModel): user_requirements: str is_revision: bool = False parent_version_id: Optional[int] = None class SavePromptRequest(BaseModel): prompt_text: str user_requirements: str generation_context: Dict[str, Any] parent_version_id: Optional[int] = None class SystemPromptResponse(BaseModel): id: int client_id: str prompt_text: str version: int user_requirements: str generation_context: Dict[str, Any] is_active: bool parent_version_id: Optional[int] created_at: datetime updated_at: datetime router = APIRouter() # Authentication API Routes @router.post("/auth/login", response_model=AuthResponse) async def api_login(request: Request, login_data: LoginRequest): """API endpoint for admin login""" db: DatabaseService = request.app.state.db if await authenticate_admin(request, login_data.username, login_data.password, db): return AuthResponse(authenticated=True, message="Login successful") else: raise HTTPException(status_code=401, detail="Invalid username or password") @router.post("/auth/logout", response_model=AuthResponse) async def api_logout(request: Request): """API endpoint for admin logout""" logout_admin(request) return AuthResponse(authenticated=False, message="Logout successful") @router.get("/auth/status", response_model=AuthResponse) async def api_auth_status(request: Request): """Check current authentication status""" authenticated = verify_admin_session(request) return AuthResponse(authenticated=authenticated) # Dashboard API Routes @router.get("/dashboard", response_model=DashboardResponse) async def api_dashboard(request: Request, _: None = Depends(require_admin_auth)): """Get dashboard data with client summary""" db: DatabaseService = request.app.state.db clients = await db.list_clients() # Discover tools to get accurate count (only if not already registered) if not tool_registry._tools: tool_registry.discover_tools() available_tools_count = len(tool_registry._tools) # Enhance client data with counts enhanced_clients = [] total_api_keys = 0 total_tools = 0 for client in clients: api_keys = await db.list_api_keys_for_client(client.id) tool_configs = await db.list_tool_configurations_for_client(client.id) active_keys = len([key for key in api_keys if key.is_active]) total_api_keys += active_keys total_tools += len(tool_configs) enhanced_clients.append(ClientSummary( id=str(client.id), name=client.name, description=client.description, api_key_count=active_keys, tool_count=len(tool_configs), created_at=client.created_at, is_active=client.is_active )) return DashboardResponse( clients=enhanced_clients, total_api_keys=total_api_keys, total_tools=total_tools, available_tools=available_tools_count ) @router.get("/clients/{client_id}", response_model=ClientDetailResponse) async def api_client_detail(request: Request, client_id: str, _: None = Depends(require_admin_auth)): """Get detailed client information with API keys, tools, and resources""" db: DatabaseService = request.app.state.db # Get client client = await db.get_client(client_id) if not client: raise HTTPException(status_code=404, detail="Client not found") # Get API keys api_keys = await db.list_api_keys_for_client(client_id) api_key_summaries = [ ApiKeySummary( id=key.id, key_value=key.key_value, name=key.name, created_at=key.created_at, expires_at=key.expires_at, is_active=key.is_active ) for key in api_keys ] # Get tool configurations tool_configs = await db.list_tool_configurations_for_client(client_id) configured_tools = {config.tool_name: config for config in tool_configs} # Get all available tools and their config schemas (only discover if not already registered) if not tool_registry._tools: tool_registry.discover_tools() available_tools = tool_registry._tools.keys() tool_schemas = tool_registry.get_tool_config_schemas() # Build tool data tools = [] for tool_name in available_tools: config = configured_tools.get(tool_name) schema = tool_schemas.get(tool_name) # Get tool description from the tool instance tool_description = None tool_instance = tool_registry._tools.get(tool_name) if tool_instance: tool_description = tool_instance.description tools.append(ToolInfo( name=tool_name, description=tool_description, requires_config=schema is not None, config_schema=schema, is_configured=config is not None, configuration=config.configuration if config else None )) # Get resource configurations resource_configs = await db.list_resource_configurations_for_client(client_id) configured_resources = {config.resource_name: config for config in resource_configs} # Get all available resources and their config schemas (only discover if not already registered) if not resource_registry._resources: resource_registry.discover_resources() available_resources = resource_registry._resources.keys() resource_schemas = resource_registry.get_resource_config_schemas() # Build resource data resources = [] for resource_name in available_resources: config = configured_resources.get(resource_name) schema = resource_schemas.get(resource_name) resources.append(ResourceInfo( name=resource_name, requires_config=schema is not None, config_schema=schema, is_configured=config is not None, configuration=config.configuration if config else None )) # Build client summary client_summary = ClientSummary( id=str(client.id), name=client.name, description=client.description, api_key_count=len([key for key in api_keys if key.is_active]), tool_count=len(tool_configs), created_at=client.created_at, is_active=client.is_active ) return ClientDetailResponse( client=client_summary, api_keys=api_key_summaries, tools=tools, resources=resources ) @router.post("/clients", response_model=ClientSummary) async def api_create_client( request: Request, client_data: CreateClientRequest, _: None = Depends(require_admin_auth) ): """Create a new client""" db: DatabaseService = request.app.state.db client = await db.create_client(client_data.name, client_data.description) if not client: raise HTTPException(status_code=500, detail="Failed to create client") return ClientSummary( id=str(client.id), name=client.name, description=client.description, api_key_count=0, # New client has no API keys yet tool_count=0, # New client has no tools configured yet created_at=client.created_at, is_active=client.is_active ) @router.put("/clients/{client_id}", response_model=ClientSummary) async def api_update_client( request: Request, client_id: str, client_data: CreateClientRequest, _: None = Depends(require_admin_auth) ): """Update an existing client""" db: DatabaseService = request.app.state.db # Update client success = await db.update_client(client_id, client_data.name, client_data.description) if not success: raise HTTPException(status_code=404, detail="Client not found") # Get updated client client = await db.get_client(client_id) if not client: raise HTTPException(status_code=404, detail="Client not found") # Get current counts api_keys = await db.list_api_keys_for_client(client_id) tool_configs = await db.list_tool_configurations_for_client(client_id) return ClientSummary( id=str(client.id), name=client.name, description=client.description, api_key_count=len([key for key in api_keys if key.is_active]), tool_count=len(tool_configs), created_at=client.created_at, is_active=client.is_active ) # API Key Management Routes @router.post("/clients/{client_id}/keys", response_model=ApiKeySummary) async def create_api_key( request: Request, client_id: str, key_data: CreateApiKeyRequest, _: None = Depends(require_admin_auth) ): """Generate a new API key for a client""" db: DatabaseService = request.app.state.db # Generate secure API key api_key = secrets.token_urlsafe(32) # Calculate expiry date if provided expires_at = None if key_data.expires_days: expires_at = datetime.now() + timedelta(days=key_data.expires_days) # Create API key key = await db.create_api_key(client_id, api_key, key_data.name, expires_at) if not key: raise HTTPException(status_code=500, detail="Failed to create API key") return ApiKeySummary( id=key.id, key_value=key.key_value, name=key.name, created_at=key.created_at, expires_at=key.expires_at, is_active=key.is_active ) @router.delete("/keys/{api_key}") async def delete_api_key(request: Request, api_key: str, _: None = Depends(require_admin_auth)): """Delete an API key""" db: DatabaseService = request.app.state.db success = await db.delete_api_key(api_key) if not success: raise HTTPException(status_code=404, detail="API key not found") return {"success": True, "message": "API key deleted"} # Tool Configuration Routes @router.post("/clients/{client_id}/tools/{tool_name:path}") async def configure_tool( request: Request, client_id: str, tool_name: str, _: None = Depends(require_admin_auth) ): """Configure a tool for a client""" db: DatabaseService = request.app.state.db # Get request body body = await request.body() try: data = json.loads(body) configuration = data.get("configuration") except json.JSONDecodeError: raise HTTPException(status_code=400, detail="Invalid JSON") # Set tool configuration success = await db.set_tool_configuration(client_id, tool_name, configuration) if not success: raise HTTPException(status_code=500, detail="Failed to configure tool") # Clear cache for this client's tools clear_config_cache(client_id, "tools") return {"success": True, "message": f"Tool {tool_name} configured"} @router.delete("/clients/{client_id}/tools/{tool_name:path}") async def delete_tool_configuration( request: Request, client_id: str, tool_name: str, _: None = Depends(require_admin_auth) ): """Remove tool configuration for a client""" db: DatabaseService = request.app.state.db success = await db.delete_tool_configuration(client_id, tool_name) if not success: raise HTTPException(status_code=404, detail="Tool configuration not found") # Clear cache for this client's tools clear_config_cache(client_id, "tools") return {"success": True, "message": f"Tool {tool_name} configuration removed"} @router.get("/clients") async def list_clients(request: Request, _: None = Depends(require_admin_auth)): """List all active clients (API endpoint)""" db: DatabaseService = request.app.state.db clients = await db.list_clients() return [client.to_dict() for client in clients] @router.delete("/clients/{client_id}") async def delete_client(request: Request, client_id: str, _: None = Depends(require_admin_auth)): """Delete a client""" db: DatabaseService = request.app.state.db success = await db.delete_client(client_id) if not success: raise HTTPException(status_code=404, detail="Client not found") return {"success": True, "message": f"Client {client_id} deleted"} # Resource Configuration Routes @router.post("/clients/{client_id}/resources/{resource_name:path}") async def configure_resource( request: Request, client_id: str, resource_name: str, _: None = Depends(require_admin_auth) ): """Configure a resource for a client""" db: DatabaseService = request.app.state.db # Get request body body = await request.body() try: data = json.loads(body) configuration = data.get("configuration") except json.JSONDecodeError: raise HTTPException(status_code=400, detail="Invalid JSON") # Set resource configuration success = await db.set_resource_configuration(client_id, resource_name, configuration) if not success: raise HTTPException(status_code=500, detail="Failed to configure resource") # Clear cache for this client's resources clear_config_cache(client_id, "resources") return {"success": True, "message": f"Resource {resource_name} configured"} @router.delete("/clients/{client_id}/resources/{resource_name:path}") async def delete_resource_configuration( request: Request, client_id: str, resource_name: str, _: None = Depends(require_admin_auth) ): """Remove resource configuration for a client""" db: DatabaseService = request.app.state.db success = await db.delete_resource_configuration(client_id, resource_name) if not success: raise HTTPException(status_code=404, detail="Resource configuration not found") # Clear cache for this client's resources clear_config_cache(client_id, "resources") return {"success": True, "message": f"Resource {resource_name} configuration removed"} # Tool Call Analytics Routes class ToolCallSummary(BaseModel): """Tool call summary response model""" id: int client_id: str tool_name: str input_data: Any # Can be dict or list or other types output_text: Optional[Any] # Text/content array responses output_json: Optional[Any] # Structured JSON responses error_message: Optional[str] execution_time_ms: Optional[int] created_at: datetime class ToolCallListResponse(BaseModel): """Tool call list response with pagination""" tool_calls: List[ToolCallSummary] total_count: int limit: int offset: int class ToolCallStatsResponse(BaseModel): """Tool call statistics response""" total_calls: int successful_calls: int failed_calls: int success_rate: float avg_execution_time_ms: Optional[float] top_tools: List[Dict[str, Any]] period_days: int @router.get("/tool-calls", response_model=ToolCallListResponse) async def list_tool_calls( request: Request, client_id: Optional[str] = None, tool_name: Optional[str] = None, search: Optional[str] = None, limit: int = 50, offset: int = 0, order_by: str = "created_at", order_dir: str = "desc", _: None = Depends(require_admin_auth) ): """List tool calls with pagination and filtering""" db: DatabaseService = request.app.state.db # Validate limit and offset if limit < 1 or limit > 1000: raise HTTPException(status_code=400, detail="Limit must be between 1 and 1000") if offset < 0: raise HTTPException(status_code=400, detail="Offset must be non-negative") # Validate order parameters valid_order_by = ["created_at", "tool_name", "execution_time_ms"] if order_by not in valid_order_by: raise HTTPException(status_code=400, detail=f"order_by must be one of {valid_order_by}") if order_dir not in ["asc", "desc"]: raise HTTPException(status_code=400, detail="order_dir must be 'asc' or 'desc'") # Get tool calls tool_calls, total_count = await db.list_tool_calls( client_id=client_id, tool_name=tool_name, search=search, limit=limit, offset=offset, order_by=order_by, order_dir=order_dir ) # Convert to response format tool_call_summaries = [ ToolCallSummary( id=call.id, client_id=str(call.client_id), tool_name=call.tool_name, input_data=call.input_data, output_text=call.output_text, output_json=call.output_json, error_message=call.error_message, execution_time_ms=call.execution_time_ms, created_at=call.created_at ) for call in tool_calls ] return ToolCallListResponse( tool_calls=tool_call_summaries, total_count=total_count, limit=limit, offset=offset ) @router.get("/tool-calls/stats", response_model=ToolCallStatsResponse) async def get_tool_call_stats( request: Request, client_id: Optional[str] = None, days: int = 30, _: None = Depends(require_admin_auth) ): """Get tool call statistics""" db: DatabaseService = request.app.state.db # Validate days parameter if days < 1 or days > 365: raise HTTPException(status_code=400, detail="Days must be between 1 and 365") # Get statistics stats = await db.get_tool_call_stats(client_id=client_id, days=days) return ToolCallStatsResponse(**stats) @router.get("/clients/{client_id}/tool-calls", response_model=ToolCallListResponse) async def list_client_tool_calls( request: Request, client_id: str, tool_name: Optional[str] = None, search: Optional[str] = None, limit: int = 50, offset: int = 0, order_by: str = "created_at", order_dir: str = "desc", _: None = Depends(require_admin_auth) ): """List tool calls for a specific client""" # This is a convenience endpoint that calls list_tool_calls with client_id return await list_tool_calls( request=request, client_id=client_id, tool_name=tool_name, search=search, limit=limit, offset=offset, order_by=order_by, order_dir=order_dir, _=_ ) @router.get("/clients/{client_id}/tool-calls/stats", response_model=ToolCallStatsResponse) async def get_client_tool_call_stats( request: Request, client_id: str, days: int = 30, _: None = Depends(require_admin_auth) ): """Get tool call statistics for a specific client""" # This is a convenience endpoint that calls get_tool_call_stats with client_id return await get_tool_call_stats( request=request, client_id=client_id, days=days, _=_ ) # System Monitoring Routes class QueueMetricsResponse(BaseModel): """Queue metrics response model""" queue_depth: int max_workers: int max_queue_size: int workers_started: int is_started: bool utilization_percent: float active_workers: int total_tasks_processed: int peak_queue_depth: int peak_active_workers: int class SystemHealthResponse(BaseModel): """System health response model""" database_status: str queue_metrics: QueueMetricsResponse timestamp: datetime @router.get("/metrics/queue", response_model=QueueMetricsResponse) async def get_queue_metrics(request: Request, _: None = Depends(require_admin_auth)): """Get current queue statistics""" from src.tools.registry import tool_registry stats = tool_registry.get_queue_stats() # Calculate utilization percentage utilization_percent = 0.0 if stats["max_queue_size"] > 0: utilization_percent = (stats["queue_depth"] / stats["max_queue_size"]) * 100 return QueueMetricsResponse( queue_depth=stats["queue_depth"], max_workers=stats["max_workers"], max_queue_size=stats["max_queue_size"], workers_started=stats["workers_started"], is_started=stats["is_started"], utilization_percent=round(utilization_percent, 1), active_workers=stats["active_workers"], total_tasks_processed=stats["total_tasks_processed"], peak_queue_depth=stats["peak_queue_depth"], peak_active_workers=stats["peak_active_workers"] ) @router.get("/health", response_model=SystemHealthResponse) async def get_system_health(request: Request, _: None = Depends(require_admin_auth)): """Get overall system health status""" db: DatabaseService = request.app.state.db # Check database health db_healthy = await db.health_check() db_status = "connected" if db_healthy else "disconnected" # Get queue metrics queue_metrics = await get_queue_metrics(request, _) return SystemHealthResponse( database_status=db_status, queue_metrics=queue_metrics, timestamp=datetime.now() ) # Admin Management Routes class AdminSummary(BaseModel): """Admin summary response model""" id: int username: str email: str created_at: datetime created_by_username: Optional[str] is_superadmin: bool class CreateAdminRequest(BaseModel): """Request model for creating new admin""" username: str email: str password: str class ChangePasswordRequest(BaseModel): """Request model for changing admin password""" new_password: str class AdminListResponse(BaseModel): """Admin list response model""" admins: List[AdminSummary] current_admin: str @router.get("/admins", response_model=AdminListResponse) async def list_admins(request: Request, _: None = Depends(require_admin_auth)): """List all admins""" db: DatabaseService = request.app.state.db admins = await db.list_admins() current_username = get_current_admin_username(request) # Build admin summaries with created_by info admin_summaries = [] for admin in admins: created_by_username = None if admin.created_by_id: # Get creator info for creator in admins: if creator.id == admin.created_by_id: created_by_username = creator.username break admin_summaries.append(AdminSummary( id=admin.id, username=admin.username, email=admin.email, created_at=admin.created_at, created_by_username=created_by_username, is_superadmin=(admin.username == "superadmin") )) return AdminListResponse( admins=admin_summaries, current_admin=current_username ) @router.post("/admins") async def create_admin(request: Request, admin_data: CreateAdminRequest, _: None = Depends(require_admin_auth)): """Create a new admin user""" db: DatabaseService = request.app.state.db # Get current admin ID from session current_admin_id = request.session.get("admin_id") if not current_admin_id: raise HTTPException(status_code=500, detail="Session error: admin ID not found") # Check if username already exists existing = await db.get_admin_by_username(admin_data.username) if existing: raise HTTPException(status_code=400, detail="Username already exists") # Create admin admin = await db.create_admin( username=admin_data.username, email=admin_data.email, password=admin_data.password, created_by_id=current_admin_id ) if not admin: raise HTTPException(status_code=500, detail="Failed to create admin") return {"success": True, "message": f"Admin '{admin_data.username}' created successfully"} @router.delete("/admins/{username}") async def delete_admin(request: Request, username: str, _: None = Depends(require_admin_auth)): """Delete an admin user (except superadmin)""" db: DatabaseService = request.app.state.db if username == "superadmin": raise HTTPException(status_code=400, detail="Cannot delete superadmin") current_username = get_current_admin_username(request) if username == current_username: raise HTTPException(status_code=400, detail="Cannot delete yourself") success = await db.delete_admin(username) if not success: raise HTTPException(status_code=404, detail="Admin not found") return {"success": True, "message": f"Admin '{username}' deleted successfully"} @router.post("/admins/{username}/change-password") async def change_admin_password( request: Request, username: str, password_data: ChangePasswordRequest, _: None = Depends(require_admin_auth) ): """Change password for an admin (admins can only change their own password)""" db: DatabaseService = request.app.state.db current_username = get_current_admin_username(request) if username != current_username: raise HTTPException(status_code=403, detail="You can only change your own password") success = await db.change_admin_password(username, password_data.new_password) if not success: raise HTTPException(status_code=404, detail="Admin not found") return {"success": True, "message": "Password changed successfully"} # System Prompt API Routes @router.get("/clients/{client_id}/prompts", response_model=List[SystemPromptSummary]) async def get_client_prompts(client_id: str, request: Request, admin: str = Depends(require_admin_auth)): """Get all system prompts for a client""" db: DatabaseService = request.app.state.db try: # Validate UUID format try: uuid.UUID(client_id) except ValueError: raise HTTPException(status_code=400, detail=f"Invalid client ID format: {client_id}") prompts = await db.list_system_prompts(client_id) return [ SystemPromptSummary( id=prompt.id, version=prompt.version, user_requirements=prompt.user_requirements, is_active=prompt.is_active, created_at=prompt.created_at, updated_at=prompt.updated_at ) for prompt in prompts ] except HTTPException: raise except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @router.post("/clients/{client_id}/prompts/generate") async def generate_system_prompt_endpoint( client_id: str, request: Request, generate_request: GeneratePromptRequest, admin: str = Depends(require_admin_auth) ): """Generate a new system prompt using LLM""" db: DatabaseService = request.app.state.db try: # Validate UUID format try: uuid.UUID(client_id) except ValueError: raise HTTPException(status_code=400, detail=f"Invalid client ID format: {client_id}") # Get client and their configurations client = await db.get_client(client_id) if not client: raise HTTPException(status_code=404, detail="Client not found") # Get tool configurations for this client tool_configs = await db.get_tool_configurations(client.id) enabled_tools = list(tool_configs.keys()) # Get resource configurations for this client resource_configs = await db.get_resource_configurations(client.id) enabled_resources = list(resource_configs.keys()) # Get previous prompt if this is a revision previous_prompt = None if generate_request.is_revision and generate_request.parent_version_id: parent_prompt = await db.get_system_prompt(generate_request.parent_version_id) if parent_prompt: previous_prompt = parent_prompt.prompt_text # Generate the prompt using utility function generated_prompt = await generate_system_prompt( tool_registry=tool_registry, resource_registry=resource_registry, enabled_tools=enabled_tools, enabled_resources=enabled_resources, user_requirements=generate_request.user_requirements, is_revision=generate_request.is_revision, previous_prompt=previous_prompt ) return {"prompt_text": generated_prompt} except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @router.post("/clients/{client_id}/prompts", response_model=SystemPromptResponse) async def save_system_prompt( client_id: str, request: Request, save_request: SavePromptRequest, admin: str = Depends(require_admin_auth) ): """Save a system prompt""" db: DatabaseService = request.app.state.db try: # Validate UUID format try: uuid.UUID(client_id) except ValueError: raise HTTPException(status_code=400, detail=f"Invalid client ID format: {client_id}") prompt = await db.create_system_prompt( client_id=client_id, prompt_text=save_request.prompt_text, user_requirements=save_request.user_requirements, generation_context=save_request.generation_context, parent_version_id=save_request.parent_version_id ) return SystemPromptResponse( id=prompt.id, client_id=str(prompt.client_id), prompt_text=prompt.prompt_text, version=prompt.version, user_requirements=prompt.user_requirements, generation_context=prompt.generation_context, is_active=prompt.is_active, parent_version_id=prompt.parent_version_id, created_at=prompt.created_at, updated_at=prompt.updated_at ) except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @router.get("/prompts/{prompt_id}", response_model=SystemPromptResponse) async def get_system_prompt(prompt_id: int, request: Request, admin: str = Depends(require_admin_auth)): """Get a specific system prompt by ID""" db: DatabaseService = request.app.state.db try: prompt = await db.get_system_prompt(prompt_id) if not prompt: raise HTTPException(status_code=404, detail="System prompt not found") return SystemPromptResponse( id=prompt.id, client_id=str(prompt.client_id), prompt_text=prompt.prompt_text, version=prompt.version, user_requirements=prompt.user_requirements, generation_context=prompt.generation_context, is_active=prompt.is_active, parent_version_id=prompt.parent_version_id, created_at=prompt.created_at, updated_at=prompt.updated_at ) except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @router.put("/clients/{client_id}/prompts/active/{prompt_id}") async def set_active_system_prompt( client_id: str, prompt_id: int, request: Request, admin: str = Depends(require_admin_auth) ): """Set a system prompt as active""" db: DatabaseService = request.app.state.db try: # Validate UUID format try: uuid.UUID(client_id) except ValueError: raise HTTPException(status_code=400, detail=f"Invalid client ID format: {client_id}") success = await db.set_active_system_prompt(client_id, prompt_id) if not success: raise HTTPException(status_code=404, detail="System prompt not found or doesn't belong to client") return {"success": True, "message": "System prompt activated successfully"} except Exception as e: raise HTTPException(status_code=500, detail=str(e))

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/GeorgeStrakhov/mcpeasy'

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