| 180 | } |
| 181 | |
| 182 | func (s *MCPServer) handleRangeQuery(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { |
| 183 | metricMCPToolCalls.WithLabelValues(toolTraceQLMetricsRange).Inc() |
| 184 | |
| 185 | query, err := request.RequireString("query") |
| 186 | if err != nil { |
| 187 | return mcp.NewToolResultError(err.Error()), nil |
| 188 | } |
| 189 | |
| 190 | var startEpochNanos, endEpochNanos int64 |
| 191 | |
| 192 | start := request.GetString("start", "") |
| 193 | end := request.GetString("end", "") |
| 194 | |
| 195 | level.Info(s.logger).Log("msg", "executing range metrics query", "query", query, "start", start, "end", end) |
| 196 | |
| 197 | if start == "" { |
| 198 | startEpochNanos = time.Now().Add(-1 * time.Hour).UnixNano() |
| 199 | } else { |
| 200 | startTS, err := time.Parse(time.RFC3339, start) |
| 201 | if err != nil { |
| 202 | return mcp.NewToolResultError(fmt.Sprintf("invalid start time: %v", err)), nil |
| 203 | } |
| 204 | startEpochNanos = startTS.UnixNano() |
| 205 | } |
| 206 | if end == "" { |
| 207 | endEpochNanos = time.Now().UnixNano() |
| 208 | } else { |
| 209 | endTS, err := time.Parse(time.RFC3339, end) |
| 210 | if err != nil { |
| 211 | return mcp.NewToolResultError(fmt.Sprintf("invalid end time: %v", err)), nil |
| 212 | } |
| 213 | endEpochNanos = endTS.UnixNano() |
| 214 | } |
| 215 | |
| 216 | parsed, err := traceql.ParseNoOptimizations(query) |
| 217 | if err != nil { |
| 218 | return mcp.NewToolResultError(fmt.Sprintf("query parse error. Consult TraceQL docs tools: %v", err)), nil |
| 219 | } |
| 220 | |
| 221 | if parsed.MetricsPipeline == nil { |
| 222 | return mcp.NewToolResultError("TraceQL search query received on range query tool. Use the traceql-search tool instead"), nil |
| 223 | } |
| 224 | |
| 225 | queryRangeReq := &tempopb.QueryRangeRequest{ |
| 226 | Query: query, |
| 227 | Start: uint64(startEpochNanos), |
| 228 | End: uint64(endEpochNanos), |
| 229 | } |
| 230 | |
| 231 | req := api.BuildQueryRangeRequest(nil, queryRangeReq, "") |
| 232 | req.URL.Path = s.buildPath(api.PathMetricsQueryRange) |
| 233 | |
| 234 | body, err := handleHTTP(ctx, s.frontend.MetricsQueryRangeHandler, req) |
| 235 | if err != nil { |
| 236 | return mcp.NewToolResultError(err.Error()), nil |
| 237 | } |
| 238 | |
| 239 | return toolResult(body, MetaTypeMetricsRange, "json", "1"), nil |