From c125a8ae8c6892e7bfa50729108c19faaf2c3473 Mon Sep 17 00:00:00 2001 From: Wesley Ellis Date: Fri, 13 Jun 2025 10:07:00 -0300 Subject: [PATCH 1/2] Add campaigns tool Expose campaigns (without checks) as a new tool. Default filter only shows in progress campaigns --- .../unreleased/Added-20250613-100609.yaml | 3 + src/cmd/root.go | 81 +++++++++++++++++++ src/submodules/opslevel-go | 2 +- 3 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 .changes/unreleased/Added-20250613-100609.yaml diff --git a/.changes/unreleased/Added-20250613-100609.yaml b/.changes/unreleased/Added-20250613-100609.yaml new file mode 100644 index 0000000..6e2d7ae --- /dev/null +++ b/.changes/unreleased/Added-20250613-100609.yaml @@ -0,0 +1,3 @@ +kind: Added +body: Add campaigns tool for listing campaigns +time: 2025-06-13T10:06:09.286358-03:00 diff --git a/src/cmd/root.go b/src/cmd/root.go index 885ab25..c51c054 100644 --- a/src/cmd/root.go +++ b/src/cmd/root.go @@ -91,6 +91,22 @@ type serializedCheckResults struct { NextLevel *serializedLevel } +type serializedCampaign struct { + Id string + Name string + ProjectBrief string + Status string + CheckStats opslevel.Stats + HtmlURL string + ServiceStats opslevel.Stats + Filter *opslevel.FilterId + Owner *opslevel.TeamId + StartDate *iso8601.Time + EndedDate *iso8601.Time + TargetDate *iso8601.Time + Reminder *opslevel.CampaignReminder +} + // newToolResult creates a CallToolResult for the passed object handling any json marshaling errors func newToolResult(obj any, err error) (*mcp.CallToolResult, error) { if err != nil { @@ -513,6 +529,71 @@ var rootCmd = &cobra.Command{ return newToolResult(result, nil) }) + // Register campaigns tool + s.AddTool( + mcp.NewTool( + "campaigns", + mcp.WithDescription("Get all the campaigns in the OpsLevel account. Campaigns are used to track and manage initiatives or projects within OpsLevel."), + mcp.WithString("status", mcp.Description("Filter campaigns by status"), mcp.Enum(opslevel.AllCampaignStatusEnum...)), + mcp.WithToolAnnotation(mcp.ToolAnnotation{ + Title: "Campaigns in OpsLevel", + ReadOnlyHint: &trueValue, + DestructiveHint: &falseValue, + IdempotentHint: &trueValue, + OpenWorldHint: &trueValue, + }), + ), + func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { + args := &opslevel.ListCampaignsVariables{} + status := req.GetString("status", "") + if status != "" { + status_enum := opslevel.CampaignStatusEnum(status) + args.Status = &status_enum + } + + resp, err := client.ListCampaigns(args) + if err != nil { + return mcp.NewToolResultErrorFromErr("failed to list campaigns", err), nil + } + var campaigns []serializedCampaign + for _, node := range resp.Nodes { + c := serializedCampaign{ + Id: string(node.Id), + Name: node.Name, + ProjectBrief: node.ProjectBrief, + Status: string(node.Status), + HtmlURL: node.HtmlUrl, + CheckStats: node.CheckStats, + ServiceStats: node.ServiceStats, + } + if node.Owner != (opslevel.TeamId{}) { + c.Owner = &node.Owner + } + if node.StartDate != (iso8601.Time{}) { + c.StartDate = &node.StartDate + } + if node.EndedDate != (iso8601.Time{}) { + c.EndedDate = &node.EndedDate + } + if node.TargetDate != (iso8601.Time{}) { + c.TargetDate = &node.TargetDate + } + if len(node.Reminder.Channels) > 0 { + c.Reminder = &node.Reminder + } + if node.Filter != (opslevel.FilterId{}) { + c.Filter = &node.Filter + } + if node.CheckStats != (opslevel.Stats{}) { + c.CheckStats = node.CheckStats + } + + campaigns = append(campaigns, c) + } + + return newToolResult(campaigns, err) + }) + log.Info().Msg("Starting MCP server...") if err := server.ServeStdio(s); err != nil { if err == context.Canceled { diff --git a/src/submodules/opslevel-go b/src/submodules/opslevel-go index 1ccf047..231146a 160000 --- a/src/submodules/opslevel-go +++ b/src/submodules/opslevel-go @@ -1 +1 @@ -Subproject commit 1ccf047d00323d97d95bdedad7ef5770d0bb8a7f +Subproject commit 231146ab92ac2fdc6aff50e05649d246d3000d1f From ceeaca0d94d7a78d54a276a7229f8279a669e05e Mon Sep 17 00:00:00 2001 From: Wesley Ellis Date: Fri, 13 Jun 2025 13:54:28 -0300 Subject: [PATCH 2/2] use released sha for opslevel-go, add note about default filter --- src/cmd/root.go | 2 +- src/submodules/opslevel-go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cmd/root.go b/src/cmd/root.go index c51c054..862239a 100644 --- a/src/cmd/root.go +++ b/src/cmd/root.go @@ -534,7 +534,7 @@ var rootCmd = &cobra.Command{ mcp.NewTool( "campaigns", mcp.WithDescription("Get all the campaigns in the OpsLevel account. Campaigns are used to track and manage initiatives or projects within OpsLevel."), - mcp.WithString("status", mcp.Description("Filter campaigns by status"), mcp.Enum(opslevel.AllCampaignStatusEnum...)), + mcp.WithString("status", mcp.Description("Filter campaigns by status, default is 'in_progress'"), mcp.Enum(opslevel.AllCampaignStatusEnum...)), mcp.WithToolAnnotation(mcp.ToolAnnotation{ Title: "Campaigns in OpsLevel", ReadOnlyHint: &trueValue, diff --git a/src/submodules/opslevel-go b/src/submodules/opslevel-go index 231146a..60109f2 160000 --- a/src/submodules/opslevel-go +++ b/src/submodules/opslevel-go @@ -1 +1 @@ -Subproject commit 231146ab92ac2fdc6aff50e05649d246d3000d1f +Subproject commit 60109f2fde2bc018b32a76932288bfada4a64aa6