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
3 changes: 3 additions & 0 deletions .changes/unreleased/Added-20250613-100609.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
kind: Added
body: Add campaigns tool for listing campaigns
time: 2025-06-13T10:06:09.286358-03:00
81 changes: 81 additions & 0 deletions src/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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."),
Comment thread
wesleyjellis marked this conversation as resolved.
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,
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
}
Comment on lines +569 to +589
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've never seen syntax like this before - TIL

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe not very golike but I don't know how else to see if the value is the same as the default constructor

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wesleyjellis no it makes sense in this usecase i've just never seen it before. Its verbose for sure but understandable. 👍


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 {
Expand Down
Loading