From 64ea3f5e6b263ea13b9c4c0e0b708e21a67ea5f0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 5 Apr 2026 16:30:05 +0000 Subject: [PATCH 1/3] Initial plan From dbacc777250ecc913d7c07668ca396bab3222f4f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 5 Apr 2026 16:52:45 +0000 Subject: [PATCH 2/3] feat: add checks as a first-class MCP tool to the gh-aw MCP server MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Exposes `gh aw checks` as an `agentic-workflows` MCP tool so review-style workflows can fetch normalized CI state without shelling out to `gh aw checks`. - pkg/cli/mcp_tools_readonly.go: registerChecksTool calling FetchChecksResult - pkg/cli/mcp_server.go: register checks tool in createMCPServer - pkg/cli/mcp_server_command.go: add checks to Long tool list - docs/…/gh-aw-as-mcp-server.md: document the new checks tool - .github/aw/debug-agentic-workflow.md: add checks to MCP equivalents list Agent-Logs-Url: https://github.com/github/gh-aw/sessions/bad45885-e19c-4193-b686-d4a71933c62e Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/aw/debug-agentic-workflow.md | 1 + .../docs/reference/gh-aw-as-mcp-server.md | 16 +++++ pkg/cli/mcp_server.go | 1 + pkg/cli/mcp_server_command.go | 1 + pkg/cli/mcp_tools_readonly.go | 70 +++++++++++++++++++ 5 files changed, 89 insertions(+) diff --git a/.github/aw/debug-agentic-workflow.md b/.github/aw/debug-agentic-workflow.md index b0293ed6c52..f584661b535 100644 --- a/.github/aw/debug-agentic-workflow.md +++ b/.github/aw/debug-agentic-workflow.md @@ -92,6 +92,7 @@ Report back with specific findings and actionable fixes. > - `compile` tool → equivalent to `gh aw compile` > - `logs` tool → equivalent to `gh aw logs` > - `audit` tool → equivalent to `gh aw audit` +> - `checks` tool → equivalent to `gh aw checks` > - `update` tool → equivalent to `gh aw update` > - `add` tool → equivalent to `gh aw add` > - `mcp-inspect` tool → equivalent to `gh aw mcp inspect` diff --git a/docs/src/content/docs/reference/gh-aw-as-mcp-server.md b/docs/src/content/docs/reference/gh-aw-as-mcp-server.md index 5699746d041..d0dcd314ad8 100644 --- a/docs/src/content/docs/reference/gh-aw-as-mcp-server.md +++ b/docs/src/content/docs/reference/gh-aw-as-mcp-server.md @@ -154,6 +154,22 @@ Investigate a workflow run, job, or specific step and generate a detailed report Returns JSON with `overview`, `metrics`, `jobs`, `downloaded_files`, `missing_tools`, `mcp_failures`, `errors`, `warnings`, `tool_usage`, and `firewall_analysis`. +### `checks` + +Classify CI check state for a pull request and return a normalized result. + +- `pr_number` (required): Pull request number to classify CI checks for +- `repo` (optional): Repository in `owner/repo` format (defaults to current repository) + +Returns JSON with: +- `state`: Aggregate check state across all check runs and commit statuses +- `required_state`: State derived from check runs and policy commit statuses only (ignores optional third-party statuses like Vercel/Netlify deployments) +- `pr_number`, `head_sha`, `check_runs`, `statuses`, `total_count` + +Normalized states: `success`, `failed`, `pending`, `no_checks`, `policy_blocked`. + +Use `required_state` as the authoritative CI verdict in repos with optional deployment integrations. + ### `mcp-inspect` Inspect MCP servers in workflows and list available tools, resources, and roots. diff --git a/pkg/cli/mcp_server.go b/pkg/cli/mcp_server.go index 8d43c6a114d..a4d22a019ff 100644 --- a/pkg/cli/mcp_server.go +++ b/pkg/cli/mcp_server.go @@ -70,6 +70,7 @@ func createMCPServer(cmdPath string, actor string, validateActor bool) *mcp.Serv } // Register remaining read-only tools + registerChecksTool(server) registerMCPInspectTool(server, execCmd) // Register workflow management tools diff --git a/pkg/cli/mcp_server_command.go b/pkg/cli/mcp_server_command.go index 62da18e461e..a8c44f09288 100644 --- a/pkg/cli/mcp_server_command.go +++ b/pkg/cli/mcp_server_command.go @@ -35,6 +35,7 @@ The server provides the following tools: - compile - Compile Markdown workflows to GitHub Actions YAML - logs - Download and analyze workflow logs (requires write+ access) - audit - Investigate a workflow run, job, or step and generate a report (requires write+ access) + - checks - Classify CI check state for a pull request - mcp-inspect - Inspect MCP servers in workflows and list available tools - add - Add workflows from remote repositories to .github/workflows - update - Update workflows from their source repositories diff --git a/pkg/cli/mcp_tools_readonly.go b/pkg/cli/mcp_tools_readonly.go index f6581467189..c7b0f46b194 100644 --- a/pkg/cli/mcp_tools_readonly.go +++ b/pkg/cli/mcp_tools_readonly.go @@ -299,6 +299,76 @@ Returns formatted text output showing: }) } +// registerChecksTool registers the checks tool with the MCP server. +// The checks tool is read-only and idempotent. +func registerChecksTool(server *mcp.Server) { + type checksArgs struct { + PRNumber string `json:"pr_number" jsonschema:"Pull request number to classify CI checks for"` + Repo string `json:"repo,omitempty" jsonschema:"Repository in owner/repo format (defaults to current repository)"` + } + + mcp.AddTool(server, &mcp.Tool{ + Name: "checks", + Annotations: &mcp.ToolAnnotations{ + ReadOnlyHint: true, + IdempotentHint: true, + OpenWorldHint: boolPtr(true), + }, + Description: `Classify CI check state for a pull request and return a normalized result. + +Maps PR check rollups to one of the following normalized states: + success - all checks passed + failed - one or more checks failed + pending - checks are still running or queued + no_checks - no checks configured or triggered + policy_blocked - policy or account gates are blocking the PR + +Returns JSON with two state fields: + state - aggregate state across all check runs and commit statuses + required_state - state derived from check runs and policy commit statuses only; + ignores optional third-party commit statuses (e.g. Vercel, + Netlify deployments) but still surfaces policy_blocked when + branch-protection or account-gate statuses fail + +Use required_state as the authoritative CI verdict in repos that have optional +deployment integrations posting commit statuses alongside required CI checks. + +Also returns pr_number, head_sha, check_runs, statuses, and total_count.`, + Icons: []mcp.Icon{ + {Source: "✅"}, + }, + }, func(ctx context.Context, req *mcp.CallToolRequest, args checksArgs) (*mcp.CallToolResult, any, error) { + // Check for cancellation before starting + select { + case <-ctx.Done(): + return nil, nil, newMCPError(jsonrpc.CodeInternalError, "request cancelled", ctx.Err().Error()) + default: + } + + if args.PRNumber == "" { + return nil, nil, newMCPError(jsonrpc.CodeInvalidParams, "missing required parameter: pr_number", nil) + } + + mcpLog.Printf("Executing checks tool: pr_number=%s, repo=%s", args.PRNumber, args.Repo) + + result, err := FetchChecksResult(args.Repo, args.PRNumber) + if err != nil { + return nil, nil, newMCPError(jsonrpc.CodeInternalError, "failed to fetch checks", map[string]any{"error": err.Error()}) + } + + jsonBytes, err := json.Marshal(result) + if err != nil { + return nil, nil, newMCPError(jsonrpc.CodeInternalError, "failed to marshal checks result", map[string]any{"error": err.Error()}) + } + + return &mcp.CallToolResult{ + Content: []mcp.Content{ + &mcp.TextContent{Text: string(jsonBytes)}, + }, + }, nil, nil + }) +} + // buildDockerErrorResults builds a []ValidationResult with a config_error for each target // workflow. It is used when Docker is unavailable so the compile tool returns consistent // structured JSON instead of a protocol-level error. From d952ec2c66d459041fe81f6daf35d9f947ac7c74 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 5 Apr 2026 17:07:54 +0000 Subject: [PATCH 3/3] test: add integration test for checks MCP tool MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds TestMCPServer_ChecksToolReturnsValidJSON to mcp_server_json_integration_test.go following the same pattern as the audit/logs tool tests: - missing pr_number → MCP error - valid pr_number without GitHub credentials → graceful error - valid pr_number with credentials → JSON with all ChecksResult fields verified Also adds checks to TestMCPServer_AllToolsReturnContent. Uses strings.HasPrefix for safe prefix checking. Agent-Logs-Url: https://github.com/github/gh-aw/sessions/c5b20c48-dfd4-4ac0-bcb8-d6e8afe3ed14 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/cli/mcp_server_json_integration_test.go | 92 +++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/pkg/cli/mcp_server_json_integration_test.go b/pkg/cli/mcp_server_json_integration_test.go index 10da4c212fc..89c146718b9 100644 --- a/pkg/cli/mcp_server_json_integration_test.go +++ b/pkg/cli/mcp_server_json_integration_test.go @@ -8,6 +8,7 @@ import ( "os" "os/exec" "path/filepath" + "strings" "testing" "time" @@ -367,6 +368,90 @@ func TestMCPServer_LogsToolReturnsValidJSON(t *testing.T) { } } +// TestMCPServer_ChecksToolReturnsValidJSON tests that the checks tool returns valid JSON +// (or a well-formed MCP error when GitHub credentials are unavailable in test environments). +func TestMCPServer_ChecksToolReturnsValidJSON(t *testing.T) { + // Skip if the binary doesn't exist + binaryPath := "../../gh-aw" + if _, err := os.Stat(binaryPath); os.IsNotExist(err) { + t.Skip("Skipping test: gh-aw binary not found. Run 'make build' first.") + } + + session, _, ctx, cancel := setupMCPServerTest(t, binaryPath) + defer cancel() + defer session.Close() + + t.Run("missing pr_number returns MCP error", func(t *testing.T) { + params := &mcp.CallToolParams{ + Name: "checks", + Arguments: map[string]any{}, + } + _, err := session.CallTool(ctx, params) + if err == nil { + t.Error("Expected MCP error when pr_number is missing") + } else { + t.Logf("Checks tool correctly returned error for missing pr_number: %v", err) + } + }) + + t.Run("valid pr_number returns JSON or auth error", func(t *testing.T) { + params := &mcp.CallToolParams{ + Name: "checks", + Arguments: map[string]any{ + "pr_number": "1", + }, + } + result, err := session.CallTool(ctx, params) + if err != nil { + // Expected: GitHub credentials are not available in the test environment + t.Logf("Checks tool correctly returned error (expected without GitHub credentials): %v", err) + return + } + + if len(result.Content) == 0 { + t.Fatal("Expected non-empty result from checks tool") + } + + textContent, ok := result.Content[0].(*mcp.TextContent) + if !ok { + t.Fatal("Expected text content from checks tool") + } + + if textContent.Text == "" { + t.Fatal("Expected non-empty text content from checks tool") + } + + // In test environments without GitHub credentials, an error message is returned + if strings.HasPrefix(textContent.Text, "Error:") { + t.Logf("Checks tool returned error message (expected in test environment without GitHub credentials)") + return + } + + // If credentials are available, verify JSON structure + jsonOutput := extractJSONFromOutput(textContent.Text) + if !isValidJSON(jsonOutput) { + t.Errorf("Checks tool did not return valid JSON. Output: %s", textContent.Text) + return + } + + var checksData map[string]any + if err := json.Unmarshal([]byte(jsonOutput), &checksData); err != nil { + t.Errorf("Failed to unmarshal checks JSON: %v", err) + return + } + + // Fields mirror the ChecksResult struct JSON tags defined in checks_command.go. + expectedFields := []string{"state", "required_state", "pr_number", "head_sha", "check_runs", "statuses", "total_count"} + for _, field := range expectedFields { + if _, ok := checksData[field]; !ok { + t.Errorf("Expected field '%s' not found in checks output", field) + } + } + + t.Logf("Checks tool returned valid JSON with state=%v", checksData["state"]) + }) +} + // TestMCPServer_AllToolsReturnContent tests that all tools return non-empty content func TestMCPServer_AllToolsReturnContent(t *testing.T) { // Skip if the binary doesn't exist @@ -417,6 +502,13 @@ func TestMCPServer_AllToolsReturnContent(t *testing.T) { expectJSON: false, // May return error message in test environment mayFailInTest: true, // Expected to fail without workflow runs }, + { + name: "checks", + toolName: "checks", + args: map[string]any{"pr_number": "1"}, + expectJSON: false, // May return error in test environment without GitHub credentials + mayFailInTest: true, // Expected to fail without GitHub credentials + }, { name: "mcp-inspect", toolName: "mcp-inspect",