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
8 changes: 7 additions & 1 deletion pkg/flashduty/incidents.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (

const defaultQueryLimit = 20

const queryIncidentsDescription = `Query incidents by IDs, time range, status, severity, channel, or free-text query. Returns the incident list with an alerts_total count per incident; for the actual alert objects of one or more incidents, call query_incident_alerts(incident_ids=...).`
const queryIncidentsDescription = `Query incidents by IDs, short ids (nums), time range, status, severity, channel, or free-text query. Returns the incident list with an alerts_total count per incident; for the actual alert objects of one or more incidents, call query_incident_alerts(incident_ids=...).`

// QueryIncidents creates a tool to query incidents with enriched data
func QueryIncidents(getClient GetFlashdutyClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
Expand All @@ -33,6 +33,7 @@ func QueryIncidents(getClient GetFlashdutyClientFn, t translations.TranslationHe
WithSince(),
WithUntil(),
mcp.WithString("query", mcp.Description("Free-text search across title, labels, and content (Doris full-text). A 24-char hex string is resolved as an incident ID; a 6-char string is resolved as an incident num. Prefer this over picking exact filter values when the user gives a fuzzy keyword."), mcp.MaxLength(200)),
mcp.WithString("nums", mcp.Description("Comma-separated short incident ids (num — the 6-char id shown in the UI, e.g. 311510). Matched within the since/until window; the backend caps the list span at ~30 days, so incidents older than that must be looked up by their full incident_id.")),
mcp.WithNumber("limit", mcp.Description(LimitDescription), mcp.DefaultNumber(20), mcp.Min(1), mcp.Max(100)),
), func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
ctx, client, err := getClient(ctx)
Expand All @@ -46,6 +47,7 @@ func QueryIncidents(getClient GetFlashdutyClientFn, t translations.TranslationHe
severity, _ := OptionalParam[string](request, "severity")
channelIdsStr, _ := OptionalParam[string](request, "channel_ids")
query, _ := OptionalParam[string](request, "query")
nums, _ := OptionalParam[string](request, "nums")
limit, _ := OptionalInt(request, "limit")

startTime, err := timeutil.ParseAny(args["since"])
Expand Down Expand Up @@ -109,6 +111,10 @@ func QueryIncidents(getClient GetFlashdutyClientFn, t translations.TranslationHe
}
}

if nums != "" {
req.Nums = parseCommaSeparatedStrings(nums)
}

if err := validateTimeWindow(startTime, endTime); err != nil {
return mcp.NewToolResultError(err.Error()), nil
}
Expand Down
68 changes: 68 additions & 0 deletions pkg/flashduty/incidents_nums_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package flashduty

import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"

flashduty "github.com/flashcatcloud/go-flashduty"
"github.com/mark3labs/mcp-go/mcp"

"github.com/flashcatcloud/flashduty-mcp-server/pkg/translations"
)

// TestQueryIncidentsNumsReachesWire verifies the new `nums` argument is split
// and sent to /incident/list as the nums array (short-id filtering), within the
// since/until window.
func TestQueryIncidentsNumsReachesWire(t *testing.T) {
t.Parallel()

var gotPath string
var gotBody map[string]any
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
gotPath = r.URL.Path
_ = json.NewDecoder(r.Body).Decode(&gotBody)
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]any{
"data": map[string]any{"items": []any{}, "total": 0},
})
}))
defer ts.Close()

client, err := flashduty.NewClient("test-key", flashduty.WithBaseURL(ts.URL))
if err != nil {
t.Fatalf("new go-flashduty client: %v", err)
}

_, handler := QueryIncidents(func(ctx context.Context) (context.Context, *Clients, error) {
return ctx, &Clients{New: client}, nil
}, translations.NullTranslationHelper)

result, err := handler(context.Background(), mcp.CallToolRequest{
Params: mcp.CallToolParams{
Name: "query_incidents",
Arguments: map[string]any{
"nums": "311510,ABC123",
"since": "7d",
"until": "now",
},
},
})
if err != nil {
t.Fatalf("handler returned error: %v", err)
}
if result.IsError {
txt, _ := mcp.AsTextContent(result.Content[0])
t.Fatalf("expected success result, got error: %s", txt.Text)
}

if gotPath != "/incident/list" {
t.Fatalf("path = %q, want /incident/list", gotPath)
}
nums, _ := gotBody["nums"].([]any)
if len(nums) != 2 || nums[0] != "311510" || nums[1] != "ABC123" {
t.Errorf("nums = %#v, want [311510 ABC123]", gotBody["nums"])
}
}
Loading