""" MCP tool for Prometheus metric metadata discovery. Returns type, help text, and unit for metrics via GET /api/v1/metadata. Supports single-metric lookup and bulk discovery with regex filtering. """ import json import re from typing import Any from pydantic import ValidationError as PydanticValidationError from src.models.get_metric_metadata import MAX_METADATA_METRICS, MetricMetadataRequest 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.get_metric_metadata") def _filter_metadata_by_match(metadata: dict[str, Any], match: str) -> dict[str, Any]: try: pattern = re.compile(match) except re.error as e: raise ValidationError(f"Invalid search pattern. Error details: {e}") from e return {k: v for k, v in metadata.items() if pattern.search(k)} @mcp.tool() async def get_metric_metadata( cluster_id: str, env: str, deploy_type: str, metric: str | None = None, match: str | None = None, limit: int | None = None, ) -> list[dict[str, Any]]: """ Look up Prometheus metric metadata (type, help text, unit). """ try: logger.info( "tool_call_start name=get_metric_metadata cluster_id=%s env=%s deploy_type=%s " "has_metric=%s has_match=%s", cluster_id, env, deploy_type, bool(metric), bool(match), ) await get_valid_server_auth() try: request = MetricMetadataRequest.model_validate( { "cluster_id": str(cluster_id).strip(), "env": env.strip(), "deploy_type": deploy_type.strip(), "metric": metric, "match": match, "limit": limit, } ) except (ValueError, PydanticValidationError) as e: raise ValidationError(f"Invalid metadata request parameters: {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, ) prom_limit = request.limit if request.metric is None else None metadata = await client.get_metadata( metric=request.metric, limit=prom_limit, timeout=DISCOVERY_TIMEOUT, ) if request.match and request.metric is None: metadata = _filter_metadata_by_match(metadata, request.match) truncated = False effective_limit = request.limit or MAX_METADATA_METRICS if len(metadata) > effective_limit: truncated = True keys = list(metadata.keys())[:effective_limit] metadata = {k: metadata[k] for k in keys} response: dict[str, Any] = {"status": "success", "data": metadata} if truncated: response["warnings"] = [ f"Only the first {effective_limit} metrics are returned. " "More metrics matched; refine the match pattern or metric parameter." ] logger.info( "tool_call_success name=get_metric_metadata cluster_id=%s metric_count=%s truncated=%s", request.cluster_id, len(metadata), truncated, ) 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=get_metric_metadata type=%s", e.__class__.__name__) return tool_text_response(format_tool_error(e)) except Exception as e: logger.exception("tool_call_unexpected_error name=get_metric_metadata") return tool_text_response(format_tool_error(e))