Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 3 additions & 6 deletions internal/mcp/tools.go
Original file line number Diff line number Diff line change
Expand Up @@ -664,7 +664,7 @@ type getChangesInput struct {
Namespace string `json:"namespace,omitempty" jsonschema:"filter to a specific namespace"`
Kind string `json:"kind,omitempty" jsonschema:"filter to a resource kind (e.g. Deployment, Pod)"`
Name string `json:"name,omitempty" jsonschema:"filter to a specific resource name"`
Since string `json:"since,omitempty" jsonschema:"duration to look back, e.g. 1h, 30m, 24h (default 1h)"`
Since string `json:"since,omitempty" jsonschema:"duration to look back, e.g. 1h, 24h, 7d, 14d (default 1h)"`
Limit int `json:"limit,omitempty" jsonschema:"max changes to return (default 20, max 50)"`
}

Expand Down Expand Up @@ -1384,12 +1384,9 @@ func isPodKind(kind string) bool {
func handleGetChanges(ctx context.Context, req *mcp.CallToolRequest, input getChangesInput) (*mcp.CallToolResult, any, error) {
since := 1 * time.Hour
if input.Since != "" {
parsed, err := time.ParseDuration(input.Since)
parsed, err := parsePromDuration(input.Since)
if err != nil {
return nil, nil, fmt.Errorf("invalid duration %q: %w", input.Since, err)
}
if parsed <= 0 {
return nil, nil, fmt.Errorf("duration must be positive, got %q", input.Since)
return nil, nil, err
}
since = parsed
}
Expand Down
34 changes: 34 additions & 0 deletions internal/mcp/tools_changes_configuration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,40 @@ func TestGetChangesEmitsApplicationConfigurationClassificationWithoutIssueAwareP
}
}

func TestGetChangesAcceptsDayDuration(t *testing.T) {
store := initCorrelationStore(t)
if err := store.Append(context.Background(), timeline.TimelineEvent{
ID: "two-weeks-of-history",
Timestamp: time.Now().Add(-13 * 24 * time.Hour),
Source: timeline.SourceInformer,
ClusterContext: k8s.ActiveClusterContext(),
Kind: "Deployment",
Namespace: "dev",
Name: "api",
EventType: timeline.EventTypeUpdate,
Diff: &timeline.DiffInfo{Fields: []timeline.FieldChange{{
Path: "spec.template.spec.containers[api].image",
}}},
}); err != nil {
t.Fatalf("append: %v", err)
}

result, _, err := handleGetChanges(context.Background(), nil, getChangesInput{
Namespace: "dev",
Since: "14d",
})
if err != nil {
t.Fatalf("handleGetChanges with 14d: %v", err)
}
var response getChangesResponseMCP
if err := json.Unmarshal([]byte(extractText(t, result)), &response); err != nil {
t.Fatalf("decode response: %v", err)
}
if len(response.Changes) != 1 || response.Changes[0].Name != "api" {
t.Fatalf("14d changes = %+v, want the 13-day-old Deployment", response.Changes)
}
}

func TestGetChangesDistinguishesOutputCappingFromFetchSaturation(t *testing.T) {
appendChanges := func(t *testing.T, count int) {
t.Helper()
Expand Down
Loading