diff --git a/.changes/unreleased/Added-20250609-161700.yaml b/.changes/unreleased/Added-20250609-161700.yaml new file mode 100644 index 0000000..7840a95 --- /dev/null +++ b/.changes/unreleased/Added-20250609-161700.yaml @@ -0,0 +1,3 @@ +kind: Added +body: Add componentChecks tool, listing checks in the rubric +time: 2025-06-09T16:17:00.521062-03:00 diff --git a/README.md b/README.md index 47d99d7..825fe32 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,8 @@ Activity Downloads - - Overall + +[![Overall](https://img.shields.io/endpoint?style=flat&url=https%3A%2F%2Fapp.opslevel.com%2Fapi%2Fservice_level%2Fdlmj6PlFjehv6iLE6IQtEGXi_uz3LF9rA5nxb35wiY8)](https://app.opslevel.com/services/opslevel_mcp/maturity-report)

diff --git a/src/cmd/root.go b/src/cmd/root.go index 18b62d1..a3739dc 100644 --- a/src/cmd/root.go +++ b/src/cmd/root.go @@ -15,6 +15,7 @@ import ( "github.com/spf13/cobra" + "github.com/relvacode/iso8601" "github.com/rs/zerolog" "github.com/rs/zerolog/log" "github.com/spf13/viper" @@ -71,6 +72,25 @@ type serializedCheck struct { Category string } +type serializedCheckResult struct { + CheckId string + CheckName string + Message string + Status string + LastUpdated iso8601.Time +} + +type serializedCheckResultsByLevel struct { + Level serializedLevel + CheckResults []serializedCheckResult +} + +type serializedCheckResults struct { + ByLevel []serializedCheckResultsByLevel + CurrentLevel serializedLevel + NextLevel *serializedLevel +} + // 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 { @@ -417,6 +437,68 @@ var rootCmd = &cobra.Command{ return newToolResult(checks, nil) }) + s.AddTool( + mcp.NewTool( + "componentChecks", + mcp.WithDescription("Get all the checks for a specific component in the OpsLevel account. Checks are organized by level in a rubric, with each level containing a set of checks that must be passed to achieve that level."), + mcp.WithString("serviceId", mcp.Required(), mcp.Description("The id of the service to fetch.")), + mcp.WithToolAnnotation(mcp.ToolAnnotation{ + Title: "Rubric of Checks for Component", + ReadOnlyHint: true, + DestructiveHint: false, + IdempotentHint: true, + OpenWorldHint: true, + }), + ), + func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { + service, err := client.GetService(req.Params.Arguments["serviceId"].(string)) + if err != nil { + return nil, err + } + if service.Id == "" { + return nil, fmt.Errorf("service with id %s not found", req.Params.Arguments["serviceId"].(string)) + } + + stats, err := service.GetServiceStats(client) + if err != nil { + return nil, err + } + + result := serializedCheckResults{ + CurrentLevel: serializedLevel{ + Alias: stats.Rubric.Level.Alias, + Index: stats.Rubric.Level.Index, + }, + } + if stats.Rubric.CheckResults.NextLevel.Level.Alias != "" { + result.NextLevel = &serializedLevel{ + Alias: stats.Rubric.CheckResults.NextLevel.Level.Alias, + Index: stats.Rubric.CheckResults.NextLevel.Level.Index, + } + } + + for _, checkResultsByLevel := range stats.Rubric.CheckResults.ByLevel.Nodes { + byLevel := serializedCheckResultsByLevel{ + Level: serializedLevel{ + Alias: checkResultsByLevel.Level.Alias, + Index: checkResultsByLevel.Level.Index, + }, + } + for _, checkResult := range checkResultsByLevel.Items.Nodes { + byLevel.CheckResults = append(byLevel.CheckResults, serializedCheckResult{ + CheckId: string(checkResult.Check.Id), + CheckName: checkResult.Check.Name, + Message: checkResult.Message, + Status: string(checkResult.Status), + LastUpdated: checkResult.LastUpdated, + }) + } + result.ByLevel = append(result.ByLevel, byLevel) + } + + return newToolResult(result, nil) + }) + log.Info().Msg("Starting MCP server...") if err := server.ServeStdio(s); err != nil { if err == context.Canceled { diff --git a/src/go.mod b/src/go.mod index e638dbe..ebe7cf9 100644 --- a/src/go.mod +++ b/src/go.mod @@ -7,6 +7,7 @@ toolchain go1.24.2 require ( github.com/mark3labs/mcp-go v0.23.1 github.com/opslevel/opslevel-go/v2025 v2025.5.28 + github.com/relvacode/iso8601 v1.6.0 github.com/rs/zerolog v1.34.0 github.com/spf13/cobra v1.9.1 github.com/spf13/viper v1.20.1 @@ -37,7 +38,6 @@ require ( github.com/opslevel/moredefaults v0.0.0-20240529152742-17d1318a3c12 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/relvacode/iso8601 v1.6.0 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/sagikazarmark/locafero v0.7.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect @@ -55,4 +55,4 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect ) -// replace github.com/opslevel/opslevel-go/v2025 => ./submodules/opslevel-go +replace github.com/opslevel/opslevel-go/v2025 => ./submodules/opslevel-go diff --git a/src/submodules/opslevel-go b/src/submodules/opslevel-go index caad994..1ccf047 160000 --- a/src/submodules/opslevel-go +++ b/src/submodules/opslevel-go @@ -1 +1 @@ -Subproject commit caad9947e0752fd7c664eb8c84162739858115fe +Subproject commit 1ccf047d00323d97d95bdedad7ef5770d0bb8a7f