Skip to content

Commit 05346d3

Browse files
committed
prevent PromQL range issues, add ApplyToParams to remove handler boilerplate, and add missing ParseTimeWindow unit tests
1 parent f6d6f07 commit 05346d3

19 files changed

Lines changed: 169 additions & 163 deletions

internal/infra/mcp/tools/README.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,9 @@ Validation rules (helper: `utils.go`):
6767
Windowed queries carry a 60 s client-side PromQL `Timeout` to fail fast before the
6868
Sysdig edge proxy's own 80–90 s cut-off.
6969

70-
The `interval` parameter on `k8s_list_top_http_errors_in_pods` and
71-
`k8s_list_top_network_errors_in_pods` is deprecated; `start`/`end` take precedence
72-
when both are present. An explicit `interval` emits a deprecation warning to the
73-
server log.
70+
For `k8s_list_top_http_errors_in_pods` and `k8s_list_top_network_errors_in_pods`,
71+
`interval` (e.g. `"1h"`) is still supported as a relative-duration shorthand; `start`/`end`
72+
take precedence when both are provided.
7473

7574
# Adding a New Tool
7675

internal/infra/mcp/tools/tool_k8s_list_clusters.go

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,22 +49,16 @@ func (t *K8sListClusters) handle(ctx context.Context, request mcp.CallToolReques
4949
if err != nil {
5050
return mcp.NewToolResultErrorFromErr("invalid time window", err), nil
5151
}
52-
evalTime, err := tw.EvalTime()
53-
if err != nil {
54-
return mcp.NewToolResultErrorFromErr("failed to build eval time", err), nil
55-
}
5652

5753
query := buildKubeClusterInfoQuery(clusterName, tw)
5854

5955
limitQuery := sysdig.LimitQuery(limit)
6056
params := &sysdig.GetQueryV1Params{
6157
Query: query,
6258
Limit: &limitQuery,
63-
Time: evalTime,
6459
}
65-
if !tw.IsZero() {
66-
timeout := sysdig.Timeout(windowedQueryTimeout)
67-
params.Timeout = &timeout
60+
if err := tw.ApplyToParams(params); err != nil {
61+
return mcp.NewToolResultErrorFromErr("failed to build eval time", err), nil
6862
}
6963

7064
httpResp, err := t.SysdigClient.GetQueryV1(ctx, params)

internal/infra/mcp/tools/tool_k8s_list_count_pods_per_cluster.go

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,22 +52,16 @@ func (t *K8sListCountPodsPerCluster) handle(ctx context.Context, request mcp.Cal
5252
if err != nil {
5353
return mcp.NewToolResultErrorFromErr("invalid time window", err), nil
5454
}
55-
evalTime, err := tw.EvalTime()
56-
if err != nil {
57-
return mcp.NewToolResultErrorFromErr("failed to build eval time", err), nil
58-
}
5955

6056
query := buildKubePodCountQuery(clusterName, namespaceName, tw)
6157

6258
limitQuery := sysdig.LimitQuery(limit)
6359
params := &sysdig.GetQueryV1Params{
6460
Query: query,
6561
Limit: &limitQuery,
66-
Time: evalTime,
6762
}
68-
if !tw.IsZero() {
69-
timeout := sysdig.Timeout(windowedQueryTimeout)
70-
params.Timeout = &timeout
63+
if err := tw.ApplyToParams(params); err != nil {
64+
return mcp.NewToolResultErrorFromErr("failed to build eval time", err), nil
7165
}
7266

7367
httpResp, err := t.SysdigClient.GetQueryV1(ctx, params)

internal/infra/mcp/tools/tool_k8s_list_cronjobs.go

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,22 +54,16 @@ func (t *K8sListCronjobs) handle(ctx context.Context, request mcp.CallToolReques
5454
if err != nil {
5555
return mcp.NewToolResultErrorFromErr("invalid time window", err), nil
5656
}
57-
evalTime, err := tw.EvalTime()
58-
if err != nil {
59-
return mcp.NewToolResultErrorFromErr("failed to build eval time", err), nil
60-
}
6157

6258
query := buildKubeCronjobInfoQuery(clusterName, namespaceName, cronjobName, tw)
6359

6460
limitQuery := sysdig.LimitQuery(limit)
6561
params := &sysdig.GetQueryV1Params{
6662
Query: query,
6763
Limit: &limitQuery,
68-
Time: evalTime,
6964
}
70-
if !tw.IsZero() {
71-
timeout := sysdig.Timeout(windowedQueryTimeout)
72-
params.Timeout = &timeout
65+
if err := tw.ApplyToParams(params); err != nil {
66+
return mcp.NewToolResultErrorFromErr("failed to build eval time", err), nil
7367
}
7468

7569
httpResp, err := t.SysdigClient.GetQueryV1(ctx, params)

internal/infra/mcp/tools/tool_k8s_list_nodes.go

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,22 +52,16 @@ func (t *K8sListNodes) handle(ctx context.Context, request mcp.CallToolRequest)
5252
if err != nil {
5353
return mcp.NewToolResultErrorFromErr("invalid time window", err), nil
5454
}
55-
evalTime, err := tw.EvalTime()
56-
if err != nil {
57-
return mcp.NewToolResultErrorFromErr("failed to build eval time", err), nil
58-
}
5955

6056
query := buildKubeNodeInfoQuery(clusterName, nodeName, tw)
6157

6258
limitQuery := sysdig.LimitQuery(limit)
6359
params := &sysdig.GetQueryV1Params{
6460
Query: query,
6561
Limit: &limitQuery,
66-
Time: evalTime,
6762
}
68-
if !tw.IsZero() {
69-
timeout := sysdig.Timeout(windowedQueryTimeout)
70-
params.Timeout = &timeout
63+
if err := tw.ApplyToParams(params); err != nil {
64+
return mcp.NewToolResultErrorFromErr("failed to build eval time", err), nil
7165
}
7266

7367
httpResp, err := t.SysdigClient.GetQueryV1(ctx, params)

internal/infra/mcp/tools/tool_k8s_list_pod_containers.go

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -64,22 +64,16 @@ func (t *K8sListPodContainers) handle(ctx context.Context, request mcp.CallToolR
6464
if err != nil {
6565
return mcp.NewToolResultErrorFromErr("invalid time window", err), nil
6666
}
67-
evalTime, err := tw.EvalTime()
68-
if err != nil {
69-
return mcp.NewToolResultErrorFromErr("failed to build eval time", err), nil
70-
}
7167

7268
query := buildKubePodContainerInfoQuery(clusterName, namespaceName, workloadType, workloadName, podName, containerName, imagePullstring, nodeName, tw)
7369

7470
limitQuery := sysdig.LimitQuery(limit)
7571
params := &sysdig.GetQueryV1Params{
7672
Query: query,
7773
Limit: &limitQuery,
78-
Time: evalTime,
7974
}
80-
if !tw.IsZero() {
81-
timeout := sysdig.Timeout(windowedQueryTimeout)
82-
params.Timeout = &timeout
75+
if err := tw.ApplyToParams(params); err != nil {
76+
return mcp.NewToolResultErrorFromErr("failed to build eval time", err), nil
8377
}
8478

8579
httpResp, err := t.SysdigClient.GetQueryV1(ctx, params)

internal/infra/mcp/tools/tool_k8s_list_top_cpu_consumed_container.go

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -56,20 +56,14 @@ func (t *K8sListTopCPUConsumedContainer) handle(ctx context.Context, request mcp
5656
if err != nil {
5757
return mcp.NewToolResultErrorFromErr("invalid time window", err), nil
5858
}
59-
evalTime, err := tw.EvalTime()
60-
if err != nil {
61-
return mcp.NewToolResultErrorFromErr("failed to build eval time", err), nil
62-
}
6359

6460
query := buildTopCPUConsumedByContainerQuery(clusterName, namespaceName, workloadType, workloadName, limit, tw)
6561

6662
params := &sysdig.GetQueryV1Params{
6763
Query: query,
68-
Time: evalTime,
6964
}
70-
if !tw.IsZero() {
71-
timeout := sysdig.Timeout(windowedQueryTimeout)
72-
params.Timeout = &timeout
65+
if err := tw.ApplyToParams(params); err != nil {
66+
return mcp.NewToolResultErrorFromErr("failed to build eval time", err), nil
7367
}
7468

7569
httpResp, err := t.SysdigClient.GetQueryV1(ctx, params)

internal/infra/mcp/tools/tool_k8s_list_top_cpu_consumed_workload.go

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -57,20 +57,13 @@ func (t *K8sListTopCPUConsumedWorkload) handle(ctx context.Context, request mcp.
5757
return mcp.NewToolResultErrorFromErr("invalid time window", err), nil
5858
}
5959

60-
evalTime, err := tw.EvalTime()
61-
if err != nil {
62-
return mcp.NewToolResultErrorFromErr("failed to build eval time", err), nil
63-
}
64-
6560
query := buildTopCPUConsumedByWorkloadQuery(clusterName, namespaceName, workloadType, workloadName, limit, tw)
6661

6762
params := &sysdig.GetQueryV1Params{
6863
Query: query,
69-
Time: evalTime,
7064
}
71-
if !tw.IsZero() {
72-
timeout := sysdig.Timeout(windowedQueryTimeout)
73-
params.Timeout = &timeout
65+
if err := tw.ApplyToParams(params); err != nil {
66+
return mcp.NewToolResultErrorFromErr("failed to build eval time", err), nil
7467
}
7568

7669
httpResp, err := t.SysdigClient.GetQueryV1(ctx, params)

internal/infra/mcp/tools/tool_k8s_list_top_http_errors_in_pods.go

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"encoding/json"
66
"fmt"
77
"io"
8-
"log/slog"
98
"strings"
109
"time"
1110

@@ -29,8 +28,8 @@ func NewK8sListTopHttpErrorsInPods(sysdigClient sysdig.ExtendedClientWithRespons
2928

3029
func (t *K8sListTopHttpErrorsInPods) RegisterInServer(s *server.MCPServer) {
3130
tool := mcp.NewTool("k8s_list_top_http_errors_in_pods",
32-
mcp.WithDescription("Lists the pods with the highest rate of HTTP 4xx and 5xx errors over a time window, allowing filtering by cluster, namespace, workload type, and workload name. Pass start/end (RFC3339) to specify the window. The legacy 'interval' param is retained for backward compatibility; start/end take precedence when both are provided."),
33-
mcp.WithString("interval", mcp.Description("Deprecated: use start/end instead. Time interval for the query (e.g. '1h', '30m'). Default is '1h'. Ignored when start is provided.")),
31+
mcp.WithDescription("Lists the pods with the highest rate of HTTP 4xx and 5xx errors over a time window, allowing filtering by cluster, namespace, workload type, and workload name. Pass start/end (RFC3339) for an explicit window, or interval for a relative duration."),
32+
mcp.WithString("interval", mcp.Description("Time interval for the query (e.g. '1h', '30m'). Default is '1h'. Ignored when start/end are provided.")),
3433
mcp.WithString("cluster_name", mcp.Description("The name of the cluster to filter by.")),
3534
mcp.WithString("namespace_name", mcp.Description("The name of the namespace to filter by.")),
3635
mcp.WithString("workload_type", mcp.Description("The type of the workload to filter by.")),
@@ -50,7 +49,6 @@ func (t *K8sListTopHttpErrorsInPods) RegisterInServer(s *server.MCPServer) {
5049

5150
func (t *K8sListTopHttpErrorsInPods) handle(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
5251
interval := mcp.ParseString(request, "interval", "1h")
53-
intervalExplicit := requestHasArg(request, "interval")
5452
clusterName := mcp.ParseString(request, "cluster_name", "")
5553
namespaceName := mcp.ParseString(request, "namespace_name", "")
5654
workloadType := mcp.ParseString(request, "workload_type", "")
@@ -61,21 +59,11 @@ func (t *K8sListTopHttpErrorsInPods) handle(ctx context.Context, request mcp.Cal
6159
if err != nil {
6260
return mcp.NewToolResultErrorFromErr("invalid time window", err), nil
6361
}
64-
evalTime, err := tw.EvalTime()
65-
if err != nil {
66-
return mcp.NewToolResultErrorFromErr("failed to build eval time", err), nil
67-
}
6862

6963
var query string
7064
if !tw.IsZero() {
71-
if intervalExplicit {
72-
slog.Warn("ignoring deprecated 'interval' param because start/end were provided", "tool", "k8s_list_top_http_errors_in_pods")
73-
}
74-
query = buildTopHttpErrorsWindowedQuery(tw.RangeSelector(), int64(tw.End.Sub(tw.Start).Seconds()), limit, clusterName, namespaceName, workloadType, workloadName)
65+
query = buildTopHttpErrorsWindowedQuery(tw.RangeSelector(), tw.WindowSeconds(), limit, clusterName, namespaceName, workloadType, workloadName)
7566
} else {
76-
if intervalExplicit {
77-
slog.Warn("'interval' param is deprecated; use start/end instead", "tool", "k8s_list_top_http_errors_in_pods")
78-
}
7967
query, err = buildTopHttpErrorsLegacyQuery(interval, limit, clusterName, namespaceName, workloadType, workloadName)
8068
if err != nil {
8169
return mcp.NewToolResultErrorFromErr("failed to build query", err), nil
@@ -86,11 +74,9 @@ func (t *K8sListTopHttpErrorsInPods) handle(ctx context.Context, request mcp.Cal
8674
params := &sysdig.GetQueryV1Params{
8775
Query: query,
8876
Limit: &limitQuery,
89-
Time: evalTime,
9077
}
91-
if !tw.IsZero() {
92-
timeout := sysdig.Timeout(windowedQueryTimeout)
93-
params.Timeout = &timeout
78+
if err := tw.ApplyToParams(params); err != nil {
79+
return mcp.NewToolResultErrorFromErr("failed to build eval time", err), nil
9480
}
9581

9682
httpResp, err := t.SysdigClient.GetQueryV1(ctx, params)

internal/infra/mcp/tools/tool_k8s_list_top_memory_consumed_container.go

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -56,22 +56,16 @@ func (t *K8sListTopMemoryConsumedContainer) handle(ctx context.Context, request
5656
if err != nil {
5757
return mcp.NewToolResultErrorFromErr("invalid time window", err), nil
5858
}
59-
evalTime, err := tw.EvalTime()
60-
if err != nil {
61-
return mcp.NewToolResultErrorFromErr("failed to build eval time", err), nil
62-
}
6359

6460
query := buildTopMemoryConsumedByContainerQuery(clusterName, namespaceName, workloadType, workloadName, limit, tw)
6561

6662
limitQuery := sysdig.LimitQuery(limit)
6763
params := &sysdig.GetQueryV1Params{
6864
Query: query,
6965
Limit: &limitQuery,
70-
Time: evalTime,
7166
}
72-
if !tw.IsZero() {
73-
timeout := sysdig.Timeout(windowedQueryTimeout)
74-
params.Timeout = &timeout
67+
if err := tw.ApplyToParams(params); err != nil {
68+
return mcp.NewToolResultErrorFromErr("failed to build eval time", err), nil
7569
}
7670

7771
httpResp, err := t.SysdigClient.GetQueryV1(ctx, params)

0 commit comments

Comments
 (0)