diff --git a/pkg/cli/audit_jq_test.go b/pkg/cli/audit_jq_test.go deleted file mode 100644 index 2ea1369076..0000000000 --- a/pkg/cli/audit_jq_test.go +++ /dev/null @@ -1,401 +0,0 @@ -//go:build !integration - -package cli - -import ( - "encoding/json" - "strings" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestAuditJqFilter_BasicFiltering(t *testing.T) { - // Create sample audit JSON output - auditJSON := `{ - "overview": { - "run_id": 123456, - "workflow_name": "test-workflow", - "status": "completed", - "conclusion": "success" - }, - "metrics": { - "token_usage": 1000, - "estimated_cost": "$0.50" - }, - "jobs": [ - {"name": "job1", "status": "completed"}, - {"name": "job2", "status": "completed"} - ] -}` - - tests := []struct { - name string - jqFilter string - validate func(t *testing.T, output string) - wantErr bool - }{ - { - name: "extract overview", - jqFilter: ".overview", - validate: func(t *testing.T, output string) { - var result map[string]any - err := json.Unmarshal([]byte(output), &result) - require.NoError(t, err, "Output should be valid JSON") - runID, ok := result["run_id"].(float64) - require.True(t, ok, "run_id should be a number") - assert.Equal(t, int64(123456), int64(runID), "Should extract run_id from overview") - assert.Equal(t, "test-workflow", result["workflow_name"], "Should extract workflow_name from overview") - }, - wantErr: false, - }, - { - name: "extract metrics", - jqFilter: ".metrics", - validate: func(t *testing.T, output string) { - var result map[string]any - err := json.Unmarshal([]byte(output), &result) - require.NoError(t, err, "Output should be valid JSON") - tokenUsage, ok := result["token_usage"].(float64) - require.True(t, ok, "token_usage should be a number") - assert.Equal(t, int64(1000), int64(tokenUsage), "Should extract token_usage from metrics") - }, - wantErr: false, - }, - { - name: "extract jobs array", - jqFilter: ".jobs", - validate: func(t *testing.T, output string) { - var result []map[string]any - err := json.Unmarshal([]byte(output), &result) - require.NoError(t, err, "Output should be valid JSON array") - assert.Len(t, result, 2, "Should have 2 jobs") - assert.Equal(t, "job1", result[0]["name"], "First job should be job1") - }, - wantErr: false, - }, - { - name: "extract specific field", - jqFilter: ".overview.run_id", - validate: func(t *testing.T, output string) { - output = strings.TrimSpace(output) - assert.Equal(t, "123456", output, "Should extract run_id as string") - }, - wantErr: false, - }, - { - name: "identity filter", - jqFilter: ".", - validate: func(t *testing.T, output string) { - var result map[string]any - err := json.Unmarshal([]byte(output), &result) - require.NoError(t, err, "Output should be valid JSON") - assert.Contains(t, result, "overview", "Should contain overview") - assert.Contains(t, result, "metrics", "Should contain metrics") - assert.Contains(t, result, "jobs", "Should contain jobs") - }, - wantErr: false, - }, - { - name: "invalid jq filter", - jqFilter: ".[invalid", - validate: nil, - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - output, err := ApplyJqFilter(auditJSON, tt.jqFilter) - - if tt.wantErr { - assert.Error(t, err, "Expected error for invalid jq filter") - return - } - - require.NoError(t, err, "Should apply jq filter without error") - if tt.validate != nil { - tt.validate(t, output) - } - }) - } -} - -func TestAuditJqFilter_ComplexFiltering(t *testing.T) { - // Create more complex audit JSON output with nested structures - auditJSON := `{ - "overview": { - "run_id": 123456, - "workflow_name": "test-workflow", - "status": "completed", - "conclusion": "success", - "created_at": "2024-01-01T00:00:00Z", - "duration": "5m30s", - "url": "https://github.com/owner/repo/actions/runs/123456" - }, - "metrics": { - "token_usage": 1000, - "estimated_cost": "$0.50", - "turns": 5, - "error_count": 0, - "warning_count": 2 - }, - "jobs": [ - {"name": "job1", "status": "completed", "conclusion": "success", "duration": "2m10s"}, - {"name": "job2", "status": "completed", "conclusion": "success", "duration": "3m20s"} - ], - "missing_tools": [ - {"tool": "github_api", "reason": "not configured"}, - {"tool": "slack_webhook", "reason": "token missing"} - ] -}` - - tests := []struct { - name string - jqFilter string - validate func(t *testing.T, output string) - }{ - { - name: "map job names", - jqFilter: ".jobs | map(.name)", - validate: func(t *testing.T, output string) { - var result []string - err := json.Unmarshal([]byte(output), &result) - require.NoError(t, err, "Output should be valid JSON array") - assert.Equal(t, []string{"job1", "job2"}, result, "Should extract job names") - }, - }, - { - name: "count jobs", - jqFilter: ".jobs | length", - validate: func(t *testing.T, output string) { - output = strings.TrimSpace(output) - assert.Equal(t, "2", output, "Should count 2 jobs") - }, - }, - { - name: "select specific job", - jqFilter: `.jobs[] | select(.name == "job1")`, - validate: func(t *testing.T, output string) { - var result map[string]any - err := json.Unmarshal([]byte(output), &result) - require.NoError(t, err, "Output should be valid JSON") - assert.Equal(t, "job1", result["name"], "Should select job1") - }, - }, - { - name: "extract missing tool names", - jqFilter: ".missing_tools | map(.tool)", - validate: func(t *testing.T, output string) { - var result []string - err := json.Unmarshal([]byte(output), &result) - require.NoError(t, err, "Output should be valid JSON array") - assert.Equal(t, []string{"github_api", "slack_webhook"}, result, "Should extract tool names") - }, - }, - { - name: "combine multiple fields", - jqFilter: `{run_id: .overview.run_id, token_usage: .metrics.token_usage, job_count: (.jobs | length)}`, - validate: func(t *testing.T, output string) { - var result map[string]any - err := json.Unmarshal([]byte(output), &result) - require.NoError(t, err, "Output should be valid JSON") - runID, ok := result["run_id"].(float64) - require.True(t, ok, "run_id should be a number") - assert.Equal(t, int64(123456), int64(runID), "Should have run_id") - tokenUsage, ok := result["token_usage"].(float64) - require.True(t, ok, "token_usage should be a number") - assert.Equal(t, int64(1000), int64(tokenUsage), "Should have token_usage") - jobCount, ok := result["job_count"].(float64) - require.True(t, ok, "job_count should be a number") - assert.Equal(t, int64(2), int64(jobCount), "Should have job_count") - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - output, err := ApplyJqFilter(auditJSON, tt.jqFilter) - require.NoError(t, err, "Should apply jq filter without error") - tt.validate(t, output) - }) - } -} - -func TestAuditJqFilter_EdgeCases(t *testing.T) { - tests := []struct { - name string - jsonInput string - jqFilter string - wantErr bool - }{ - { - name: "empty jq filter", - jsonInput: `{"data": "test"}`, - jqFilter: "", - wantErr: true, - }, - { - name: "empty JSON object", - jsonInput: `{}`, - jqFilter: ".", - wantErr: false, - }, - { - name: "null value", - jsonInput: `{"value": null}`, - jqFilter: ".value", - wantErr: false, - }, - { - name: "array with null", - jsonInput: `[1, null, 3]`, - jqFilter: ".", - wantErr: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - _, err := ApplyJqFilter(tt.jsonInput, tt.jqFilter) - if tt.wantErr { - assert.Error(t, err, "Expected error") - } else { - assert.NoError(t, err, "Should not error") - } - }) - } -} - -// TestAuditJqFilter_RealWorldExample tests with a realistic audit output structure -func TestAuditJqFilter_RealWorldExample(t *testing.T) { - // Realistic audit output based on actual AuditData structure - auditJSON := `{ - "overview": { - "run_id": 21784234145, - "workflow_name": "Test Workflow", - "status": "completed", - "conclusion": "success", - "created_at": "2026-02-07T10:00:00Z", - "started_at": "2026-02-07T10:01:00Z", - "updated_at": "2026-02-07T10:15:00Z", - "duration": "14m0s", - "event": "workflow_dispatch", - "branch": "main", - "url": "https://github.com/github/gh-aw/actions/runs/21784234145", - "logs_path": ".github/aw/logs/run-21784234145" - }, - "metrics": { - "token_usage": 15234, - "estimated_cost": "$0.23", - "turns": 8, - "error_count": 0, - "warning_count": 3 - }, - "jobs": [ - { - "name": "agent", - "status": "completed", - "conclusion": "success", - "duration": "12m30s" - } - ], - "downloaded_files": [ - { - "path": "aw_info.json", - "size": 1024, - "size_formatted": "1.0 KB", - "description": "Workflow configuration", - "is_directory": false - } - ], - "missing_tools": [], - "mcp_failures": [], - "errors": [], - "warnings": [ - { - "file": "workflow.md", - "line": 10, - "type": "deprecation", - "message": "Using deprecated syntax" - } - ], - "tool_usage": [ - { - "name": "bash", - "call_count": 15, - "max_input_size": 256, - "max_output_size": 1024, - "max_duration": "2.5s" - } - ], - "firewall_analysis": null -}` - - tests := []struct { - name string - jqFilter string - validate func(t *testing.T, output string) - }{ - { - name: "extract overview section", - jqFilter: ".overview", - validate: func(t *testing.T, output string) { - var overview map[string]any - err := json.Unmarshal([]byte(output), &overview) - require.NoError(t, err, "Should parse overview") - runID, ok := overview["run_id"].(float64) - require.True(t, ok, "run_id should be a number") - assert.Equal(t, int64(21784234145), int64(runID), "Should have correct run_id") - assert.Equal(t, "Test Workflow", overview["workflow_name"], "Should have workflow name") - }, - }, - { - name: "extract key metrics", - jqFilter: `{token_usage: .metrics.token_usage, cost: .metrics.estimated_cost, duration: .overview.duration}`, - validate: func(t *testing.T, output string) { - var result map[string]any - err := json.Unmarshal([]byte(output), &result) - require.NoError(t, err, "Should parse result") - tokenUsage, ok := result["token_usage"].(float64) - require.True(t, ok, "token_usage should be a number") - assert.Equal(t, int64(15234), int64(tokenUsage), "Should have token_usage") - assert.Equal(t, "$0.23", result["cost"], "Should have cost") - assert.Equal(t, "14m0s", result["duration"], "Should have duration") - }, - }, - { - name: "check for missing tools", - jqFilter: `.missing_tools | length`, - validate: func(t *testing.T, output string) { - output = strings.TrimSpace(output) - assert.Equal(t, "0", output, "Should have 0 missing tools") - }, - }, - { - name: "summary with job count", - jqFilter: `{run_id: .overview.run_id, status: .overview.conclusion, job_count: (.jobs | length)}`, - validate: func(t *testing.T, output string) { - var result map[string]any - err := json.Unmarshal([]byte(output), &result) - require.NoError(t, err, "Should parse summary") - runID, ok := result["run_id"].(float64) - require.True(t, ok, "run_id should be a number") - assert.Equal(t, int64(21784234145), int64(runID), "Should have run_id") - assert.Equal(t, "success", result["status"], "Should have status") - jobCount, ok := result["job_count"].(float64) - require.True(t, ok, "job_count should be a number") - assert.Equal(t, int64(1), int64(jobCount), "Should have 1 job") - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - output, err := ApplyJqFilter(auditJSON, tt.jqFilter) - require.NoError(t, err, "Should apply jq filter without error") - tt.validate(t, output) - }) - } -} diff --git a/pkg/cli/jq.go b/pkg/cli/jq.go deleted file mode 100644 index 45dfabf614..0000000000 --- a/pkg/cli/jq.go +++ /dev/null @@ -1,45 +0,0 @@ -package cli - -import ( - "bytes" - "fmt" - "os/exec" - "strings" - - "github.com/github/gh-aw/pkg/logger" -) - -var jqLog = logger.New("cli:jq") - -// ApplyJqFilter applies a jq filter to JSON input -func ApplyJqFilter(jsonInput string, jqFilter string) (string, error) { - jqLog.Printf("Applying jq filter: %s (input size: %d bytes)", jqFilter, len(jsonInput)) - - // Validate filter is not empty - if jqFilter == "" { - return "", fmt.Errorf("jq filter cannot be empty") - } - - // Check if jq is available - jqPath, err := exec.LookPath("jq") - if err != nil { - jqLog.Printf("jq not found in PATH") - return "", fmt.Errorf("jq not found in PATH") - } - jqLog.Printf("Found jq at: %s", jqPath) - - // Pipe through jq - cmd := exec.Command(jqPath, jqFilter) - cmd.Stdin = strings.NewReader(jsonInput) - var stdout, stderr bytes.Buffer - cmd.Stdout = &stdout - cmd.Stderr = &stderr - - if err := cmd.Run(); err != nil { - jqLog.Printf("jq filter failed: %v, stderr: %s", err, stderr.String()) - return "", fmt.Errorf("jq filter failed: %w, stderr: %s", err, stderr.String()) - } - - jqLog.Printf("jq filter succeeded (output size: %d bytes)", stdout.Len()) - return stdout.String(), nil -} diff --git a/pkg/cli/jq_integration_test.go b/pkg/cli/jq_integration_test.go deleted file mode 100644 index e9a66b1373..0000000000 --- a/pkg/cli/jq_integration_test.go +++ /dev/null @@ -1,130 +0,0 @@ -//go:build integration - -package cli - -import ( - "context" - "os" - "os/exec" - "path/filepath" - "testing" - - "time" - - "github.com/github/gh-aw/pkg/testutil" - - "github.com/modelcontextprotocol/go-sdk/mcp" -) - -// TestMCPServer_StatusToolWithJq tests the status tool with jq filter parameter -func TestMCPServer_StatusToolWithJq(t *testing.T) { - // Skip if jq is not available - if _, err := exec.LookPath("jq"); err != nil { - t.Skip("Skipping test: jq not found in PATH") - } - - // 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.") - } - - // Get absolute path to binary before changing directories - absBinaryPath, err := filepath.Abs(binaryPath) - if err != nil { - t.Fatalf("Failed to get absolute path to binary: %v", err) - } - - // Create a temporary directory with a workflow file - tmpDir := testutil.TempDir(t, "test-*") - workflowsDir := filepath.Join(tmpDir, ".github", "workflows") - if err := os.MkdirAll(workflowsDir, 0755); err != nil { - t.Fatalf("Failed to create workflows directory: %v", err) - } - - // Create a test workflow file - workflowContent := `--- -on: push -engine: copilot ---- -# Test Workflow -` - workflowFile := filepath.Join(workflowsDir, "test.md") - if err := os.WriteFile(workflowFile, []byte(workflowContent), 0644); err != nil { - t.Fatalf("Failed to write workflow file: %v", err) - } - - // Save current directory and change to temp directory - originalDir, _ := os.Getwd() - defer os.Chdir(originalDir) - - // Initialize git repository in the temp directory using shared helper - if err := initTestGitRepo(tmpDir); err != nil { - t.Fatalf("Failed to initialize git repository: %v", err) - } - - // Create MCP client - client := mcp.NewClient(&mcp.Implementation{ - Name: "test-client", - Version: "1.0.0", - }, nil) - - // Start the MCP server as a subprocess with --cmd flag to use binary directly - serverCmd := exec.Command(absBinaryPath, "mcp-server", "--cmd", absBinaryPath) - serverCmd.Dir = tmpDir - transport := &mcp.CommandTransport{Command: serverCmd} - - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - session, err := client.Connect(ctx, transport, nil) - if err != nil { - t.Fatalf("Failed to connect to MCP server: %v", err) - } - defer session.Close() - - // Test 1: Call status tool with jq filter to get just workflow names - params := &mcp.CallToolParams{ - Name: "status", - Arguments: map[string]any{ - "jq": ".[].workflow", - }, - } - result, err := session.CallTool(ctx, params) - if err != nil { - t.Fatalf("Failed to call status tool with jq filter: %v", err) - } - - // Verify result contains the workflow name - if textContent, ok := result.Content[0].(*mcp.TextContent); ok { - if textContent.Text == "" { - t.Error("Expected non-empty text content from status tool with jq filter") - } - // The output should contain "test" (the workflow name) - t.Logf("Status tool output with jq filter: %s", textContent.Text) - } else { - t.Error("Expected text content from status tool with jq filter") - } - - // Test 2: Call status tool with jq filter to count workflows - params = &mcp.CallToolParams{ - Name: "status", - Arguments: map[string]any{ - "jq": "length", - }, - } - result, err = session.CallTool(ctx, params) - if err != nil { - t.Fatalf("Failed to call status tool with jq count filter: %v", err) - } - - // Verify result contains a number - if textContent, ok := result.Content[0].(*mcp.TextContent); ok { - if textContent.Text == "" { - t.Error("Expected non-empty text content from status tool with jq count filter") - } - t.Logf("Status tool count output: %s", textContent.Text) - } else { - t.Error("Expected text content from status tool with jq count filter") - } -} diff --git a/pkg/cli/jq_test.go b/pkg/cli/jq_test.go deleted file mode 100644 index f549de1c87..0000000000 --- a/pkg/cli/jq_test.go +++ /dev/null @@ -1,157 +0,0 @@ -//go:build !integration - -package cli - -import ( - "os/exec" - "strings" - "testing" -) - -func TestApplyJqFilter(t *testing.T) { - // Skip if jq is not available - if _, err := exec.LookPath("jq"); err != nil { - t.Skip("Skipping test: jq not found in PATH") - } - - tests := []struct { - name string - jsonInput string - jqFilter string - wantErr bool - validate func(t *testing.T, output string) - }{ - { - name: "simple filter - identity", - jsonInput: `{"name":"test"}`, - jqFilter: ".", - wantErr: false, - validate: func(t *testing.T, output string) { - output = strings.TrimSpace(output) - if !strings.Contains(output, "test") { - t.Errorf("Expected output to contain 'test', got %q", output) - } - }, - }, - { - name: "simple filter - get first element", - jsonInput: `[{"name":"a"},{"name":"b"}]`, - jqFilter: ".[0]", - wantErr: false, - validate: func(t *testing.T, output string) { - if output == "" { - t.Error("Expected non-empty output") - } - }, - }, - { - name: "filter - count array length", - jsonInput: `[{"name":"a"},{"name":"b"},{"name":"c"}]`, - jqFilter: "length", - wantErr: false, - validate: func(t *testing.T, output string) { - if output != "3\n" { - t.Errorf("Expected '3\\n', got %q", output) - } - }, - }, - { - name: "filter - map and select", - jsonInput: `[{"name":"a","type":"x"},{"name":"b","type":"y"},{"name":"c","type":"x"}]`, - jqFilter: `[.[] | select(.type == "x") | .name]`, - wantErr: false, - validate: func(t *testing.T, output string) { - if output == "" { - t.Error("Expected non-empty output") - } - }, - }, - { - name: "filter - extract specific field", - jsonInput: `{"name":"value","id":123}`, - jqFilter: ".name", - wantErr: false, - validate: func(t *testing.T, output string) { - output = strings.TrimSpace(output) - if !strings.Contains(output, "value") { - t.Errorf("Expected output to contain 'value', got %q", output) - } - }, - }, - { - name: "filter - empty input", - jsonInput: `{}`, - jqFilter: ".", - wantErr: false, - validate: func(t *testing.T, output string) { - output = strings.TrimSpace(output) - if output != "{}" { - t.Errorf("Expected '{}', got %q", output) - } - }, - }, - { - name: "filter - array transformation", - jsonInput: `[1,2,3]`, - jqFilter: "map(. * 2)", - wantErr: false, - validate: func(t *testing.T, output string) { - if !strings.Contains(output, "2") && !strings.Contains(output, "4") && !strings.Contains(output, "6") { - t.Error("Expected transformed array output") - } - }, - }, - { - name: "invalid filter - syntax error", - jsonInput: `[{"name":"a"}]`, - jqFilter: ".[invalid", - wantErr: true, - validate: nil, - }, - { - name: "invalid JSON input", - jsonInput: `{invalid json}`, - jqFilter: ".", - wantErr: true, - validate: nil, - }, - { - name: "empty filter", - jsonInput: `{"data":"test"}`, - jqFilter: "", - wantErr: true, - validate: nil, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - output, err := ApplyJqFilter(tt.jsonInput, tt.jqFilter) - if (err != nil) != tt.wantErr { - t.Errorf("ApplyJqFilter() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !tt.wantErr && tt.validate != nil { - tt.validate(t, output) - } - }) - } -} - -func TestApplyJqFilter_JqNotAvailable(t *testing.T) { - // This test verifies the error message when jq is not available - // We can't easily mock exec.LookPath, so we'll just verify the function structure - - // If jq is available, skip this test - if _, err := exec.LookPath("jq"); err == nil { - t.Skip("Skipping test: jq is available, cannot test 'not found' scenario") - } - - _, err := ApplyJqFilter(`[]`, ".") - if err == nil { - t.Error("Expected error when jq is not available") - } - if err != nil && err.Error() != "jq not found in PATH" { - t.Errorf("Expected 'jq not found in PATH' error, got: %v", err) - } -} diff --git a/pkg/cli/mcp_logs_guardrail.go b/pkg/cli/mcp_logs_guardrail.go index 8cfefd4740..efb593a016 100644 --- a/pkg/cli/mcp_logs_guardrail.go +++ b/pkg/cli/mcp_logs_guardrail.go @@ -22,11 +22,10 @@ const ( // MCPLogsGuardrailResponse represents the response when output is too large type MCPLogsGuardrailResponse struct { - Message string `json:"message"` - OutputTokens int `json:"output_tokens"` - OutputSizeLimit int `json:"output_size_limit"` - Schema LogsDataSchema `json:"schema"` - SuggestedQueries []SuggestedJqQuery `json:"suggested_queries"` + Message string `json:"message"` + OutputTokens int `json:"output_tokens"` + OutputSizeLimit int `json:"output_size_limit"` + Schema LogsDataSchema `json:"schema"` } // LogsDataSchema describes the structure of the full logs output @@ -42,13 +41,6 @@ type SchemaField struct { Description string `json:"description"` } -// SuggestedJqQuery represents a suggested jq filter -type SuggestedJqQuery struct { - Description string `json:"description"` - Query string `json:"query"` - Example string `json:"example,omitempty"` -} - // estimateTokens estimates the number of tokens in a string // Using the approximation: ~4 characters per token func estimateTokens(text string) int { @@ -76,14 +68,13 @@ func checkLogsOutputSize(outputStr string, maxTokens int) (string, bool) { guardrail := MCPLogsGuardrailResponse{ Message: fmt.Sprintf( "āš ļø Output size (%d tokens) exceeds the limit (%d tokens). "+ - "To reduce output size, use the 'jq' parameter with one of the suggested queries below.", + "To reduce output size, increase the 'max_tokens' parameter or narrow your query with filters like workflow_name, start_date, end_date, or count.", outputTokens, maxTokens, ), - OutputTokens: outputTokens, - OutputSizeLimit: maxTokens, - Schema: getLogsDataSchema(), - SuggestedQueries: getSuggestedJqQueries(), + OutputTokens: outputTokens, + OutputSizeLimit: maxTokens, + Schema: getLogsDataSchema(), } // Marshal to JSON @@ -93,13 +84,13 @@ func checkLogsOutputSize(outputStr string, maxTokens int) (string, bool) { // Fallback to simple text message if JSON marshaling fails return fmt.Sprintf( "Output size (%d tokens) exceeds the limit (%d tokens). "+ - "Please use the 'jq' parameter to filter the output.", + "Please increase the 'max_tokens' parameter or narrow your query.", outputTokens, maxTokens, ), true } - mcpLogsGuardrailLog.Printf("Generated guardrail response with %d suggested queries", len(guardrail.SuggestedQueries)) + mcpLogsGuardrailLog.Print("Generated guardrail response") return string(guardrailJSON), true } @@ -153,52 +144,6 @@ func getLogsDataSchema() LogsDataSchema { } } -// getSuggestedJqQueries returns a list of useful jq queries to reduce output -func getSuggestedJqQueries() []SuggestedJqQuery { - return []SuggestedJqQuery{ - { - Description: "Get only the summary statistics", - Query: ".summary", - Example: "Use jq parameter: \".summary\"", - }, - { - Description: "Get list of run IDs and workflow names", - Query: ".runs | map({database_id, workflow_name, status})", - Example: "Use jq parameter: \".runs | map({database_id, workflow_name, status})\"", - }, - { - Description: "Get only failed runs", - Query: ".runs | map(select(.conclusion == \"failure\"))", - Example: "Use jq parameter: \".runs | map(select(.conclusion == \\\"failure\\\"))\"", - }, - { - Description: "Get summary with first 5 runs", - Query: "{summary, runs: .runs[:5]}", - Example: "Use jq parameter: \"{summary, runs: .runs[:5]}\"", - }, - { - Description: "Get only error and warning summaries", - Query: "{errors_and_warnings, missing_tools, mcp_failures}", - Example: "Use jq parameter: \"{errors_and_warnings, missing_tools, mcp_failures}\"", - }, - { - Description: "Get tool usage statistics", - Query: ".tool_usage", - Example: "Use jq parameter: \".tool_usage\"", - }, - { - Description: "Get runs with high token usage (>10k tokens)", - Query: ".runs | map(select(.token_usage > 10000))", - Example: "Use jq parameter: \".runs | map(select(.token_usage > 10000))\"", - }, - { - Description: "Get runs from specific workflow", - Query: ".runs | map(select(.workflow_name == \"YOUR_WORKFLOW_NAME\"))", - Example: "Use jq parameter: \".runs | map(select(.workflow_name == \\\"YOUR_WORKFLOW_NAME\\\"))\"", - }, - } -} - // formatGuardrailMessage creates a user-friendly text message from the guardrail response func formatGuardrailMessage(guardrail MCPLogsGuardrailResponse) string { var builder strings.Builder @@ -215,15 +160,5 @@ func formatGuardrailMessage(guardrail MCPLogsGuardrailResponse) string { fmt.Fprintf(&builder, " - %s (%s): %s\n", field, schema.Type, schema.Description) } - builder.WriteString("\nšŸ’” Suggested jq Queries:\n") - for i, query := range guardrail.SuggestedQueries { - fmt.Fprintf(&builder, " %d. %s\n", i+1, query.Description) - fmt.Fprintf(&builder, " Query: %s\n", query.Query) - if query.Example != "" { - fmt.Fprintf(&builder, " %s\n", query.Example) - } - builder.WriteString("\n") - } - return builder.String() } diff --git a/pkg/cli/mcp_logs_guardrail_integration_test.go b/pkg/cli/mcp_logs_guardrail_integration_test.go index c59fa5de2f..2350ac99eb 100644 --- a/pkg/cli/mcp_logs_guardrail_integration_test.go +++ b/pkg/cli/mcp_logs_guardrail_integration_test.go @@ -90,10 +90,6 @@ func TestMCPServer_LogsGuardrail(t *testing.T) { t.Error("Schema should have fields") } - if len(guardrail.SuggestedQueries) == 0 { - t.Error("Should have suggested queries") - } - // Verify some expected fields are in the schema expectedFields := []string{"summary", "runs", "tool_usage", "errors_and_warnings"} for _, field := range expectedFields { @@ -101,15 +97,5 @@ func TestMCPServer_LogsGuardrail(t *testing.T) { t.Errorf("Schema should include field '%s'", field) } } - - // Verify suggested queries have proper structure - for i, query := range guardrail.SuggestedQueries { - if query.Description == "" { - t.Errorf("Query %d should have description", i) - } - if query.Query == "" { - t.Errorf("Query %d should have query string", i) - } - } }) } diff --git a/pkg/cli/mcp_logs_guardrail_test.go b/pkg/cli/mcp_logs_guardrail_test.go index 4c27369729..6cfe70d46c 100644 --- a/pkg/cli/mcp_logs_guardrail_test.go +++ b/pkg/cli/mcp_logs_guardrail_test.go @@ -59,10 +59,6 @@ func TestCheckLogsOutputSize_LargeOutput(t *testing.T) { t.Errorf("Guardrail should report correct limit: expected %d, got %d", DefaultMaxMCPLogsOutputTokens, guardrail.OutputSizeLimit) } - if len(guardrail.SuggestedQueries) == 0 { - t.Error("Guardrail response should have suggested queries") - } - if len(guardrail.Schema.Fields) == 0 { t.Error("Guardrail response should have schema fields") } @@ -161,44 +157,12 @@ func TestGetLogsDataSchema(t *testing.T) { } } -func TestGetSuggestedJqQueries(t *testing.T) { - queries := getSuggestedJqQueries() - - if len(queries) == 0 { - t.Error("Should have at least one suggested query") - } - - // Verify each query has required fields - for i, query := range queries { - if query.Description == "" { - t.Errorf("Query %d should have a description", i) - } - if query.Query == "" { - t.Errorf("Query %d should have a query string", i) - } - } - - // Verify we have some common useful queries - hasBasicQueries := false - for _, query := range queries { - if strings.Contains(query.Query, ".summary") { - hasBasicQueries = true - break - } - } - - if !hasBasicQueries { - t.Error("Should have basic summary query in suggestions") - } -} - func TestFormatGuardrailMessage(t *testing.T) { guardrail := MCPLogsGuardrailResponse{ - Message: "Test message", - OutputTokens: 15000, - OutputSizeLimit: DefaultMaxMCPLogsOutputTokens, - Schema: getLogsDataSchema(), - SuggestedQueries: getSuggestedJqQueries(), + Message: "Test message", + OutputTokens: 15000, + OutputSizeLimit: DefaultMaxMCPLogsOutputTokens, + Schema: getLogsDataSchema(), } message := formatGuardrailMessage(guardrail) @@ -212,10 +176,6 @@ func TestFormatGuardrailMessage(t *testing.T) { t.Error("Formatted message should contain schema section") } - if !strings.Contains(message, "Suggested jq Queries") { - t.Error("Formatted message should contain suggested queries section") - } - // Verify it mentions some fields if !strings.Contains(message, "summary") { t.Error("Formatted message should mention 'summary' field") @@ -259,20 +219,6 @@ func TestGuardrailResponseJSON(t *testing.T) { if len(guardrail.Schema.Fields) == 0 { t.Error("JSON should have schema.fields") } - - if len(guardrail.SuggestedQueries) == 0 { - t.Error("JSON should have suggested_queries") - } - - // Verify each suggested query has the expected fields - for i, query := range guardrail.SuggestedQueries { - if query.Description == "" { - t.Errorf("Query %d should have description in JSON", i) - } - if query.Query == "" { - t.Errorf("Query %d should have query in JSON", i) - } - } } func TestDefaultMaxTokensConstant(t *testing.T) { diff --git a/pkg/cli/mcp_server.go b/pkg/cli/mcp_server.go index e352cfe074..0ef3b63aed 100644 --- a/pkg/cli/mcp_server.go +++ b/pkg/cli/mcp_server.go @@ -180,8 +180,7 @@ func createMCPServer(cmdPath string) *mcp.Server { // Add status tool type statusArgs struct { - Pattern string `json:"pattern,omitempty" jsonschema:"Optional pattern to filter workflows by name"` - JqFilter string `json:"jq,omitempty" jsonschema:"Optional jq filter to apply to JSON output"` + Pattern string `json:"pattern,omitempty" jsonschema:"Optional pattern to filter workflows by name"` } mcp.AddTool(server, &mcp.Tool{ @@ -193,9 +192,7 @@ Returns a JSON array where each element has the following structure: - agent: AI engine used (e.g., "copilot", "claude", "codex") - compiled: Whether the workflow is compiled ("Yes", "No", or "N/A") - status: GitHub workflow status ("active", "disabled", "Unknown") -- time_remaining: Time remaining until workflow deadline (if applicable) - -Note: Output can be filtered using the jq parameter.`, +- time_remaining: Time remaining until workflow deadline (if applicable)`, Icons: []mcp.Icon{ {Source: "šŸ“Š"}, }, @@ -211,7 +208,7 @@ Note: Output can be filtered using the jq parameter.`, default: } - mcpLog.Printf("Executing status tool: pattern=%s, jqFilter=%s", args.Pattern, args.JqFilter) + mcpLog.Printf("Executing status tool: pattern=%s", args.Pattern) // Call GetWorkflowStatuses directly instead of spawning subprocess statuses, err := GetWorkflowStatuses(args.Pattern, "", "", "") @@ -235,19 +232,6 @@ Note: Output can be filtered using the jq parameter.`, outputStr := string(jsonBytes) - // Apply jq filter if provided - if args.JqFilter != "" { - filteredOutput, jqErr := ApplyJqFilter(outputStr, args.JqFilter) - if jqErr != nil { - return nil, nil, &jsonrpc.Error{ - Code: jsonrpc.CodeInvalidParams, - Message: "invalid jq filter expression", - Data: mcpErrorData(map[string]any{"error": jqErr.Error(), "filter": args.JqFilter}), - } - } - outputStr = filteredOutput - } - return &mcp.CallToolResult{ Content: []mcp.Content{ &mcp.TextContent{Text: outputStr}, @@ -263,7 +247,6 @@ Note: Output can be filtered using the jq parameter.`, Poutine bool `json:"poutine,omitempty" jsonschema:"Run poutine security scanner on generated .lock.yml files"` Actionlint bool `json:"actionlint,omitempty" jsonschema:"Run actionlint linter on generated .lock.yml files"` Fix bool `json:"fix,omitempty" jsonschema:"Apply automatic codemod fixes to workflows before compiling"` - JqFilter string `json:"jq,omitempty" jsonschema:"Optional jq filter to apply to JSON output"` } // Generate schema with elicitation defaults @@ -294,9 +277,7 @@ Returns JSON array with validation results for each workflow: - valid: Boolean indicating if compilation was successful - errors: Array of error objects with type, message, and optional line number - warnings: Array of warning objects -- compiled_file: Path to the generated .lock.yml file - -Note: Output can be filtered using the jq parameter.`, +- compiled_file: Path to the generated .lock.yml file`, InputSchema: compileSchema, Icons: []mcp.Icon{ {Source: "šŸ”Ø"}, @@ -399,19 +380,6 @@ Note: Output can be filtered using the jq parameter.`, // and return it to the LLM } - // Apply jq filter if provided - if args.JqFilter != "" { - filteredOutput, jqErr := ApplyJqFilter(outputStr, args.JqFilter) - if jqErr != nil { - return nil, nil, &jsonrpc.Error{ - Code: jsonrpc.CodeInvalidParams, - Message: "invalid jq filter expression", - Data: mcpErrorData(map[string]any{"error": jqErr.Error(), "filter": args.JqFilter}), - } - } - outputStr = filteredOutput - } - return &mcp.CallToolResult{ Content: []mcp.Content{ &mcp.TextContent{Text: outputStr}, @@ -432,7 +400,6 @@ Note: Output can be filtered using the jq parameter.`, AfterRunID int64 `json:"after_run_id,omitempty" jsonschema:"Filter runs with database ID after this value (exclusive)"` BeforeRunID int64 `json:"before_run_id,omitempty" jsonschema:"Filter runs with database ID before this value (exclusive)"` Timeout int `json:"timeout,omitempty" jsonschema:"Maximum time in seconds to spend downloading logs (default: 50 for MCP server)"` - JqFilter string `json:"jq,omitempty" jsonschema:"Optional jq filter to apply to JSON output"` MaxTokens int `json:"max_tokens,omitempty" jsonschema:"Maximum number of tokens in output before triggering guardrail (default: 12000)"` } @@ -465,11 +432,7 @@ The continuation field includes all necessary parameters (before_run_id, etc.) t the previous request stopped due to timeout. āš ļø Output Size Guardrail: If the output exceeds the token limit (default: 12000 tokens), the tool will -return a schema description and suggested jq filters instead of the full output. Use the 'jq' parameter -to filter the output to a manageable size, or adjust the 'max_tokens' parameter. Common filters include: - - .summary (get only summary statistics) - - .runs[:5] (get first 5 runs) - - .runs | map(select(.conclusion == "failure")) (get only failed runs)`, +return a schema description instead of the full output. Adjust the 'max_tokens' parameter to control this behavior.`, InputSchema: logsSchema, Icons: []mcp.Icon{ {Source: "šŸ“œ"}, @@ -584,19 +547,6 @@ to filter the output to a manageable size, or adjust the 'max_tokens' parameter. } } - // Apply jq filter if provided - if args.JqFilter != "" { - filteredOutput, err := ApplyJqFilter(outputStr, args.JqFilter) - if err != nil { - return nil, nil, &jsonrpc.Error{ - Code: jsonrpc.CodeInvalidParams, - Message: "invalid jq filter expression", - Data: mcpErrorData(map[string]any{"error": err.Error(), "filter": args.JqFilter}), - } - } - outputStr = filteredOutput - } - // Check output size and apply guardrail if needed finalOutput, _ := checkLogsOutputSize(outputStr, args.MaxTokens) @@ -610,7 +560,6 @@ to filter the output to a manageable size, or adjust the 'max_tokens' parameter. // Add audit tool type auditArgs struct { RunIDOrURL string `json:"run_id_or_url" jsonschema:"GitHub Actions workflow run ID or URL. Accepts: numeric run ID (e.g., 1234567890), run URL (https://github.com/owner/repo/actions/runs/1234567890), job URL (https://github.com/owner/repo/actions/runs/1234567890/job/9876543210), or job URL with step (https://github.com/owner/repo/actions/runs/1234567890/job/9876543210#step:7:1)"` - JqFilter string `json:"jq,omitempty" jsonschema:"Optional jq filter to apply to JSON output"` } // Generate schema for audit tool @@ -645,9 +594,7 @@ Returns JSON with the following structure: - errors: Error details (file, line, type, message) - warnings: Warning details (file, line, type, message) - tool_usage: Tool usage statistics (name, call_count, max_output_size, max_duration) -- firewall_analysis: Network firewall analysis if available (total_requests, allowed_requests, blocked_requests, allowed_domains, blocked_domains) - -Note: Output can be filtered using the jq parameter.`, +- firewall_analysis: Network firewall analysis if available (total_requests, allowed_requests, blocked_requests, allowed_domains, blocked_domains)`, InputSchema: auditSchema, Icons: []mcp.Icon{ {Source: "šŸ”"}, @@ -682,19 +629,7 @@ Note: Output can be filtered using the jq parameter.`, } } - // Apply jq filter if provided outputStr := string(output) - if args.JqFilter != "" { - filteredOutput, jqErr := ApplyJqFilter(outputStr, args.JqFilter) - if jqErr != nil { - return nil, nil, &jsonrpc.Error{ - Code: jsonrpc.CodeInvalidParams, - Message: "invalid jq filter expression", - Data: mcpErrorData(map[string]any{"error": jqErr.Error(), "filter": args.JqFilter}), - } - } - outputStr = filteredOutput - } return &mcp.CallToolResult{ Content: []mcp.Content{ diff --git a/pkg/cli/mcp_server_compile_test.go b/pkg/cli/mcp_server_compile_test.go index 834e2d7fc6..1cf973b557 100644 --- a/pkg/cli/mcp_server_compile_test.go +++ b/pkg/cli/mcp_server_compile_test.go @@ -566,186 +566,6 @@ This workflow has strict mode disabled in frontmatter. t.Logf("Compile tool with strict mode returned: %s", textContent.Text) } -// TestMCPServer_CompileToolWithJqFilter tests compile with jq filter parameter - -func TestMCPServer_CompileToolWithJqFilter(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.") - } - - // Create a temporary directory with a workflow - tmpDir := testutil.TempDir(t, "test-*") - workflowsDir := filepath.Join(tmpDir, ".github", "workflows") - if err := os.MkdirAll(workflowsDir, 0755); err != nil { - t.Fatalf("Failed to create workflows directory: %v", err) - } - - // Create a test workflow - workflowContent := `--- -on: push -engine: copilot -permissions: - contents: read ---- -# Test Workflow -Test workflow for jq filtering. -` - workflowPath := filepath.Join(workflowsDir, "test.md") - if err := os.WriteFile(workflowPath, []byte(workflowContent), 0644); err != nil { - t.Fatalf("Failed to write workflow: %v", err) - } - - // Change to the temporary directory - originalDir, _ := os.Getwd() - defer os.Chdir(originalDir) - os.Chdir(tmpDir) - - // Initialize git repository using shared helper - if err := initTestGitRepo(tmpDir); err != nil { - t.Fatalf("Failed to initialize git repository: %v", err) - } - - // Create MCP client - client := mcp.NewClient(&mcp.Implementation{ - Name: "test-client", - Version: "1.0.0", - }, nil) - - // Start the MCP server - serverCmd := exec.Command(filepath.Join(originalDir, binaryPath), "mcp-server", "--cmd", filepath.Join(originalDir, binaryPath)) - serverCmd.Dir = tmpDir - transport := &mcp.CommandTransport{Command: serverCmd} - - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - session, err := client.Connect(ctx, transport, nil) - if err != nil { - t.Fatalf("Failed to connect to MCP server: %v", err) - } - defer session.Close() - - // Call compile tool with jq filter to extract only workflow names - params := &mcp.CallToolParams{ - Name: "compile", - Arguments: map[string]any{ - "jq": ".[].workflow", - }, - } - result, err := session.CallTool(ctx, params) - - // Should not return MCP error - if err != nil { - t.Errorf("Compile tool should not return MCP error with jq filter, got: %v", err) - } - - // Verify we got filtered results - if result == nil || len(result.Content) == 0 { - t.Fatal("Expected non-empty result content") - } - - textContent, ok := result.Content[0].(*mcp.TextContent) - if !ok { - t.Fatal("Expected text content from compile tool") - } - - // The output should be filtered by jq - if !strings.Contains(textContent.Text, "test.md") { - t.Errorf("Expected jq-filtered output to contain workflow name, got: %s", textContent.Text) - } - - t.Logf("Compile tool with jq filter returned: %s", textContent.Text) -} - -// TestMCPServer_CompileToolWithInvalidJqFilter tests compile with invalid jq filter - -func TestMCPServer_CompileToolWithInvalidJqFilter(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.") - } - - // Create a temporary directory with a workflow - tmpDir := testutil.TempDir(t, "test-*") - workflowsDir := filepath.Join(tmpDir, ".github", "workflows") - if err := os.MkdirAll(workflowsDir, 0755); err != nil { - t.Fatalf("Failed to create workflows directory: %v", err) - } - - // Create a test workflow - workflowContent := `--- -on: push -engine: copilot -permissions: - contents: read ---- -# Test Workflow -Test workflow. -` - workflowPath := filepath.Join(workflowsDir, "test.md") - if err := os.WriteFile(workflowPath, []byte(workflowContent), 0644); err != nil { - t.Fatalf("Failed to write workflow: %v", err) - } - - // Change to the temporary directory - originalDir, _ := os.Getwd() - defer os.Chdir(originalDir) - os.Chdir(tmpDir) - - // Initialize git repository using shared helper - if err := initTestGitRepo(tmpDir); err != nil { - t.Fatalf("Failed to initialize git repository: %v", err) - } - - // Create MCP client - client := mcp.NewClient(&mcp.Implementation{ - Name: "test-client", - Version: "1.0.0", - }, nil) - - // Start the MCP server - serverCmd := exec.Command(filepath.Join(originalDir, binaryPath), "mcp-server", "--cmd", filepath.Join(originalDir, binaryPath)) - serverCmd.Dir = tmpDir - transport := &mcp.CommandTransport{Command: serverCmd} - - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - session, err := client.Connect(ctx, transport, nil) - if err != nil { - t.Fatalf("Failed to connect to MCP server: %v", err) - } - defer session.Close() - - // Call compile tool with invalid jq filter - params := &mcp.CallToolParams{ - Name: "compile", - Arguments: map[string]any{ - "jq": ".[invalid syntax", - }, - } - result, err := session.CallTool(ctx, params) - - // Should return MCP error for invalid jq filter - if err == nil { - t.Error("Expected MCP error for invalid jq filter") - } - - // Error should mention jq filter - if err != nil && !strings.Contains(err.Error(), "jq") { - t.Errorf("Expected error message to mention jq filter, got: %v", err) - } - - if result != nil { - t.Log("Got unexpected result despite invalid jq filter") - } - - t.Logf("Compile tool correctly rejected invalid jq filter: %v", err) -} - // TestMCPServer_CompileToolWithSpecificWorkflows tests compiling specific workflows by name func TestMCPServer_CompileToolWithSpecificWorkflows(t *testing.T) { diff --git a/pkg/cli/mcp_server_defaults_test.go b/pkg/cli/mcp_server_defaults_test.go index 06a5615a03..2446838953 100644 --- a/pkg/cli/mcp_server_defaults_test.go +++ b/pkg/cli/mcp_server_defaults_test.go @@ -20,7 +20,6 @@ func TestMCPToolElicitationDefaults(t *testing.T) { Poutine bool `json:"poutine,omitempty" jsonschema:"Run poutine security scanner on generated .lock.yml files"` Actionlint bool `json:"actionlint,omitempty" jsonschema:"Run actionlint linter on generated .lock.yml files"` Fix bool `json:"fix,omitempty" jsonschema:"Apply automatic codemod fixes to workflows before compiling"` - JqFilter string `json:"jq,omitempty" jsonschema:"Optional jq filter to apply to JSON output"` } schema, err := GenerateOutputSchema[compileArgs]() diff --git a/pkg/cli/mcp_server_error_codes_test.go b/pkg/cli/mcp_server_error_codes_test.go index 4d7835ddbc..ad354cddfa 100644 --- a/pkg/cli/mcp_server_error_codes_test.go +++ b/pkg/cli/mcp_server_error_codes_test.go @@ -11,7 +11,6 @@ import ( "testing" "time" - "github.com/github/gh-aw/pkg/testutil" "github.com/modelcontextprotocol/go-sdk/mcp" ) @@ -92,69 +91,6 @@ func TestMCPServer_ErrorCodes_InvalidParams(t *testing.T) { } }) - // Test 3: invalid jq filter - t.Run("status_invalid_jq_filter", func(t *testing.T) { - // Create a temporary directory with a workflow file - tmpDir := testutil.TempDir(t, "test-*") - workflowsDir := filepath.Join(tmpDir, ".github", "workflows") - if err := os.MkdirAll(workflowsDir, 0755); err != nil { - t.Fatalf("Failed to create workflows directory: %v", err) - } - - // Create a test workflow file - workflowContent := `--- -on: push -engine: copilot ---- -# Test Workflow - -This is a test workflow. -` - workflowPath := filepath.Join(workflowsDir, "test.md") - if err := os.WriteFile(workflowPath, []byte(workflowContent), 0644); err != nil { - t.Fatalf("Failed to write workflow file: %v", err) - } - - // Initialize git repository using shared helper - if err := initTestGitRepo(tmpDir); err != nil { - t.Fatalf("Failed to initialize git repository: %v", err) - } - - // Start new MCP server in the temp directory - serverCmd := exec.Command(filepath.Join(originalDir, binaryPath), "mcp-server", "--cmd", filepath.Join(originalDir, binaryPath)) - serverCmd.Dir = tmpDir - transport := &mcp.CommandTransport{Command: serverCmd} - - ctx2, cancel2 := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel2() - - session2, err := client.Connect(ctx2, transport, nil) - if err != nil { - t.Fatalf("Failed to connect to MCP server: %v", err) - } - defer session2.Close() - - params := &mcp.CallToolParams{ - Name: "status", - Arguments: map[string]any{ - "jq": ".invalid[syntax", // Invalid jq filter - }, - } - - _, err = session2.CallTool(ctx2, params) - if err == nil { - t.Error("Expected error for invalid jq filter, got nil") - return - } - - // The error message should contain the invalid jq filter error - errMsg := err.Error() - if !strings.Contains(errMsg, "invalid jq filter") { - t.Errorf("Expected error message about invalid jq filter, got: %s", errMsg) - } else { - t.Logf("āœ“ Correct error for invalid jq filter: %s", errMsg) - } - }) } // TestMCPServer_ErrorCodes_InternalError tests that InternalError code is returned for execution failures diff --git a/pkg/cli/mcp_server_json_integration_test.go b/pkg/cli/mcp_server_json_integration_test.go index c77e842f26..10da4c212f 100644 --- a/pkg/cli/mcp_server_json_integration_test.go +++ b/pkg/cli/mcp_server_json_integration_test.go @@ -367,54 +367,6 @@ func TestMCPServer_LogsToolReturnsValidJSON(t *testing.T) { } } -// TestMCPServer_StatusToolWithJqFilter tests that the status tool respects jq filters -func TestMCPServer_StatusToolWithJqFilter(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() - - // Call status tool with jq filter to get only workflow names - params := &mcp.CallToolParams{ - Name: "status", - Arguments: map[string]any{ - "jq": ".[].workflow", - }, - } - result, err := session.CallTool(ctx, params) - if err != nil { - t.Fatalf("Failed to call status tool: %v", err) - } - - // Verify result is not empty - if len(result.Content) == 0 { - t.Fatal("Expected non-empty result from status tool") - } - - // Get text content - textContent, ok := result.Content[0].(*mcp.TextContent) - if !ok { - t.Fatal("Expected text content from status tool") - } - - if textContent.Text == "" { - t.Fatal("Expected non-empty text content from status tool") - } - - // The jq filter should return valid JSON (workflow names as strings) - jsonOutput := extractJSONFromOutput(textContent.Text) - if !isValidJSON(jsonOutput) { - t.Errorf("Status tool with jq filter did not return valid JSON. Output: %s", textContent.Text) - } - - t.Logf("Status tool with jq filter returned: %s", jsonOutput) -} - // TestMCPServer_AllToolsReturnContent tests that all tools return non-empty content func TestMCPServer_AllToolsReturnContent(t *testing.T) { // Skip if the binary doesn't exist