""" MCP tool for Metric Discovery: list available Prometheus metric names. Retrieval and filtering via optional match (regex or prefix) per specs/002-range-query. Returns JSON in Prometheus API format: {"status": "success", "data": [...]}. """ import json from typing import Any from pydantic import ValidationError as PydanticValidationError from src.models.list_metrics import ( ListMetricsRequest, ) from src.services.auth import get_valid_server_auth from src.services.prometheus import ( DISCOVERY_TIMEOUT, get_prometheus_client_for_cluster, ) from src.tools.query_range import mcp from src.utils.errors import ( AuthorizationError, AuthServiceError, NetworkError, PrometheusAPIError, PrometheusTimeoutError, ValidationError, ) from src.utils.logging_utils import get_logger from src.utils.tool_response import format_tool_error, tool_text_response logger = get_logger("tool.list_metrics") @mcp.tool() async def list_metrics( cluster_id: str, env: str, deploy_type: str, match: str | None = None, start: str | None = None, end: str | None = None, limit: int = 500, ) -> list[dict[str, Any]]: """ DISCOVERY: call this first to discover metric names before querying. List available Prometheus metric names for a cluster matches the series selector defined in `match`. Result size is controlled by `limit`. """ try: logger.info( "tool_call_start name=list_metrics cluster_id=%s env=%s deploy_type=%s has_match=%s", cluster_id, env, deploy_type, bool(match), ) await get_valid_server_auth() try: request = ListMetricsRequest.model_validate( { "cluster_id": str(cluster_id).strip(), "env": env.strip(), "deploy_type": deploy_type.strip(), "match": match, "start": start, "end": end, "limit": limit, } ) except (ValueError, PydanticValidationError) as e: raise ValidationError(str(e)) from e client = await get_prometheus_client_for_cluster( cluster_id=request.cluster_id, env=request.env, deploy_type=request.deploy_type, timeout=DISCOVERY_TIMEOUT, ) result = await client.list_metric_names( match=request.match, start=request.start, end=request.end, limit=request.limit, timeout=DISCOVERY_TIMEOUT, ) # Prometheus API format: same as GET /api/v1/label/__name__/values response: dict[str, Any] = {"status": "success", "data": result} logger.info( "tool_call_success name=list_metrics cluster_id=%s metric_count=%s", request.cluster_id, len(result), ) return tool_text_response(json.dumps(response, ensure_ascii=False)) except ( AuthServiceError, AuthorizationError, ValidationError, NetworkError, PrometheusTimeoutError, PrometheusAPIError, ) as e: logger.warning("tool_call_error name=list_metrics type=%s", e.__class__.__name__) return tool_text_response(format_tool_error(e)) except Exception as e: logger.exception("tool_call_unexpected_error name=list_metrics") return tool_text_response(format_tool_error(e))