From da13b5338e2165b982680609df88e6aaf5dfbc7c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 21 Jan 2026 19:05:31 +0000 Subject: [PATCH 1/2] Initial plan From 347641666b68dfaed36f90f136bdd2a4c117528a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 21 Jan 2026 19:28:11 +0000 Subject: [PATCH 2/2] Add safe-input tools support for Daily Performance Summary workflow Co-authored-by: mnkiefer <8320933+mnkiefer@users.noreply.github.com> --- .../daily-performance-summary.lock.yml | 1 + .../workflows/daily-performance-summary.md | 278 ++++++++++++++++++ pkg/parser/mcp.go | 25 ++ 3 files changed, 304 insertions(+) diff --git a/.github/workflows/daily-performance-summary.lock.yml b/.github/workflows/daily-performance-summary.lock.yml index 5b5c679a2da..d46af8b528e 100644 --- a/.github/workflows/daily-performance-summary.lock.yml +++ b/.github/workflows/daily-performance-summary.lock.yml @@ -645,6 +645,7 @@ jobs: EOF fi + EOFSH_github-discussion-query chmod +x /opt/gh-aw/safe-inputs/github-discussion-query.sh cat > /opt/gh-aw/safe-inputs/github-issue-query.sh << 'EOFSH_github-issue-query' diff --git a/.github/workflows/daily-performance-summary.md b/.github/workflows/daily-performance-summary.md index e9c17cc59ff..590759a3ffc 100644 --- a/.github/workflows/daily-performance-summary.md +++ b/.github/workflows/daily-performance-summary.md @@ -17,6 +17,284 @@ features: tools: github: toolsets: [default, discussions] +safe-inputs: + github-issue-query: + description: "Query GitHub issues with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter." + inputs: + repo: + type: string + description: "Repository in owner/repo format (defaults to current repository)" + required: false + state: + type: string + description: "Issue state: open, closed, all (default: open)" + required: false + limit: + type: number + description: "Maximum number of issues to fetch (default: 30)" + required: false + jq: + type: string + description: "jq filter expression to apply to output. If not provided, returns schema info instead of full data." + required: false + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -e + + # Default values + REPO="${INPUT_REPO:-}" + STATE="${INPUT_STATE:-open}" + LIMIT="${INPUT_LIMIT:-30}" + JQ_FILTER="${INPUT_JQ:-}" + + # JSON fields to fetch + JSON_FIELDS="number,title,state,author,createdAt,updatedAt,closedAt,body,labels,assignees,comments,milestone,url" + + # Build and execute gh command + if [[ -n "$REPO" ]]; then + OUTPUT=$(gh issue list --state "$STATE" --limit "$LIMIT" --json "$JSON_FIELDS" --repo "$REPO") + else + OUTPUT=$(gh issue list --state "$STATE" --limit "$LIMIT" --json "$JSON_FIELDS") + fi + + # Apply jq filter if specified + if [[ -n "$JQ_FILTER" ]]; then + jq "$JQ_FILTER" <<< "$OUTPUT" + else + # Return schema and size instead of full data + ITEM_COUNT=$(jq 'length' <<< "$OUTPUT") + DATA_SIZE=${#OUTPUT} + + # Validate values are numeric + if ! [[ "$ITEM_COUNT" =~ ^[0-9]+$ ]]; then + ITEM_COUNT=0 + fi + if ! [[ "$DATA_SIZE" =~ ^[0-9]+$ ]]; then + DATA_SIZE=0 + fi + + cat << EOF + { + "message": "No --jq filter provided. Use --jq to filter and retrieve data.", + "item_count": $ITEM_COUNT, + "data_size_bytes": $DATA_SIZE, + "schema": { + "type": "array", + "description": "Array of issue objects", + "item_fields": { + "number": "integer - Issue number", + "title": "string - Issue title", + "state": "string - Issue state (OPEN, CLOSED)", + "author": "object - Author info with login field", + "createdAt": "string - ISO timestamp of creation", + "updatedAt": "string - ISO timestamp of last update", + "closedAt": "string|null - ISO timestamp of close", + "body": "string - Issue body content", + "labels": "array - Array of label objects with name field", + "assignees": "array - Array of assignee objects with login field", + "comments": "object - Comments info with totalCount field", + "milestone": "object|null - Milestone info with title field", + "url": "string - Issue URL" + } + }, + "suggested_queries": [ + {"description": "Get all data", "query": "."}, + {"description": "Get issue numbers and titles", "query": ".[] | {number, title}"}, + {"description": "Get open issues only", "query": ".[] | select(.state == \"OPEN\")"}, + {"description": "Get issues by author", "query": ".[] | select(.author.login == \"USERNAME\")"}, + {"description": "Get issues with label", "query": ".[] | select(.labels | map(.name) | index(\"bug\"))"}, + {"description": "Get issues with many comments", "query": ".[] | select(.comments.totalCount > 5) | {number, title, comments: .comments.totalCount}"}, + {"description": "Count by state", "query": "group_by(.state) | map({state: .[0].state, count: length})"} + ] + } + EOF + fi + + github-pr-query: + description: "Query GitHub pull requests with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter." + inputs: + repo: + type: string + description: "Repository in owner/repo format (defaults to current repository)" + required: false + state: + type: string + description: "PR state: open, closed, merged, all (default: open)" + required: false + limit: + type: number + description: "Maximum number of PRs to fetch (default: 30)" + required: false + jq: + type: string + description: "jq filter expression to apply to output. If not provided, returns schema info instead of full data." + required: false + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -e + + # Default values + REPO="${INPUT_REPO:-}" + STATE="${INPUT_STATE:-open}" + LIMIT="${INPUT_LIMIT:-30}" + JQ_FILTER="${INPUT_JQ:-}" + + # JSON fields to fetch + JSON_FIELDS="number,title,state,author,createdAt,updatedAt,mergedAt,closedAt,headRefName,baseRefName,isDraft,reviewDecision,additions,deletions,changedFiles,labels,assignees,reviewRequests,url" + + # Build and execute gh command + if [[ -n "$REPO" ]]; then + OUTPUT=$(gh pr list --state "$STATE" --limit "$LIMIT" --json "$JSON_FIELDS" --repo "$REPO") + else + OUTPUT=$(gh pr list --state "$STATE" --limit "$LIMIT" --json "$JSON_FIELDS") + fi + + # Apply jq filter if specified + if [[ -n "$JQ_FILTER" ]]; then + jq "$JQ_FILTER" <<< "$OUTPUT" + else + # Return schema and size instead of full data + ITEM_COUNT=$(jq 'length' <<< "$OUTPUT") + DATA_SIZE=${#OUTPUT} + + # Validate values are numeric + if ! [[ "$ITEM_COUNT" =~ ^[0-9]+$ ]]; then + ITEM_COUNT=0 + fi + if ! [[ "$DATA_SIZE" =~ ^[0-9]+$ ]]; then + DATA_SIZE=0 + fi + + cat << EOF + { + "message": "No --jq filter provided. Use --jq to filter and retrieve data.", + "item_count": $ITEM_COUNT, + "data_size_bytes": $DATA_SIZE, + "schema": { + "type": "array", + "description": "Array of pull request objects", + "item_fields": { + "number": "integer - PR number", + "title": "string - PR title", + "state": "string - PR state (OPEN, CLOSED, MERGED)", + "author": "object - Author info with login field", + "createdAt": "string - ISO timestamp of creation", + "updatedAt": "string - ISO timestamp of last update", + "mergedAt": "string|null - ISO timestamp of merge", + "closedAt": "string|null - ISO timestamp of close", + "headRefName": "string - Source branch name", + "baseRefName": "string - Target branch name", + "isDraft": "boolean - Whether PR is a draft", + "reviewDecision": "string|null - Review decision (APPROVED, CHANGES_REQUESTED, REVIEW_REQUIRED)", + "additions": "integer - Lines added", + "deletions": "integer - Lines deleted", + "changedFiles": "integer - Number of files changed", + "labels": "array - Array of label objects with name field", + "assignees": "array - Array of assignee objects with login field", + "reviewRequests": "array - Array of review request objects", + "url": "string - PR URL" + } + }, + "suggested_queries": [ + {"description": "Get all data", "query": "."}, + {"description": "Get PR numbers and titles", "query": ".[] | {number, title}"}, + {"description": "Get open PRs only", "query": ".[] | select(.state == \"OPEN\")"}, + {"description": "Get merged PRs", "query": ".[] | select(.mergedAt != null)"}, + {"description": "Get PRs by author", "query": ".[] | select(.author.login == \"USERNAME\")"}, + {"description": "Get large PRs", "query": ".[] | select(.changedFiles > 10) | {number, title, changedFiles}"}, + {"description": "Count by state", "query": "group_by(.state) | map({state: .[0].state, count: length})"} + ] + } + EOF + fi + + github-discussion-query: + description: "Query GitHub discussions with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter." + inputs: + repo: + type: string + description: "Repository in owner/repo format (defaults to current repository)" + required: false + limit: + type: number + description: "Maximum number of discussions to fetch (default: 30)" + required: false + jq: + type: string + description: "jq filter expression to apply to output. If not provided, returns schema info instead of full data." + required: false + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -e + + # Default values + REPO="${INPUT_REPO:-}" + LIMIT="${INPUT_LIMIT:-30}" + JQ_FILTER="${INPUT_JQ:-}" + + # JSON fields to fetch + JSON_FIELDS="number,title,author,createdAt,updatedAt,body,category,labels,comments,answer,url" + + # Build and execute gh command + if [[ -n "$REPO" ]]; then + OUTPUT=$(gh discussion list --limit "$LIMIT" --json "$JSON_FIELDS" --repo "$REPO") + else + OUTPUT=$(gh discussion list --limit "$LIMIT" --json "$JSON_FIELDS") + fi + + # Apply jq filter if specified + if [[ -n "$JQ_FILTER" ]]; then + jq "$JQ_FILTER" <<< "$OUTPUT" + else + # Return schema and size instead of full data + ITEM_COUNT=$(jq 'length' <<< "$OUTPUT") + DATA_SIZE=${#OUTPUT} + + # Validate values are numeric + if ! [[ "$ITEM_COUNT" =~ ^[0-9]+$ ]]; then + ITEM_COUNT=0 + fi + if ! [[ "$DATA_SIZE" =~ ^[0-9]+$ ]]; then + DATA_SIZE=0 + fi + + cat << EOF + { + "message": "No --jq filter provided. Use --jq to filter and retrieve data.", + "item_count": $ITEM_COUNT, + "data_size_bytes": $DATA_SIZE, + "schema": { + "type": "array", + "description": "Array of discussion objects", + "item_fields": { + "number": "integer - Discussion number", + "title": "string - Discussion title", + "author": "object - Author info with login field", + "createdAt": "string - ISO timestamp of creation", + "updatedAt": "string - ISO timestamp of last update", + "body": "string - Discussion body content", + "category": "object - Category info with name field", + "labels": "array - Array of label objects with name field", + "comments": "object - Comments info with totalCount field", + "answer": "object|null - Accepted answer if exists", + "url": "string - Discussion URL" + } + }, + "suggested_queries": [ + {"description": "Get all data", "query": "."}, + {"description": "Get discussion numbers and titles", "query": ".[] | {number, title}"}, + {"description": "Get discussions by author", "query": ".[] | select(.author.login == \"USERNAME\")"}, + {"description": "Get discussions in category", "query": ".[] | select(.category.name == \"Ideas\")"}, + {"description": "Get answered discussions", "query": ".[] | select(.answer != null)"}, + {"description": "Get unanswered discussions", "query": ".[] | select(.answer == null) | {number, title, category: .category.name}"}, + {"description": "Count by category", "query": "group_by(.category.name) | map({category: .[0].category.name, count: length})"} + ] + } + EOF + fi safe-outputs: upload-asset: create-discussion: diff --git a/pkg/parser/mcp.go b/pkg/parser/mcp.go index ccf9384fdad..d6f218c8b27 100644 --- a/pkg/parser/mcp.go +++ b/pkg/parser/mcp.go @@ -191,6 +191,31 @@ func ExtractMCPConfigurations(frontmatter map[string]any, serverFilter string) ( } } + // Check for safe-inputs configuration (built-in MCP) + if safeInputsSection, hasSafeInputs := frontmatter["safe-inputs"]; hasSafeInputs { + mcpLog.Print("Found safe-inputs configuration") + // Apply server filter if specified + if serverFilter == "" || strings.Contains(constants.SafeInputsMCPServerID, strings.ToLower(serverFilter)) { + config := MCPServerConfig{ + BaseMCPServerConfig: types.BaseMCPServerConfig{ + Type: "http", + Command: "", + Env: make(map[string]string), + }, + Name: constants.SafeInputsMCPServerID, + } + + // Parse safe-inputs configuration to determine enabled tools + if safeInputsMap, ok := safeInputsSection.(map[string]any); ok { + for toolName := range safeInputsMap { + config.Allowed = append(config.Allowed, toolName) + } + } + + configs = append(configs, config) + } + } + // Get mcp-servers section from frontmatter mcpServersSection, hasMCPServers := frontmatter["mcp-servers"] if !hasMCPServers {