"""
List SLOs tool
"""
import json
import logging
from typing import Any, Dict
from mcp.types import CallToolRequest, CallToolResult, Tool, TextContent
logger = logging.getLogger(__name__)
from ..utils.datadog_client import fetch_slos
def get_tool_definition() -> Tool:
"""Get the tool definition for list_slos."""
return Tool(
name="list_slos",
description="List Service Level Objectives (SLOs) from Datadog. SLOs define service level targets and track performance against those targets.",
inputSchema={
"type": "object",
"properties": {
"tags": {
"type": "string",
"description": "Filter SLOs by tags (e.g., 'team:backend,env:prod'). Leave empty to list all SLOs.",
"default": "",
},
"query": {
"type": "string",
"description": "Filter SLOs by name or description (substring match). Leave empty to include all SLOs.",
"default": "",
},
"limit": {
"type": "integer",
"description": "Maximum number of SLOs to return (default: 50, max: 1000)",
"default": 50,
"minimum": 1,
"maximum": 1000,
},
"offset": {
"type": "integer",
"description": "Number of SLOs to skip (default: 0)",
"default": 0,
"minimum": 0,
},
"format": {
"type": "string",
"description": "Output format",
"enum": ["table", "json", "summary"],
"default": "table",
},
},
"additionalProperties": False,
"required": [],
},
)
async def handle_call(request: CallToolRequest) -> CallToolResult:
"""Handle the list_slos tool call."""
try:
args = request.arguments or {}
tags = args.get("tags", "")
query = args.get("query", "")
limit = args.get("limit", 50)
offset = args.get("offset", 0)
format_type = args.get("format", "table")
# Fetch SLOs list
slos = await fetch_slos(
tags=tags if tags else None,
query=query if query else None,
limit=limit,
offset=offset
)
if not slos:
return CallToolResult(
content=[TextContent(type="text", text="No SLOs found")],
isError=False,
)
# Format output
if format_type == "json":
content = json.dumps(slos, indent=2)
elif format_type == "summary":
content = f"Found {len(slos)} SLOs"
if limit < 1000 or offset > 0:
page_info = []
if offset > 0:
page_info.append(f"offset {offset}")
if limit < 1000:
page_info.append(f"limit {limit}")
content += f" ({', '.join(page_info)})"
if tags or query:
filters = []
if tags:
filters.append(f"tags: '{tags}'")
if query:
filters.append(f"query: '{query}'")
content += f" matching filters: {', '.join(filters)}"
# Group by type and status
by_type = {}
by_status = {}
targets = []
for slo in slos:
slo_type = slo.get("type", "unknown")
by_type[slo_type] = by_type.get(slo_type, 0) + 1
# Get SLO status from thresholds
thresholds = slo.get("thresholds", [])
if thresholds:
for threshold in thresholds:
target = threshold.get("target")
if target:
targets.append(target)
warning = threshold.get("warning")
if warning and target:
if target >= warning:
status = "healthy"
else:
status = "warning"
else:
status = "unknown"
by_status[status] = by_status.get(status, 0) + 1
break
content += "\n\nBy Type:"
for type_name, count in sorted(by_type.items()):
content += f"\n {type_name}: {count}"
if by_status:
content += "\n\nBy Status:"
for status, count in sorted(by_status.items()):
content += f"\n {status}: {count}"
if targets:
avg_target = sum(targets) / len(targets)
content += f"\n\nAverage Target: {avg_target:.2%}"
else: # table format
content = f"Datadog SLOs"
filters = []
if tags:
filters.append(f"tags: '{tags}'")
if query:
filters.append(f"query: '{query}'")
if filters:
content += f" (filtered by: {', '.join(filters)})"
content += f" | Total: {len(slos)}"
if limit < 1000 or offset > 0:
page_info = []
if offset > 0:
page_info.append(f"offset {offset}")
if limit < 1000:
page_info.append(f"limit {limit}")
content += f" ({', '.join(page_info)})"
content += "\n" + "=" * len(content.split('\n')[-1]) + "\n\n"
for i, slo in enumerate(slos, 1):
slo_id = slo.get("id", "unknown")
slo_name = slo.get("name", "Unnamed")
slo_type = slo.get("type", "unknown")
# Get target from thresholds
thresholds = slo.get("thresholds", [])
target_str = "N/A"
warning_str = ""
if thresholds:
for threshold in thresholds:
target = threshold.get("target")
warning = threshold.get("warning")
if target is not None:
target_str = f"{target:.2%}"
if warning is not None:
warning_str = f" (warn: {warning:.2%})"
break
# Get tags if available
slo_tags = slo.get("tags", [])
tags_str = ", ".join(slo_tags[:3]) # Show first 3 tags
if len(slo_tags) > 3:
tags_str += f" (+{len(slo_tags) - 3} more)"
# Get description
description = slo.get("description", "")
desc_str = f" - {description[:50]}..." if len(description) > 50 else f" - {description}" if description else ""
content += f"{i:3d}. {slo_name}{desc_str}\n"
content += f" ID: {slo_id} | Type: {slo_type} | Target: {target_str}{warning_str}"
if tags_str:
content += f"\n Tags: {tags_str}"
content += "\n\n"
return CallToolResult(
content=[TextContent(type="text", text=content)],
isError=False,
)
except Exception as e:
logger.error(f"Error in list_slos: {e}")
return CallToolResult(
content=[TextContent(type="text", text=f"Error: {str(e)}")],
isError=True,
)