diff --git a/.github/workflows/artifacts-summary.lock.yml b/.github/workflows/artifacts-summary.lock.yml index 40f539533c2..0d052d95c3e 100644 --- a/.github/workflows/artifacts-summary.lock.yml +++ b/.github/workflows/artifacts-summary.lock.yml @@ -459,251 +459,35 @@ jobs: }; }; const normTool = toolName => (toolName ? toolName.replace(/-/g, "_").toLowerCase() : undefined); - const ALL_TOOLS = [ - { - name: "create_issue", - description: "Create a new GitHub issue", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Issue title" }, - body: { type: "string", description: "Issue body/description" }, - labels: { - type: "array", - items: { type: "string" }, - description: "Issue labels", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_discussion", - description: "Create a new GitHub discussion", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Discussion title" }, - body: { type: "string", description: "Discussion body/content" }, - category: { type: "string", description: "Discussion category" }, - }, - additionalProperties: false, - }, - }, - { - name: "add_comment", - description: "Add a comment to a GitHub issue or pull request", - inputSchema: { - type: "object", - required: ["body"], - properties: { - body: { type: "string", description: "Comment body/content" }, - issue_number: { - type: "number", - description: "Issue or PR number (optional for current context)", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_pull_request", - description: "Create a new GitHub pull request", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Pull request title" }, - body: { - type: "string", - description: "Pull request body/description", - }, - branch: { - type: "string", - description: "Optional branch name. If not provided, the current branch will be used.", - }, - labels: { - type: "array", - items: { type: "string" }, - description: "Optional labels to add to the PR", - }, - }, - additionalProperties: false, - }, - handler: createPullRequestHandler, - }, - { - name: "create_pull_request_review_comment", - description: "Create a review comment on a GitHub pull request", - inputSchema: { - type: "object", - required: ["path", "line", "body"], - properties: { - path: { - type: "string", - description: "File path for the review comment", - }, - line: { - type: ["number", "string"], - description: "Line number for the comment", - }, - body: { type: "string", description: "Comment body content" }, - start_line: { - type: ["number", "string"], - description: "Optional start line for multi-line comments", - }, - side: { - type: "string", - enum: ["LEFT", "RIGHT"], - description: "Optional side of the diff: LEFT or RIGHT", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_code_scanning_alert", - description: "Create a code scanning alert. severity MUST be one of 'error', 'warning', 'info', 'note'.", - inputSchema: { - type: "object", - required: ["file", "line", "severity", "message"], - properties: { - file: { - type: "string", - description: "File path where the issue was found", - }, - line: { - type: ["number", "string"], - description: "Line number where the issue was found", - }, - severity: { - type: "string", - enum: ["error", "warning", "info", "note"], - description: - ' Security severity levels follow the industry-standard Common Vulnerability Scoring System (CVSS) that is also used for advisories in the GitHub Advisory Database and must be one of "error", "warning", "info", "note".', - }, - message: { - type: "string", - description: "Alert message describing the issue", - }, - column: { - type: ["number", "string"], - description: "Optional column number", - }, - ruleIdSuffix: { - type: "string", - description: "Optional rule ID suffix for uniqueness", - }, - }, - additionalProperties: false, - }, - }, - { - name: "add_labels", - description: "Add labels to a GitHub issue or pull request", - inputSchema: { - type: "object", - required: ["labels"], - properties: { - labels: { - type: "array", - items: { type: "string" }, - description: "Labels to add", - }, - issue_number: { - type: "number", - description: "Issue or PR number (optional for current context)", - }, - }, - additionalProperties: false, - }, - }, - { - name: "update_issue", - description: "Update a GitHub issue", - inputSchema: { - type: "object", - properties: { - status: { - type: "string", - enum: ["open", "closed"], - description: "Optional new issue status", - }, - title: { type: "string", description: "Optional new issue title" }, - body: { type: "string", description: "Optional new issue body" }, - issue_number: { - type: ["number", "string"], - description: "Optional issue number for target '*'", - }, - }, - additionalProperties: false, - }, - }, - { - name: "push_to_pull_request_branch", - description: "Push changes to a pull request branch", - inputSchema: { - type: "object", - required: ["message"], - properties: { - branch: { - type: "string", - description: "Optional branch name. If not provided, the current branch will be used.", - }, - message: { type: "string", description: "Commit message" }, - pull_request_number: { - type: ["number", "string"], - description: "Optional pull request number for target '*'", - }, - }, - additionalProperties: false, - }, - handler: pushToPullRequestBranchHandler, - }, - { - name: "upload_asset", - description: "Publish a file as a URL-addressable asset to an orphaned git branch", - inputSchema: { - type: "object", - required: ["path"], - properties: { - path: { - type: "string", - description: - "Path to the file to publish as an asset. Must be a file under the current workspace or /tmp directory. By default, images (.png, .jpg, .jpeg) are allowed, but can be configured via workflow settings.", - }, - }, - additionalProperties: false, - }, - handler: uploadAssetHandler, - }, - { - name: "missing_tool", - description: "Report a missing tool or functionality needed to complete tasks", - inputSchema: { - type: "object", - required: ["tool", "reason"], - properties: { - tool: { type: "string", description: "Name of the missing tool" }, - reason: { type: "string", description: "Why this tool is needed" }, - alternatives: { - type: "string", - description: "Possible alternatives or workarounds", - }, - }, - additionalProperties: false, - }, - }, - ]; + const toolsEnv = process.env.GITHUB_AW_SAFE_OUTPUTS_TOOLS; + if (!toolsEnv) { + throw new Error("GITHUB_AW_SAFE_OUTPUTS_TOOLS environment variable is required"); + } + let PREDEFINED_TOOLS = {}; + debug(`Loading tools from GITHUB_AW_SAFE_OUTPUTS_TOOLS environment variable`); + try { + PREDEFINED_TOOLS = JSON.parse(toolsEnv); + debug(`Loaded ${Object.keys(PREDEFINED_TOOLS).length} predefined tools from environment`); + } catch (error) { + debug(`Error parsing GITHUB_AW_SAFE_OUTPUTS_TOOLS: ${error instanceof Error ? error.message : String(error)}`); + throw new Error(`Failed to parse GITHUB_AW_SAFE_OUTPUTS_TOOLS: ${error instanceof Error ? error.message : String(error)}`); + } debug(`v${SERVER_INFO.version} ready on stdio`); debug(` output file: ${outputFile}`); debug(` config: ${JSON.stringify(safeOutputsConfig)}`); const TOOLS = {}; - ALL_TOOLS.forEach(tool => { - if (Object.keys(safeOutputsConfig).find(config => normTool(config) === tool.name)) { - TOOLS[tool.name] = tool; + debug(`Using pre-filtered tools from environment variable`); + Object.keys(PREDEFINED_TOOLS).forEach(toolName => { + const tool = PREDEFINED_TOOLS[toolName]; + TOOLS[toolName] = tool; + if (tool.hasHandler) { + if (toolName === "create_pull_request") { + TOOLS[toolName].handler = createPullRequestHandler; + } else if (toolName === "push_to_pull_request_branch") { + TOOLS[toolName].handler = pushToPullRequestBranchHandler; + } else if (toolName === "upload_asset") { + TOOLS[toolName].handler = uploadAssetHandler; + } } }); Object.keys(safeOutputsConfig).forEach(configKey => { @@ -711,7 +495,8 @@ jobs: if (TOOLS[normalizedKey]) { return; } - if (!ALL_TOOLS.find(t => t.name === normalizedKey)) { + const isKnownTool = PREDEFINED_TOOLS[normalizedKey]; + if (!isKnownTool) { const jobConfig = safeOutputsConfig[configKey]; const dynamicTool = { name: normalizedKey, @@ -765,6 +550,7 @@ jobs: }); debug(` tools: ${Object.keys(TOOLS).join(", ")}`); if (!Object.keys(TOOLS).length) throw new Error("No tools enabled in configuration"); + console.error(`[${SERVER_INFO.name}] Filtered tools:\n${JSON.stringify(TOOLS, null, 2)}`); function handleMessage(req) { if (!req || typeof req !== "object") { debug(`Invalid message: not an object`); @@ -875,6 +661,7 @@ jobs: env: GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }} GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"create-discussion\":{\"max\":1},\"missing-tool\":{}}" + GITHUB_AW_SAFE_OUTPUTS_TOOLS: "{\"create_discussion\":{\"name\":\"create_discussion\",\"description\":\"Create a new GitHub discussion\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"body\":{\"description\":\"Discussion body/content\",\"type\":\"string\"},\"category\":{\"description\":\"Discussion category\",\"type\":\"string\"},\"title\":{\"description\":\"Discussion title\",\"type\":\"string\"}},\"required\":[\"title\",\"body\"],\"type\":\"object\"}},\"missing_tool\":{\"name\":\"missing_tool\",\"description\":\"Report a missing tool or functionality needed to complete tasks\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"alternatives\":{\"description\":\"Possible alternatives or workarounds\",\"type\":\"string\"},\"reason\":{\"description\":\"Why this tool is needed\",\"type\":\"string\"},\"tool\":{\"description\":\"Name of the missing tool\",\"type\":\"string\"}},\"required\":[\"tool\",\"reason\"],\"type\":\"object\"}}}" run: | mkdir -p /tmp/gh-aw/mcp-config mkdir -p /home/runner/.copilot diff --git a/.github/workflows/audit-workflows.lock.yml b/.github/workflows/audit-workflows.lock.yml index 4833ebdee42..d26979bab35 100644 --- a/.github/workflows/audit-workflows.lock.yml +++ b/.github/workflows/audit-workflows.lock.yml @@ -603,251 +603,35 @@ jobs: }; }; const normTool = toolName => (toolName ? toolName.replace(/-/g, "_").toLowerCase() : undefined); - const ALL_TOOLS = [ - { - name: "create_issue", - description: "Create a new GitHub issue", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Issue title" }, - body: { type: "string", description: "Issue body/description" }, - labels: { - type: "array", - items: { type: "string" }, - description: "Issue labels", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_discussion", - description: "Create a new GitHub discussion", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Discussion title" }, - body: { type: "string", description: "Discussion body/content" }, - category: { type: "string", description: "Discussion category" }, - }, - additionalProperties: false, - }, - }, - { - name: "add_comment", - description: "Add a comment to a GitHub issue or pull request", - inputSchema: { - type: "object", - required: ["body"], - properties: { - body: { type: "string", description: "Comment body/content" }, - issue_number: { - type: "number", - description: "Issue or PR number (optional for current context)", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_pull_request", - description: "Create a new GitHub pull request", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Pull request title" }, - body: { - type: "string", - description: "Pull request body/description", - }, - branch: { - type: "string", - description: "Optional branch name. If not provided, the current branch will be used.", - }, - labels: { - type: "array", - items: { type: "string" }, - description: "Optional labels to add to the PR", - }, - }, - additionalProperties: false, - }, - handler: createPullRequestHandler, - }, - { - name: "create_pull_request_review_comment", - description: "Create a review comment on a GitHub pull request", - inputSchema: { - type: "object", - required: ["path", "line", "body"], - properties: { - path: { - type: "string", - description: "File path for the review comment", - }, - line: { - type: ["number", "string"], - description: "Line number for the comment", - }, - body: { type: "string", description: "Comment body content" }, - start_line: { - type: ["number", "string"], - description: "Optional start line for multi-line comments", - }, - side: { - type: "string", - enum: ["LEFT", "RIGHT"], - description: "Optional side of the diff: LEFT or RIGHT", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_code_scanning_alert", - description: "Create a code scanning alert. severity MUST be one of 'error', 'warning', 'info', 'note'.", - inputSchema: { - type: "object", - required: ["file", "line", "severity", "message"], - properties: { - file: { - type: "string", - description: "File path where the issue was found", - }, - line: { - type: ["number", "string"], - description: "Line number where the issue was found", - }, - severity: { - type: "string", - enum: ["error", "warning", "info", "note"], - description: - ' Security severity levels follow the industry-standard Common Vulnerability Scoring System (CVSS) that is also used for advisories in the GitHub Advisory Database and must be one of "error", "warning", "info", "note".', - }, - message: { - type: "string", - description: "Alert message describing the issue", - }, - column: { - type: ["number", "string"], - description: "Optional column number", - }, - ruleIdSuffix: { - type: "string", - description: "Optional rule ID suffix for uniqueness", - }, - }, - additionalProperties: false, - }, - }, - { - name: "add_labels", - description: "Add labels to a GitHub issue or pull request", - inputSchema: { - type: "object", - required: ["labels"], - properties: { - labels: { - type: "array", - items: { type: "string" }, - description: "Labels to add", - }, - issue_number: { - type: "number", - description: "Issue or PR number (optional for current context)", - }, - }, - additionalProperties: false, - }, - }, - { - name: "update_issue", - description: "Update a GitHub issue", - inputSchema: { - type: "object", - properties: { - status: { - type: "string", - enum: ["open", "closed"], - description: "Optional new issue status", - }, - title: { type: "string", description: "Optional new issue title" }, - body: { type: "string", description: "Optional new issue body" }, - issue_number: { - type: ["number", "string"], - description: "Optional issue number for target '*'", - }, - }, - additionalProperties: false, - }, - }, - { - name: "push_to_pull_request_branch", - description: "Push changes to a pull request branch", - inputSchema: { - type: "object", - required: ["message"], - properties: { - branch: { - type: "string", - description: "Optional branch name. If not provided, the current branch will be used.", - }, - message: { type: "string", description: "Commit message" }, - pull_request_number: { - type: ["number", "string"], - description: "Optional pull request number for target '*'", - }, - }, - additionalProperties: false, - }, - handler: pushToPullRequestBranchHandler, - }, - { - name: "upload_asset", - description: "Publish a file as a URL-addressable asset to an orphaned git branch", - inputSchema: { - type: "object", - required: ["path"], - properties: { - path: { - type: "string", - description: - "Path to the file to publish as an asset. Must be a file under the current workspace or /tmp directory. By default, images (.png, .jpg, .jpeg) are allowed, but can be configured via workflow settings.", - }, - }, - additionalProperties: false, - }, - handler: uploadAssetHandler, - }, - { - name: "missing_tool", - description: "Report a missing tool or functionality needed to complete tasks", - inputSchema: { - type: "object", - required: ["tool", "reason"], - properties: { - tool: { type: "string", description: "Name of the missing tool" }, - reason: { type: "string", description: "Why this tool is needed" }, - alternatives: { - type: "string", - description: "Possible alternatives or workarounds", - }, - }, - additionalProperties: false, - }, - }, - ]; + const toolsEnv = process.env.GITHUB_AW_SAFE_OUTPUTS_TOOLS; + if (!toolsEnv) { + throw new Error("GITHUB_AW_SAFE_OUTPUTS_TOOLS environment variable is required"); + } + let PREDEFINED_TOOLS = {}; + debug(`Loading tools from GITHUB_AW_SAFE_OUTPUTS_TOOLS environment variable`); + try { + PREDEFINED_TOOLS = JSON.parse(toolsEnv); + debug(`Loaded ${Object.keys(PREDEFINED_TOOLS).length} predefined tools from environment`); + } catch (error) { + debug(`Error parsing GITHUB_AW_SAFE_OUTPUTS_TOOLS: ${error instanceof Error ? error.message : String(error)}`); + throw new Error(`Failed to parse GITHUB_AW_SAFE_OUTPUTS_TOOLS: ${error instanceof Error ? error.message : String(error)}`); + } debug(`v${SERVER_INFO.version} ready on stdio`); debug(` output file: ${outputFile}`); debug(` config: ${JSON.stringify(safeOutputsConfig)}`); const TOOLS = {}; - ALL_TOOLS.forEach(tool => { - if (Object.keys(safeOutputsConfig).find(config => normTool(config) === tool.name)) { - TOOLS[tool.name] = tool; + debug(`Using pre-filtered tools from environment variable`); + Object.keys(PREDEFINED_TOOLS).forEach(toolName => { + const tool = PREDEFINED_TOOLS[toolName]; + TOOLS[toolName] = tool; + if (tool.hasHandler) { + if (toolName === "create_pull_request") { + TOOLS[toolName].handler = createPullRequestHandler; + } else if (toolName === "push_to_pull_request_branch") { + TOOLS[toolName].handler = pushToPullRequestBranchHandler; + } else if (toolName === "upload_asset") { + TOOLS[toolName].handler = uploadAssetHandler; + } } }); Object.keys(safeOutputsConfig).forEach(configKey => { @@ -855,7 +639,8 @@ jobs: if (TOOLS[normalizedKey]) { return; } - if (!ALL_TOOLS.find(t => t.name === normalizedKey)) { + const isKnownTool = PREDEFINED_TOOLS[normalizedKey]; + if (!isKnownTool) { const jobConfig = safeOutputsConfig[configKey]; const dynamicTool = { name: normalizedKey, @@ -909,6 +694,7 @@ jobs: }); debug(` tools: ${Object.keys(TOOLS).join(", ")}`); if (!Object.keys(TOOLS).length) throw new Error("No tools enabled in configuration"); + console.error(`[${SERVER_INFO.name}] Filtered tools:\n${JSON.stringify(TOOLS, null, 2)}`); function handleMessage(req) { if (!req || typeof req !== "object") { debug(`Invalid message: not an object`); @@ -1019,6 +805,7 @@ jobs: env: GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }} GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"create-discussion\":{\"max\":1},\"missing-tool\":{}}" + GITHUB_AW_SAFE_OUTPUTS_TOOLS: "{\"create_discussion\":{\"name\":\"create_discussion\",\"description\":\"Create a new GitHub discussion\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"body\":{\"description\":\"Discussion body/content\",\"type\":\"string\"},\"category\":{\"description\":\"Discussion category\",\"type\":\"string\"},\"title\":{\"description\":\"Discussion title\",\"type\":\"string\"}},\"required\":[\"title\",\"body\"],\"type\":\"object\"}},\"missing_tool\":{\"name\":\"missing_tool\",\"description\":\"Report a missing tool or functionality needed to complete tasks\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"alternatives\":{\"description\":\"Possible alternatives or workarounds\",\"type\":\"string\"},\"reason\":{\"description\":\"Why this tool is needed\",\"type\":\"string\"},\"tool\":{\"description\":\"Name of the missing tool\",\"type\":\"string\"}},\"required\":[\"tool\",\"reason\"],\"type\":\"object\"}}}" run: | mkdir -p /tmp/gh-aw/mcp-config cat > /tmp/gh-aw/mcp-config/mcp-servers.json << 'EOF' @@ -1048,6 +835,7 @@ jobs: "env": { "GITHUB_AW_SAFE_OUTPUTS": "${{ env.GITHUB_AW_SAFE_OUTPUTS }}", "GITHUB_AW_SAFE_OUTPUTS_CONFIG": ${{ toJSON(env.GITHUB_AW_SAFE_OUTPUTS_CONFIG) }}, + "GITHUB_AW_SAFE_OUTPUTS_TOOLS": ${{ toJSON(env.GITHUB_AW_SAFE_OUTPUTS_TOOLS) }}, "GITHUB_AW_ASSETS_BRANCH": "${{ env.GITHUB_AW_ASSETS_BRANCH }}", "GITHUB_AW_ASSETS_MAX_SIZE_KB": "${{ env.GITHUB_AW_ASSETS_MAX_SIZE_KB }}", "GITHUB_AW_ASSETS_ALLOWED_EXTS": "${{ env.GITHUB_AW_ASSETS_ALLOWED_EXTS }}" diff --git a/.github/workflows/brave.lock.yml b/.github/workflows/brave.lock.yml index e5f0a5e0b14..34e3a0dc087 100644 --- a/.github/workflows/brave.lock.yml +++ b/.github/workflows/brave.lock.yml @@ -924,251 +924,35 @@ jobs: }; }; const normTool = toolName => (toolName ? toolName.replace(/-/g, "_").toLowerCase() : undefined); - const ALL_TOOLS = [ - { - name: "create_issue", - description: "Create a new GitHub issue", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Issue title" }, - body: { type: "string", description: "Issue body/description" }, - labels: { - type: "array", - items: { type: "string" }, - description: "Issue labels", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_discussion", - description: "Create a new GitHub discussion", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Discussion title" }, - body: { type: "string", description: "Discussion body/content" }, - category: { type: "string", description: "Discussion category" }, - }, - additionalProperties: false, - }, - }, - { - name: "add_comment", - description: "Add a comment to a GitHub issue or pull request", - inputSchema: { - type: "object", - required: ["body"], - properties: { - body: { type: "string", description: "Comment body/content" }, - issue_number: { - type: "number", - description: "Issue or PR number (optional for current context)", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_pull_request", - description: "Create a new GitHub pull request", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Pull request title" }, - body: { - type: "string", - description: "Pull request body/description", - }, - branch: { - type: "string", - description: "Optional branch name. If not provided, the current branch will be used.", - }, - labels: { - type: "array", - items: { type: "string" }, - description: "Optional labels to add to the PR", - }, - }, - additionalProperties: false, - }, - handler: createPullRequestHandler, - }, - { - name: "create_pull_request_review_comment", - description: "Create a review comment on a GitHub pull request", - inputSchema: { - type: "object", - required: ["path", "line", "body"], - properties: { - path: { - type: "string", - description: "File path for the review comment", - }, - line: { - type: ["number", "string"], - description: "Line number for the comment", - }, - body: { type: "string", description: "Comment body content" }, - start_line: { - type: ["number", "string"], - description: "Optional start line for multi-line comments", - }, - side: { - type: "string", - enum: ["LEFT", "RIGHT"], - description: "Optional side of the diff: LEFT or RIGHT", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_code_scanning_alert", - description: "Create a code scanning alert. severity MUST be one of 'error', 'warning', 'info', 'note'.", - inputSchema: { - type: "object", - required: ["file", "line", "severity", "message"], - properties: { - file: { - type: "string", - description: "File path where the issue was found", - }, - line: { - type: ["number", "string"], - description: "Line number where the issue was found", - }, - severity: { - type: "string", - enum: ["error", "warning", "info", "note"], - description: - ' Security severity levels follow the industry-standard Common Vulnerability Scoring System (CVSS) that is also used for advisories in the GitHub Advisory Database and must be one of "error", "warning", "info", "note".', - }, - message: { - type: "string", - description: "Alert message describing the issue", - }, - column: { - type: ["number", "string"], - description: "Optional column number", - }, - ruleIdSuffix: { - type: "string", - description: "Optional rule ID suffix for uniqueness", - }, - }, - additionalProperties: false, - }, - }, - { - name: "add_labels", - description: "Add labels to a GitHub issue or pull request", - inputSchema: { - type: "object", - required: ["labels"], - properties: { - labels: { - type: "array", - items: { type: "string" }, - description: "Labels to add", - }, - issue_number: { - type: "number", - description: "Issue or PR number (optional for current context)", - }, - }, - additionalProperties: false, - }, - }, - { - name: "update_issue", - description: "Update a GitHub issue", - inputSchema: { - type: "object", - properties: { - status: { - type: "string", - enum: ["open", "closed"], - description: "Optional new issue status", - }, - title: { type: "string", description: "Optional new issue title" }, - body: { type: "string", description: "Optional new issue body" }, - issue_number: { - type: ["number", "string"], - description: "Optional issue number for target '*'", - }, - }, - additionalProperties: false, - }, - }, - { - name: "push_to_pull_request_branch", - description: "Push changes to a pull request branch", - inputSchema: { - type: "object", - required: ["message"], - properties: { - branch: { - type: "string", - description: "Optional branch name. If not provided, the current branch will be used.", - }, - message: { type: "string", description: "Commit message" }, - pull_request_number: { - type: ["number", "string"], - description: "Optional pull request number for target '*'", - }, - }, - additionalProperties: false, - }, - handler: pushToPullRequestBranchHandler, - }, - { - name: "upload_asset", - description: "Publish a file as a URL-addressable asset to an orphaned git branch", - inputSchema: { - type: "object", - required: ["path"], - properties: { - path: { - type: "string", - description: - "Path to the file to publish as an asset. Must be a file under the current workspace or /tmp directory. By default, images (.png, .jpg, .jpeg) are allowed, but can be configured via workflow settings.", - }, - }, - additionalProperties: false, - }, - handler: uploadAssetHandler, - }, - { - name: "missing_tool", - description: "Report a missing tool or functionality needed to complete tasks", - inputSchema: { - type: "object", - required: ["tool", "reason"], - properties: { - tool: { type: "string", description: "Name of the missing tool" }, - reason: { type: "string", description: "Why this tool is needed" }, - alternatives: { - type: "string", - description: "Possible alternatives or workarounds", - }, - }, - additionalProperties: false, - }, - }, - ]; + const toolsEnv = process.env.GITHUB_AW_SAFE_OUTPUTS_TOOLS; + if (!toolsEnv) { + throw new Error("GITHUB_AW_SAFE_OUTPUTS_TOOLS environment variable is required"); + } + let PREDEFINED_TOOLS = {}; + debug(`Loading tools from GITHUB_AW_SAFE_OUTPUTS_TOOLS environment variable`); + try { + PREDEFINED_TOOLS = JSON.parse(toolsEnv); + debug(`Loaded ${Object.keys(PREDEFINED_TOOLS).length} predefined tools from environment`); + } catch (error) { + debug(`Error parsing GITHUB_AW_SAFE_OUTPUTS_TOOLS: ${error instanceof Error ? error.message : String(error)}`); + throw new Error(`Failed to parse GITHUB_AW_SAFE_OUTPUTS_TOOLS: ${error instanceof Error ? error.message : String(error)}`); + } debug(`v${SERVER_INFO.version} ready on stdio`); debug(` output file: ${outputFile}`); debug(` config: ${JSON.stringify(safeOutputsConfig)}`); const TOOLS = {}; - ALL_TOOLS.forEach(tool => { - if (Object.keys(safeOutputsConfig).find(config => normTool(config) === tool.name)) { - TOOLS[tool.name] = tool; + debug(`Using pre-filtered tools from environment variable`); + Object.keys(PREDEFINED_TOOLS).forEach(toolName => { + const tool = PREDEFINED_TOOLS[toolName]; + TOOLS[toolName] = tool; + if (tool.hasHandler) { + if (toolName === "create_pull_request") { + TOOLS[toolName].handler = createPullRequestHandler; + } else if (toolName === "push_to_pull_request_branch") { + TOOLS[toolName].handler = pushToPullRequestBranchHandler; + } else if (toolName === "upload_asset") { + TOOLS[toolName].handler = uploadAssetHandler; + } } }); Object.keys(safeOutputsConfig).forEach(configKey => { @@ -1176,7 +960,8 @@ jobs: if (TOOLS[normalizedKey]) { return; } - if (!ALL_TOOLS.find(t => t.name === normalizedKey)) { + const isKnownTool = PREDEFINED_TOOLS[normalizedKey]; + if (!isKnownTool) { const jobConfig = safeOutputsConfig[configKey]; const dynamicTool = { name: normalizedKey, @@ -1230,6 +1015,7 @@ jobs: }); debug(` tools: ${Object.keys(TOOLS).join(", ")}`); if (!Object.keys(TOOLS).length) throw new Error("No tools enabled in configuration"); + console.error(`[${SERVER_INFO.name}] Filtered tools:\n${JSON.stringify(TOOLS, null, 2)}`); function handleMessage(req) { if (!req || typeof req !== "object") { debug(`Invalid message: not an object`); @@ -1340,6 +1126,7 @@ jobs: env: GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }} GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-comment\":{\"max\":1},\"missing-tool\":{}}" + GITHUB_AW_SAFE_OUTPUTS_TOOLS: "{\"add_comment\":{\"name\":\"add_comment\",\"description\":\"Add a comment to a GitHub issue or pull request\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"body\":{\"description\":\"Comment body/content\",\"type\":\"string\"},\"issue_number\":{\"description\":\"Issue or PR number (optional for current context)\",\"type\":\"number\"}},\"required\":[\"body\"],\"type\":\"object\"}},\"missing_tool\":{\"name\":\"missing_tool\",\"description\":\"Report a missing tool or functionality needed to complete tasks\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"alternatives\":{\"description\":\"Possible alternatives or workarounds\",\"type\":\"string\"},\"reason\":{\"description\":\"Why this tool is needed\",\"type\":\"string\"},\"tool\":{\"description\":\"Name of the missing tool\",\"type\":\"string\"}},\"required\":[\"tool\",\"reason\"],\"type\":\"object\"}}}" run: | mkdir -p /tmp/gh-aw/mcp-config mkdir -p /home/runner/.copilot diff --git a/.github/workflows/changeset-generator.lock.yml b/.github/workflows/changeset-generator.lock.yml index a84b00cb558..d31c4725c5c 100644 --- a/.github/workflows/changeset-generator.lock.yml +++ b/.github/workflows/changeset-generator.lock.yml @@ -894,251 +894,35 @@ jobs: }; }; const normTool = toolName => (toolName ? toolName.replace(/-/g, "_").toLowerCase() : undefined); - const ALL_TOOLS = [ - { - name: "create_issue", - description: "Create a new GitHub issue", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Issue title" }, - body: { type: "string", description: "Issue body/description" }, - labels: { - type: "array", - items: { type: "string" }, - description: "Issue labels", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_discussion", - description: "Create a new GitHub discussion", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Discussion title" }, - body: { type: "string", description: "Discussion body/content" }, - category: { type: "string", description: "Discussion category" }, - }, - additionalProperties: false, - }, - }, - { - name: "add_comment", - description: "Add a comment to a GitHub issue or pull request", - inputSchema: { - type: "object", - required: ["body"], - properties: { - body: { type: "string", description: "Comment body/content" }, - issue_number: { - type: "number", - description: "Issue or PR number (optional for current context)", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_pull_request", - description: "Create a new GitHub pull request", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Pull request title" }, - body: { - type: "string", - description: "Pull request body/description", - }, - branch: { - type: "string", - description: "Optional branch name. If not provided, the current branch will be used.", - }, - labels: { - type: "array", - items: { type: "string" }, - description: "Optional labels to add to the PR", - }, - }, - additionalProperties: false, - }, - handler: createPullRequestHandler, - }, - { - name: "create_pull_request_review_comment", - description: "Create a review comment on a GitHub pull request", - inputSchema: { - type: "object", - required: ["path", "line", "body"], - properties: { - path: { - type: "string", - description: "File path for the review comment", - }, - line: { - type: ["number", "string"], - description: "Line number for the comment", - }, - body: { type: "string", description: "Comment body content" }, - start_line: { - type: ["number", "string"], - description: "Optional start line for multi-line comments", - }, - side: { - type: "string", - enum: ["LEFT", "RIGHT"], - description: "Optional side of the diff: LEFT or RIGHT", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_code_scanning_alert", - description: "Create a code scanning alert. severity MUST be one of 'error', 'warning', 'info', 'note'.", - inputSchema: { - type: "object", - required: ["file", "line", "severity", "message"], - properties: { - file: { - type: "string", - description: "File path where the issue was found", - }, - line: { - type: ["number", "string"], - description: "Line number where the issue was found", - }, - severity: { - type: "string", - enum: ["error", "warning", "info", "note"], - description: - ' Security severity levels follow the industry-standard Common Vulnerability Scoring System (CVSS) that is also used for advisories in the GitHub Advisory Database and must be one of "error", "warning", "info", "note".', - }, - message: { - type: "string", - description: "Alert message describing the issue", - }, - column: { - type: ["number", "string"], - description: "Optional column number", - }, - ruleIdSuffix: { - type: "string", - description: "Optional rule ID suffix for uniqueness", - }, - }, - additionalProperties: false, - }, - }, - { - name: "add_labels", - description: "Add labels to a GitHub issue or pull request", - inputSchema: { - type: "object", - required: ["labels"], - properties: { - labels: { - type: "array", - items: { type: "string" }, - description: "Labels to add", - }, - issue_number: { - type: "number", - description: "Issue or PR number (optional for current context)", - }, - }, - additionalProperties: false, - }, - }, - { - name: "update_issue", - description: "Update a GitHub issue", - inputSchema: { - type: "object", - properties: { - status: { - type: "string", - enum: ["open", "closed"], - description: "Optional new issue status", - }, - title: { type: "string", description: "Optional new issue title" }, - body: { type: "string", description: "Optional new issue body" }, - issue_number: { - type: ["number", "string"], - description: "Optional issue number for target '*'", - }, - }, - additionalProperties: false, - }, - }, - { - name: "push_to_pull_request_branch", - description: "Push changes to a pull request branch", - inputSchema: { - type: "object", - required: ["message"], - properties: { - branch: { - type: "string", - description: "Optional branch name. If not provided, the current branch will be used.", - }, - message: { type: "string", description: "Commit message" }, - pull_request_number: { - type: ["number", "string"], - description: "Optional pull request number for target '*'", - }, - }, - additionalProperties: false, - }, - handler: pushToPullRequestBranchHandler, - }, - { - name: "upload_asset", - description: "Publish a file as a URL-addressable asset to an orphaned git branch", - inputSchema: { - type: "object", - required: ["path"], - properties: { - path: { - type: "string", - description: - "Path to the file to publish as an asset. Must be a file under the current workspace or /tmp directory. By default, images (.png, .jpg, .jpeg) are allowed, but can be configured via workflow settings.", - }, - }, - additionalProperties: false, - }, - handler: uploadAssetHandler, - }, - { - name: "missing_tool", - description: "Report a missing tool or functionality needed to complete tasks", - inputSchema: { - type: "object", - required: ["tool", "reason"], - properties: { - tool: { type: "string", description: "Name of the missing tool" }, - reason: { type: "string", description: "Why this tool is needed" }, - alternatives: { - type: "string", - description: "Possible alternatives or workarounds", - }, - }, - additionalProperties: false, - }, - }, - ]; + const toolsEnv = process.env.GITHUB_AW_SAFE_OUTPUTS_TOOLS; + if (!toolsEnv) { + throw new Error("GITHUB_AW_SAFE_OUTPUTS_TOOLS environment variable is required"); + } + let PREDEFINED_TOOLS = {}; + debug(`Loading tools from GITHUB_AW_SAFE_OUTPUTS_TOOLS environment variable`); + try { + PREDEFINED_TOOLS = JSON.parse(toolsEnv); + debug(`Loaded ${Object.keys(PREDEFINED_TOOLS).length} predefined tools from environment`); + } catch (error) { + debug(`Error parsing GITHUB_AW_SAFE_OUTPUTS_TOOLS: ${error instanceof Error ? error.message : String(error)}`); + throw new Error(`Failed to parse GITHUB_AW_SAFE_OUTPUTS_TOOLS: ${error instanceof Error ? error.message : String(error)}`); + } debug(`v${SERVER_INFO.version} ready on stdio`); debug(` output file: ${outputFile}`); debug(` config: ${JSON.stringify(safeOutputsConfig)}`); const TOOLS = {}; - ALL_TOOLS.forEach(tool => { - if (Object.keys(safeOutputsConfig).find(config => normTool(config) === tool.name)) { - TOOLS[tool.name] = tool; + debug(`Using pre-filtered tools from environment variable`); + Object.keys(PREDEFINED_TOOLS).forEach(toolName => { + const tool = PREDEFINED_TOOLS[toolName]; + TOOLS[toolName] = tool; + if (tool.hasHandler) { + if (toolName === "create_pull_request") { + TOOLS[toolName].handler = createPullRequestHandler; + } else if (toolName === "push_to_pull_request_branch") { + TOOLS[toolName].handler = pushToPullRequestBranchHandler; + } else if (toolName === "upload_asset") { + TOOLS[toolName].handler = uploadAssetHandler; + } } }); Object.keys(safeOutputsConfig).forEach(configKey => { @@ -1146,7 +930,8 @@ jobs: if (TOOLS[normalizedKey]) { return; } - if (!ALL_TOOLS.find(t => t.name === normalizedKey)) { + const isKnownTool = PREDEFINED_TOOLS[normalizedKey]; + if (!isKnownTool) { const jobConfig = safeOutputsConfig[configKey]; const dynamicTool = { name: normalizedKey, @@ -1200,6 +985,7 @@ jobs: }); debug(` tools: ${Object.keys(TOOLS).join(", ")}`); if (!Object.keys(TOOLS).length) throw new Error("No tools enabled in configuration"); + console.error(`[${SERVER_INFO.name}] Filtered tools:\n${JSON.stringify(TOOLS, null, 2)}`); function handleMessage(req) { if (!req || typeof req !== "object") { debug(`Invalid message: not an object`); @@ -1310,6 +1096,7 @@ jobs: env: GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }} GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"missing-tool\":{},\"push-to-pull-request-branch\":{}}" + GITHUB_AW_SAFE_OUTPUTS_TOOLS: "{\"missing_tool\":{\"name\":\"missing_tool\",\"description\":\"Report a missing tool or functionality needed to complete tasks\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"alternatives\":{\"description\":\"Possible alternatives or workarounds\",\"type\":\"string\"},\"reason\":{\"description\":\"Why this tool is needed\",\"type\":\"string\"},\"tool\":{\"description\":\"Name of the missing tool\",\"type\":\"string\"}},\"required\":[\"tool\",\"reason\"],\"type\":\"object\"}},\"push_to_pull_request_branch\":{\"name\":\"push_to_pull_request_branch\",\"description\":\"Push changes to a pull request branch\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"branch\":{\"description\":\"Optional branch name. If not provided, the current branch will be used.\",\"type\":\"string\"},\"message\":{\"description\":\"Commit message\",\"type\":\"string\"},\"pull_request_number\":{\"description\":\"Optional pull request number for target '*'\",\"type\":[\"number\",\"string\"]}},\"required\":[\"message\"],\"type\":\"object\"},\"hasHandler\":true}}" run: | mkdir -p /tmp/gh-aw/mcp-config cat > /tmp/gh-aw/mcp-config/mcp-servers.json << 'EOF' @@ -1335,6 +1122,7 @@ jobs: "env": { "GITHUB_AW_SAFE_OUTPUTS": "${{ env.GITHUB_AW_SAFE_OUTPUTS }}", "GITHUB_AW_SAFE_OUTPUTS_CONFIG": ${{ toJSON(env.GITHUB_AW_SAFE_OUTPUTS_CONFIG) }}, + "GITHUB_AW_SAFE_OUTPUTS_TOOLS": ${{ toJSON(env.GITHUB_AW_SAFE_OUTPUTS_TOOLS) }}, "GITHUB_AW_ASSETS_BRANCH": "${{ env.GITHUB_AW_ASSETS_BRANCH }}", "GITHUB_AW_ASSETS_MAX_SIZE_KB": "${{ env.GITHUB_AW_ASSETS_MAX_SIZE_KB }}", "GITHUB_AW_ASSETS_ALLOWED_EXTS": "${{ env.GITHUB_AW_ASSETS_ALLOWED_EXTS }}" diff --git a/.github/workflows/ci-doctor.lock.yml b/.github/workflows/ci-doctor.lock.yml index a184a5fb815..407462f3224 100644 --- a/.github/workflows/ci-doctor.lock.yml +++ b/.github/workflows/ci-doctor.lock.yml @@ -430,251 +430,35 @@ jobs: }; }; const normTool = toolName => (toolName ? toolName.replace(/-/g, "_").toLowerCase() : undefined); - const ALL_TOOLS = [ - { - name: "create_issue", - description: "Create a new GitHub issue", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Issue title" }, - body: { type: "string", description: "Issue body/description" }, - labels: { - type: "array", - items: { type: "string" }, - description: "Issue labels", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_discussion", - description: "Create a new GitHub discussion", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Discussion title" }, - body: { type: "string", description: "Discussion body/content" }, - category: { type: "string", description: "Discussion category" }, - }, - additionalProperties: false, - }, - }, - { - name: "add_comment", - description: "Add a comment to a GitHub issue or pull request", - inputSchema: { - type: "object", - required: ["body"], - properties: { - body: { type: "string", description: "Comment body/content" }, - issue_number: { - type: "number", - description: "Issue or PR number (optional for current context)", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_pull_request", - description: "Create a new GitHub pull request", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Pull request title" }, - body: { - type: "string", - description: "Pull request body/description", - }, - branch: { - type: "string", - description: "Optional branch name. If not provided, the current branch will be used.", - }, - labels: { - type: "array", - items: { type: "string" }, - description: "Optional labels to add to the PR", - }, - }, - additionalProperties: false, - }, - handler: createPullRequestHandler, - }, - { - name: "create_pull_request_review_comment", - description: "Create a review comment on a GitHub pull request", - inputSchema: { - type: "object", - required: ["path", "line", "body"], - properties: { - path: { - type: "string", - description: "File path for the review comment", - }, - line: { - type: ["number", "string"], - description: "Line number for the comment", - }, - body: { type: "string", description: "Comment body content" }, - start_line: { - type: ["number", "string"], - description: "Optional start line for multi-line comments", - }, - side: { - type: "string", - enum: ["LEFT", "RIGHT"], - description: "Optional side of the diff: LEFT or RIGHT", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_code_scanning_alert", - description: "Create a code scanning alert. severity MUST be one of 'error', 'warning', 'info', 'note'.", - inputSchema: { - type: "object", - required: ["file", "line", "severity", "message"], - properties: { - file: { - type: "string", - description: "File path where the issue was found", - }, - line: { - type: ["number", "string"], - description: "Line number where the issue was found", - }, - severity: { - type: "string", - enum: ["error", "warning", "info", "note"], - description: - ' Security severity levels follow the industry-standard Common Vulnerability Scoring System (CVSS) that is also used for advisories in the GitHub Advisory Database and must be one of "error", "warning", "info", "note".', - }, - message: { - type: "string", - description: "Alert message describing the issue", - }, - column: { - type: ["number", "string"], - description: "Optional column number", - }, - ruleIdSuffix: { - type: "string", - description: "Optional rule ID suffix for uniqueness", - }, - }, - additionalProperties: false, - }, - }, - { - name: "add_labels", - description: "Add labels to a GitHub issue or pull request", - inputSchema: { - type: "object", - required: ["labels"], - properties: { - labels: { - type: "array", - items: { type: "string" }, - description: "Labels to add", - }, - issue_number: { - type: "number", - description: "Issue or PR number (optional for current context)", - }, - }, - additionalProperties: false, - }, - }, - { - name: "update_issue", - description: "Update a GitHub issue", - inputSchema: { - type: "object", - properties: { - status: { - type: "string", - enum: ["open", "closed"], - description: "Optional new issue status", - }, - title: { type: "string", description: "Optional new issue title" }, - body: { type: "string", description: "Optional new issue body" }, - issue_number: { - type: ["number", "string"], - description: "Optional issue number for target '*'", - }, - }, - additionalProperties: false, - }, - }, - { - name: "push_to_pull_request_branch", - description: "Push changes to a pull request branch", - inputSchema: { - type: "object", - required: ["message"], - properties: { - branch: { - type: "string", - description: "Optional branch name. If not provided, the current branch will be used.", - }, - message: { type: "string", description: "Commit message" }, - pull_request_number: { - type: ["number", "string"], - description: "Optional pull request number for target '*'", - }, - }, - additionalProperties: false, - }, - handler: pushToPullRequestBranchHandler, - }, - { - name: "upload_asset", - description: "Publish a file as a URL-addressable asset to an orphaned git branch", - inputSchema: { - type: "object", - required: ["path"], - properties: { - path: { - type: "string", - description: - "Path to the file to publish as an asset. Must be a file under the current workspace or /tmp directory. By default, images (.png, .jpg, .jpeg) are allowed, but can be configured via workflow settings.", - }, - }, - additionalProperties: false, - }, - handler: uploadAssetHandler, - }, - { - name: "missing_tool", - description: "Report a missing tool or functionality needed to complete tasks", - inputSchema: { - type: "object", - required: ["tool", "reason"], - properties: { - tool: { type: "string", description: "Name of the missing tool" }, - reason: { type: "string", description: "Why this tool is needed" }, - alternatives: { - type: "string", - description: "Possible alternatives or workarounds", - }, - }, - additionalProperties: false, - }, - }, - ]; + const toolsEnv = process.env.GITHUB_AW_SAFE_OUTPUTS_TOOLS; + if (!toolsEnv) { + throw new Error("GITHUB_AW_SAFE_OUTPUTS_TOOLS environment variable is required"); + } + let PREDEFINED_TOOLS = {}; + debug(`Loading tools from GITHUB_AW_SAFE_OUTPUTS_TOOLS environment variable`); + try { + PREDEFINED_TOOLS = JSON.parse(toolsEnv); + debug(`Loaded ${Object.keys(PREDEFINED_TOOLS).length} predefined tools from environment`); + } catch (error) { + debug(`Error parsing GITHUB_AW_SAFE_OUTPUTS_TOOLS: ${error instanceof Error ? error.message : String(error)}`); + throw new Error(`Failed to parse GITHUB_AW_SAFE_OUTPUTS_TOOLS: ${error instanceof Error ? error.message : String(error)}`); + } debug(`v${SERVER_INFO.version} ready on stdio`); debug(` output file: ${outputFile}`); debug(` config: ${JSON.stringify(safeOutputsConfig)}`); const TOOLS = {}; - ALL_TOOLS.forEach(tool => { - if (Object.keys(safeOutputsConfig).find(config => normTool(config) === tool.name)) { - TOOLS[tool.name] = tool; + debug(`Using pre-filtered tools from environment variable`); + Object.keys(PREDEFINED_TOOLS).forEach(toolName => { + const tool = PREDEFINED_TOOLS[toolName]; + TOOLS[toolName] = tool; + if (tool.hasHandler) { + if (toolName === "create_pull_request") { + TOOLS[toolName].handler = createPullRequestHandler; + } else if (toolName === "push_to_pull_request_branch") { + TOOLS[toolName].handler = pushToPullRequestBranchHandler; + } else if (toolName === "upload_asset") { + TOOLS[toolName].handler = uploadAssetHandler; + } } }); Object.keys(safeOutputsConfig).forEach(configKey => { @@ -682,7 +466,8 @@ jobs: if (TOOLS[normalizedKey]) { return; } - if (!ALL_TOOLS.find(t => t.name === normalizedKey)) { + const isKnownTool = PREDEFINED_TOOLS[normalizedKey]; + if (!isKnownTool) { const jobConfig = safeOutputsConfig[configKey]; const dynamicTool = { name: normalizedKey, @@ -736,6 +521,7 @@ jobs: }); debug(` tools: ${Object.keys(TOOLS).join(", ")}`); if (!Object.keys(TOOLS).length) throw new Error("No tools enabled in configuration"); + console.error(`[${SERVER_INFO.name}] Filtered tools:\n${JSON.stringify(TOOLS, null, 2)}`); function handleMessage(req) { if (!req || typeof req !== "object") { debug(`Invalid message: not an object`); @@ -846,6 +632,7 @@ jobs: env: GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }} GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-comment\":{\"max\":1},\"create-issue\":{\"max\":1},\"missing-tool\":{}}" + GITHUB_AW_SAFE_OUTPUTS_TOOLS: "{\"add_comment\":{\"name\":\"add_comment\",\"description\":\"Add a comment to a GitHub issue or pull request\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"body\":{\"description\":\"Comment body/content\",\"type\":\"string\"},\"issue_number\":{\"description\":\"Issue or PR number (optional for current context)\",\"type\":\"number\"}},\"required\":[\"body\"],\"type\":\"object\"}},\"create_issue\":{\"name\":\"create_issue\",\"description\":\"Create a new GitHub issue\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"body\":{\"description\":\"Issue body/description\",\"type\":\"string\"},\"labels\":{\"description\":\"Issue labels\",\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"title\":{\"description\":\"Issue title\",\"type\":\"string\"}},\"required\":[\"title\",\"body\"],\"type\":\"object\"}},\"missing_tool\":{\"name\":\"missing_tool\",\"description\":\"Report a missing tool or functionality needed to complete tasks\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"alternatives\":{\"description\":\"Possible alternatives or workarounds\",\"type\":\"string\"},\"reason\":{\"description\":\"Why this tool is needed\",\"type\":\"string\"},\"tool\":{\"description\":\"Name of the missing tool\",\"type\":\"string\"}},\"required\":[\"tool\",\"reason\"],\"type\":\"object\"}}}" run: | mkdir -p /tmp/gh-aw/mcp-config mkdir -p /home/runner/.copilot diff --git a/.github/workflows/cli-version-checker.lock.yml b/.github/workflows/cli-version-checker.lock.yml index 601602e1ad0..0845b93abd3 100644 --- a/.github/workflows/cli-version-checker.lock.yml +++ b/.github/workflows/cli-version-checker.lock.yml @@ -565,251 +565,35 @@ jobs: }; }; const normTool = toolName => (toolName ? toolName.replace(/-/g, "_").toLowerCase() : undefined); - const ALL_TOOLS = [ - { - name: "create_issue", - description: "Create a new GitHub issue", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Issue title" }, - body: { type: "string", description: "Issue body/description" }, - labels: { - type: "array", - items: { type: "string" }, - description: "Issue labels", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_discussion", - description: "Create a new GitHub discussion", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Discussion title" }, - body: { type: "string", description: "Discussion body/content" }, - category: { type: "string", description: "Discussion category" }, - }, - additionalProperties: false, - }, - }, - { - name: "add_comment", - description: "Add a comment to a GitHub issue or pull request", - inputSchema: { - type: "object", - required: ["body"], - properties: { - body: { type: "string", description: "Comment body/content" }, - issue_number: { - type: "number", - description: "Issue or PR number (optional for current context)", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_pull_request", - description: "Create a new GitHub pull request", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Pull request title" }, - body: { - type: "string", - description: "Pull request body/description", - }, - branch: { - type: "string", - description: "Optional branch name. If not provided, the current branch will be used.", - }, - labels: { - type: "array", - items: { type: "string" }, - description: "Optional labels to add to the PR", - }, - }, - additionalProperties: false, - }, - handler: createPullRequestHandler, - }, - { - name: "create_pull_request_review_comment", - description: "Create a review comment on a GitHub pull request", - inputSchema: { - type: "object", - required: ["path", "line", "body"], - properties: { - path: { - type: "string", - description: "File path for the review comment", - }, - line: { - type: ["number", "string"], - description: "Line number for the comment", - }, - body: { type: "string", description: "Comment body content" }, - start_line: { - type: ["number", "string"], - description: "Optional start line for multi-line comments", - }, - side: { - type: "string", - enum: ["LEFT", "RIGHT"], - description: "Optional side of the diff: LEFT or RIGHT", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_code_scanning_alert", - description: "Create a code scanning alert. severity MUST be one of 'error', 'warning', 'info', 'note'.", - inputSchema: { - type: "object", - required: ["file", "line", "severity", "message"], - properties: { - file: { - type: "string", - description: "File path where the issue was found", - }, - line: { - type: ["number", "string"], - description: "Line number where the issue was found", - }, - severity: { - type: "string", - enum: ["error", "warning", "info", "note"], - description: - ' Security severity levels follow the industry-standard Common Vulnerability Scoring System (CVSS) that is also used for advisories in the GitHub Advisory Database and must be one of "error", "warning", "info", "note".', - }, - message: { - type: "string", - description: "Alert message describing the issue", - }, - column: { - type: ["number", "string"], - description: "Optional column number", - }, - ruleIdSuffix: { - type: "string", - description: "Optional rule ID suffix for uniqueness", - }, - }, - additionalProperties: false, - }, - }, - { - name: "add_labels", - description: "Add labels to a GitHub issue or pull request", - inputSchema: { - type: "object", - required: ["labels"], - properties: { - labels: { - type: "array", - items: { type: "string" }, - description: "Labels to add", - }, - issue_number: { - type: "number", - description: "Issue or PR number (optional for current context)", - }, - }, - additionalProperties: false, - }, - }, - { - name: "update_issue", - description: "Update a GitHub issue", - inputSchema: { - type: "object", - properties: { - status: { - type: "string", - enum: ["open", "closed"], - description: "Optional new issue status", - }, - title: { type: "string", description: "Optional new issue title" }, - body: { type: "string", description: "Optional new issue body" }, - issue_number: { - type: ["number", "string"], - description: "Optional issue number for target '*'", - }, - }, - additionalProperties: false, - }, - }, - { - name: "push_to_pull_request_branch", - description: "Push changes to a pull request branch", - inputSchema: { - type: "object", - required: ["message"], - properties: { - branch: { - type: "string", - description: "Optional branch name. If not provided, the current branch will be used.", - }, - message: { type: "string", description: "Commit message" }, - pull_request_number: { - type: ["number", "string"], - description: "Optional pull request number for target '*'", - }, - }, - additionalProperties: false, - }, - handler: pushToPullRequestBranchHandler, - }, - { - name: "upload_asset", - description: "Publish a file as a URL-addressable asset to an orphaned git branch", - inputSchema: { - type: "object", - required: ["path"], - properties: { - path: { - type: "string", - description: - "Path to the file to publish as an asset. Must be a file under the current workspace or /tmp directory. By default, images (.png, .jpg, .jpeg) are allowed, but can be configured via workflow settings.", - }, - }, - additionalProperties: false, - }, - handler: uploadAssetHandler, - }, - { - name: "missing_tool", - description: "Report a missing tool or functionality needed to complete tasks", - inputSchema: { - type: "object", - required: ["tool", "reason"], - properties: { - tool: { type: "string", description: "Name of the missing tool" }, - reason: { type: "string", description: "Why this tool is needed" }, - alternatives: { - type: "string", - description: "Possible alternatives or workarounds", - }, - }, - additionalProperties: false, - }, - }, - ]; + const toolsEnv = process.env.GITHUB_AW_SAFE_OUTPUTS_TOOLS; + if (!toolsEnv) { + throw new Error("GITHUB_AW_SAFE_OUTPUTS_TOOLS environment variable is required"); + } + let PREDEFINED_TOOLS = {}; + debug(`Loading tools from GITHUB_AW_SAFE_OUTPUTS_TOOLS environment variable`); + try { + PREDEFINED_TOOLS = JSON.parse(toolsEnv); + debug(`Loaded ${Object.keys(PREDEFINED_TOOLS).length} predefined tools from environment`); + } catch (error) { + debug(`Error parsing GITHUB_AW_SAFE_OUTPUTS_TOOLS: ${error instanceof Error ? error.message : String(error)}`); + throw new Error(`Failed to parse GITHUB_AW_SAFE_OUTPUTS_TOOLS: ${error instanceof Error ? error.message : String(error)}`); + } debug(`v${SERVER_INFO.version} ready on stdio`); debug(` output file: ${outputFile}`); debug(` config: ${JSON.stringify(safeOutputsConfig)}`); const TOOLS = {}; - ALL_TOOLS.forEach(tool => { - if (Object.keys(safeOutputsConfig).find(config => normTool(config) === tool.name)) { - TOOLS[tool.name] = tool; + debug(`Using pre-filtered tools from environment variable`); + Object.keys(PREDEFINED_TOOLS).forEach(toolName => { + const tool = PREDEFINED_TOOLS[toolName]; + TOOLS[toolName] = tool; + if (tool.hasHandler) { + if (toolName === "create_pull_request") { + TOOLS[toolName].handler = createPullRequestHandler; + } else if (toolName === "push_to_pull_request_branch") { + TOOLS[toolName].handler = pushToPullRequestBranchHandler; + } else if (toolName === "upload_asset") { + TOOLS[toolName].handler = uploadAssetHandler; + } } }); Object.keys(safeOutputsConfig).forEach(configKey => { @@ -817,7 +601,8 @@ jobs: if (TOOLS[normalizedKey]) { return; } - if (!ALL_TOOLS.find(t => t.name === normalizedKey)) { + const isKnownTool = PREDEFINED_TOOLS[normalizedKey]; + if (!isKnownTool) { const jobConfig = safeOutputsConfig[configKey]; const dynamicTool = { name: normalizedKey, @@ -871,6 +656,7 @@ jobs: }); debug(` tools: ${Object.keys(TOOLS).join(", ")}`); if (!Object.keys(TOOLS).length) throw new Error("No tools enabled in configuration"); + console.error(`[${SERVER_INFO.name}] Filtered tools:\n${JSON.stringify(TOOLS, null, 2)}`); function handleMessage(req) { if (!req || typeof req !== "object") { debug(`Invalid message: not an object`); @@ -981,6 +767,7 @@ jobs: env: GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }} GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"create-pull-request\":{},\"missing-tool\":{}}" + GITHUB_AW_SAFE_OUTPUTS_TOOLS: "{\"create_pull_request\":{\"name\":\"create_pull_request\",\"description\":\"Create a new GitHub pull request\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"body\":{\"description\":\"Pull request body/description\",\"type\":\"string\"},\"branch\":{\"description\":\"Optional branch name. If not provided, the current branch will be used.\",\"type\":\"string\"},\"labels\":{\"description\":\"Optional labels to add to the PR\",\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"title\":{\"description\":\"Pull request title\",\"type\":\"string\"}},\"required\":[\"title\",\"body\"],\"type\":\"object\"},\"hasHandler\":true},\"missing_tool\":{\"name\":\"missing_tool\",\"description\":\"Report a missing tool or functionality needed to complete tasks\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"alternatives\":{\"description\":\"Possible alternatives or workarounds\",\"type\":\"string\"},\"reason\":{\"description\":\"Why this tool is needed\",\"type\":\"string\"},\"tool\":{\"description\":\"Name of the missing tool\",\"type\":\"string\"}},\"required\":[\"tool\",\"reason\"],\"type\":\"object\"}}}" run: | mkdir -p /tmp/gh-aw/mcp-config cat > /tmp/gh-aw/mcp-config/mcp-servers.json << 'EOF' @@ -1006,6 +793,7 @@ jobs: "env": { "GITHUB_AW_SAFE_OUTPUTS": "${{ env.GITHUB_AW_SAFE_OUTPUTS }}", "GITHUB_AW_SAFE_OUTPUTS_CONFIG": ${{ toJSON(env.GITHUB_AW_SAFE_OUTPUTS_CONFIG) }}, + "GITHUB_AW_SAFE_OUTPUTS_TOOLS": ${{ toJSON(env.GITHUB_AW_SAFE_OUTPUTS_TOOLS) }}, "GITHUB_AW_ASSETS_BRANCH": "${{ env.GITHUB_AW_ASSETS_BRANCH }}", "GITHUB_AW_ASSETS_MAX_SIZE_KB": "${{ env.GITHUB_AW_ASSETS_MAX_SIZE_KB }}", "GITHUB_AW_ASSETS_ALLOWED_EXTS": "${{ env.GITHUB_AW_ASSETS_ALLOWED_EXTS }}" diff --git a/.github/workflows/daily-news.lock.yml b/.github/workflows/daily-news.lock.yml index b7145009fb5..35142718261 100644 --- a/.github/workflows/daily-news.lock.yml +++ b/.github/workflows/daily-news.lock.yml @@ -461,251 +461,35 @@ jobs: }; }; const normTool = toolName => (toolName ? toolName.replace(/-/g, "_").toLowerCase() : undefined); - const ALL_TOOLS = [ - { - name: "create_issue", - description: "Create a new GitHub issue", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Issue title" }, - body: { type: "string", description: "Issue body/description" }, - labels: { - type: "array", - items: { type: "string" }, - description: "Issue labels", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_discussion", - description: "Create a new GitHub discussion", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Discussion title" }, - body: { type: "string", description: "Discussion body/content" }, - category: { type: "string", description: "Discussion category" }, - }, - additionalProperties: false, - }, - }, - { - name: "add_comment", - description: "Add a comment to a GitHub issue or pull request", - inputSchema: { - type: "object", - required: ["body"], - properties: { - body: { type: "string", description: "Comment body/content" }, - issue_number: { - type: "number", - description: "Issue or PR number (optional for current context)", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_pull_request", - description: "Create a new GitHub pull request", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Pull request title" }, - body: { - type: "string", - description: "Pull request body/description", - }, - branch: { - type: "string", - description: "Optional branch name. If not provided, the current branch will be used.", - }, - labels: { - type: "array", - items: { type: "string" }, - description: "Optional labels to add to the PR", - }, - }, - additionalProperties: false, - }, - handler: createPullRequestHandler, - }, - { - name: "create_pull_request_review_comment", - description: "Create a review comment on a GitHub pull request", - inputSchema: { - type: "object", - required: ["path", "line", "body"], - properties: { - path: { - type: "string", - description: "File path for the review comment", - }, - line: { - type: ["number", "string"], - description: "Line number for the comment", - }, - body: { type: "string", description: "Comment body content" }, - start_line: { - type: ["number", "string"], - description: "Optional start line for multi-line comments", - }, - side: { - type: "string", - enum: ["LEFT", "RIGHT"], - description: "Optional side of the diff: LEFT or RIGHT", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_code_scanning_alert", - description: "Create a code scanning alert. severity MUST be one of 'error', 'warning', 'info', 'note'.", - inputSchema: { - type: "object", - required: ["file", "line", "severity", "message"], - properties: { - file: { - type: "string", - description: "File path where the issue was found", - }, - line: { - type: ["number", "string"], - description: "Line number where the issue was found", - }, - severity: { - type: "string", - enum: ["error", "warning", "info", "note"], - description: - ' Security severity levels follow the industry-standard Common Vulnerability Scoring System (CVSS) that is also used for advisories in the GitHub Advisory Database and must be one of "error", "warning", "info", "note".', - }, - message: { - type: "string", - description: "Alert message describing the issue", - }, - column: { - type: ["number", "string"], - description: "Optional column number", - }, - ruleIdSuffix: { - type: "string", - description: "Optional rule ID suffix for uniqueness", - }, - }, - additionalProperties: false, - }, - }, - { - name: "add_labels", - description: "Add labels to a GitHub issue or pull request", - inputSchema: { - type: "object", - required: ["labels"], - properties: { - labels: { - type: "array", - items: { type: "string" }, - description: "Labels to add", - }, - issue_number: { - type: "number", - description: "Issue or PR number (optional for current context)", - }, - }, - additionalProperties: false, - }, - }, - { - name: "update_issue", - description: "Update a GitHub issue", - inputSchema: { - type: "object", - properties: { - status: { - type: "string", - enum: ["open", "closed"], - description: "Optional new issue status", - }, - title: { type: "string", description: "Optional new issue title" }, - body: { type: "string", description: "Optional new issue body" }, - issue_number: { - type: ["number", "string"], - description: "Optional issue number for target '*'", - }, - }, - additionalProperties: false, - }, - }, - { - name: "push_to_pull_request_branch", - description: "Push changes to a pull request branch", - inputSchema: { - type: "object", - required: ["message"], - properties: { - branch: { - type: "string", - description: "Optional branch name. If not provided, the current branch will be used.", - }, - message: { type: "string", description: "Commit message" }, - pull_request_number: { - type: ["number", "string"], - description: "Optional pull request number for target '*'", - }, - }, - additionalProperties: false, - }, - handler: pushToPullRequestBranchHandler, - }, - { - name: "upload_asset", - description: "Publish a file as a URL-addressable asset to an orphaned git branch", - inputSchema: { - type: "object", - required: ["path"], - properties: { - path: { - type: "string", - description: - "Path to the file to publish as an asset. Must be a file under the current workspace or /tmp directory. By default, images (.png, .jpg, .jpeg) are allowed, but can be configured via workflow settings.", - }, - }, - additionalProperties: false, - }, - handler: uploadAssetHandler, - }, - { - name: "missing_tool", - description: "Report a missing tool or functionality needed to complete tasks", - inputSchema: { - type: "object", - required: ["tool", "reason"], - properties: { - tool: { type: "string", description: "Name of the missing tool" }, - reason: { type: "string", description: "Why this tool is needed" }, - alternatives: { - type: "string", - description: "Possible alternatives or workarounds", - }, - }, - additionalProperties: false, - }, - }, - ]; + const toolsEnv = process.env.GITHUB_AW_SAFE_OUTPUTS_TOOLS; + if (!toolsEnv) { + throw new Error("GITHUB_AW_SAFE_OUTPUTS_TOOLS environment variable is required"); + } + let PREDEFINED_TOOLS = {}; + debug(`Loading tools from GITHUB_AW_SAFE_OUTPUTS_TOOLS environment variable`); + try { + PREDEFINED_TOOLS = JSON.parse(toolsEnv); + debug(`Loaded ${Object.keys(PREDEFINED_TOOLS).length} predefined tools from environment`); + } catch (error) { + debug(`Error parsing GITHUB_AW_SAFE_OUTPUTS_TOOLS: ${error instanceof Error ? error.message : String(error)}`); + throw new Error(`Failed to parse GITHUB_AW_SAFE_OUTPUTS_TOOLS: ${error instanceof Error ? error.message : String(error)}`); + } debug(`v${SERVER_INFO.version} ready on stdio`); debug(` output file: ${outputFile}`); debug(` config: ${JSON.stringify(safeOutputsConfig)}`); const TOOLS = {}; - ALL_TOOLS.forEach(tool => { - if (Object.keys(safeOutputsConfig).find(config => normTool(config) === tool.name)) { - TOOLS[tool.name] = tool; + debug(`Using pre-filtered tools from environment variable`); + Object.keys(PREDEFINED_TOOLS).forEach(toolName => { + const tool = PREDEFINED_TOOLS[toolName]; + TOOLS[toolName] = tool; + if (tool.hasHandler) { + if (toolName === "create_pull_request") { + TOOLS[toolName].handler = createPullRequestHandler; + } else if (toolName === "push_to_pull_request_branch") { + TOOLS[toolName].handler = pushToPullRequestBranchHandler; + } else if (toolName === "upload_asset") { + TOOLS[toolName].handler = uploadAssetHandler; + } } }); Object.keys(safeOutputsConfig).forEach(configKey => { @@ -713,7 +497,8 @@ jobs: if (TOOLS[normalizedKey]) { return; } - if (!ALL_TOOLS.find(t => t.name === normalizedKey)) { + const isKnownTool = PREDEFINED_TOOLS[normalizedKey]; + if (!isKnownTool) { const jobConfig = safeOutputsConfig[configKey]; const dynamicTool = { name: normalizedKey, @@ -767,6 +552,7 @@ jobs: }); debug(` tools: ${Object.keys(TOOLS).join(", ")}`); if (!Object.keys(TOOLS).length) throw new Error("No tools enabled in configuration"); + console.error(`[${SERVER_INFO.name}] Filtered tools:\n${JSON.stringify(TOOLS, null, 2)}`); function handleMessage(req) { if (!req || typeof req !== "object") { debug(`Invalid message: not an object`); @@ -877,6 +663,7 @@ jobs: env: GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }} GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"create-discussion\":{\"max\":1},\"missing-tool\":{}}" + GITHUB_AW_SAFE_OUTPUTS_TOOLS: "{\"create_discussion\":{\"name\":\"create_discussion\",\"description\":\"Create a new GitHub discussion\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"body\":{\"description\":\"Discussion body/content\",\"type\":\"string\"},\"category\":{\"description\":\"Discussion category\",\"type\":\"string\"},\"title\":{\"description\":\"Discussion title\",\"type\":\"string\"}},\"required\":[\"title\",\"body\"],\"type\":\"object\"}},\"missing_tool\":{\"name\":\"missing_tool\",\"description\":\"Report a missing tool or functionality needed to complete tasks\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"alternatives\":{\"description\":\"Possible alternatives or workarounds\",\"type\":\"string\"},\"reason\":{\"description\":\"Why this tool is needed\",\"type\":\"string\"},\"tool\":{\"description\":\"Name of the missing tool\",\"type\":\"string\"}},\"required\":[\"tool\",\"reason\"],\"type\":\"object\"}}}" run: | mkdir -p /tmp/gh-aw/mcp-config mkdir -p /home/runner/.copilot diff --git a/.github/workflows/dev.lock.yml b/.github/workflows/dev.lock.yml index 820845aad76..cca86b54df3 100644 --- a/.github/workflows/dev.lock.yml +++ b/.github/workflows/dev.lock.yml @@ -609,251 +609,35 @@ jobs: }; }; const normTool = toolName => (toolName ? toolName.replace(/-/g, "_").toLowerCase() : undefined); - const ALL_TOOLS = [ - { - name: "create_issue", - description: "Create a new GitHub issue", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Issue title" }, - body: { type: "string", description: "Issue body/description" }, - labels: { - type: "array", - items: { type: "string" }, - description: "Issue labels", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_discussion", - description: "Create a new GitHub discussion", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Discussion title" }, - body: { type: "string", description: "Discussion body/content" }, - category: { type: "string", description: "Discussion category" }, - }, - additionalProperties: false, - }, - }, - { - name: "add_comment", - description: "Add a comment to a GitHub issue or pull request", - inputSchema: { - type: "object", - required: ["body"], - properties: { - body: { type: "string", description: "Comment body/content" }, - issue_number: { - type: "number", - description: "Issue or PR number (optional for current context)", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_pull_request", - description: "Create a new GitHub pull request", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Pull request title" }, - body: { - type: "string", - description: "Pull request body/description", - }, - branch: { - type: "string", - description: "Optional branch name. If not provided, the current branch will be used.", - }, - labels: { - type: "array", - items: { type: "string" }, - description: "Optional labels to add to the PR", - }, - }, - additionalProperties: false, - }, - handler: createPullRequestHandler, - }, - { - name: "create_pull_request_review_comment", - description: "Create a review comment on a GitHub pull request", - inputSchema: { - type: "object", - required: ["path", "line", "body"], - properties: { - path: { - type: "string", - description: "File path for the review comment", - }, - line: { - type: ["number", "string"], - description: "Line number for the comment", - }, - body: { type: "string", description: "Comment body content" }, - start_line: { - type: ["number", "string"], - description: "Optional start line for multi-line comments", - }, - side: { - type: "string", - enum: ["LEFT", "RIGHT"], - description: "Optional side of the diff: LEFT or RIGHT", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_code_scanning_alert", - description: "Create a code scanning alert. severity MUST be one of 'error', 'warning', 'info', 'note'.", - inputSchema: { - type: "object", - required: ["file", "line", "severity", "message"], - properties: { - file: { - type: "string", - description: "File path where the issue was found", - }, - line: { - type: ["number", "string"], - description: "Line number where the issue was found", - }, - severity: { - type: "string", - enum: ["error", "warning", "info", "note"], - description: - ' Security severity levels follow the industry-standard Common Vulnerability Scoring System (CVSS) that is also used for advisories in the GitHub Advisory Database and must be one of "error", "warning", "info", "note".', - }, - message: { - type: "string", - description: "Alert message describing the issue", - }, - column: { - type: ["number", "string"], - description: "Optional column number", - }, - ruleIdSuffix: { - type: "string", - description: "Optional rule ID suffix for uniqueness", - }, - }, - additionalProperties: false, - }, - }, - { - name: "add_labels", - description: "Add labels to a GitHub issue or pull request", - inputSchema: { - type: "object", - required: ["labels"], - properties: { - labels: { - type: "array", - items: { type: "string" }, - description: "Labels to add", - }, - issue_number: { - type: "number", - description: "Issue or PR number (optional for current context)", - }, - }, - additionalProperties: false, - }, - }, - { - name: "update_issue", - description: "Update a GitHub issue", - inputSchema: { - type: "object", - properties: { - status: { - type: "string", - enum: ["open", "closed"], - description: "Optional new issue status", - }, - title: { type: "string", description: "Optional new issue title" }, - body: { type: "string", description: "Optional new issue body" }, - issue_number: { - type: ["number", "string"], - description: "Optional issue number for target '*'", - }, - }, - additionalProperties: false, - }, - }, - { - name: "push_to_pull_request_branch", - description: "Push changes to a pull request branch", - inputSchema: { - type: "object", - required: ["message"], - properties: { - branch: { - type: "string", - description: "Optional branch name. If not provided, the current branch will be used.", - }, - message: { type: "string", description: "Commit message" }, - pull_request_number: { - type: ["number", "string"], - description: "Optional pull request number for target '*'", - }, - }, - additionalProperties: false, - }, - handler: pushToPullRequestBranchHandler, - }, - { - name: "upload_asset", - description: "Publish a file as a URL-addressable asset to an orphaned git branch", - inputSchema: { - type: "object", - required: ["path"], - properties: { - path: { - type: "string", - description: - "Path to the file to publish as an asset. Must be a file under the current workspace or /tmp directory. By default, images (.png, .jpg, .jpeg) are allowed, but can be configured via workflow settings.", - }, - }, - additionalProperties: false, - }, - handler: uploadAssetHandler, - }, - { - name: "missing_tool", - description: "Report a missing tool or functionality needed to complete tasks", - inputSchema: { - type: "object", - required: ["tool", "reason"], - properties: { - tool: { type: "string", description: "Name of the missing tool" }, - reason: { type: "string", description: "Why this tool is needed" }, - alternatives: { - type: "string", - description: "Possible alternatives or workarounds", - }, - }, - additionalProperties: false, - }, - }, - ]; + const toolsEnv = process.env.GITHUB_AW_SAFE_OUTPUTS_TOOLS; + if (!toolsEnv) { + throw new Error("GITHUB_AW_SAFE_OUTPUTS_TOOLS environment variable is required"); + } + let PREDEFINED_TOOLS = {}; + debug(`Loading tools from GITHUB_AW_SAFE_OUTPUTS_TOOLS environment variable`); + try { + PREDEFINED_TOOLS = JSON.parse(toolsEnv); + debug(`Loaded ${Object.keys(PREDEFINED_TOOLS).length} predefined tools from environment`); + } catch (error) { + debug(`Error parsing GITHUB_AW_SAFE_OUTPUTS_TOOLS: ${error instanceof Error ? error.message : String(error)}`); + throw new Error(`Failed to parse GITHUB_AW_SAFE_OUTPUTS_TOOLS: ${error instanceof Error ? error.message : String(error)}`); + } debug(`v${SERVER_INFO.version} ready on stdio`); debug(` output file: ${outputFile}`); debug(` config: ${JSON.stringify(safeOutputsConfig)}`); const TOOLS = {}; - ALL_TOOLS.forEach(tool => { - if (Object.keys(safeOutputsConfig).find(config => normTool(config) === tool.name)) { - TOOLS[tool.name] = tool; + debug(`Using pre-filtered tools from environment variable`); + Object.keys(PREDEFINED_TOOLS).forEach(toolName => { + const tool = PREDEFINED_TOOLS[toolName]; + TOOLS[toolName] = tool; + if (tool.hasHandler) { + if (toolName === "create_pull_request") { + TOOLS[toolName].handler = createPullRequestHandler; + } else if (toolName === "push_to_pull_request_branch") { + TOOLS[toolName].handler = pushToPullRequestBranchHandler; + } else if (toolName === "upload_asset") { + TOOLS[toolName].handler = uploadAssetHandler; + } } }); Object.keys(safeOutputsConfig).forEach(configKey => { @@ -861,7 +645,8 @@ jobs: if (TOOLS[normalizedKey]) { return; } - if (!ALL_TOOLS.find(t => t.name === normalizedKey)) { + const isKnownTool = PREDEFINED_TOOLS[normalizedKey]; + if (!isKnownTool) { const jobConfig = safeOutputsConfig[configKey]; const dynamicTool = { name: normalizedKey, @@ -915,6 +700,7 @@ jobs: }); debug(` tools: ${Object.keys(TOOLS).join(", ")}`); if (!Object.keys(TOOLS).length) throw new Error("No tools enabled in configuration"); + console.error(`[${SERVER_INFO.name}] Filtered tools:\n${JSON.stringify(TOOLS, null, 2)}`); function handleMessage(req) { if (!req || typeof req !== "object") { debug(`Invalid message: not an object`); @@ -1025,6 +811,7 @@ jobs: env: GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }} GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"create-issue\":{\"max\":1},\"missing-tool\":{}}" + GITHUB_AW_SAFE_OUTPUTS_TOOLS: "{\"create_issue\":{\"name\":\"create_issue\",\"description\":\"Create a new GitHub issue\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"body\":{\"description\":\"Issue body/description\",\"type\":\"string\"},\"labels\":{\"description\":\"Issue labels\",\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"title\":{\"description\":\"Issue title\",\"type\":\"string\"}},\"required\":[\"title\",\"body\"],\"type\":\"object\"}},\"missing_tool\":{\"name\":\"missing_tool\",\"description\":\"Report a missing tool or functionality needed to complete tasks\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"alternatives\":{\"description\":\"Possible alternatives or workarounds\",\"type\":\"string\"},\"reason\":{\"description\":\"Why this tool is needed\",\"type\":\"string\"},\"tool\":{\"description\":\"Name of the missing tool\",\"type\":\"string\"}},\"required\":[\"tool\",\"reason\"],\"type\":\"object\"}}}" run: | mkdir -p /tmp/gh-aw/mcp-config cat > /tmp/gh-aw/mcp-config/config.toml << EOF diff --git a/.github/workflows/duplicate-code-detector.lock.yml b/.github/workflows/duplicate-code-detector.lock.yml index a2d85c68e98..4a77e5e10ee 100644 --- a/.github/workflows/duplicate-code-detector.lock.yml +++ b/.github/workflows/duplicate-code-detector.lock.yml @@ -475,251 +475,35 @@ jobs: }; }; const normTool = toolName => (toolName ? toolName.replace(/-/g, "_").toLowerCase() : undefined); - const ALL_TOOLS = [ - { - name: "create_issue", - description: "Create a new GitHub issue", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Issue title" }, - body: { type: "string", description: "Issue body/description" }, - labels: { - type: "array", - items: { type: "string" }, - description: "Issue labels", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_discussion", - description: "Create a new GitHub discussion", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Discussion title" }, - body: { type: "string", description: "Discussion body/content" }, - category: { type: "string", description: "Discussion category" }, - }, - additionalProperties: false, - }, - }, - { - name: "add_comment", - description: "Add a comment to a GitHub issue or pull request", - inputSchema: { - type: "object", - required: ["body"], - properties: { - body: { type: "string", description: "Comment body/content" }, - issue_number: { - type: "number", - description: "Issue or PR number (optional for current context)", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_pull_request", - description: "Create a new GitHub pull request", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Pull request title" }, - body: { - type: "string", - description: "Pull request body/description", - }, - branch: { - type: "string", - description: "Optional branch name. If not provided, the current branch will be used.", - }, - labels: { - type: "array", - items: { type: "string" }, - description: "Optional labels to add to the PR", - }, - }, - additionalProperties: false, - }, - handler: createPullRequestHandler, - }, - { - name: "create_pull_request_review_comment", - description: "Create a review comment on a GitHub pull request", - inputSchema: { - type: "object", - required: ["path", "line", "body"], - properties: { - path: { - type: "string", - description: "File path for the review comment", - }, - line: { - type: ["number", "string"], - description: "Line number for the comment", - }, - body: { type: "string", description: "Comment body content" }, - start_line: { - type: ["number", "string"], - description: "Optional start line for multi-line comments", - }, - side: { - type: "string", - enum: ["LEFT", "RIGHT"], - description: "Optional side of the diff: LEFT or RIGHT", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_code_scanning_alert", - description: "Create a code scanning alert. severity MUST be one of 'error', 'warning', 'info', 'note'.", - inputSchema: { - type: "object", - required: ["file", "line", "severity", "message"], - properties: { - file: { - type: "string", - description: "File path where the issue was found", - }, - line: { - type: ["number", "string"], - description: "Line number where the issue was found", - }, - severity: { - type: "string", - enum: ["error", "warning", "info", "note"], - description: - ' Security severity levels follow the industry-standard Common Vulnerability Scoring System (CVSS) that is also used for advisories in the GitHub Advisory Database and must be one of "error", "warning", "info", "note".', - }, - message: { - type: "string", - description: "Alert message describing the issue", - }, - column: { - type: ["number", "string"], - description: "Optional column number", - }, - ruleIdSuffix: { - type: "string", - description: "Optional rule ID suffix for uniqueness", - }, - }, - additionalProperties: false, - }, - }, - { - name: "add_labels", - description: "Add labels to a GitHub issue or pull request", - inputSchema: { - type: "object", - required: ["labels"], - properties: { - labels: { - type: "array", - items: { type: "string" }, - description: "Labels to add", - }, - issue_number: { - type: "number", - description: "Issue or PR number (optional for current context)", - }, - }, - additionalProperties: false, - }, - }, - { - name: "update_issue", - description: "Update a GitHub issue", - inputSchema: { - type: "object", - properties: { - status: { - type: "string", - enum: ["open", "closed"], - description: "Optional new issue status", - }, - title: { type: "string", description: "Optional new issue title" }, - body: { type: "string", description: "Optional new issue body" }, - issue_number: { - type: ["number", "string"], - description: "Optional issue number for target '*'", - }, - }, - additionalProperties: false, - }, - }, - { - name: "push_to_pull_request_branch", - description: "Push changes to a pull request branch", - inputSchema: { - type: "object", - required: ["message"], - properties: { - branch: { - type: "string", - description: "Optional branch name. If not provided, the current branch will be used.", - }, - message: { type: "string", description: "Commit message" }, - pull_request_number: { - type: ["number", "string"], - description: "Optional pull request number for target '*'", - }, - }, - additionalProperties: false, - }, - handler: pushToPullRequestBranchHandler, - }, - { - name: "upload_asset", - description: "Publish a file as a URL-addressable asset to an orphaned git branch", - inputSchema: { - type: "object", - required: ["path"], - properties: { - path: { - type: "string", - description: - "Path to the file to publish as an asset. Must be a file under the current workspace or /tmp directory. By default, images (.png, .jpg, .jpeg) are allowed, but can be configured via workflow settings.", - }, - }, - additionalProperties: false, - }, - handler: uploadAssetHandler, - }, - { - name: "missing_tool", - description: "Report a missing tool or functionality needed to complete tasks", - inputSchema: { - type: "object", - required: ["tool", "reason"], - properties: { - tool: { type: "string", description: "Name of the missing tool" }, - reason: { type: "string", description: "Why this tool is needed" }, - alternatives: { - type: "string", - description: "Possible alternatives or workarounds", - }, - }, - additionalProperties: false, - }, - }, - ]; + const toolsEnv = process.env.GITHUB_AW_SAFE_OUTPUTS_TOOLS; + if (!toolsEnv) { + throw new Error("GITHUB_AW_SAFE_OUTPUTS_TOOLS environment variable is required"); + } + let PREDEFINED_TOOLS = {}; + debug(`Loading tools from GITHUB_AW_SAFE_OUTPUTS_TOOLS environment variable`); + try { + PREDEFINED_TOOLS = JSON.parse(toolsEnv); + debug(`Loaded ${Object.keys(PREDEFINED_TOOLS).length} predefined tools from environment`); + } catch (error) { + debug(`Error parsing GITHUB_AW_SAFE_OUTPUTS_TOOLS: ${error instanceof Error ? error.message : String(error)}`); + throw new Error(`Failed to parse GITHUB_AW_SAFE_OUTPUTS_TOOLS: ${error instanceof Error ? error.message : String(error)}`); + } debug(`v${SERVER_INFO.version} ready on stdio`); debug(` output file: ${outputFile}`); debug(` config: ${JSON.stringify(safeOutputsConfig)}`); const TOOLS = {}; - ALL_TOOLS.forEach(tool => { - if (Object.keys(safeOutputsConfig).find(config => normTool(config) === tool.name)) { - TOOLS[tool.name] = tool; + debug(`Using pre-filtered tools from environment variable`); + Object.keys(PREDEFINED_TOOLS).forEach(toolName => { + const tool = PREDEFINED_TOOLS[toolName]; + TOOLS[toolName] = tool; + if (tool.hasHandler) { + if (toolName === "create_pull_request") { + TOOLS[toolName].handler = createPullRequestHandler; + } else if (toolName === "push_to_pull_request_branch") { + TOOLS[toolName].handler = pushToPullRequestBranchHandler; + } else if (toolName === "upload_asset") { + TOOLS[toolName].handler = uploadAssetHandler; + } } }); Object.keys(safeOutputsConfig).forEach(configKey => { @@ -727,7 +511,8 @@ jobs: if (TOOLS[normalizedKey]) { return; } - if (!ALL_TOOLS.find(t => t.name === normalizedKey)) { + const isKnownTool = PREDEFINED_TOOLS[normalizedKey]; + if (!isKnownTool) { const jobConfig = safeOutputsConfig[configKey]; const dynamicTool = { name: normalizedKey, @@ -781,6 +566,7 @@ jobs: }); debug(` tools: ${Object.keys(TOOLS).join(", ")}`); if (!Object.keys(TOOLS).length) throw new Error("No tools enabled in configuration"); + console.error(`[${SERVER_INFO.name}] Filtered tools:\n${JSON.stringify(TOOLS, null, 2)}`); function handleMessage(req) { if (!req || typeof req !== "object") { debug(`Invalid message: not an object`); @@ -891,6 +677,7 @@ jobs: env: GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }} GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"create-issue\":{\"max\":1},\"missing-tool\":{}}" + GITHUB_AW_SAFE_OUTPUTS_TOOLS: "{\"create_issue\":{\"name\":\"create_issue\",\"description\":\"Create a new GitHub issue\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"body\":{\"description\":\"Issue body/description\",\"type\":\"string\"},\"labels\":{\"description\":\"Issue labels\",\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"title\":{\"description\":\"Issue title\",\"type\":\"string\"}},\"required\":[\"title\",\"body\"],\"type\":\"object\"}},\"missing_tool\":{\"name\":\"missing_tool\",\"description\":\"Report a missing tool or functionality needed to complete tasks\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"alternatives\":{\"description\":\"Possible alternatives or workarounds\",\"type\":\"string\"},\"reason\":{\"description\":\"Why this tool is needed\",\"type\":\"string\"},\"tool\":{\"description\":\"Name of the missing tool\",\"type\":\"string\"}},\"required\":[\"tool\",\"reason\"],\"type\":\"object\"}}}" run: | mkdir -p /tmp/gh-aw/mcp-config cat > /tmp/gh-aw/mcp-config/config.toml << EOF diff --git a/.github/workflows/go-pattern-detector.lock.yml b/.github/workflows/go-pattern-detector.lock.yml index d23e229e2bc..24f2e63d1df 100644 --- a/.github/workflows/go-pattern-detector.lock.yml +++ b/.github/workflows/go-pattern-detector.lock.yml @@ -572,251 +572,35 @@ jobs: }; }; const normTool = toolName => (toolName ? toolName.replace(/-/g, "_").toLowerCase() : undefined); - const ALL_TOOLS = [ - { - name: "create_issue", - description: "Create a new GitHub issue", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Issue title" }, - body: { type: "string", description: "Issue body/description" }, - labels: { - type: "array", - items: { type: "string" }, - description: "Issue labels", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_discussion", - description: "Create a new GitHub discussion", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Discussion title" }, - body: { type: "string", description: "Discussion body/content" }, - category: { type: "string", description: "Discussion category" }, - }, - additionalProperties: false, - }, - }, - { - name: "add_comment", - description: "Add a comment to a GitHub issue or pull request", - inputSchema: { - type: "object", - required: ["body"], - properties: { - body: { type: "string", description: "Comment body/content" }, - issue_number: { - type: "number", - description: "Issue or PR number (optional for current context)", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_pull_request", - description: "Create a new GitHub pull request", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Pull request title" }, - body: { - type: "string", - description: "Pull request body/description", - }, - branch: { - type: "string", - description: "Optional branch name. If not provided, the current branch will be used.", - }, - labels: { - type: "array", - items: { type: "string" }, - description: "Optional labels to add to the PR", - }, - }, - additionalProperties: false, - }, - handler: createPullRequestHandler, - }, - { - name: "create_pull_request_review_comment", - description: "Create a review comment on a GitHub pull request", - inputSchema: { - type: "object", - required: ["path", "line", "body"], - properties: { - path: { - type: "string", - description: "File path for the review comment", - }, - line: { - type: ["number", "string"], - description: "Line number for the comment", - }, - body: { type: "string", description: "Comment body content" }, - start_line: { - type: ["number", "string"], - description: "Optional start line for multi-line comments", - }, - side: { - type: "string", - enum: ["LEFT", "RIGHT"], - description: "Optional side of the diff: LEFT or RIGHT", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_code_scanning_alert", - description: "Create a code scanning alert. severity MUST be one of 'error', 'warning', 'info', 'note'.", - inputSchema: { - type: "object", - required: ["file", "line", "severity", "message"], - properties: { - file: { - type: "string", - description: "File path where the issue was found", - }, - line: { - type: ["number", "string"], - description: "Line number where the issue was found", - }, - severity: { - type: "string", - enum: ["error", "warning", "info", "note"], - description: - ' Security severity levels follow the industry-standard Common Vulnerability Scoring System (CVSS) that is also used for advisories in the GitHub Advisory Database and must be one of "error", "warning", "info", "note".', - }, - message: { - type: "string", - description: "Alert message describing the issue", - }, - column: { - type: ["number", "string"], - description: "Optional column number", - }, - ruleIdSuffix: { - type: "string", - description: "Optional rule ID suffix for uniqueness", - }, - }, - additionalProperties: false, - }, - }, - { - name: "add_labels", - description: "Add labels to a GitHub issue or pull request", - inputSchema: { - type: "object", - required: ["labels"], - properties: { - labels: { - type: "array", - items: { type: "string" }, - description: "Labels to add", - }, - issue_number: { - type: "number", - description: "Issue or PR number (optional for current context)", - }, - }, - additionalProperties: false, - }, - }, - { - name: "update_issue", - description: "Update a GitHub issue", - inputSchema: { - type: "object", - properties: { - status: { - type: "string", - enum: ["open", "closed"], - description: "Optional new issue status", - }, - title: { type: "string", description: "Optional new issue title" }, - body: { type: "string", description: "Optional new issue body" }, - issue_number: { - type: ["number", "string"], - description: "Optional issue number for target '*'", - }, - }, - additionalProperties: false, - }, - }, - { - name: "push_to_pull_request_branch", - description: "Push changes to a pull request branch", - inputSchema: { - type: "object", - required: ["message"], - properties: { - branch: { - type: "string", - description: "Optional branch name. If not provided, the current branch will be used.", - }, - message: { type: "string", description: "Commit message" }, - pull_request_number: { - type: ["number", "string"], - description: "Optional pull request number for target '*'", - }, - }, - additionalProperties: false, - }, - handler: pushToPullRequestBranchHandler, - }, - { - name: "upload_asset", - description: "Publish a file as a URL-addressable asset to an orphaned git branch", - inputSchema: { - type: "object", - required: ["path"], - properties: { - path: { - type: "string", - description: - "Path to the file to publish as an asset. Must be a file under the current workspace or /tmp directory. By default, images (.png, .jpg, .jpeg) are allowed, but can be configured via workflow settings.", - }, - }, - additionalProperties: false, - }, - handler: uploadAssetHandler, - }, - { - name: "missing_tool", - description: "Report a missing tool or functionality needed to complete tasks", - inputSchema: { - type: "object", - required: ["tool", "reason"], - properties: { - tool: { type: "string", description: "Name of the missing tool" }, - reason: { type: "string", description: "Why this tool is needed" }, - alternatives: { - type: "string", - description: "Possible alternatives or workarounds", - }, - }, - additionalProperties: false, - }, - }, - ]; + const toolsEnv = process.env.GITHUB_AW_SAFE_OUTPUTS_TOOLS; + if (!toolsEnv) { + throw new Error("GITHUB_AW_SAFE_OUTPUTS_TOOLS environment variable is required"); + } + let PREDEFINED_TOOLS = {}; + debug(`Loading tools from GITHUB_AW_SAFE_OUTPUTS_TOOLS environment variable`); + try { + PREDEFINED_TOOLS = JSON.parse(toolsEnv); + debug(`Loaded ${Object.keys(PREDEFINED_TOOLS).length} predefined tools from environment`); + } catch (error) { + debug(`Error parsing GITHUB_AW_SAFE_OUTPUTS_TOOLS: ${error instanceof Error ? error.message : String(error)}`); + throw new Error(`Failed to parse GITHUB_AW_SAFE_OUTPUTS_TOOLS: ${error instanceof Error ? error.message : String(error)}`); + } debug(`v${SERVER_INFO.version} ready on stdio`); debug(` output file: ${outputFile}`); debug(` config: ${JSON.stringify(safeOutputsConfig)}`); const TOOLS = {}; - ALL_TOOLS.forEach(tool => { - if (Object.keys(safeOutputsConfig).find(config => normTool(config) === tool.name)) { - TOOLS[tool.name] = tool; + debug(`Using pre-filtered tools from environment variable`); + Object.keys(PREDEFINED_TOOLS).forEach(toolName => { + const tool = PREDEFINED_TOOLS[toolName]; + TOOLS[toolName] = tool; + if (tool.hasHandler) { + if (toolName === "create_pull_request") { + TOOLS[toolName].handler = createPullRequestHandler; + } else if (toolName === "push_to_pull_request_branch") { + TOOLS[toolName].handler = pushToPullRequestBranchHandler; + } else if (toolName === "upload_asset") { + TOOLS[toolName].handler = uploadAssetHandler; + } } }); Object.keys(safeOutputsConfig).forEach(configKey => { @@ -824,7 +608,8 @@ jobs: if (TOOLS[normalizedKey]) { return; } - if (!ALL_TOOLS.find(t => t.name === normalizedKey)) { + const isKnownTool = PREDEFINED_TOOLS[normalizedKey]; + if (!isKnownTool) { const jobConfig = safeOutputsConfig[configKey]; const dynamicTool = { name: normalizedKey, @@ -878,6 +663,7 @@ jobs: }); debug(` tools: ${Object.keys(TOOLS).join(", ")}`); if (!Object.keys(TOOLS).length) throw new Error("No tools enabled in configuration"); + console.error(`[${SERVER_INFO.name}] Filtered tools:\n${JSON.stringify(TOOLS, null, 2)}`); function handleMessage(req) { if (!req || typeof req !== "object") { debug(`Invalid message: not an object`); @@ -988,6 +774,7 @@ jobs: env: GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }} GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"create-issue\":{\"max\":1},\"missing-tool\":{}}" + GITHUB_AW_SAFE_OUTPUTS_TOOLS: "{\"create_issue\":{\"name\":\"create_issue\",\"description\":\"Create a new GitHub issue\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"body\":{\"description\":\"Issue body/description\",\"type\":\"string\"},\"labels\":{\"description\":\"Issue labels\",\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"title\":{\"description\":\"Issue title\",\"type\":\"string\"}},\"required\":[\"title\",\"body\"],\"type\":\"object\"}},\"missing_tool\":{\"name\":\"missing_tool\",\"description\":\"Report a missing tool or functionality needed to complete tasks\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"alternatives\":{\"description\":\"Possible alternatives or workarounds\",\"type\":\"string\"},\"reason\":{\"description\":\"Why this tool is needed\",\"type\":\"string\"},\"tool\":{\"description\":\"Name of the missing tool\",\"type\":\"string\"}},\"required\":[\"tool\",\"reason\"],\"type\":\"object\"}}}" run: | mkdir -p /tmp/gh-aw/mcp-config cat > /tmp/gh-aw/mcp-config/mcp-servers.json << 'EOF' @@ -1023,6 +810,7 @@ jobs: "env": { "GITHUB_AW_SAFE_OUTPUTS": "${{ env.GITHUB_AW_SAFE_OUTPUTS }}", "GITHUB_AW_SAFE_OUTPUTS_CONFIG": ${{ toJSON(env.GITHUB_AW_SAFE_OUTPUTS_CONFIG) }}, + "GITHUB_AW_SAFE_OUTPUTS_TOOLS": ${{ toJSON(env.GITHUB_AW_SAFE_OUTPUTS_TOOLS) }}, "GITHUB_AW_ASSETS_BRANCH": "${{ env.GITHUB_AW_ASSETS_BRANCH }}", "GITHUB_AW_ASSETS_MAX_SIZE_KB": "${{ env.GITHUB_AW_ASSETS_MAX_SIZE_KB }}", "GITHUB_AW_ASSETS_ALLOWED_EXTS": "${{ env.GITHUB_AW_ASSETS_ALLOWED_EXTS }}" diff --git a/.github/workflows/issue-classifier.lock.yml b/.github/workflows/issue-classifier.lock.yml index 745a52b33dc..d50712138ea 100644 --- a/.github/workflows/issue-classifier.lock.yml +++ b/.github/workflows/issue-classifier.lock.yml @@ -782,251 +782,35 @@ jobs: }; }; const normTool = toolName => (toolName ? toolName.replace(/-/g, "_").toLowerCase() : undefined); - const ALL_TOOLS = [ - { - name: "create_issue", - description: "Create a new GitHub issue", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Issue title" }, - body: { type: "string", description: "Issue body/description" }, - labels: { - type: "array", - items: { type: "string" }, - description: "Issue labels", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_discussion", - description: "Create a new GitHub discussion", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Discussion title" }, - body: { type: "string", description: "Discussion body/content" }, - category: { type: "string", description: "Discussion category" }, - }, - additionalProperties: false, - }, - }, - { - name: "add_comment", - description: "Add a comment to a GitHub issue or pull request", - inputSchema: { - type: "object", - required: ["body"], - properties: { - body: { type: "string", description: "Comment body/content" }, - issue_number: { - type: "number", - description: "Issue or PR number (optional for current context)", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_pull_request", - description: "Create a new GitHub pull request", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Pull request title" }, - body: { - type: "string", - description: "Pull request body/description", - }, - branch: { - type: "string", - description: "Optional branch name. If not provided, the current branch will be used.", - }, - labels: { - type: "array", - items: { type: "string" }, - description: "Optional labels to add to the PR", - }, - }, - additionalProperties: false, - }, - handler: createPullRequestHandler, - }, - { - name: "create_pull_request_review_comment", - description: "Create a review comment on a GitHub pull request", - inputSchema: { - type: "object", - required: ["path", "line", "body"], - properties: { - path: { - type: "string", - description: "File path for the review comment", - }, - line: { - type: ["number", "string"], - description: "Line number for the comment", - }, - body: { type: "string", description: "Comment body content" }, - start_line: { - type: ["number", "string"], - description: "Optional start line for multi-line comments", - }, - side: { - type: "string", - enum: ["LEFT", "RIGHT"], - description: "Optional side of the diff: LEFT or RIGHT", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_code_scanning_alert", - description: "Create a code scanning alert. severity MUST be one of 'error', 'warning', 'info', 'note'.", - inputSchema: { - type: "object", - required: ["file", "line", "severity", "message"], - properties: { - file: { - type: "string", - description: "File path where the issue was found", - }, - line: { - type: ["number", "string"], - description: "Line number where the issue was found", - }, - severity: { - type: "string", - enum: ["error", "warning", "info", "note"], - description: - ' Security severity levels follow the industry-standard Common Vulnerability Scoring System (CVSS) that is also used for advisories in the GitHub Advisory Database and must be one of "error", "warning", "info", "note".', - }, - message: { - type: "string", - description: "Alert message describing the issue", - }, - column: { - type: ["number", "string"], - description: "Optional column number", - }, - ruleIdSuffix: { - type: "string", - description: "Optional rule ID suffix for uniqueness", - }, - }, - additionalProperties: false, - }, - }, - { - name: "add_labels", - description: "Add labels to a GitHub issue or pull request", - inputSchema: { - type: "object", - required: ["labels"], - properties: { - labels: { - type: "array", - items: { type: "string" }, - description: "Labels to add", - }, - issue_number: { - type: "number", - description: "Issue or PR number (optional for current context)", - }, - }, - additionalProperties: false, - }, - }, - { - name: "update_issue", - description: "Update a GitHub issue", - inputSchema: { - type: "object", - properties: { - status: { - type: "string", - enum: ["open", "closed"], - description: "Optional new issue status", - }, - title: { type: "string", description: "Optional new issue title" }, - body: { type: "string", description: "Optional new issue body" }, - issue_number: { - type: ["number", "string"], - description: "Optional issue number for target '*'", - }, - }, - additionalProperties: false, - }, - }, - { - name: "push_to_pull_request_branch", - description: "Push changes to a pull request branch", - inputSchema: { - type: "object", - required: ["message"], - properties: { - branch: { - type: "string", - description: "Optional branch name. If not provided, the current branch will be used.", - }, - message: { type: "string", description: "Commit message" }, - pull_request_number: { - type: ["number", "string"], - description: "Optional pull request number for target '*'", - }, - }, - additionalProperties: false, - }, - handler: pushToPullRequestBranchHandler, - }, - { - name: "upload_asset", - description: "Publish a file as a URL-addressable asset to an orphaned git branch", - inputSchema: { - type: "object", - required: ["path"], - properties: { - path: { - type: "string", - description: - "Path to the file to publish as an asset. Must be a file under the current workspace or /tmp directory. By default, images (.png, .jpg, .jpeg) are allowed, but can be configured via workflow settings.", - }, - }, - additionalProperties: false, - }, - handler: uploadAssetHandler, - }, - { - name: "missing_tool", - description: "Report a missing tool or functionality needed to complete tasks", - inputSchema: { - type: "object", - required: ["tool", "reason"], - properties: { - tool: { type: "string", description: "Name of the missing tool" }, - reason: { type: "string", description: "Why this tool is needed" }, - alternatives: { - type: "string", - description: "Possible alternatives or workarounds", - }, - }, - additionalProperties: false, - }, - }, - ]; + const toolsEnv = process.env.GITHUB_AW_SAFE_OUTPUTS_TOOLS; + if (!toolsEnv) { + throw new Error("GITHUB_AW_SAFE_OUTPUTS_TOOLS environment variable is required"); + } + let PREDEFINED_TOOLS = {}; + debug(`Loading tools from GITHUB_AW_SAFE_OUTPUTS_TOOLS environment variable`); + try { + PREDEFINED_TOOLS = JSON.parse(toolsEnv); + debug(`Loaded ${Object.keys(PREDEFINED_TOOLS).length} predefined tools from environment`); + } catch (error) { + debug(`Error parsing GITHUB_AW_SAFE_OUTPUTS_TOOLS: ${error instanceof Error ? error.message : String(error)}`); + throw new Error(`Failed to parse GITHUB_AW_SAFE_OUTPUTS_TOOLS: ${error instanceof Error ? error.message : String(error)}`); + } debug(`v${SERVER_INFO.version} ready on stdio`); debug(` output file: ${outputFile}`); debug(` config: ${JSON.stringify(safeOutputsConfig)}`); const TOOLS = {}; - ALL_TOOLS.forEach(tool => { - if (Object.keys(safeOutputsConfig).find(config => normTool(config) === tool.name)) { - TOOLS[tool.name] = tool; + debug(`Using pre-filtered tools from environment variable`); + Object.keys(PREDEFINED_TOOLS).forEach(toolName => { + const tool = PREDEFINED_TOOLS[toolName]; + TOOLS[toolName] = tool; + if (tool.hasHandler) { + if (toolName === "create_pull_request") { + TOOLS[toolName].handler = createPullRequestHandler; + } else if (toolName === "push_to_pull_request_branch") { + TOOLS[toolName].handler = pushToPullRequestBranchHandler; + } else if (toolName === "upload_asset") { + TOOLS[toolName].handler = uploadAssetHandler; + } } }); Object.keys(safeOutputsConfig).forEach(configKey => { @@ -1034,7 +818,8 @@ jobs: if (TOOLS[normalizedKey]) { return; } - if (!ALL_TOOLS.find(t => t.name === normalizedKey)) { + const isKnownTool = PREDEFINED_TOOLS[normalizedKey]; + if (!isKnownTool) { const jobConfig = safeOutputsConfig[configKey]; const dynamicTool = { name: normalizedKey, @@ -1088,6 +873,7 @@ jobs: }); debug(` tools: ${Object.keys(TOOLS).join(", ")}`); if (!Object.keys(TOOLS).length) throw new Error("No tools enabled in configuration"); + console.error(`[${SERVER_INFO.name}] Filtered tools:\n${JSON.stringify(TOOLS, null, 2)}`); function handleMessage(req) { if (!req || typeof req !== "object") { debug(`Invalid message: not an object`); @@ -1198,6 +984,7 @@ jobs: env: GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }} GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-labels\":{\"allowed\":[\"bug\",\"feature\",\"enhancement\",\"documentation\"],\"max\":1},\"missing-tool\":{}}" + GITHUB_AW_SAFE_OUTPUTS_TOOLS: "{\"add_labels\":{\"name\":\"add_labels\",\"description\":\"Add labels to a GitHub issue or pull request\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"issue_number\":{\"description\":\"Issue or PR number (optional for current context)\",\"type\":\"number\"},\"labels\":{\"description\":\"Labels to add\",\"items\":{\"type\":\"string\"},\"type\":\"array\"}},\"required\":[\"labels\"],\"type\":\"object\"}},\"missing_tool\":{\"name\":\"missing_tool\",\"description\":\"Report a missing tool or functionality needed to complete tasks\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"alternatives\":{\"description\":\"Possible alternatives or workarounds\",\"type\":\"string\"},\"reason\":{\"description\":\"Why this tool is needed\",\"type\":\"string\"},\"tool\":{\"description\":\"Name of the missing tool\",\"type\":\"string\"}},\"required\":[\"tool\",\"reason\"],\"type\":\"object\"}}}" run: | mkdir -p /tmp/gh-aw/mcp-config cat > /tmp/gh-aw/mcp-config/mcp-servers.json << 'EOF' diff --git a/.github/workflows/issue-summarizer-genaiscript.lock.yml b/.github/workflows/issue-summarizer-genaiscript.lock.yml index 65fed08ae22..9893ad72d69 100644 --- a/.github/workflows/issue-summarizer-genaiscript.lock.yml +++ b/.github/workflows/issue-summarizer-genaiscript.lock.yml @@ -611,251 +611,35 @@ jobs: }; }; const normTool = toolName => (toolName ? toolName.replace(/-/g, "_").toLowerCase() : undefined); - const ALL_TOOLS = [ - { - name: "create_issue", - description: "Create a new GitHub issue", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Issue title" }, - body: { type: "string", description: "Issue body/description" }, - labels: { - type: "array", - items: { type: "string" }, - description: "Issue labels", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_discussion", - description: "Create a new GitHub discussion", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Discussion title" }, - body: { type: "string", description: "Discussion body/content" }, - category: { type: "string", description: "Discussion category" }, - }, - additionalProperties: false, - }, - }, - { - name: "add_comment", - description: "Add a comment to a GitHub issue or pull request", - inputSchema: { - type: "object", - required: ["body"], - properties: { - body: { type: "string", description: "Comment body/content" }, - issue_number: { - type: "number", - description: "Issue or PR number (optional for current context)", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_pull_request", - description: "Create a new GitHub pull request", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Pull request title" }, - body: { - type: "string", - description: "Pull request body/description", - }, - branch: { - type: "string", - description: "Optional branch name. If not provided, the current branch will be used.", - }, - labels: { - type: "array", - items: { type: "string" }, - description: "Optional labels to add to the PR", - }, - }, - additionalProperties: false, - }, - handler: createPullRequestHandler, - }, - { - name: "create_pull_request_review_comment", - description: "Create a review comment on a GitHub pull request", - inputSchema: { - type: "object", - required: ["path", "line", "body"], - properties: { - path: { - type: "string", - description: "File path for the review comment", - }, - line: { - type: ["number", "string"], - description: "Line number for the comment", - }, - body: { type: "string", description: "Comment body content" }, - start_line: { - type: ["number", "string"], - description: "Optional start line for multi-line comments", - }, - side: { - type: "string", - enum: ["LEFT", "RIGHT"], - description: "Optional side of the diff: LEFT or RIGHT", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_code_scanning_alert", - description: "Create a code scanning alert. severity MUST be one of 'error', 'warning', 'info', 'note'.", - inputSchema: { - type: "object", - required: ["file", "line", "severity", "message"], - properties: { - file: { - type: "string", - description: "File path where the issue was found", - }, - line: { - type: ["number", "string"], - description: "Line number where the issue was found", - }, - severity: { - type: "string", - enum: ["error", "warning", "info", "note"], - description: - ' Security severity levels follow the industry-standard Common Vulnerability Scoring System (CVSS) that is also used for advisories in the GitHub Advisory Database and must be one of "error", "warning", "info", "note".', - }, - message: { - type: "string", - description: "Alert message describing the issue", - }, - column: { - type: ["number", "string"], - description: "Optional column number", - }, - ruleIdSuffix: { - type: "string", - description: "Optional rule ID suffix for uniqueness", - }, - }, - additionalProperties: false, - }, - }, - { - name: "add_labels", - description: "Add labels to a GitHub issue or pull request", - inputSchema: { - type: "object", - required: ["labels"], - properties: { - labels: { - type: "array", - items: { type: "string" }, - description: "Labels to add", - }, - issue_number: { - type: "number", - description: "Issue or PR number (optional for current context)", - }, - }, - additionalProperties: false, - }, - }, - { - name: "update_issue", - description: "Update a GitHub issue", - inputSchema: { - type: "object", - properties: { - status: { - type: "string", - enum: ["open", "closed"], - description: "Optional new issue status", - }, - title: { type: "string", description: "Optional new issue title" }, - body: { type: "string", description: "Optional new issue body" }, - issue_number: { - type: ["number", "string"], - description: "Optional issue number for target '*'", - }, - }, - additionalProperties: false, - }, - }, - { - name: "push_to_pull_request_branch", - description: "Push changes to a pull request branch", - inputSchema: { - type: "object", - required: ["message"], - properties: { - branch: { - type: "string", - description: "Optional branch name. If not provided, the current branch will be used.", - }, - message: { type: "string", description: "Commit message" }, - pull_request_number: { - type: ["number", "string"], - description: "Optional pull request number for target '*'", - }, - }, - additionalProperties: false, - }, - handler: pushToPullRequestBranchHandler, - }, - { - name: "upload_asset", - description: "Publish a file as a URL-addressable asset to an orphaned git branch", - inputSchema: { - type: "object", - required: ["path"], - properties: { - path: { - type: "string", - description: - "Path to the file to publish as an asset. Must be a file under the current workspace or /tmp directory. By default, images (.png, .jpg, .jpeg) are allowed, but can be configured via workflow settings.", - }, - }, - additionalProperties: false, - }, - handler: uploadAssetHandler, - }, - { - name: "missing_tool", - description: "Report a missing tool or functionality needed to complete tasks", - inputSchema: { - type: "object", - required: ["tool", "reason"], - properties: { - tool: { type: "string", description: "Name of the missing tool" }, - reason: { type: "string", description: "Why this tool is needed" }, - alternatives: { - type: "string", - description: "Possible alternatives or workarounds", - }, - }, - additionalProperties: false, - }, - }, - ]; + const toolsEnv = process.env.GITHUB_AW_SAFE_OUTPUTS_TOOLS; + if (!toolsEnv) { + throw new Error("GITHUB_AW_SAFE_OUTPUTS_TOOLS environment variable is required"); + } + let PREDEFINED_TOOLS = {}; + debug(`Loading tools from GITHUB_AW_SAFE_OUTPUTS_TOOLS environment variable`); + try { + PREDEFINED_TOOLS = JSON.parse(toolsEnv); + debug(`Loaded ${Object.keys(PREDEFINED_TOOLS).length} predefined tools from environment`); + } catch (error) { + debug(`Error parsing GITHUB_AW_SAFE_OUTPUTS_TOOLS: ${error instanceof Error ? error.message : String(error)}`); + throw new Error(`Failed to parse GITHUB_AW_SAFE_OUTPUTS_TOOLS: ${error instanceof Error ? error.message : String(error)}`); + } debug(`v${SERVER_INFO.version} ready on stdio`); debug(` output file: ${outputFile}`); debug(` config: ${JSON.stringify(safeOutputsConfig)}`); const TOOLS = {}; - ALL_TOOLS.forEach(tool => { - if (Object.keys(safeOutputsConfig).find(config => normTool(config) === tool.name)) { - TOOLS[tool.name] = tool; + debug(`Using pre-filtered tools from environment variable`); + Object.keys(PREDEFINED_TOOLS).forEach(toolName => { + const tool = PREDEFINED_TOOLS[toolName]; + TOOLS[toolName] = tool; + if (tool.hasHandler) { + if (toolName === "create_pull_request") { + TOOLS[toolName].handler = createPullRequestHandler; + } else if (toolName === "push_to_pull_request_branch") { + TOOLS[toolName].handler = pushToPullRequestBranchHandler; + } else if (toolName === "upload_asset") { + TOOLS[toolName].handler = uploadAssetHandler; + } } }); Object.keys(safeOutputsConfig).forEach(configKey => { @@ -863,7 +647,8 @@ jobs: if (TOOLS[normalizedKey]) { return; } - if (!ALL_TOOLS.find(t => t.name === normalizedKey)) { + const isKnownTool = PREDEFINED_TOOLS[normalizedKey]; + if (!isKnownTool) { const jobConfig = safeOutputsConfig[configKey]; const dynamicTool = { name: normalizedKey, @@ -917,6 +702,7 @@ jobs: }); debug(` tools: ${Object.keys(TOOLS).join(", ")}`); if (!Object.keys(TOOLS).length) throw new Error("No tools enabled in configuration"); + console.error(`[${SERVER_INFO.name}] Filtered tools:\n${JSON.stringify(TOOLS, null, 2)}`); function handleMessage(req) { if (!req || typeof req !== "object") { debug(`Invalid message: not an object`); @@ -1027,6 +813,7 @@ jobs: env: GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }} GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-comment\":{\"max\":1},\"missing-tool\":{}}" + GITHUB_AW_SAFE_OUTPUTS_TOOLS: "{\"add_comment\":{\"name\":\"add_comment\",\"description\":\"Add a comment to a GitHub issue or pull request\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"body\":{\"description\":\"Comment body/content\",\"type\":\"string\"},\"issue_number\":{\"description\":\"Issue or PR number (optional for current context)\",\"type\":\"number\"}},\"required\":[\"body\"],\"type\":\"object\"}},\"missing_tool\":{\"name\":\"missing_tool\",\"description\":\"Report a missing tool or functionality needed to complete tasks\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"alternatives\":{\"description\":\"Possible alternatives or workarounds\",\"type\":\"string\"},\"reason\":{\"description\":\"Why this tool is needed\",\"type\":\"string\"},\"tool\":{\"description\":\"Name of the missing tool\",\"type\":\"string\"}},\"required\":[\"tool\",\"reason\"],\"type\":\"object\"}}}" run: | mkdir -p /tmp/gh-aw/mcp-config cat > /tmp/gh-aw/mcp-config/mcp-servers.json << 'EOF' diff --git a/.github/workflows/notion-issue-summary.lock.yml b/.github/workflows/notion-issue-summary.lock.yml index 142304b26ef..a002467fd3c 100644 --- a/.github/workflows/notion-issue-summary.lock.yml +++ b/.github/workflows/notion-issue-summary.lock.yml @@ -727,251 +727,35 @@ jobs: }; }; const normTool = toolName => (toolName ? toolName.replace(/-/g, "_").toLowerCase() : undefined); - const ALL_TOOLS = [ - { - name: "create_issue", - description: "Create a new GitHub issue", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Issue title" }, - body: { type: "string", description: "Issue body/description" }, - labels: { - type: "array", - items: { type: "string" }, - description: "Issue labels", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_discussion", - description: "Create a new GitHub discussion", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Discussion title" }, - body: { type: "string", description: "Discussion body/content" }, - category: { type: "string", description: "Discussion category" }, - }, - additionalProperties: false, - }, - }, - { - name: "add_comment", - description: "Add a comment to a GitHub issue or pull request", - inputSchema: { - type: "object", - required: ["body"], - properties: { - body: { type: "string", description: "Comment body/content" }, - issue_number: { - type: "number", - description: "Issue or PR number (optional for current context)", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_pull_request", - description: "Create a new GitHub pull request", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Pull request title" }, - body: { - type: "string", - description: "Pull request body/description", - }, - branch: { - type: "string", - description: "Optional branch name. If not provided, the current branch will be used.", - }, - labels: { - type: "array", - items: { type: "string" }, - description: "Optional labels to add to the PR", - }, - }, - additionalProperties: false, - }, - handler: createPullRequestHandler, - }, - { - name: "create_pull_request_review_comment", - description: "Create a review comment on a GitHub pull request", - inputSchema: { - type: "object", - required: ["path", "line", "body"], - properties: { - path: { - type: "string", - description: "File path for the review comment", - }, - line: { - type: ["number", "string"], - description: "Line number for the comment", - }, - body: { type: "string", description: "Comment body content" }, - start_line: { - type: ["number", "string"], - description: "Optional start line for multi-line comments", - }, - side: { - type: "string", - enum: ["LEFT", "RIGHT"], - description: "Optional side of the diff: LEFT or RIGHT", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_code_scanning_alert", - description: "Create a code scanning alert. severity MUST be one of 'error', 'warning', 'info', 'note'.", - inputSchema: { - type: "object", - required: ["file", "line", "severity", "message"], - properties: { - file: { - type: "string", - description: "File path where the issue was found", - }, - line: { - type: ["number", "string"], - description: "Line number where the issue was found", - }, - severity: { - type: "string", - enum: ["error", "warning", "info", "note"], - description: - ' Security severity levels follow the industry-standard Common Vulnerability Scoring System (CVSS) that is also used for advisories in the GitHub Advisory Database and must be one of "error", "warning", "info", "note".', - }, - message: { - type: "string", - description: "Alert message describing the issue", - }, - column: { - type: ["number", "string"], - description: "Optional column number", - }, - ruleIdSuffix: { - type: "string", - description: "Optional rule ID suffix for uniqueness", - }, - }, - additionalProperties: false, - }, - }, - { - name: "add_labels", - description: "Add labels to a GitHub issue or pull request", - inputSchema: { - type: "object", - required: ["labels"], - properties: { - labels: { - type: "array", - items: { type: "string" }, - description: "Labels to add", - }, - issue_number: { - type: "number", - description: "Issue or PR number (optional for current context)", - }, - }, - additionalProperties: false, - }, - }, - { - name: "update_issue", - description: "Update a GitHub issue", - inputSchema: { - type: "object", - properties: { - status: { - type: "string", - enum: ["open", "closed"], - description: "Optional new issue status", - }, - title: { type: "string", description: "Optional new issue title" }, - body: { type: "string", description: "Optional new issue body" }, - issue_number: { - type: ["number", "string"], - description: "Optional issue number for target '*'", - }, - }, - additionalProperties: false, - }, - }, - { - name: "push_to_pull_request_branch", - description: "Push changes to a pull request branch", - inputSchema: { - type: "object", - required: ["message"], - properties: { - branch: { - type: "string", - description: "Optional branch name. If not provided, the current branch will be used.", - }, - message: { type: "string", description: "Commit message" }, - pull_request_number: { - type: ["number", "string"], - description: "Optional pull request number for target '*'", - }, - }, - additionalProperties: false, - }, - handler: pushToPullRequestBranchHandler, - }, - { - name: "upload_asset", - description: "Publish a file as a URL-addressable asset to an orphaned git branch", - inputSchema: { - type: "object", - required: ["path"], - properties: { - path: { - type: "string", - description: - "Path to the file to publish as an asset. Must be a file under the current workspace or /tmp directory. By default, images (.png, .jpg, .jpeg) are allowed, but can be configured via workflow settings.", - }, - }, - additionalProperties: false, - }, - handler: uploadAssetHandler, - }, - { - name: "missing_tool", - description: "Report a missing tool or functionality needed to complete tasks", - inputSchema: { - type: "object", - required: ["tool", "reason"], - properties: { - tool: { type: "string", description: "Name of the missing tool" }, - reason: { type: "string", description: "Why this tool is needed" }, - alternatives: { - type: "string", - description: "Possible alternatives or workarounds", - }, - }, - additionalProperties: false, - }, - }, - ]; + const toolsEnv = process.env.GITHUB_AW_SAFE_OUTPUTS_TOOLS; + if (!toolsEnv) { + throw new Error("GITHUB_AW_SAFE_OUTPUTS_TOOLS environment variable is required"); + } + let PREDEFINED_TOOLS = {}; + debug(`Loading tools from GITHUB_AW_SAFE_OUTPUTS_TOOLS environment variable`); + try { + PREDEFINED_TOOLS = JSON.parse(toolsEnv); + debug(`Loaded ${Object.keys(PREDEFINED_TOOLS).length} predefined tools from environment`); + } catch (error) { + debug(`Error parsing GITHUB_AW_SAFE_OUTPUTS_TOOLS: ${error instanceof Error ? error.message : String(error)}`); + throw new Error(`Failed to parse GITHUB_AW_SAFE_OUTPUTS_TOOLS: ${error instanceof Error ? error.message : String(error)}`); + } debug(`v${SERVER_INFO.version} ready on stdio`); debug(` output file: ${outputFile}`); debug(` config: ${JSON.stringify(safeOutputsConfig)}`); const TOOLS = {}; - ALL_TOOLS.forEach(tool => { - if (Object.keys(safeOutputsConfig).find(config => normTool(config) === tool.name)) { - TOOLS[tool.name] = tool; + debug(`Using pre-filtered tools from environment variable`); + Object.keys(PREDEFINED_TOOLS).forEach(toolName => { + const tool = PREDEFINED_TOOLS[toolName]; + TOOLS[toolName] = tool; + if (tool.hasHandler) { + if (toolName === "create_pull_request") { + TOOLS[toolName].handler = createPullRequestHandler; + } else if (toolName === "push_to_pull_request_branch") { + TOOLS[toolName].handler = pushToPullRequestBranchHandler; + } else if (toolName === "upload_asset") { + TOOLS[toolName].handler = uploadAssetHandler; + } } }); Object.keys(safeOutputsConfig).forEach(configKey => { @@ -979,7 +763,8 @@ jobs: if (TOOLS[normalizedKey]) { return; } - if (!ALL_TOOLS.find(t => t.name === normalizedKey)) { + const isKnownTool = PREDEFINED_TOOLS[normalizedKey]; + if (!isKnownTool) { const jobConfig = safeOutputsConfig[configKey]; const dynamicTool = { name: normalizedKey, @@ -1033,6 +818,7 @@ jobs: }); debug(` tools: ${Object.keys(TOOLS).join(", ")}`); if (!Object.keys(TOOLS).length) throw new Error("No tools enabled in configuration"); + console.error(`[${SERVER_INFO.name}] Filtered tools:\n${JSON.stringify(TOOLS, null, 2)}`); function handleMessage(req) { if (!req || typeof req !== "object") { debug(`Invalid message: not an object`); @@ -1143,6 +929,7 @@ jobs: env: GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }} GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"notion-add-comment\":{\"description\":\"Add a comment to a Notion page\",\"inputs\":{\"comment\":{\"description\":\"The comment text to add\",\"required\":true,\"type\":\"string\"},\"page_id\":{\"description\":\"The Notion page ID to add a comment to\",\"required\":true,\"type\":\"string\"}},\"output\":\"Comment added to Notion successfully!\"}}" + GITHUB_AW_SAFE_OUTPUTS_TOOLS: "{}" run: | mkdir -p /tmp/gh-aw/mcp-config cat > /tmp/gh-aw/mcp-config/mcp-servers.json << 'EOF' @@ -1183,6 +970,7 @@ jobs: "env": { "GITHUB_AW_SAFE_OUTPUTS": "${{ env.GITHUB_AW_SAFE_OUTPUTS }}", "GITHUB_AW_SAFE_OUTPUTS_CONFIG": ${{ toJSON(env.GITHUB_AW_SAFE_OUTPUTS_CONFIG) }}, + "GITHUB_AW_SAFE_OUTPUTS_TOOLS": ${{ toJSON(env.GITHUB_AW_SAFE_OUTPUTS_TOOLS) }}, "GITHUB_AW_ASSETS_BRANCH": "${{ env.GITHUB_AW_ASSETS_BRANCH }}", "GITHUB_AW_ASSETS_MAX_SIZE_KB": "${{ env.GITHUB_AW_ASSETS_MAX_SIZE_KB }}", "GITHUB_AW_ASSETS_ALLOWED_EXTS": "${{ env.GITHUB_AW_ASSETS_ALLOWED_EXTS }}" diff --git a/.github/workflows/pdf-summary.lock.yml b/.github/workflows/pdf-summary.lock.yml index 1139fcbc434..0f1071003ac 100644 --- a/.github/workflows/pdf-summary.lock.yml +++ b/.github/workflows/pdf-summary.lock.yml @@ -838,251 +838,35 @@ jobs: }; }; const normTool = toolName => (toolName ? toolName.replace(/-/g, "_").toLowerCase() : undefined); - const ALL_TOOLS = [ - { - name: "create_issue", - description: "Create a new GitHub issue", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Issue title" }, - body: { type: "string", description: "Issue body/description" }, - labels: { - type: "array", - items: { type: "string" }, - description: "Issue labels", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_discussion", - description: "Create a new GitHub discussion", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Discussion title" }, - body: { type: "string", description: "Discussion body/content" }, - category: { type: "string", description: "Discussion category" }, - }, - additionalProperties: false, - }, - }, - { - name: "add_comment", - description: "Add a comment to a GitHub issue or pull request", - inputSchema: { - type: "object", - required: ["body"], - properties: { - body: { type: "string", description: "Comment body/content" }, - issue_number: { - type: "number", - description: "Issue or PR number (optional for current context)", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_pull_request", - description: "Create a new GitHub pull request", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Pull request title" }, - body: { - type: "string", - description: "Pull request body/description", - }, - branch: { - type: "string", - description: "Optional branch name. If not provided, the current branch will be used.", - }, - labels: { - type: "array", - items: { type: "string" }, - description: "Optional labels to add to the PR", - }, - }, - additionalProperties: false, - }, - handler: createPullRequestHandler, - }, - { - name: "create_pull_request_review_comment", - description: "Create a review comment on a GitHub pull request", - inputSchema: { - type: "object", - required: ["path", "line", "body"], - properties: { - path: { - type: "string", - description: "File path for the review comment", - }, - line: { - type: ["number", "string"], - description: "Line number for the comment", - }, - body: { type: "string", description: "Comment body content" }, - start_line: { - type: ["number", "string"], - description: "Optional start line for multi-line comments", - }, - side: { - type: "string", - enum: ["LEFT", "RIGHT"], - description: "Optional side of the diff: LEFT or RIGHT", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_code_scanning_alert", - description: "Create a code scanning alert. severity MUST be one of 'error', 'warning', 'info', 'note'.", - inputSchema: { - type: "object", - required: ["file", "line", "severity", "message"], - properties: { - file: { - type: "string", - description: "File path where the issue was found", - }, - line: { - type: ["number", "string"], - description: "Line number where the issue was found", - }, - severity: { - type: "string", - enum: ["error", "warning", "info", "note"], - description: - ' Security severity levels follow the industry-standard Common Vulnerability Scoring System (CVSS) that is also used for advisories in the GitHub Advisory Database and must be one of "error", "warning", "info", "note".', - }, - message: { - type: "string", - description: "Alert message describing the issue", - }, - column: { - type: ["number", "string"], - description: "Optional column number", - }, - ruleIdSuffix: { - type: "string", - description: "Optional rule ID suffix for uniqueness", - }, - }, - additionalProperties: false, - }, - }, - { - name: "add_labels", - description: "Add labels to a GitHub issue or pull request", - inputSchema: { - type: "object", - required: ["labels"], - properties: { - labels: { - type: "array", - items: { type: "string" }, - description: "Labels to add", - }, - issue_number: { - type: "number", - description: "Issue or PR number (optional for current context)", - }, - }, - additionalProperties: false, - }, - }, - { - name: "update_issue", - description: "Update a GitHub issue", - inputSchema: { - type: "object", - properties: { - status: { - type: "string", - enum: ["open", "closed"], - description: "Optional new issue status", - }, - title: { type: "string", description: "Optional new issue title" }, - body: { type: "string", description: "Optional new issue body" }, - issue_number: { - type: ["number", "string"], - description: "Optional issue number for target '*'", - }, - }, - additionalProperties: false, - }, - }, - { - name: "push_to_pull_request_branch", - description: "Push changes to a pull request branch", - inputSchema: { - type: "object", - required: ["message"], - properties: { - branch: { - type: "string", - description: "Optional branch name. If not provided, the current branch will be used.", - }, - message: { type: "string", description: "Commit message" }, - pull_request_number: { - type: ["number", "string"], - description: "Optional pull request number for target '*'", - }, - }, - additionalProperties: false, - }, - handler: pushToPullRequestBranchHandler, - }, - { - name: "upload_asset", - description: "Publish a file as a URL-addressable asset to an orphaned git branch", - inputSchema: { - type: "object", - required: ["path"], - properties: { - path: { - type: "string", - description: - "Path to the file to publish as an asset. Must be a file under the current workspace or /tmp directory. By default, images (.png, .jpg, .jpeg) are allowed, but can be configured via workflow settings.", - }, - }, - additionalProperties: false, - }, - handler: uploadAssetHandler, - }, - { - name: "missing_tool", - description: "Report a missing tool or functionality needed to complete tasks", - inputSchema: { - type: "object", - required: ["tool", "reason"], - properties: { - tool: { type: "string", description: "Name of the missing tool" }, - reason: { type: "string", description: "Why this tool is needed" }, - alternatives: { - type: "string", - description: "Possible alternatives or workarounds", - }, - }, - additionalProperties: false, - }, - }, - ]; + const toolsEnv = process.env.GITHUB_AW_SAFE_OUTPUTS_TOOLS; + if (!toolsEnv) { + throw new Error("GITHUB_AW_SAFE_OUTPUTS_TOOLS environment variable is required"); + } + let PREDEFINED_TOOLS = {}; + debug(`Loading tools from GITHUB_AW_SAFE_OUTPUTS_TOOLS environment variable`); + try { + PREDEFINED_TOOLS = JSON.parse(toolsEnv); + debug(`Loaded ${Object.keys(PREDEFINED_TOOLS).length} predefined tools from environment`); + } catch (error) { + debug(`Error parsing GITHUB_AW_SAFE_OUTPUTS_TOOLS: ${error instanceof Error ? error.message : String(error)}`); + throw new Error(`Failed to parse GITHUB_AW_SAFE_OUTPUTS_TOOLS: ${error instanceof Error ? error.message : String(error)}`); + } debug(`v${SERVER_INFO.version} ready on stdio`); debug(` output file: ${outputFile}`); debug(` config: ${JSON.stringify(safeOutputsConfig)}`); const TOOLS = {}; - ALL_TOOLS.forEach(tool => { - if (Object.keys(safeOutputsConfig).find(config => normTool(config) === tool.name)) { - TOOLS[tool.name] = tool; + debug(`Using pre-filtered tools from environment variable`); + Object.keys(PREDEFINED_TOOLS).forEach(toolName => { + const tool = PREDEFINED_TOOLS[toolName]; + TOOLS[toolName] = tool; + if (tool.hasHandler) { + if (toolName === "create_pull_request") { + TOOLS[toolName].handler = createPullRequestHandler; + } else if (toolName === "push_to_pull_request_branch") { + TOOLS[toolName].handler = pushToPullRequestBranchHandler; + } else if (toolName === "upload_asset") { + TOOLS[toolName].handler = uploadAssetHandler; + } } }); Object.keys(safeOutputsConfig).forEach(configKey => { @@ -1090,7 +874,8 @@ jobs: if (TOOLS[normalizedKey]) { return; } - if (!ALL_TOOLS.find(t => t.name === normalizedKey)) { + const isKnownTool = PREDEFINED_TOOLS[normalizedKey]; + if (!isKnownTool) { const jobConfig = safeOutputsConfig[configKey]; const dynamicTool = { name: normalizedKey, @@ -1144,6 +929,7 @@ jobs: }); debug(` tools: ${Object.keys(TOOLS).join(", ")}`); if (!Object.keys(TOOLS).length) throw new Error("No tools enabled in configuration"); + console.error(`[${SERVER_INFO.name}] Filtered tools:\n${JSON.stringify(TOOLS, null, 2)}`); function handleMessage(req) { if (!req || typeof req !== "object") { debug(`Invalid message: not an object`); @@ -1254,6 +1040,7 @@ jobs: env: GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }} GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-comment\":{\"max\":1},\"missing-tool\":{}}" + GITHUB_AW_SAFE_OUTPUTS_TOOLS: "{\"add_comment\":{\"name\":\"add_comment\",\"description\":\"Add a comment to a GitHub issue or pull request\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"body\":{\"description\":\"Comment body/content\",\"type\":\"string\"},\"issue_number\":{\"description\":\"Issue or PR number (optional for current context)\",\"type\":\"number\"}},\"required\":[\"body\"],\"type\":\"object\"}},\"missing_tool\":{\"name\":\"missing_tool\",\"description\":\"Report a missing tool or functionality needed to complete tasks\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"alternatives\":{\"description\":\"Possible alternatives or workarounds\",\"type\":\"string\"},\"reason\":{\"description\":\"Why this tool is needed\",\"type\":\"string\"},\"tool\":{\"description\":\"Name of the missing tool\",\"type\":\"string\"}},\"required\":[\"tool\",\"reason\"],\"type\":\"object\"}}}" run: | mkdir -p /tmp/gh-aw/mcp-config mkdir -p /home/runner/.copilot diff --git a/.github/workflows/plan.lock.yml b/.github/workflows/plan.lock.yml index d600795423a..d46728a3894 100644 --- a/.github/workflows/plan.lock.yml +++ b/.github/workflows/plan.lock.yml @@ -793,251 +793,35 @@ jobs: }; }; const normTool = toolName => (toolName ? toolName.replace(/-/g, "_").toLowerCase() : undefined); - const ALL_TOOLS = [ - { - name: "create_issue", - description: "Create a new GitHub issue", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Issue title" }, - body: { type: "string", description: "Issue body/description" }, - labels: { - type: "array", - items: { type: "string" }, - description: "Issue labels", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_discussion", - description: "Create a new GitHub discussion", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Discussion title" }, - body: { type: "string", description: "Discussion body/content" }, - category: { type: "string", description: "Discussion category" }, - }, - additionalProperties: false, - }, - }, - { - name: "add_comment", - description: "Add a comment to a GitHub issue or pull request", - inputSchema: { - type: "object", - required: ["body"], - properties: { - body: { type: "string", description: "Comment body/content" }, - issue_number: { - type: "number", - description: "Issue or PR number (optional for current context)", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_pull_request", - description: "Create a new GitHub pull request", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Pull request title" }, - body: { - type: "string", - description: "Pull request body/description", - }, - branch: { - type: "string", - description: "Optional branch name. If not provided, the current branch will be used.", - }, - labels: { - type: "array", - items: { type: "string" }, - description: "Optional labels to add to the PR", - }, - }, - additionalProperties: false, - }, - handler: createPullRequestHandler, - }, - { - name: "create_pull_request_review_comment", - description: "Create a review comment on a GitHub pull request", - inputSchema: { - type: "object", - required: ["path", "line", "body"], - properties: { - path: { - type: "string", - description: "File path for the review comment", - }, - line: { - type: ["number", "string"], - description: "Line number for the comment", - }, - body: { type: "string", description: "Comment body content" }, - start_line: { - type: ["number", "string"], - description: "Optional start line for multi-line comments", - }, - side: { - type: "string", - enum: ["LEFT", "RIGHT"], - description: "Optional side of the diff: LEFT or RIGHT", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_code_scanning_alert", - description: "Create a code scanning alert. severity MUST be one of 'error', 'warning', 'info', 'note'.", - inputSchema: { - type: "object", - required: ["file", "line", "severity", "message"], - properties: { - file: { - type: "string", - description: "File path where the issue was found", - }, - line: { - type: ["number", "string"], - description: "Line number where the issue was found", - }, - severity: { - type: "string", - enum: ["error", "warning", "info", "note"], - description: - ' Security severity levels follow the industry-standard Common Vulnerability Scoring System (CVSS) that is also used for advisories in the GitHub Advisory Database and must be one of "error", "warning", "info", "note".', - }, - message: { - type: "string", - description: "Alert message describing the issue", - }, - column: { - type: ["number", "string"], - description: "Optional column number", - }, - ruleIdSuffix: { - type: "string", - description: "Optional rule ID suffix for uniqueness", - }, - }, - additionalProperties: false, - }, - }, - { - name: "add_labels", - description: "Add labels to a GitHub issue or pull request", - inputSchema: { - type: "object", - required: ["labels"], - properties: { - labels: { - type: "array", - items: { type: "string" }, - description: "Labels to add", - }, - issue_number: { - type: "number", - description: "Issue or PR number (optional for current context)", - }, - }, - additionalProperties: false, - }, - }, - { - name: "update_issue", - description: "Update a GitHub issue", - inputSchema: { - type: "object", - properties: { - status: { - type: "string", - enum: ["open", "closed"], - description: "Optional new issue status", - }, - title: { type: "string", description: "Optional new issue title" }, - body: { type: "string", description: "Optional new issue body" }, - issue_number: { - type: ["number", "string"], - description: "Optional issue number for target '*'", - }, - }, - additionalProperties: false, - }, - }, - { - name: "push_to_pull_request_branch", - description: "Push changes to a pull request branch", - inputSchema: { - type: "object", - required: ["message"], - properties: { - branch: { - type: "string", - description: "Optional branch name. If not provided, the current branch will be used.", - }, - message: { type: "string", description: "Commit message" }, - pull_request_number: { - type: ["number", "string"], - description: "Optional pull request number for target '*'", - }, - }, - additionalProperties: false, - }, - handler: pushToPullRequestBranchHandler, - }, - { - name: "upload_asset", - description: "Publish a file as a URL-addressable asset to an orphaned git branch", - inputSchema: { - type: "object", - required: ["path"], - properties: { - path: { - type: "string", - description: - "Path to the file to publish as an asset. Must be a file under the current workspace or /tmp directory. By default, images (.png, .jpg, .jpeg) are allowed, but can be configured via workflow settings.", - }, - }, - additionalProperties: false, - }, - handler: uploadAssetHandler, - }, - { - name: "missing_tool", - description: "Report a missing tool or functionality needed to complete tasks", - inputSchema: { - type: "object", - required: ["tool", "reason"], - properties: { - tool: { type: "string", description: "Name of the missing tool" }, - reason: { type: "string", description: "Why this tool is needed" }, - alternatives: { - type: "string", - description: "Possible alternatives or workarounds", - }, - }, - additionalProperties: false, - }, - }, - ]; + const toolsEnv = process.env.GITHUB_AW_SAFE_OUTPUTS_TOOLS; + if (!toolsEnv) { + throw new Error("GITHUB_AW_SAFE_OUTPUTS_TOOLS environment variable is required"); + } + let PREDEFINED_TOOLS = {}; + debug(`Loading tools from GITHUB_AW_SAFE_OUTPUTS_TOOLS environment variable`); + try { + PREDEFINED_TOOLS = JSON.parse(toolsEnv); + debug(`Loaded ${Object.keys(PREDEFINED_TOOLS).length} predefined tools from environment`); + } catch (error) { + debug(`Error parsing GITHUB_AW_SAFE_OUTPUTS_TOOLS: ${error instanceof Error ? error.message : String(error)}`); + throw new Error(`Failed to parse GITHUB_AW_SAFE_OUTPUTS_TOOLS: ${error instanceof Error ? error.message : String(error)}`); + } debug(`v${SERVER_INFO.version} ready on stdio`); debug(` output file: ${outputFile}`); debug(` config: ${JSON.stringify(safeOutputsConfig)}`); const TOOLS = {}; - ALL_TOOLS.forEach(tool => { - if (Object.keys(safeOutputsConfig).find(config => normTool(config) === tool.name)) { - TOOLS[tool.name] = tool; + debug(`Using pre-filtered tools from environment variable`); + Object.keys(PREDEFINED_TOOLS).forEach(toolName => { + const tool = PREDEFINED_TOOLS[toolName]; + TOOLS[toolName] = tool; + if (tool.hasHandler) { + if (toolName === "create_pull_request") { + TOOLS[toolName].handler = createPullRequestHandler; + } else if (toolName === "push_to_pull_request_branch") { + TOOLS[toolName].handler = pushToPullRequestBranchHandler; + } else if (toolName === "upload_asset") { + TOOLS[toolName].handler = uploadAssetHandler; + } } }); Object.keys(safeOutputsConfig).forEach(configKey => { @@ -1045,7 +829,8 @@ jobs: if (TOOLS[normalizedKey]) { return; } - if (!ALL_TOOLS.find(t => t.name === normalizedKey)) { + const isKnownTool = PREDEFINED_TOOLS[normalizedKey]; + if (!isKnownTool) { const jobConfig = safeOutputsConfig[configKey]; const dynamicTool = { name: normalizedKey, @@ -1099,6 +884,7 @@ jobs: }); debug(` tools: ${Object.keys(TOOLS).join(", ")}`); if (!Object.keys(TOOLS).length) throw new Error("No tools enabled in configuration"); + console.error(`[${SERVER_INFO.name}] Filtered tools:\n${JSON.stringify(TOOLS, null, 2)}`); function handleMessage(req) { if (!req || typeof req !== "object") { debug(`Invalid message: not an object`); @@ -1209,6 +995,7 @@ jobs: env: GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }} GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"create-issue\":{\"max\":5},\"missing-tool\":{}}" + GITHUB_AW_SAFE_OUTPUTS_TOOLS: "{\"create_issue\":{\"name\":\"create_issue\",\"description\":\"Create a new GitHub issue\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"body\":{\"description\":\"Issue body/description\",\"type\":\"string\"},\"labels\":{\"description\":\"Issue labels\",\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"title\":{\"description\":\"Issue title\",\"type\":\"string\"}},\"required\":[\"title\",\"body\"],\"type\":\"object\"}},\"missing_tool\":{\"name\":\"missing_tool\",\"description\":\"Report a missing tool or functionality needed to complete tasks\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"alternatives\":{\"description\":\"Possible alternatives or workarounds\",\"type\":\"string\"},\"reason\":{\"description\":\"Why this tool is needed\",\"type\":\"string\"},\"tool\":{\"description\":\"Name of the missing tool\",\"type\":\"string\"}},\"required\":[\"tool\",\"reason\"],\"type\":\"object\"}}}" run: | mkdir -p /tmp/gh-aw/mcp-config mkdir -p /home/runner/.copilot diff --git a/.github/workflows/poem-bot.lock.yml b/.github/workflows/poem-bot.lock.yml index 0898aba249d..4585c18d018 100644 --- a/.github/workflows/poem-bot.lock.yml +++ b/.github/workflows/poem-bot.lock.yml @@ -823,251 +823,35 @@ jobs: }; }; const normTool = toolName => (toolName ? toolName.replace(/-/g, "_").toLowerCase() : undefined); - const ALL_TOOLS = [ - { - name: "create_issue", - description: "Create a new GitHub issue", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Issue title" }, - body: { type: "string", description: "Issue body/description" }, - labels: { - type: "array", - items: { type: "string" }, - description: "Issue labels", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_discussion", - description: "Create a new GitHub discussion", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Discussion title" }, - body: { type: "string", description: "Discussion body/content" }, - category: { type: "string", description: "Discussion category" }, - }, - additionalProperties: false, - }, - }, - { - name: "add_comment", - description: "Add a comment to a GitHub issue or pull request", - inputSchema: { - type: "object", - required: ["body"], - properties: { - body: { type: "string", description: "Comment body/content" }, - issue_number: { - type: "number", - description: "Issue or PR number (optional for current context)", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_pull_request", - description: "Create a new GitHub pull request", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Pull request title" }, - body: { - type: "string", - description: "Pull request body/description", - }, - branch: { - type: "string", - description: "Optional branch name. If not provided, the current branch will be used.", - }, - labels: { - type: "array", - items: { type: "string" }, - description: "Optional labels to add to the PR", - }, - }, - additionalProperties: false, - }, - handler: createPullRequestHandler, - }, - { - name: "create_pull_request_review_comment", - description: "Create a review comment on a GitHub pull request", - inputSchema: { - type: "object", - required: ["path", "line", "body"], - properties: { - path: { - type: "string", - description: "File path for the review comment", - }, - line: { - type: ["number", "string"], - description: "Line number for the comment", - }, - body: { type: "string", description: "Comment body content" }, - start_line: { - type: ["number", "string"], - description: "Optional start line for multi-line comments", - }, - side: { - type: "string", - enum: ["LEFT", "RIGHT"], - description: "Optional side of the diff: LEFT or RIGHT", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_code_scanning_alert", - description: "Create a code scanning alert. severity MUST be one of 'error', 'warning', 'info', 'note'.", - inputSchema: { - type: "object", - required: ["file", "line", "severity", "message"], - properties: { - file: { - type: "string", - description: "File path where the issue was found", - }, - line: { - type: ["number", "string"], - description: "Line number where the issue was found", - }, - severity: { - type: "string", - enum: ["error", "warning", "info", "note"], - description: - ' Security severity levels follow the industry-standard Common Vulnerability Scoring System (CVSS) that is also used for advisories in the GitHub Advisory Database and must be one of "error", "warning", "info", "note".', - }, - message: { - type: "string", - description: "Alert message describing the issue", - }, - column: { - type: ["number", "string"], - description: "Optional column number", - }, - ruleIdSuffix: { - type: "string", - description: "Optional rule ID suffix for uniqueness", - }, - }, - additionalProperties: false, - }, - }, - { - name: "add_labels", - description: "Add labels to a GitHub issue or pull request", - inputSchema: { - type: "object", - required: ["labels"], - properties: { - labels: { - type: "array", - items: { type: "string" }, - description: "Labels to add", - }, - issue_number: { - type: "number", - description: "Issue or PR number (optional for current context)", - }, - }, - additionalProperties: false, - }, - }, - { - name: "update_issue", - description: "Update a GitHub issue", - inputSchema: { - type: "object", - properties: { - status: { - type: "string", - enum: ["open", "closed"], - description: "Optional new issue status", - }, - title: { type: "string", description: "Optional new issue title" }, - body: { type: "string", description: "Optional new issue body" }, - issue_number: { - type: ["number", "string"], - description: "Optional issue number for target '*'", - }, - }, - additionalProperties: false, - }, - }, - { - name: "push_to_pull_request_branch", - description: "Push changes to a pull request branch", - inputSchema: { - type: "object", - required: ["message"], - properties: { - branch: { - type: "string", - description: "Optional branch name. If not provided, the current branch will be used.", - }, - message: { type: "string", description: "Commit message" }, - pull_request_number: { - type: ["number", "string"], - description: "Optional pull request number for target '*'", - }, - }, - additionalProperties: false, - }, - handler: pushToPullRequestBranchHandler, - }, - { - name: "upload_asset", - description: "Publish a file as a URL-addressable asset to an orphaned git branch", - inputSchema: { - type: "object", - required: ["path"], - properties: { - path: { - type: "string", - description: - "Path to the file to publish as an asset. Must be a file under the current workspace or /tmp directory. By default, images (.png, .jpg, .jpeg) are allowed, but can be configured via workflow settings.", - }, - }, - additionalProperties: false, - }, - handler: uploadAssetHandler, - }, - { - name: "missing_tool", - description: "Report a missing tool or functionality needed to complete tasks", - inputSchema: { - type: "object", - required: ["tool", "reason"], - properties: { - tool: { type: "string", description: "Name of the missing tool" }, - reason: { type: "string", description: "Why this tool is needed" }, - alternatives: { - type: "string", - description: "Possible alternatives or workarounds", - }, - }, - additionalProperties: false, - }, - }, - ]; + const toolsEnv = process.env.GITHUB_AW_SAFE_OUTPUTS_TOOLS; + if (!toolsEnv) { + throw new Error("GITHUB_AW_SAFE_OUTPUTS_TOOLS environment variable is required"); + } + let PREDEFINED_TOOLS = {}; + debug(`Loading tools from GITHUB_AW_SAFE_OUTPUTS_TOOLS environment variable`); + try { + PREDEFINED_TOOLS = JSON.parse(toolsEnv); + debug(`Loaded ${Object.keys(PREDEFINED_TOOLS).length} predefined tools from environment`); + } catch (error) { + debug(`Error parsing GITHUB_AW_SAFE_OUTPUTS_TOOLS: ${error instanceof Error ? error.message : String(error)}`); + throw new Error(`Failed to parse GITHUB_AW_SAFE_OUTPUTS_TOOLS: ${error instanceof Error ? error.message : String(error)}`); + } debug(`v${SERVER_INFO.version} ready on stdio`); debug(` output file: ${outputFile}`); debug(` config: ${JSON.stringify(safeOutputsConfig)}`); const TOOLS = {}; - ALL_TOOLS.forEach(tool => { - if (Object.keys(safeOutputsConfig).find(config => normTool(config) === tool.name)) { - TOOLS[tool.name] = tool; + debug(`Using pre-filtered tools from environment variable`); + Object.keys(PREDEFINED_TOOLS).forEach(toolName => { + const tool = PREDEFINED_TOOLS[toolName]; + TOOLS[toolName] = tool; + if (tool.hasHandler) { + if (toolName === "create_pull_request") { + TOOLS[toolName].handler = createPullRequestHandler; + } else if (toolName === "push_to_pull_request_branch") { + TOOLS[toolName].handler = pushToPullRequestBranchHandler; + } else if (toolName === "upload_asset") { + TOOLS[toolName].handler = uploadAssetHandler; + } } }); Object.keys(safeOutputsConfig).forEach(configKey => { @@ -1075,7 +859,8 @@ jobs: if (TOOLS[normalizedKey]) { return; } - if (!ALL_TOOLS.find(t => t.name === normalizedKey)) { + const isKnownTool = PREDEFINED_TOOLS[normalizedKey]; + if (!isKnownTool) { const jobConfig = safeOutputsConfig[configKey]; const dynamicTool = { name: normalizedKey, @@ -1129,6 +914,7 @@ jobs: }); debug(` tools: ${Object.keys(TOOLS).join(", ")}`); if (!Object.keys(TOOLS).length) throw new Error("No tools enabled in configuration"); + console.error(`[${SERVER_INFO.name}] Filtered tools:\n${JSON.stringify(TOOLS, null, 2)}`); function handleMessage(req) { if (!req || typeof req !== "object") { debug(`Invalid message: not an object`); @@ -1239,6 +1025,7 @@ jobs: env: GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }} GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-comment\":{\"max\":3,\"target\":\"*\"},\"add-labels\":{\"allowed\":[\"poetry\",\"creative\",\"automation\",\"ai-generated\",\"epic\",\"haiku\",\"sonnet\",\"limerick\"],\"max\":5},\"create-issue\":{\"max\":2},\"create-pull-request\":{},\"create-pull-request-review-comment\":{\"max\":2},\"missing-tool\":{},\"push-to-pull-request-branch\":{},\"update-issue\":{\"max\":2},\"upload-asset\":{}}" + GITHUB_AW_SAFE_OUTPUTS_TOOLS: "{\"add_comment\":{\"name\":\"add_comment\",\"description\":\"Add a comment to a GitHub issue or pull request\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"body\":{\"description\":\"Comment body/content\",\"type\":\"string\"},\"issue_number\":{\"description\":\"Issue or PR number (optional for current context)\",\"type\":\"number\"}},\"required\":[\"body\"],\"type\":\"object\"}},\"add_labels\":{\"name\":\"add_labels\",\"description\":\"Add labels to a GitHub issue or pull request\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"issue_number\":{\"description\":\"Issue or PR number (optional for current context)\",\"type\":\"number\"},\"labels\":{\"description\":\"Labels to add\",\"items\":{\"type\":\"string\"},\"type\":\"array\"}},\"required\":[\"labels\"],\"type\":\"object\"}},\"create_issue\":{\"name\":\"create_issue\",\"description\":\"Create a new GitHub issue\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"body\":{\"description\":\"Issue body/description\",\"type\":\"string\"},\"labels\":{\"description\":\"Issue labels\",\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"title\":{\"description\":\"Issue title\",\"type\":\"string\"}},\"required\":[\"title\",\"body\"],\"type\":\"object\"}},\"create_pull_request\":{\"name\":\"create_pull_request\",\"description\":\"Create a new GitHub pull request\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"body\":{\"description\":\"Pull request body/description\",\"type\":\"string\"},\"branch\":{\"description\":\"Optional branch name. If not provided, the current branch will be used.\",\"type\":\"string\"},\"labels\":{\"description\":\"Optional labels to add to the PR\",\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"title\":{\"description\":\"Pull request title\",\"type\":\"string\"}},\"required\":[\"title\",\"body\"],\"type\":\"object\"},\"hasHandler\":true},\"create_pull_request_review_comment\":{\"name\":\"create_pull_request_review_comment\",\"description\":\"Create a review comment on a GitHub pull request\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"body\":{\"description\":\"Comment body content\",\"type\":\"string\"},\"line\":{\"description\":\"Line number for the comment\",\"type\":[\"number\",\"string\"]},\"path\":{\"description\":\"File path for the review comment\",\"type\":\"string\"},\"side\":{\"description\":\"Optional side of the diff: LEFT or RIGHT\",\"enum\":[\"LEFT\",\"RIGHT\"],\"type\":\"string\"},\"start_line\":{\"description\":\"Optional start line for multi-line comments\",\"type\":[\"number\",\"string\"]}},\"required\":[\"path\",\"line\",\"body\"],\"type\":\"object\"}},\"missing_tool\":{\"name\":\"missing_tool\",\"description\":\"Report a missing tool or functionality needed to complete tasks\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"alternatives\":{\"description\":\"Possible alternatives or workarounds\",\"type\":\"string\"},\"reason\":{\"description\":\"Why this tool is needed\",\"type\":\"string\"},\"tool\":{\"description\":\"Name of the missing tool\",\"type\":\"string\"}},\"required\":[\"tool\",\"reason\"],\"type\":\"object\"}},\"push_to_pull_request_branch\":{\"name\":\"push_to_pull_request_branch\",\"description\":\"Push changes to a pull request branch\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"branch\":{\"description\":\"Optional branch name. If not provided, the current branch will be used.\",\"type\":\"string\"},\"message\":{\"description\":\"Commit message\",\"type\":\"string\"},\"pull_request_number\":{\"description\":\"Optional pull request number for target '*'\",\"type\":[\"number\",\"string\"]}},\"required\":[\"message\"],\"type\":\"object\"},\"hasHandler\":true},\"update_issue\":{\"name\":\"update_issue\",\"description\":\"Update a GitHub issue\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"body\":{\"description\":\"Optional new issue body\",\"type\":\"string\"},\"issue_number\":{\"description\":\"Optional issue number for target '*'\",\"type\":[\"number\",\"string\"]},\"status\":{\"description\":\"Optional new issue status\",\"enum\":[\"open\",\"closed\"],\"type\":\"string\"},\"title\":{\"description\":\"Optional new issue title\",\"type\":\"string\"}},\"type\":\"object\"}},\"upload_asset\":{\"name\":\"upload_asset\",\"description\":\"Publish a file as a URL-addressable asset to an orphaned git branch\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"path\":{\"description\":\"Path to the file to publish as an asset. Must be a file under the current workspace or /tmp directory. By default, images (.png, .jpg, .jpeg) are allowed, but can be configured via workflow settings.\",\"type\":\"string\"}},\"required\":[\"path\"],\"type\":\"object\"},\"hasHandler\":true}}" GITHUB_AW_ASSETS_BRANCH: "assets/${{ github.workflow }}" GITHUB_AW_ASSETS_MAX_SIZE_KB: 10240 GITHUB_AW_ASSETS_ALLOWED_EXTS: ".png,.jpg,.jpeg" diff --git a/.github/workflows/repo-tree-map.lock.yml b/.github/workflows/repo-tree-map.lock.yml index 1754d20e006..d63948b2a3f 100644 --- a/.github/workflows/repo-tree-map.lock.yml +++ b/.github/workflows/repo-tree-map.lock.yml @@ -457,251 +457,35 @@ jobs: }; }; const normTool = toolName => (toolName ? toolName.replace(/-/g, "_").toLowerCase() : undefined); - const ALL_TOOLS = [ - { - name: "create_issue", - description: "Create a new GitHub issue", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Issue title" }, - body: { type: "string", description: "Issue body/description" }, - labels: { - type: "array", - items: { type: "string" }, - description: "Issue labels", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_discussion", - description: "Create a new GitHub discussion", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Discussion title" }, - body: { type: "string", description: "Discussion body/content" }, - category: { type: "string", description: "Discussion category" }, - }, - additionalProperties: false, - }, - }, - { - name: "add_comment", - description: "Add a comment to a GitHub issue or pull request", - inputSchema: { - type: "object", - required: ["body"], - properties: { - body: { type: "string", description: "Comment body/content" }, - issue_number: { - type: "number", - description: "Issue or PR number (optional for current context)", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_pull_request", - description: "Create a new GitHub pull request", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Pull request title" }, - body: { - type: "string", - description: "Pull request body/description", - }, - branch: { - type: "string", - description: "Optional branch name. If not provided, the current branch will be used.", - }, - labels: { - type: "array", - items: { type: "string" }, - description: "Optional labels to add to the PR", - }, - }, - additionalProperties: false, - }, - handler: createPullRequestHandler, - }, - { - name: "create_pull_request_review_comment", - description: "Create a review comment on a GitHub pull request", - inputSchema: { - type: "object", - required: ["path", "line", "body"], - properties: { - path: { - type: "string", - description: "File path for the review comment", - }, - line: { - type: ["number", "string"], - description: "Line number for the comment", - }, - body: { type: "string", description: "Comment body content" }, - start_line: { - type: ["number", "string"], - description: "Optional start line for multi-line comments", - }, - side: { - type: "string", - enum: ["LEFT", "RIGHT"], - description: "Optional side of the diff: LEFT or RIGHT", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_code_scanning_alert", - description: "Create a code scanning alert. severity MUST be one of 'error', 'warning', 'info', 'note'.", - inputSchema: { - type: "object", - required: ["file", "line", "severity", "message"], - properties: { - file: { - type: "string", - description: "File path where the issue was found", - }, - line: { - type: ["number", "string"], - description: "Line number where the issue was found", - }, - severity: { - type: "string", - enum: ["error", "warning", "info", "note"], - description: - ' Security severity levels follow the industry-standard Common Vulnerability Scoring System (CVSS) that is also used for advisories in the GitHub Advisory Database and must be one of "error", "warning", "info", "note".', - }, - message: { - type: "string", - description: "Alert message describing the issue", - }, - column: { - type: ["number", "string"], - description: "Optional column number", - }, - ruleIdSuffix: { - type: "string", - description: "Optional rule ID suffix for uniqueness", - }, - }, - additionalProperties: false, - }, - }, - { - name: "add_labels", - description: "Add labels to a GitHub issue or pull request", - inputSchema: { - type: "object", - required: ["labels"], - properties: { - labels: { - type: "array", - items: { type: "string" }, - description: "Labels to add", - }, - issue_number: { - type: "number", - description: "Issue or PR number (optional for current context)", - }, - }, - additionalProperties: false, - }, - }, - { - name: "update_issue", - description: "Update a GitHub issue", - inputSchema: { - type: "object", - properties: { - status: { - type: "string", - enum: ["open", "closed"], - description: "Optional new issue status", - }, - title: { type: "string", description: "Optional new issue title" }, - body: { type: "string", description: "Optional new issue body" }, - issue_number: { - type: ["number", "string"], - description: "Optional issue number for target '*'", - }, - }, - additionalProperties: false, - }, - }, - { - name: "push_to_pull_request_branch", - description: "Push changes to a pull request branch", - inputSchema: { - type: "object", - required: ["message"], - properties: { - branch: { - type: "string", - description: "Optional branch name. If not provided, the current branch will be used.", - }, - message: { type: "string", description: "Commit message" }, - pull_request_number: { - type: ["number", "string"], - description: "Optional pull request number for target '*'", - }, - }, - additionalProperties: false, - }, - handler: pushToPullRequestBranchHandler, - }, - { - name: "upload_asset", - description: "Publish a file as a URL-addressable asset to an orphaned git branch", - inputSchema: { - type: "object", - required: ["path"], - properties: { - path: { - type: "string", - description: - "Path to the file to publish as an asset. Must be a file under the current workspace or /tmp directory. By default, images (.png, .jpg, .jpeg) are allowed, but can be configured via workflow settings.", - }, - }, - additionalProperties: false, - }, - handler: uploadAssetHandler, - }, - { - name: "missing_tool", - description: "Report a missing tool or functionality needed to complete tasks", - inputSchema: { - type: "object", - required: ["tool", "reason"], - properties: { - tool: { type: "string", description: "Name of the missing tool" }, - reason: { type: "string", description: "Why this tool is needed" }, - alternatives: { - type: "string", - description: "Possible alternatives or workarounds", - }, - }, - additionalProperties: false, - }, - }, - ]; + const toolsEnv = process.env.GITHUB_AW_SAFE_OUTPUTS_TOOLS; + if (!toolsEnv) { + throw new Error("GITHUB_AW_SAFE_OUTPUTS_TOOLS environment variable is required"); + } + let PREDEFINED_TOOLS = {}; + debug(`Loading tools from GITHUB_AW_SAFE_OUTPUTS_TOOLS environment variable`); + try { + PREDEFINED_TOOLS = JSON.parse(toolsEnv); + debug(`Loaded ${Object.keys(PREDEFINED_TOOLS).length} predefined tools from environment`); + } catch (error) { + debug(`Error parsing GITHUB_AW_SAFE_OUTPUTS_TOOLS: ${error instanceof Error ? error.message : String(error)}`); + throw new Error(`Failed to parse GITHUB_AW_SAFE_OUTPUTS_TOOLS: ${error instanceof Error ? error.message : String(error)}`); + } debug(`v${SERVER_INFO.version} ready on stdio`); debug(` output file: ${outputFile}`); debug(` config: ${JSON.stringify(safeOutputsConfig)}`); const TOOLS = {}; - ALL_TOOLS.forEach(tool => { - if (Object.keys(safeOutputsConfig).find(config => normTool(config) === tool.name)) { - TOOLS[tool.name] = tool; + debug(`Using pre-filtered tools from environment variable`); + Object.keys(PREDEFINED_TOOLS).forEach(toolName => { + const tool = PREDEFINED_TOOLS[toolName]; + TOOLS[toolName] = tool; + if (tool.hasHandler) { + if (toolName === "create_pull_request") { + TOOLS[toolName].handler = createPullRequestHandler; + } else if (toolName === "push_to_pull_request_branch") { + TOOLS[toolName].handler = pushToPullRequestBranchHandler; + } else if (toolName === "upload_asset") { + TOOLS[toolName].handler = uploadAssetHandler; + } } }); Object.keys(safeOutputsConfig).forEach(configKey => { @@ -709,7 +493,8 @@ jobs: if (TOOLS[normalizedKey]) { return; } - if (!ALL_TOOLS.find(t => t.name === normalizedKey)) { + const isKnownTool = PREDEFINED_TOOLS[normalizedKey]; + if (!isKnownTool) { const jobConfig = safeOutputsConfig[configKey]; const dynamicTool = { name: normalizedKey, @@ -763,6 +548,7 @@ jobs: }); debug(` tools: ${Object.keys(TOOLS).join(", ")}`); if (!Object.keys(TOOLS).length) throw new Error("No tools enabled in configuration"); + console.error(`[${SERVER_INFO.name}] Filtered tools:\n${JSON.stringify(TOOLS, null, 2)}`); function handleMessage(req) { if (!req || typeof req !== "object") { debug(`Invalid message: not an object`); @@ -873,6 +659,7 @@ jobs: env: GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }} GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"create-discussion\":{\"max\":1},\"missing-tool\":{}}" + GITHUB_AW_SAFE_OUTPUTS_TOOLS: "{\"create_discussion\":{\"name\":\"create_discussion\",\"description\":\"Create a new GitHub discussion\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"body\":{\"description\":\"Discussion body/content\",\"type\":\"string\"},\"category\":{\"description\":\"Discussion category\",\"type\":\"string\"},\"title\":{\"description\":\"Discussion title\",\"type\":\"string\"}},\"required\":[\"title\",\"body\"],\"type\":\"object\"}},\"missing_tool\":{\"name\":\"missing_tool\",\"description\":\"Report a missing tool or functionality needed to complete tasks\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"alternatives\":{\"description\":\"Possible alternatives or workarounds\",\"type\":\"string\"},\"reason\":{\"description\":\"Why this tool is needed\",\"type\":\"string\"},\"tool\":{\"description\":\"Name of the missing tool\",\"type\":\"string\"}},\"required\":[\"tool\",\"reason\"],\"type\":\"object\"}}}" run: | mkdir -p /tmp/gh-aw/mcp-config mkdir -p /home/runner/.copilot diff --git a/.github/workflows/scout.lock.yml b/.github/workflows/scout.lock.yml index 157d7d87c7c..84286a3c394 100644 --- a/.github/workflows/scout.lock.yml +++ b/.github/workflows/scout.lock.yml @@ -1104,251 +1104,35 @@ jobs: }; }; const normTool = toolName => (toolName ? toolName.replace(/-/g, "_").toLowerCase() : undefined); - const ALL_TOOLS = [ - { - name: "create_issue", - description: "Create a new GitHub issue", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Issue title" }, - body: { type: "string", description: "Issue body/description" }, - labels: { - type: "array", - items: { type: "string" }, - description: "Issue labels", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_discussion", - description: "Create a new GitHub discussion", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Discussion title" }, - body: { type: "string", description: "Discussion body/content" }, - category: { type: "string", description: "Discussion category" }, - }, - additionalProperties: false, - }, - }, - { - name: "add_comment", - description: "Add a comment to a GitHub issue or pull request", - inputSchema: { - type: "object", - required: ["body"], - properties: { - body: { type: "string", description: "Comment body/content" }, - issue_number: { - type: "number", - description: "Issue or PR number (optional for current context)", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_pull_request", - description: "Create a new GitHub pull request", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Pull request title" }, - body: { - type: "string", - description: "Pull request body/description", - }, - branch: { - type: "string", - description: "Optional branch name. If not provided, the current branch will be used.", - }, - labels: { - type: "array", - items: { type: "string" }, - description: "Optional labels to add to the PR", - }, - }, - additionalProperties: false, - }, - handler: createPullRequestHandler, - }, - { - name: "create_pull_request_review_comment", - description: "Create a review comment on a GitHub pull request", - inputSchema: { - type: "object", - required: ["path", "line", "body"], - properties: { - path: { - type: "string", - description: "File path for the review comment", - }, - line: { - type: ["number", "string"], - description: "Line number for the comment", - }, - body: { type: "string", description: "Comment body content" }, - start_line: { - type: ["number", "string"], - description: "Optional start line for multi-line comments", - }, - side: { - type: "string", - enum: ["LEFT", "RIGHT"], - description: "Optional side of the diff: LEFT or RIGHT", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_code_scanning_alert", - description: "Create a code scanning alert. severity MUST be one of 'error', 'warning', 'info', 'note'.", - inputSchema: { - type: "object", - required: ["file", "line", "severity", "message"], - properties: { - file: { - type: "string", - description: "File path where the issue was found", - }, - line: { - type: ["number", "string"], - description: "Line number where the issue was found", - }, - severity: { - type: "string", - enum: ["error", "warning", "info", "note"], - description: - ' Security severity levels follow the industry-standard Common Vulnerability Scoring System (CVSS) that is also used for advisories in the GitHub Advisory Database and must be one of "error", "warning", "info", "note".', - }, - message: { - type: "string", - description: "Alert message describing the issue", - }, - column: { - type: ["number", "string"], - description: "Optional column number", - }, - ruleIdSuffix: { - type: "string", - description: "Optional rule ID suffix for uniqueness", - }, - }, - additionalProperties: false, - }, - }, - { - name: "add_labels", - description: "Add labels to a GitHub issue or pull request", - inputSchema: { - type: "object", - required: ["labels"], - properties: { - labels: { - type: "array", - items: { type: "string" }, - description: "Labels to add", - }, - issue_number: { - type: "number", - description: "Issue or PR number (optional for current context)", - }, - }, - additionalProperties: false, - }, - }, - { - name: "update_issue", - description: "Update a GitHub issue", - inputSchema: { - type: "object", - properties: { - status: { - type: "string", - enum: ["open", "closed"], - description: "Optional new issue status", - }, - title: { type: "string", description: "Optional new issue title" }, - body: { type: "string", description: "Optional new issue body" }, - issue_number: { - type: ["number", "string"], - description: "Optional issue number for target '*'", - }, - }, - additionalProperties: false, - }, - }, - { - name: "push_to_pull_request_branch", - description: "Push changes to a pull request branch", - inputSchema: { - type: "object", - required: ["message"], - properties: { - branch: { - type: "string", - description: "Optional branch name. If not provided, the current branch will be used.", - }, - message: { type: "string", description: "Commit message" }, - pull_request_number: { - type: ["number", "string"], - description: "Optional pull request number for target '*'", - }, - }, - additionalProperties: false, - }, - handler: pushToPullRequestBranchHandler, - }, - { - name: "upload_asset", - description: "Publish a file as a URL-addressable asset to an orphaned git branch", - inputSchema: { - type: "object", - required: ["path"], - properties: { - path: { - type: "string", - description: - "Path to the file to publish as an asset. Must be a file under the current workspace or /tmp directory. By default, images (.png, .jpg, .jpeg) are allowed, but can be configured via workflow settings.", - }, - }, - additionalProperties: false, - }, - handler: uploadAssetHandler, - }, - { - name: "missing_tool", - description: "Report a missing tool or functionality needed to complete tasks", - inputSchema: { - type: "object", - required: ["tool", "reason"], - properties: { - tool: { type: "string", description: "Name of the missing tool" }, - reason: { type: "string", description: "Why this tool is needed" }, - alternatives: { - type: "string", - description: "Possible alternatives or workarounds", - }, - }, - additionalProperties: false, - }, - }, - ]; + const toolsEnv = process.env.GITHUB_AW_SAFE_OUTPUTS_TOOLS; + if (!toolsEnv) { + throw new Error("GITHUB_AW_SAFE_OUTPUTS_TOOLS environment variable is required"); + } + let PREDEFINED_TOOLS = {}; + debug(`Loading tools from GITHUB_AW_SAFE_OUTPUTS_TOOLS environment variable`); + try { + PREDEFINED_TOOLS = JSON.parse(toolsEnv); + debug(`Loaded ${Object.keys(PREDEFINED_TOOLS).length} predefined tools from environment`); + } catch (error) { + debug(`Error parsing GITHUB_AW_SAFE_OUTPUTS_TOOLS: ${error instanceof Error ? error.message : String(error)}`); + throw new Error(`Failed to parse GITHUB_AW_SAFE_OUTPUTS_TOOLS: ${error instanceof Error ? error.message : String(error)}`); + } debug(`v${SERVER_INFO.version} ready on stdio`); debug(` output file: ${outputFile}`); debug(` config: ${JSON.stringify(safeOutputsConfig)}`); const TOOLS = {}; - ALL_TOOLS.forEach(tool => { - if (Object.keys(safeOutputsConfig).find(config => normTool(config) === tool.name)) { - TOOLS[tool.name] = tool; + debug(`Using pre-filtered tools from environment variable`); + Object.keys(PREDEFINED_TOOLS).forEach(toolName => { + const tool = PREDEFINED_TOOLS[toolName]; + TOOLS[toolName] = tool; + if (tool.hasHandler) { + if (toolName === "create_pull_request") { + TOOLS[toolName].handler = createPullRequestHandler; + } else if (toolName === "push_to_pull_request_branch") { + TOOLS[toolName].handler = pushToPullRequestBranchHandler; + } else if (toolName === "upload_asset") { + TOOLS[toolName].handler = uploadAssetHandler; + } } }); Object.keys(safeOutputsConfig).forEach(configKey => { @@ -1356,7 +1140,8 @@ jobs: if (TOOLS[normalizedKey]) { return; } - if (!ALL_TOOLS.find(t => t.name === normalizedKey)) { + const isKnownTool = PREDEFINED_TOOLS[normalizedKey]; + if (!isKnownTool) { const jobConfig = safeOutputsConfig[configKey]; const dynamicTool = { name: normalizedKey, @@ -1410,6 +1195,7 @@ jobs: }); debug(` tools: ${Object.keys(TOOLS).join(", ")}`); if (!Object.keys(TOOLS).length) throw new Error("No tools enabled in configuration"); + console.error(`[${SERVER_INFO.name}] Filtered tools:\n${JSON.stringify(TOOLS, null, 2)}`); function handleMessage(req) { if (!req || typeof req !== "object") { debug(`Invalid message: not an object`); @@ -1520,6 +1306,7 @@ jobs: env: GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }} GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-comment\":{\"max\":1},\"missing-tool\":{}}" + GITHUB_AW_SAFE_OUTPUTS_TOOLS: "{\"add_comment\":{\"name\":\"add_comment\",\"description\":\"Add a comment to a GitHub issue or pull request\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"body\":{\"description\":\"Comment body/content\",\"type\":\"string\"},\"issue_number\":{\"description\":\"Issue or PR number (optional for current context)\",\"type\":\"number\"}},\"required\":[\"body\"],\"type\":\"object\"}},\"missing_tool\":{\"name\":\"missing_tool\",\"description\":\"Report a missing tool or functionality needed to complete tasks\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"alternatives\":{\"description\":\"Possible alternatives or workarounds\",\"type\":\"string\"},\"reason\":{\"description\":\"Why this tool is needed\",\"type\":\"string\"},\"tool\":{\"description\":\"Name of the missing tool\",\"type\":\"string\"}},\"required\":[\"tool\",\"reason\"],\"type\":\"object\"}}}" run: | mkdir -p /tmp/gh-aw/mcp-config mkdir -p /home/runner/.copilot diff --git a/.github/workflows/security-fix-pr.lock.yml b/.github/workflows/security-fix-pr.lock.yml index de0d3b497d6..6c6d5f45b07 100644 --- a/.github/workflows/security-fix-pr.lock.yml +++ b/.github/workflows/security-fix-pr.lock.yml @@ -563,251 +563,35 @@ jobs: }; }; const normTool = toolName => (toolName ? toolName.replace(/-/g, "_").toLowerCase() : undefined); - const ALL_TOOLS = [ - { - name: "create_issue", - description: "Create a new GitHub issue", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Issue title" }, - body: { type: "string", description: "Issue body/description" }, - labels: { - type: "array", - items: { type: "string" }, - description: "Issue labels", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_discussion", - description: "Create a new GitHub discussion", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Discussion title" }, - body: { type: "string", description: "Discussion body/content" }, - category: { type: "string", description: "Discussion category" }, - }, - additionalProperties: false, - }, - }, - { - name: "add_comment", - description: "Add a comment to a GitHub issue or pull request", - inputSchema: { - type: "object", - required: ["body"], - properties: { - body: { type: "string", description: "Comment body/content" }, - issue_number: { - type: "number", - description: "Issue or PR number (optional for current context)", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_pull_request", - description: "Create a new GitHub pull request", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Pull request title" }, - body: { - type: "string", - description: "Pull request body/description", - }, - branch: { - type: "string", - description: "Optional branch name. If not provided, the current branch will be used.", - }, - labels: { - type: "array", - items: { type: "string" }, - description: "Optional labels to add to the PR", - }, - }, - additionalProperties: false, - }, - handler: createPullRequestHandler, - }, - { - name: "create_pull_request_review_comment", - description: "Create a review comment on a GitHub pull request", - inputSchema: { - type: "object", - required: ["path", "line", "body"], - properties: { - path: { - type: "string", - description: "File path for the review comment", - }, - line: { - type: ["number", "string"], - description: "Line number for the comment", - }, - body: { type: "string", description: "Comment body content" }, - start_line: { - type: ["number", "string"], - description: "Optional start line for multi-line comments", - }, - side: { - type: "string", - enum: ["LEFT", "RIGHT"], - description: "Optional side of the diff: LEFT or RIGHT", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_code_scanning_alert", - description: "Create a code scanning alert. severity MUST be one of 'error', 'warning', 'info', 'note'.", - inputSchema: { - type: "object", - required: ["file", "line", "severity", "message"], - properties: { - file: { - type: "string", - description: "File path where the issue was found", - }, - line: { - type: ["number", "string"], - description: "Line number where the issue was found", - }, - severity: { - type: "string", - enum: ["error", "warning", "info", "note"], - description: - ' Security severity levels follow the industry-standard Common Vulnerability Scoring System (CVSS) that is also used for advisories in the GitHub Advisory Database and must be one of "error", "warning", "info", "note".', - }, - message: { - type: "string", - description: "Alert message describing the issue", - }, - column: { - type: ["number", "string"], - description: "Optional column number", - }, - ruleIdSuffix: { - type: "string", - description: "Optional rule ID suffix for uniqueness", - }, - }, - additionalProperties: false, - }, - }, - { - name: "add_labels", - description: "Add labels to a GitHub issue or pull request", - inputSchema: { - type: "object", - required: ["labels"], - properties: { - labels: { - type: "array", - items: { type: "string" }, - description: "Labels to add", - }, - issue_number: { - type: "number", - description: "Issue or PR number (optional for current context)", - }, - }, - additionalProperties: false, - }, - }, - { - name: "update_issue", - description: "Update a GitHub issue", - inputSchema: { - type: "object", - properties: { - status: { - type: "string", - enum: ["open", "closed"], - description: "Optional new issue status", - }, - title: { type: "string", description: "Optional new issue title" }, - body: { type: "string", description: "Optional new issue body" }, - issue_number: { - type: ["number", "string"], - description: "Optional issue number for target '*'", - }, - }, - additionalProperties: false, - }, - }, - { - name: "push_to_pull_request_branch", - description: "Push changes to a pull request branch", - inputSchema: { - type: "object", - required: ["message"], - properties: { - branch: { - type: "string", - description: "Optional branch name. If not provided, the current branch will be used.", - }, - message: { type: "string", description: "Commit message" }, - pull_request_number: { - type: ["number", "string"], - description: "Optional pull request number for target '*'", - }, - }, - additionalProperties: false, - }, - handler: pushToPullRequestBranchHandler, - }, - { - name: "upload_asset", - description: "Publish a file as a URL-addressable asset to an orphaned git branch", - inputSchema: { - type: "object", - required: ["path"], - properties: { - path: { - type: "string", - description: - "Path to the file to publish as an asset. Must be a file under the current workspace or /tmp directory. By default, images (.png, .jpg, .jpeg) are allowed, but can be configured via workflow settings.", - }, - }, - additionalProperties: false, - }, - handler: uploadAssetHandler, - }, - { - name: "missing_tool", - description: "Report a missing tool or functionality needed to complete tasks", - inputSchema: { - type: "object", - required: ["tool", "reason"], - properties: { - tool: { type: "string", description: "Name of the missing tool" }, - reason: { type: "string", description: "Why this tool is needed" }, - alternatives: { - type: "string", - description: "Possible alternatives or workarounds", - }, - }, - additionalProperties: false, - }, - }, - ]; + const toolsEnv = process.env.GITHUB_AW_SAFE_OUTPUTS_TOOLS; + if (!toolsEnv) { + throw new Error("GITHUB_AW_SAFE_OUTPUTS_TOOLS environment variable is required"); + } + let PREDEFINED_TOOLS = {}; + debug(`Loading tools from GITHUB_AW_SAFE_OUTPUTS_TOOLS environment variable`); + try { + PREDEFINED_TOOLS = JSON.parse(toolsEnv); + debug(`Loaded ${Object.keys(PREDEFINED_TOOLS).length} predefined tools from environment`); + } catch (error) { + debug(`Error parsing GITHUB_AW_SAFE_OUTPUTS_TOOLS: ${error instanceof Error ? error.message : String(error)}`); + throw new Error(`Failed to parse GITHUB_AW_SAFE_OUTPUTS_TOOLS: ${error instanceof Error ? error.message : String(error)}`); + } debug(`v${SERVER_INFO.version} ready on stdio`); debug(` output file: ${outputFile}`); debug(` config: ${JSON.stringify(safeOutputsConfig)}`); const TOOLS = {}; - ALL_TOOLS.forEach(tool => { - if (Object.keys(safeOutputsConfig).find(config => normTool(config) === tool.name)) { - TOOLS[tool.name] = tool; + debug(`Using pre-filtered tools from environment variable`); + Object.keys(PREDEFINED_TOOLS).forEach(toolName => { + const tool = PREDEFINED_TOOLS[toolName]; + TOOLS[toolName] = tool; + if (tool.hasHandler) { + if (toolName === "create_pull_request") { + TOOLS[toolName].handler = createPullRequestHandler; + } else if (toolName === "push_to_pull_request_branch") { + TOOLS[toolName].handler = pushToPullRequestBranchHandler; + } else if (toolName === "upload_asset") { + TOOLS[toolName].handler = uploadAssetHandler; + } } }); Object.keys(safeOutputsConfig).forEach(configKey => { @@ -815,7 +599,8 @@ jobs: if (TOOLS[normalizedKey]) { return; } - if (!ALL_TOOLS.find(t => t.name === normalizedKey)) { + const isKnownTool = PREDEFINED_TOOLS[normalizedKey]; + if (!isKnownTool) { const jobConfig = safeOutputsConfig[configKey]; const dynamicTool = { name: normalizedKey, @@ -869,6 +654,7 @@ jobs: }); debug(` tools: ${Object.keys(TOOLS).join(", ")}`); if (!Object.keys(TOOLS).length) throw new Error("No tools enabled in configuration"); + console.error(`[${SERVER_INFO.name}] Filtered tools:\n${JSON.stringify(TOOLS, null, 2)}`); function handleMessage(req) { if (!req || typeof req !== "object") { debug(`Invalid message: not an object`); @@ -979,6 +765,7 @@ jobs: env: GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }} GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"create-pull-request\":{},\"missing-tool\":{}}" + GITHUB_AW_SAFE_OUTPUTS_TOOLS: "{\"create_pull_request\":{\"name\":\"create_pull_request\",\"description\":\"Create a new GitHub pull request\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"body\":{\"description\":\"Pull request body/description\",\"type\":\"string\"},\"branch\":{\"description\":\"Optional branch name. If not provided, the current branch will be used.\",\"type\":\"string\"},\"labels\":{\"description\":\"Optional labels to add to the PR\",\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"title\":{\"description\":\"Pull request title\",\"type\":\"string\"}},\"required\":[\"title\",\"body\"],\"type\":\"object\"},\"hasHandler\":true},\"missing_tool\":{\"name\":\"missing_tool\",\"description\":\"Report a missing tool or functionality needed to complete tasks\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"alternatives\":{\"description\":\"Possible alternatives or workarounds\",\"type\":\"string\"},\"reason\":{\"description\":\"Why this tool is needed\",\"type\":\"string\"},\"tool\":{\"description\":\"Name of the missing tool\",\"type\":\"string\"}},\"required\":[\"tool\",\"reason\"],\"type\":\"object\"}}}" run: | mkdir -p /tmp/gh-aw/mcp-config cat > /tmp/gh-aw/mcp-config/mcp-servers.json << 'EOF' @@ -1005,6 +792,7 @@ jobs: "env": { "GITHUB_AW_SAFE_OUTPUTS": "${{ env.GITHUB_AW_SAFE_OUTPUTS }}", "GITHUB_AW_SAFE_OUTPUTS_CONFIG": ${{ toJSON(env.GITHUB_AW_SAFE_OUTPUTS_CONFIG) }}, + "GITHUB_AW_SAFE_OUTPUTS_TOOLS": ${{ toJSON(env.GITHUB_AW_SAFE_OUTPUTS_TOOLS) }}, "GITHUB_AW_ASSETS_BRANCH": "${{ env.GITHUB_AW_ASSETS_BRANCH }}", "GITHUB_AW_ASSETS_MAX_SIZE_KB": "${{ env.GITHUB_AW_ASSETS_MAX_SIZE_KB }}", "GITHUB_AW_ASSETS_ALLOWED_EXTS": "${{ env.GITHUB_AW_ASSETS_ALLOWED_EXTS }}" diff --git a/.github/workflows/smoke-claude.lock.yml b/.github/workflows/smoke-claude.lock.yml index cf3c144f9bf..9d855b7e27b 100644 --- a/.github/workflows/smoke-claude.lock.yml +++ b/.github/workflows/smoke-claude.lock.yml @@ -561,251 +561,35 @@ jobs: }; }; const normTool = toolName => (toolName ? toolName.replace(/-/g, "_").toLowerCase() : undefined); - const ALL_TOOLS = [ - { - name: "create_issue", - description: "Create a new GitHub issue", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Issue title" }, - body: { type: "string", description: "Issue body/description" }, - labels: { - type: "array", - items: { type: "string" }, - description: "Issue labels", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_discussion", - description: "Create a new GitHub discussion", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Discussion title" }, - body: { type: "string", description: "Discussion body/content" }, - category: { type: "string", description: "Discussion category" }, - }, - additionalProperties: false, - }, - }, - { - name: "add_comment", - description: "Add a comment to a GitHub issue or pull request", - inputSchema: { - type: "object", - required: ["body"], - properties: { - body: { type: "string", description: "Comment body/content" }, - issue_number: { - type: "number", - description: "Issue or PR number (optional for current context)", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_pull_request", - description: "Create a new GitHub pull request", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Pull request title" }, - body: { - type: "string", - description: "Pull request body/description", - }, - branch: { - type: "string", - description: "Optional branch name. If not provided, the current branch will be used.", - }, - labels: { - type: "array", - items: { type: "string" }, - description: "Optional labels to add to the PR", - }, - }, - additionalProperties: false, - }, - handler: createPullRequestHandler, - }, - { - name: "create_pull_request_review_comment", - description: "Create a review comment on a GitHub pull request", - inputSchema: { - type: "object", - required: ["path", "line", "body"], - properties: { - path: { - type: "string", - description: "File path for the review comment", - }, - line: { - type: ["number", "string"], - description: "Line number for the comment", - }, - body: { type: "string", description: "Comment body content" }, - start_line: { - type: ["number", "string"], - description: "Optional start line for multi-line comments", - }, - side: { - type: "string", - enum: ["LEFT", "RIGHT"], - description: "Optional side of the diff: LEFT or RIGHT", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_code_scanning_alert", - description: "Create a code scanning alert. severity MUST be one of 'error', 'warning', 'info', 'note'.", - inputSchema: { - type: "object", - required: ["file", "line", "severity", "message"], - properties: { - file: { - type: "string", - description: "File path where the issue was found", - }, - line: { - type: ["number", "string"], - description: "Line number where the issue was found", - }, - severity: { - type: "string", - enum: ["error", "warning", "info", "note"], - description: - ' Security severity levels follow the industry-standard Common Vulnerability Scoring System (CVSS) that is also used for advisories in the GitHub Advisory Database and must be one of "error", "warning", "info", "note".', - }, - message: { - type: "string", - description: "Alert message describing the issue", - }, - column: { - type: ["number", "string"], - description: "Optional column number", - }, - ruleIdSuffix: { - type: "string", - description: "Optional rule ID suffix for uniqueness", - }, - }, - additionalProperties: false, - }, - }, - { - name: "add_labels", - description: "Add labels to a GitHub issue or pull request", - inputSchema: { - type: "object", - required: ["labels"], - properties: { - labels: { - type: "array", - items: { type: "string" }, - description: "Labels to add", - }, - issue_number: { - type: "number", - description: "Issue or PR number (optional for current context)", - }, - }, - additionalProperties: false, - }, - }, - { - name: "update_issue", - description: "Update a GitHub issue", - inputSchema: { - type: "object", - properties: { - status: { - type: "string", - enum: ["open", "closed"], - description: "Optional new issue status", - }, - title: { type: "string", description: "Optional new issue title" }, - body: { type: "string", description: "Optional new issue body" }, - issue_number: { - type: ["number", "string"], - description: "Optional issue number for target '*'", - }, - }, - additionalProperties: false, - }, - }, - { - name: "push_to_pull_request_branch", - description: "Push changes to a pull request branch", - inputSchema: { - type: "object", - required: ["message"], - properties: { - branch: { - type: "string", - description: "Optional branch name. If not provided, the current branch will be used.", - }, - message: { type: "string", description: "Commit message" }, - pull_request_number: { - type: ["number", "string"], - description: "Optional pull request number for target '*'", - }, - }, - additionalProperties: false, - }, - handler: pushToPullRequestBranchHandler, - }, - { - name: "upload_asset", - description: "Publish a file as a URL-addressable asset to an orphaned git branch", - inputSchema: { - type: "object", - required: ["path"], - properties: { - path: { - type: "string", - description: - "Path to the file to publish as an asset. Must be a file under the current workspace or /tmp directory. By default, images (.png, .jpg, .jpeg) are allowed, but can be configured via workflow settings.", - }, - }, - additionalProperties: false, - }, - handler: uploadAssetHandler, - }, - { - name: "missing_tool", - description: "Report a missing tool or functionality needed to complete tasks", - inputSchema: { - type: "object", - required: ["tool", "reason"], - properties: { - tool: { type: "string", description: "Name of the missing tool" }, - reason: { type: "string", description: "Why this tool is needed" }, - alternatives: { - type: "string", - description: "Possible alternatives or workarounds", - }, - }, - additionalProperties: false, - }, - }, - ]; + const toolsEnv = process.env.GITHUB_AW_SAFE_OUTPUTS_TOOLS; + if (!toolsEnv) { + throw new Error("GITHUB_AW_SAFE_OUTPUTS_TOOLS environment variable is required"); + } + let PREDEFINED_TOOLS = {}; + debug(`Loading tools from GITHUB_AW_SAFE_OUTPUTS_TOOLS environment variable`); + try { + PREDEFINED_TOOLS = JSON.parse(toolsEnv); + debug(`Loaded ${Object.keys(PREDEFINED_TOOLS).length} predefined tools from environment`); + } catch (error) { + debug(`Error parsing GITHUB_AW_SAFE_OUTPUTS_TOOLS: ${error instanceof Error ? error.message : String(error)}`); + throw new Error(`Failed to parse GITHUB_AW_SAFE_OUTPUTS_TOOLS: ${error instanceof Error ? error.message : String(error)}`); + } debug(`v${SERVER_INFO.version} ready on stdio`); debug(` output file: ${outputFile}`); debug(` config: ${JSON.stringify(safeOutputsConfig)}`); const TOOLS = {}; - ALL_TOOLS.forEach(tool => { - if (Object.keys(safeOutputsConfig).find(config => normTool(config) === tool.name)) { - TOOLS[tool.name] = tool; + debug(`Using pre-filtered tools from environment variable`); + Object.keys(PREDEFINED_TOOLS).forEach(toolName => { + const tool = PREDEFINED_TOOLS[toolName]; + TOOLS[toolName] = tool; + if (tool.hasHandler) { + if (toolName === "create_pull_request") { + TOOLS[toolName].handler = createPullRequestHandler; + } else if (toolName === "push_to_pull_request_branch") { + TOOLS[toolName].handler = pushToPullRequestBranchHandler; + } else if (toolName === "upload_asset") { + TOOLS[toolName].handler = uploadAssetHandler; + } } }); Object.keys(safeOutputsConfig).forEach(configKey => { @@ -813,7 +597,8 @@ jobs: if (TOOLS[normalizedKey]) { return; } - if (!ALL_TOOLS.find(t => t.name === normalizedKey)) { + const isKnownTool = PREDEFINED_TOOLS[normalizedKey]; + if (!isKnownTool) { const jobConfig = safeOutputsConfig[configKey]; const dynamicTool = { name: normalizedKey, @@ -867,6 +652,7 @@ jobs: }); debug(` tools: ${Object.keys(TOOLS).join(", ")}`); if (!Object.keys(TOOLS).length) throw new Error("No tools enabled in configuration"); + console.error(`[${SERVER_INFO.name}] Filtered tools:\n${JSON.stringify(TOOLS, null, 2)}`); function handleMessage(req) { if (!req || typeof req !== "object") { debug(`Invalid message: not an object`); @@ -977,6 +763,7 @@ jobs: env: GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }} GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"create-issue\":{\"max\":1,\"min\":1},\"missing-tool\":{}}" + GITHUB_AW_SAFE_OUTPUTS_TOOLS: "{\"create_issue\":{\"name\":\"create_issue\",\"description\":\"Create a new GitHub issue\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"body\":{\"description\":\"Issue body/description\",\"type\":\"string\"},\"labels\":{\"description\":\"Issue labels\",\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"title\":{\"description\":\"Issue title\",\"type\":\"string\"}},\"required\":[\"title\",\"body\"],\"type\":\"object\"}},\"missing_tool\":{\"name\":\"missing_tool\",\"description\":\"Report a missing tool or functionality needed to complete tasks\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"alternatives\":{\"description\":\"Possible alternatives or workarounds\",\"type\":\"string\"},\"reason\":{\"description\":\"Why this tool is needed\",\"type\":\"string\"},\"tool\":{\"description\":\"Name of the missing tool\",\"type\":\"string\"}},\"required\":[\"tool\",\"reason\"],\"type\":\"object\"}}}" run: | mkdir -p /tmp/gh-aw/mcp-config cat > /tmp/gh-aw/mcp-config/mcp-servers.json << 'EOF' @@ -1002,6 +789,7 @@ jobs: "env": { "GITHUB_AW_SAFE_OUTPUTS": "${{ env.GITHUB_AW_SAFE_OUTPUTS }}", "GITHUB_AW_SAFE_OUTPUTS_CONFIG": ${{ toJSON(env.GITHUB_AW_SAFE_OUTPUTS_CONFIG) }}, + "GITHUB_AW_SAFE_OUTPUTS_TOOLS": ${{ toJSON(env.GITHUB_AW_SAFE_OUTPUTS_TOOLS) }}, "GITHUB_AW_ASSETS_BRANCH": "${{ env.GITHUB_AW_ASSETS_BRANCH }}", "GITHUB_AW_ASSETS_MAX_SIZE_KB": "${{ env.GITHUB_AW_ASSETS_MAX_SIZE_KB }}", "GITHUB_AW_ASSETS_ALLOWED_EXTS": "${{ env.GITHUB_AW_ASSETS_ALLOWED_EXTS }}" diff --git a/.github/workflows/smoke-codex.lock.yml b/.github/workflows/smoke-codex.lock.yml index 4fcf628a6c2..f24cf1861b0 100644 --- a/.github/workflows/smoke-codex.lock.yml +++ b/.github/workflows/smoke-codex.lock.yml @@ -453,251 +453,35 @@ jobs: }; }; const normTool = toolName => (toolName ? toolName.replace(/-/g, "_").toLowerCase() : undefined); - const ALL_TOOLS = [ - { - name: "create_issue", - description: "Create a new GitHub issue", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Issue title" }, - body: { type: "string", description: "Issue body/description" }, - labels: { - type: "array", - items: { type: "string" }, - description: "Issue labels", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_discussion", - description: "Create a new GitHub discussion", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Discussion title" }, - body: { type: "string", description: "Discussion body/content" }, - category: { type: "string", description: "Discussion category" }, - }, - additionalProperties: false, - }, - }, - { - name: "add_comment", - description: "Add a comment to a GitHub issue or pull request", - inputSchema: { - type: "object", - required: ["body"], - properties: { - body: { type: "string", description: "Comment body/content" }, - issue_number: { - type: "number", - description: "Issue or PR number (optional for current context)", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_pull_request", - description: "Create a new GitHub pull request", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Pull request title" }, - body: { - type: "string", - description: "Pull request body/description", - }, - branch: { - type: "string", - description: "Optional branch name. If not provided, the current branch will be used.", - }, - labels: { - type: "array", - items: { type: "string" }, - description: "Optional labels to add to the PR", - }, - }, - additionalProperties: false, - }, - handler: createPullRequestHandler, - }, - { - name: "create_pull_request_review_comment", - description: "Create a review comment on a GitHub pull request", - inputSchema: { - type: "object", - required: ["path", "line", "body"], - properties: { - path: { - type: "string", - description: "File path for the review comment", - }, - line: { - type: ["number", "string"], - description: "Line number for the comment", - }, - body: { type: "string", description: "Comment body content" }, - start_line: { - type: ["number", "string"], - description: "Optional start line for multi-line comments", - }, - side: { - type: "string", - enum: ["LEFT", "RIGHT"], - description: "Optional side of the diff: LEFT or RIGHT", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_code_scanning_alert", - description: "Create a code scanning alert. severity MUST be one of 'error', 'warning', 'info', 'note'.", - inputSchema: { - type: "object", - required: ["file", "line", "severity", "message"], - properties: { - file: { - type: "string", - description: "File path where the issue was found", - }, - line: { - type: ["number", "string"], - description: "Line number where the issue was found", - }, - severity: { - type: "string", - enum: ["error", "warning", "info", "note"], - description: - ' Security severity levels follow the industry-standard Common Vulnerability Scoring System (CVSS) that is also used for advisories in the GitHub Advisory Database and must be one of "error", "warning", "info", "note".', - }, - message: { - type: "string", - description: "Alert message describing the issue", - }, - column: { - type: ["number", "string"], - description: "Optional column number", - }, - ruleIdSuffix: { - type: "string", - description: "Optional rule ID suffix for uniqueness", - }, - }, - additionalProperties: false, - }, - }, - { - name: "add_labels", - description: "Add labels to a GitHub issue or pull request", - inputSchema: { - type: "object", - required: ["labels"], - properties: { - labels: { - type: "array", - items: { type: "string" }, - description: "Labels to add", - }, - issue_number: { - type: "number", - description: "Issue or PR number (optional for current context)", - }, - }, - additionalProperties: false, - }, - }, - { - name: "update_issue", - description: "Update a GitHub issue", - inputSchema: { - type: "object", - properties: { - status: { - type: "string", - enum: ["open", "closed"], - description: "Optional new issue status", - }, - title: { type: "string", description: "Optional new issue title" }, - body: { type: "string", description: "Optional new issue body" }, - issue_number: { - type: ["number", "string"], - description: "Optional issue number for target '*'", - }, - }, - additionalProperties: false, - }, - }, - { - name: "push_to_pull_request_branch", - description: "Push changes to a pull request branch", - inputSchema: { - type: "object", - required: ["message"], - properties: { - branch: { - type: "string", - description: "Optional branch name. If not provided, the current branch will be used.", - }, - message: { type: "string", description: "Commit message" }, - pull_request_number: { - type: ["number", "string"], - description: "Optional pull request number for target '*'", - }, - }, - additionalProperties: false, - }, - handler: pushToPullRequestBranchHandler, - }, - { - name: "upload_asset", - description: "Publish a file as a URL-addressable asset to an orphaned git branch", - inputSchema: { - type: "object", - required: ["path"], - properties: { - path: { - type: "string", - description: - "Path to the file to publish as an asset. Must be a file under the current workspace or /tmp directory. By default, images (.png, .jpg, .jpeg) are allowed, but can be configured via workflow settings.", - }, - }, - additionalProperties: false, - }, - handler: uploadAssetHandler, - }, - { - name: "missing_tool", - description: "Report a missing tool or functionality needed to complete tasks", - inputSchema: { - type: "object", - required: ["tool", "reason"], - properties: { - tool: { type: "string", description: "Name of the missing tool" }, - reason: { type: "string", description: "Why this tool is needed" }, - alternatives: { - type: "string", - description: "Possible alternatives or workarounds", - }, - }, - additionalProperties: false, - }, - }, - ]; + const toolsEnv = process.env.GITHUB_AW_SAFE_OUTPUTS_TOOLS; + if (!toolsEnv) { + throw new Error("GITHUB_AW_SAFE_OUTPUTS_TOOLS environment variable is required"); + } + let PREDEFINED_TOOLS = {}; + debug(`Loading tools from GITHUB_AW_SAFE_OUTPUTS_TOOLS environment variable`); + try { + PREDEFINED_TOOLS = JSON.parse(toolsEnv); + debug(`Loaded ${Object.keys(PREDEFINED_TOOLS).length} predefined tools from environment`); + } catch (error) { + debug(`Error parsing GITHUB_AW_SAFE_OUTPUTS_TOOLS: ${error instanceof Error ? error.message : String(error)}`); + throw new Error(`Failed to parse GITHUB_AW_SAFE_OUTPUTS_TOOLS: ${error instanceof Error ? error.message : String(error)}`); + } debug(`v${SERVER_INFO.version} ready on stdio`); debug(` output file: ${outputFile}`); debug(` config: ${JSON.stringify(safeOutputsConfig)}`); const TOOLS = {}; - ALL_TOOLS.forEach(tool => { - if (Object.keys(safeOutputsConfig).find(config => normTool(config) === tool.name)) { - TOOLS[tool.name] = tool; + debug(`Using pre-filtered tools from environment variable`); + Object.keys(PREDEFINED_TOOLS).forEach(toolName => { + const tool = PREDEFINED_TOOLS[toolName]; + TOOLS[toolName] = tool; + if (tool.hasHandler) { + if (toolName === "create_pull_request") { + TOOLS[toolName].handler = createPullRequestHandler; + } else if (toolName === "push_to_pull_request_branch") { + TOOLS[toolName].handler = pushToPullRequestBranchHandler; + } else if (toolName === "upload_asset") { + TOOLS[toolName].handler = uploadAssetHandler; + } } }); Object.keys(safeOutputsConfig).forEach(configKey => { @@ -705,7 +489,8 @@ jobs: if (TOOLS[normalizedKey]) { return; } - if (!ALL_TOOLS.find(t => t.name === normalizedKey)) { + const isKnownTool = PREDEFINED_TOOLS[normalizedKey]; + if (!isKnownTool) { const jobConfig = safeOutputsConfig[configKey]; const dynamicTool = { name: normalizedKey, @@ -759,6 +544,7 @@ jobs: }); debug(` tools: ${Object.keys(TOOLS).join(", ")}`); if (!Object.keys(TOOLS).length) throw new Error("No tools enabled in configuration"); + console.error(`[${SERVER_INFO.name}] Filtered tools:\n${JSON.stringify(TOOLS, null, 2)}`); function handleMessage(req) { if (!req || typeof req !== "object") { debug(`Invalid message: not an object`); @@ -869,6 +655,7 @@ jobs: env: GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }} GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"create-issue\":{\"max\":1,\"min\":1},\"missing-tool\":{}}" + GITHUB_AW_SAFE_OUTPUTS_TOOLS: "{\"create_issue\":{\"name\":\"create_issue\",\"description\":\"Create a new GitHub issue\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"body\":{\"description\":\"Issue body/description\",\"type\":\"string\"},\"labels\":{\"description\":\"Issue labels\",\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"title\":{\"description\":\"Issue title\",\"type\":\"string\"}},\"required\":[\"title\",\"body\"],\"type\":\"object\"}},\"missing_tool\":{\"name\":\"missing_tool\",\"description\":\"Report a missing tool or functionality needed to complete tasks\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"alternatives\":{\"description\":\"Possible alternatives or workarounds\",\"type\":\"string\"},\"reason\":{\"description\":\"Why this tool is needed\",\"type\":\"string\"},\"tool\":{\"description\":\"Name of the missing tool\",\"type\":\"string\"}},\"required\":[\"tool\",\"reason\"],\"type\":\"object\"}}}" run: | mkdir -p /tmp/gh-aw/mcp-config cat > /tmp/gh-aw/mcp-config/config.toml << EOF diff --git a/.github/workflows/smoke-copilot.lock.yml b/.github/workflows/smoke-copilot.lock.yml index 25870ca5dcf..ab5eda014e3 100644 --- a/.github/workflows/smoke-copilot.lock.yml +++ b/.github/workflows/smoke-copilot.lock.yml @@ -455,251 +455,35 @@ jobs: }; }; const normTool = toolName => (toolName ? toolName.replace(/-/g, "_").toLowerCase() : undefined); - const ALL_TOOLS = [ - { - name: "create_issue", - description: "Create a new GitHub issue", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Issue title" }, - body: { type: "string", description: "Issue body/description" }, - labels: { - type: "array", - items: { type: "string" }, - description: "Issue labels", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_discussion", - description: "Create a new GitHub discussion", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Discussion title" }, - body: { type: "string", description: "Discussion body/content" }, - category: { type: "string", description: "Discussion category" }, - }, - additionalProperties: false, - }, - }, - { - name: "add_comment", - description: "Add a comment to a GitHub issue or pull request", - inputSchema: { - type: "object", - required: ["body"], - properties: { - body: { type: "string", description: "Comment body/content" }, - issue_number: { - type: "number", - description: "Issue or PR number (optional for current context)", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_pull_request", - description: "Create a new GitHub pull request", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Pull request title" }, - body: { - type: "string", - description: "Pull request body/description", - }, - branch: { - type: "string", - description: "Optional branch name. If not provided, the current branch will be used.", - }, - labels: { - type: "array", - items: { type: "string" }, - description: "Optional labels to add to the PR", - }, - }, - additionalProperties: false, - }, - handler: createPullRequestHandler, - }, - { - name: "create_pull_request_review_comment", - description: "Create a review comment on a GitHub pull request", - inputSchema: { - type: "object", - required: ["path", "line", "body"], - properties: { - path: { - type: "string", - description: "File path for the review comment", - }, - line: { - type: ["number", "string"], - description: "Line number for the comment", - }, - body: { type: "string", description: "Comment body content" }, - start_line: { - type: ["number", "string"], - description: "Optional start line for multi-line comments", - }, - side: { - type: "string", - enum: ["LEFT", "RIGHT"], - description: "Optional side of the diff: LEFT or RIGHT", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_code_scanning_alert", - description: "Create a code scanning alert. severity MUST be one of 'error', 'warning', 'info', 'note'.", - inputSchema: { - type: "object", - required: ["file", "line", "severity", "message"], - properties: { - file: { - type: "string", - description: "File path where the issue was found", - }, - line: { - type: ["number", "string"], - description: "Line number where the issue was found", - }, - severity: { - type: "string", - enum: ["error", "warning", "info", "note"], - description: - ' Security severity levels follow the industry-standard Common Vulnerability Scoring System (CVSS) that is also used for advisories in the GitHub Advisory Database and must be one of "error", "warning", "info", "note".', - }, - message: { - type: "string", - description: "Alert message describing the issue", - }, - column: { - type: ["number", "string"], - description: "Optional column number", - }, - ruleIdSuffix: { - type: "string", - description: "Optional rule ID suffix for uniqueness", - }, - }, - additionalProperties: false, - }, - }, - { - name: "add_labels", - description: "Add labels to a GitHub issue or pull request", - inputSchema: { - type: "object", - required: ["labels"], - properties: { - labels: { - type: "array", - items: { type: "string" }, - description: "Labels to add", - }, - issue_number: { - type: "number", - description: "Issue or PR number (optional for current context)", - }, - }, - additionalProperties: false, - }, - }, - { - name: "update_issue", - description: "Update a GitHub issue", - inputSchema: { - type: "object", - properties: { - status: { - type: "string", - enum: ["open", "closed"], - description: "Optional new issue status", - }, - title: { type: "string", description: "Optional new issue title" }, - body: { type: "string", description: "Optional new issue body" }, - issue_number: { - type: ["number", "string"], - description: "Optional issue number for target '*'", - }, - }, - additionalProperties: false, - }, - }, - { - name: "push_to_pull_request_branch", - description: "Push changes to a pull request branch", - inputSchema: { - type: "object", - required: ["message"], - properties: { - branch: { - type: "string", - description: "Optional branch name. If not provided, the current branch will be used.", - }, - message: { type: "string", description: "Commit message" }, - pull_request_number: { - type: ["number", "string"], - description: "Optional pull request number for target '*'", - }, - }, - additionalProperties: false, - }, - handler: pushToPullRequestBranchHandler, - }, - { - name: "upload_asset", - description: "Publish a file as a URL-addressable asset to an orphaned git branch", - inputSchema: { - type: "object", - required: ["path"], - properties: { - path: { - type: "string", - description: - "Path to the file to publish as an asset. Must be a file under the current workspace or /tmp directory. By default, images (.png, .jpg, .jpeg) are allowed, but can be configured via workflow settings.", - }, - }, - additionalProperties: false, - }, - handler: uploadAssetHandler, - }, - { - name: "missing_tool", - description: "Report a missing tool or functionality needed to complete tasks", - inputSchema: { - type: "object", - required: ["tool", "reason"], - properties: { - tool: { type: "string", description: "Name of the missing tool" }, - reason: { type: "string", description: "Why this tool is needed" }, - alternatives: { - type: "string", - description: "Possible alternatives or workarounds", - }, - }, - additionalProperties: false, - }, - }, - ]; + const toolsEnv = process.env.GITHUB_AW_SAFE_OUTPUTS_TOOLS; + if (!toolsEnv) { + throw new Error("GITHUB_AW_SAFE_OUTPUTS_TOOLS environment variable is required"); + } + let PREDEFINED_TOOLS = {}; + debug(`Loading tools from GITHUB_AW_SAFE_OUTPUTS_TOOLS environment variable`); + try { + PREDEFINED_TOOLS = JSON.parse(toolsEnv); + debug(`Loaded ${Object.keys(PREDEFINED_TOOLS).length} predefined tools from environment`); + } catch (error) { + debug(`Error parsing GITHUB_AW_SAFE_OUTPUTS_TOOLS: ${error instanceof Error ? error.message : String(error)}`); + throw new Error(`Failed to parse GITHUB_AW_SAFE_OUTPUTS_TOOLS: ${error instanceof Error ? error.message : String(error)}`); + } debug(`v${SERVER_INFO.version} ready on stdio`); debug(` output file: ${outputFile}`); debug(` config: ${JSON.stringify(safeOutputsConfig)}`); const TOOLS = {}; - ALL_TOOLS.forEach(tool => { - if (Object.keys(safeOutputsConfig).find(config => normTool(config) === tool.name)) { - TOOLS[tool.name] = tool; + debug(`Using pre-filtered tools from environment variable`); + Object.keys(PREDEFINED_TOOLS).forEach(toolName => { + const tool = PREDEFINED_TOOLS[toolName]; + TOOLS[toolName] = tool; + if (tool.hasHandler) { + if (toolName === "create_pull_request") { + TOOLS[toolName].handler = createPullRequestHandler; + } else if (toolName === "push_to_pull_request_branch") { + TOOLS[toolName].handler = pushToPullRequestBranchHandler; + } else if (toolName === "upload_asset") { + TOOLS[toolName].handler = uploadAssetHandler; + } } }); Object.keys(safeOutputsConfig).forEach(configKey => { @@ -707,7 +491,8 @@ jobs: if (TOOLS[normalizedKey]) { return; } - if (!ALL_TOOLS.find(t => t.name === normalizedKey)) { + const isKnownTool = PREDEFINED_TOOLS[normalizedKey]; + if (!isKnownTool) { const jobConfig = safeOutputsConfig[configKey]; const dynamicTool = { name: normalizedKey, @@ -761,6 +546,7 @@ jobs: }); debug(` tools: ${Object.keys(TOOLS).join(", ")}`); if (!Object.keys(TOOLS).length) throw new Error("No tools enabled in configuration"); + console.error(`[${SERVER_INFO.name}] Filtered tools:\n${JSON.stringify(TOOLS, null, 2)}`); function handleMessage(req) { if (!req || typeof req !== "object") { debug(`Invalid message: not an object`); @@ -871,6 +657,7 @@ jobs: env: GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }} GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"create-issue\":{\"max\":1,\"min\":1},\"missing-tool\":{}}" + GITHUB_AW_SAFE_OUTPUTS_TOOLS: "{\"create_issue\":{\"name\":\"create_issue\",\"description\":\"Create a new GitHub issue\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"body\":{\"description\":\"Issue body/description\",\"type\":\"string\"},\"labels\":{\"description\":\"Issue labels\",\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"title\":{\"description\":\"Issue title\",\"type\":\"string\"}},\"required\":[\"title\",\"body\"],\"type\":\"object\"}},\"missing_tool\":{\"name\":\"missing_tool\",\"description\":\"Report a missing tool or functionality needed to complete tasks\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"alternatives\":{\"description\":\"Possible alternatives or workarounds\",\"type\":\"string\"},\"reason\":{\"description\":\"Why this tool is needed\",\"type\":\"string\"},\"tool\":{\"description\":\"Name of the missing tool\",\"type\":\"string\"}},\"required\":[\"tool\",\"reason\"],\"type\":\"object\"}}}" run: | mkdir -p /tmp/gh-aw/mcp-config mkdir -p /home/runner/.copilot diff --git a/.github/workflows/smoke-genaiscript.lock.yml b/.github/workflows/smoke-genaiscript.lock.yml index a623bfc01a3..01eceff591d 100644 --- a/.github/workflows/smoke-genaiscript.lock.yml +++ b/.github/workflows/smoke-genaiscript.lock.yml @@ -455,251 +455,35 @@ jobs: }; }; const normTool = toolName => (toolName ? toolName.replace(/-/g, "_").toLowerCase() : undefined); - const ALL_TOOLS = [ - { - name: "create_issue", - description: "Create a new GitHub issue", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Issue title" }, - body: { type: "string", description: "Issue body/description" }, - labels: { - type: "array", - items: { type: "string" }, - description: "Issue labels", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_discussion", - description: "Create a new GitHub discussion", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Discussion title" }, - body: { type: "string", description: "Discussion body/content" }, - category: { type: "string", description: "Discussion category" }, - }, - additionalProperties: false, - }, - }, - { - name: "add_comment", - description: "Add a comment to a GitHub issue or pull request", - inputSchema: { - type: "object", - required: ["body"], - properties: { - body: { type: "string", description: "Comment body/content" }, - issue_number: { - type: "number", - description: "Issue or PR number (optional for current context)", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_pull_request", - description: "Create a new GitHub pull request", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Pull request title" }, - body: { - type: "string", - description: "Pull request body/description", - }, - branch: { - type: "string", - description: "Optional branch name. If not provided, the current branch will be used.", - }, - labels: { - type: "array", - items: { type: "string" }, - description: "Optional labels to add to the PR", - }, - }, - additionalProperties: false, - }, - handler: createPullRequestHandler, - }, - { - name: "create_pull_request_review_comment", - description: "Create a review comment on a GitHub pull request", - inputSchema: { - type: "object", - required: ["path", "line", "body"], - properties: { - path: { - type: "string", - description: "File path for the review comment", - }, - line: { - type: ["number", "string"], - description: "Line number for the comment", - }, - body: { type: "string", description: "Comment body content" }, - start_line: { - type: ["number", "string"], - description: "Optional start line for multi-line comments", - }, - side: { - type: "string", - enum: ["LEFT", "RIGHT"], - description: "Optional side of the diff: LEFT or RIGHT", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_code_scanning_alert", - description: "Create a code scanning alert. severity MUST be one of 'error', 'warning', 'info', 'note'.", - inputSchema: { - type: "object", - required: ["file", "line", "severity", "message"], - properties: { - file: { - type: "string", - description: "File path where the issue was found", - }, - line: { - type: ["number", "string"], - description: "Line number where the issue was found", - }, - severity: { - type: "string", - enum: ["error", "warning", "info", "note"], - description: - ' Security severity levels follow the industry-standard Common Vulnerability Scoring System (CVSS) that is also used for advisories in the GitHub Advisory Database and must be one of "error", "warning", "info", "note".', - }, - message: { - type: "string", - description: "Alert message describing the issue", - }, - column: { - type: ["number", "string"], - description: "Optional column number", - }, - ruleIdSuffix: { - type: "string", - description: "Optional rule ID suffix for uniqueness", - }, - }, - additionalProperties: false, - }, - }, - { - name: "add_labels", - description: "Add labels to a GitHub issue or pull request", - inputSchema: { - type: "object", - required: ["labels"], - properties: { - labels: { - type: "array", - items: { type: "string" }, - description: "Labels to add", - }, - issue_number: { - type: "number", - description: "Issue or PR number (optional for current context)", - }, - }, - additionalProperties: false, - }, - }, - { - name: "update_issue", - description: "Update a GitHub issue", - inputSchema: { - type: "object", - properties: { - status: { - type: "string", - enum: ["open", "closed"], - description: "Optional new issue status", - }, - title: { type: "string", description: "Optional new issue title" }, - body: { type: "string", description: "Optional new issue body" }, - issue_number: { - type: ["number", "string"], - description: "Optional issue number for target '*'", - }, - }, - additionalProperties: false, - }, - }, - { - name: "push_to_pull_request_branch", - description: "Push changes to a pull request branch", - inputSchema: { - type: "object", - required: ["message"], - properties: { - branch: { - type: "string", - description: "Optional branch name. If not provided, the current branch will be used.", - }, - message: { type: "string", description: "Commit message" }, - pull_request_number: { - type: ["number", "string"], - description: "Optional pull request number for target '*'", - }, - }, - additionalProperties: false, - }, - handler: pushToPullRequestBranchHandler, - }, - { - name: "upload_asset", - description: "Publish a file as a URL-addressable asset to an orphaned git branch", - inputSchema: { - type: "object", - required: ["path"], - properties: { - path: { - type: "string", - description: - "Path to the file to publish as an asset. Must be a file under the current workspace or /tmp directory. By default, images (.png, .jpg, .jpeg) are allowed, but can be configured via workflow settings.", - }, - }, - additionalProperties: false, - }, - handler: uploadAssetHandler, - }, - { - name: "missing_tool", - description: "Report a missing tool or functionality needed to complete tasks", - inputSchema: { - type: "object", - required: ["tool", "reason"], - properties: { - tool: { type: "string", description: "Name of the missing tool" }, - reason: { type: "string", description: "Why this tool is needed" }, - alternatives: { - type: "string", - description: "Possible alternatives or workarounds", - }, - }, - additionalProperties: false, - }, - }, - ]; + const toolsEnv = process.env.GITHUB_AW_SAFE_OUTPUTS_TOOLS; + if (!toolsEnv) { + throw new Error("GITHUB_AW_SAFE_OUTPUTS_TOOLS environment variable is required"); + } + let PREDEFINED_TOOLS = {}; + debug(`Loading tools from GITHUB_AW_SAFE_OUTPUTS_TOOLS environment variable`); + try { + PREDEFINED_TOOLS = JSON.parse(toolsEnv); + debug(`Loaded ${Object.keys(PREDEFINED_TOOLS).length} predefined tools from environment`); + } catch (error) { + debug(`Error parsing GITHUB_AW_SAFE_OUTPUTS_TOOLS: ${error instanceof Error ? error.message : String(error)}`); + throw new Error(`Failed to parse GITHUB_AW_SAFE_OUTPUTS_TOOLS: ${error instanceof Error ? error.message : String(error)}`); + } debug(`v${SERVER_INFO.version} ready on stdio`); debug(` output file: ${outputFile}`); debug(` config: ${JSON.stringify(safeOutputsConfig)}`); const TOOLS = {}; - ALL_TOOLS.forEach(tool => { - if (Object.keys(safeOutputsConfig).find(config => normTool(config) === tool.name)) { - TOOLS[tool.name] = tool; + debug(`Using pre-filtered tools from environment variable`); + Object.keys(PREDEFINED_TOOLS).forEach(toolName => { + const tool = PREDEFINED_TOOLS[toolName]; + TOOLS[toolName] = tool; + if (tool.hasHandler) { + if (toolName === "create_pull_request") { + TOOLS[toolName].handler = createPullRequestHandler; + } else if (toolName === "push_to_pull_request_branch") { + TOOLS[toolName].handler = pushToPullRequestBranchHandler; + } else if (toolName === "upload_asset") { + TOOLS[toolName].handler = uploadAssetHandler; + } } }); Object.keys(safeOutputsConfig).forEach(configKey => { @@ -707,7 +491,8 @@ jobs: if (TOOLS[normalizedKey]) { return; } - if (!ALL_TOOLS.find(t => t.name === normalizedKey)) { + const isKnownTool = PREDEFINED_TOOLS[normalizedKey]; + if (!isKnownTool) { const jobConfig = safeOutputsConfig[configKey]; const dynamicTool = { name: normalizedKey, @@ -761,6 +546,7 @@ jobs: }); debug(` tools: ${Object.keys(TOOLS).join(", ")}`); if (!Object.keys(TOOLS).length) throw new Error("No tools enabled in configuration"); + console.error(`[${SERVER_INFO.name}] Filtered tools:\n${JSON.stringify(TOOLS, null, 2)}`); function handleMessage(req) { if (!req || typeof req !== "object") { debug(`Invalid message: not an object`); @@ -871,6 +657,7 @@ jobs: env: GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }} GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"create-issue\":{\"max\":1,\"min\":1},\"missing-tool\":{}}" + GITHUB_AW_SAFE_OUTPUTS_TOOLS: "{\"create_issue\":{\"name\":\"create_issue\",\"description\":\"Create a new GitHub issue\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"body\":{\"description\":\"Issue body/description\",\"type\":\"string\"},\"labels\":{\"description\":\"Issue labels\",\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"title\":{\"description\":\"Issue title\",\"type\":\"string\"}},\"required\":[\"title\",\"body\"],\"type\":\"object\"}},\"missing_tool\":{\"name\":\"missing_tool\",\"description\":\"Report a missing tool or functionality needed to complete tasks\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"alternatives\":{\"description\":\"Possible alternatives or workarounds\",\"type\":\"string\"},\"reason\":{\"description\":\"Why this tool is needed\",\"type\":\"string\"},\"tool\":{\"description\":\"Name of the missing tool\",\"type\":\"string\"}},\"required\":[\"tool\",\"reason\"],\"type\":\"object\"}}}" run: | mkdir -p /tmp/gh-aw/mcp-config cat > /tmp/gh-aw/mcp-config/mcp-servers.json << 'EOF' diff --git a/.github/workflows/technical-doc-writer.lock.yml b/.github/workflows/technical-doc-writer.lock.yml index 94cfa7bdbc9..dbc1b22d062 100644 --- a/.github/workflows/technical-doc-writer.lock.yml +++ b/.github/workflows/technical-doc-writer.lock.yml @@ -600,251 +600,35 @@ jobs: }; }; const normTool = toolName => (toolName ? toolName.replace(/-/g, "_").toLowerCase() : undefined); - const ALL_TOOLS = [ - { - name: "create_issue", - description: "Create a new GitHub issue", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Issue title" }, - body: { type: "string", description: "Issue body/description" }, - labels: { - type: "array", - items: { type: "string" }, - description: "Issue labels", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_discussion", - description: "Create a new GitHub discussion", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Discussion title" }, - body: { type: "string", description: "Discussion body/content" }, - category: { type: "string", description: "Discussion category" }, - }, - additionalProperties: false, - }, - }, - { - name: "add_comment", - description: "Add a comment to a GitHub issue or pull request", - inputSchema: { - type: "object", - required: ["body"], - properties: { - body: { type: "string", description: "Comment body/content" }, - issue_number: { - type: "number", - description: "Issue or PR number (optional for current context)", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_pull_request", - description: "Create a new GitHub pull request", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Pull request title" }, - body: { - type: "string", - description: "Pull request body/description", - }, - branch: { - type: "string", - description: "Optional branch name. If not provided, the current branch will be used.", - }, - labels: { - type: "array", - items: { type: "string" }, - description: "Optional labels to add to the PR", - }, - }, - additionalProperties: false, - }, - handler: createPullRequestHandler, - }, - { - name: "create_pull_request_review_comment", - description: "Create a review comment on a GitHub pull request", - inputSchema: { - type: "object", - required: ["path", "line", "body"], - properties: { - path: { - type: "string", - description: "File path for the review comment", - }, - line: { - type: ["number", "string"], - description: "Line number for the comment", - }, - body: { type: "string", description: "Comment body content" }, - start_line: { - type: ["number", "string"], - description: "Optional start line for multi-line comments", - }, - side: { - type: "string", - enum: ["LEFT", "RIGHT"], - description: "Optional side of the diff: LEFT or RIGHT", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_code_scanning_alert", - description: "Create a code scanning alert. severity MUST be one of 'error', 'warning', 'info', 'note'.", - inputSchema: { - type: "object", - required: ["file", "line", "severity", "message"], - properties: { - file: { - type: "string", - description: "File path where the issue was found", - }, - line: { - type: ["number", "string"], - description: "Line number where the issue was found", - }, - severity: { - type: "string", - enum: ["error", "warning", "info", "note"], - description: - ' Security severity levels follow the industry-standard Common Vulnerability Scoring System (CVSS) that is also used for advisories in the GitHub Advisory Database and must be one of "error", "warning", "info", "note".', - }, - message: { - type: "string", - description: "Alert message describing the issue", - }, - column: { - type: ["number", "string"], - description: "Optional column number", - }, - ruleIdSuffix: { - type: "string", - description: "Optional rule ID suffix for uniqueness", - }, - }, - additionalProperties: false, - }, - }, - { - name: "add_labels", - description: "Add labels to a GitHub issue or pull request", - inputSchema: { - type: "object", - required: ["labels"], - properties: { - labels: { - type: "array", - items: { type: "string" }, - description: "Labels to add", - }, - issue_number: { - type: "number", - description: "Issue or PR number (optional for current context)", - }, - }, - additionalProperties: false, - }, - }, - { - name: "update_issue", - description: "Update a GitHub issue", - inputSchema: { - type: "object", - properties: { - status: { - type: "string", - enum: ["open", "closed"], - description: "Optional new issue status", - }, - title: { type: "string", description: "Optional new issue title" }, - body: { type: "string", description: "Optional new issue body" }, - issue_number: { - type: ["number", "string"], - description: "Optional issue number for target '*'", - }, - }, - additionalProperties: false, - }, - }, - { - name: "push_to_pull_request_branch", - description: "Push changes to a pull request branch", - inputSchema: { - type: "object", - required: ["message"], - properties: { - branch: { - type: "string", - description: "Optional branch name. If not provided, the current branch will be used.", - }, - message: { type: "string", description: "Commit message" }, - pull_request_number: { - type: ["number", "string"], - description: "Optional pull request number for target '*'", - }, - }, - additionalProperties: false, - }, - handler: pushToPullRequestBranchHandler, - }, - { - name: "upload_asset", - description: "Publish a file as a URL-addressable asset to an orphaned git branch", - inputSchema: { - type: "object", - required: ["path"], - properties: { - path: { - type: "string", - description: - "Path to the file to publish as an asset. Must be a file under the current workspace or /tmp directory. By default, images (.png, .jpg, .jpeg) are allowed, but can be configured via workflow settings.", - }, - }, - additionalProperties: false, - }, - handler: uploadAssetHandler, - }, - { - name: "missing_tool", - description: "Report a missing tool or functionality needed to complete tasks", - inputSchema: { - type: "object", - required: ["tool", "reason"], - properties: { - tool: { type: "string", description: "Name of the missing tool" }, - reason: { type: "string", description: "Why this tool is needed" }, - alternatives: { - type: "string", - description: "Possible alternatives or workarounds", - }, - }, - additionalProperties: false, - }, - }, - ]; + const toolsEnv = process.env.GITHUB_AW_SAFE_OUTPUTS_TOOLS; + if (!toolsEnv) { + throw new Error("GITHUB_AW_SAFE_OUTPUTS_TOOLS environment variable is required"); + } + let PREDEFINED_TOOLS = {}; + debug(`Loading tools from GITHUB_AW_SAFE_OUTPUTS_TOOLS environment variable`); + try { + PREDEFINED_TOOLS = JSON.parse(toolsEnv); + debug(`Loaded ${Object.keys(PREDEFINED_TOOLS).length} predefined tools from environment`); + } catch (error) { + debug(`Error parsing GITHUB_AW_SAFE_OUTPUTS_TOOLS: ${error instanceof Error ? error.message : String(error)}`); + throw new Error(`Failed to parse GITHUB_AW_SAFE_OUTPUTS_TOOLS: ${error instanceof Error ? error.message : String(error)}`); + } debug(`v${SERVER_INFO.version} ready on stdio`); debug(` output file: ${outputFile}`); debug(` config: ${JSON.stringify(safeOutputsConfig)}`); const TOOLS = {}; - ALL_TOOLS.forEach(tool => { - if (Object.keys(safeOutputsConfig).find(config => normTool(config) === tool.name)) { - TOOLS[tool.name] = tool; + debug(`Using pre-filtered tools from environment variable`); + Object.keys(PREDEFINED_TOOLS).forEach(toolName => { + const tool = PREDEFINED_TOOLS[toolName]; + TOOLS[toolName] = tool; + if (tool.hasHandler) { + if (toolName === "create_pull_request") { + TOOLS[toolName].handler = createPullRequestHandler; + } else if (toolName === "push_to_pull_request_branch") { + TOOLS[toolName].handler = pushToPullRequestBranchHandler; + } else if (toolName === "upload_asset") { + TOOLS[toolName].handler = uploadAssetHandler; + } } }); Object.keys(safeOutputsConfig).forEach(configKey => { @@ -852,7 +636,8 @@ jobs: if (TOOLS[normalizedKey]) { return; } - if (!ALL_TOOLS.find(t => t.name === normalizedKey)) { + const isKnownTool = PREDEFINED_TOOLS[normalizedKey]; + if (!isKnownTool) { const jobConfig = safeOutputsConfig[configKey]; const dynamicTool = { name: normalizedKey, @@ -906,6 +691,7 @@ jobs: }); debug(` tools: ${Object.keys(TOOLS).join(", ")}`); if (!Object.keys(TOOLS).length) throw new Error("No tools enabled in configuration"); + console.error(`[${SERVER_INFO.name}] Filtered tools:\n${JSON.stringify(TOOLS, null, 2)}`); function handleMessage(req) { if (!req || typeof req !== "object") { debug(`Invalid message: not an object`); @@ -1016,6 +802,7 @@ jobs: env: GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }} GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-comment\":{\"max\":1},\"create-pull-request\":{},\"missing-tool\":{},\"upload-asset\":{}}" + GITHUB_AW_SAFE_OUTPUTS_TOOLS: "{\"add_comment\":{\"name\":\"add_comment\",\"description\":\"Add a comment to a GitHub issue or pull request\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"body\":{\"description\":\"Comment body/content\",\"type\":\"string\"},\"issue_number\":{\"description\":\"Issue or PR number (optional for current context)\",\"type\":\"number\"}},\"required\":[\"body\"],\"type\":\"object\"}},\"create_pull_request\":{\"name\":\"create_pull_request\",\"description\":\"Create a new GitHub pull request\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"body\":{\"description\":\"Pull request body/description\",\"type\":\"string\"},\"branch\":{\"description\":\"Optional branch name. If not provided, the current branch will be used.\",\"type\":\"string\"},\"labels\":{\"description\":\"Optional labels to add to the PR\",\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"title\":{\"description\":\"Pull request title\",\"type\":\"string\"}},\"required\":[\"title\",\"body\"],\"type\":\"object\"},\"hasHandler\":true},\"missing_tool\":{\"name\":\"missing_tool\",\"description\":\"Report a missing tool or functionality needed to complete tasks\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"alternatives\":{\"description\":\"Possible alternatives or workarounds\",\"type\":\"string\"},\"reason\":{\"description\":\"Why this tool is needed\",\"type\":\"string\"},\"tool\":{\"description\":\"Name of the missing tool\",\"type\":\"string\"}},\"required\":[\"tool\",\"reason\"],\"type\":\"object\"}},\"upload_asset\":{\"name\":\"upload_asset\",\"description\":\"Publish a file as a URL-addressable asset to an orphaned git branch\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"path\":{\"description\":\"Path to the file to publish as an asset. Must be a file under the current workspace or /tmp directory. By default, images (.png, .jpg, .jpeg) are allowed, but can be configured via workflow settings.\",\"type\":\"string\"}},\"required\":[\"path\"],\"type\":\"object\"},\"hasHandler\":true}}" GITHUB_AW_ASSETS_BRANCH: "assets/${{ github.workflow }}" GITHUB_AW_ASSETS_MAX_SIZE_KB: 10240 GITHUB_AW_ASSETS_ALLOWED_EXTS: ".png,.jpg,.jpeg" @@ -1044,6 +831,7 @@ jobs: "env": { "GITHUB_AW_SAFE_OUTPUTS": "${{ env.GITHUB_AW_SAFE_OUTPUTS }}", "GITHUB_AW_SAFE_OUTPUTS_CONFIG": ${{ toJSON(env.GITHUB_AW_SAFE_OUTPUTS_CONFIG) }}, + "GITHUB_AW_SAFE_OUTPUTS_TOOLS": ${{ toJSON(env.GITHUB_AW_SAFE_OUTPUTS_TOOLS) }}, "GITHUB_AW_ASSETS_BRANCH": "${{ env.GITHUB_AW_ASSETS_BRANCH }}", "GITHUB_AW_ASSETS_MAX_SIZE_KB": "${{ env.GITHUB_AW_ASSETS_MAX_SIZE_KB }}", "GITHUB_AW_ASSETS_ALLOWED_EXTS": "${{ env.GITHUB_AW_ASSETS_ALLOWED_EXTS }}" diff --git a/.github/workflows/tidy.lock.yml b/.github/workflows/tidy.lock.yml index 7c49c9b0e09..26626d910bc 100644 --- a/.github/workflows/tidy.lock.yml +++ b/.github/workflows/tidy.lock.yml @@ -666,251 +666,35 @@ jobs: }; }; const normTool = toolName => (toolName ? toolName.replace(/-/g, "_").toLowerCase() : undefined); - const ALL_TOOLS = [ - { - name: "create_issue", - description: "Create a new GitHub issue", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Issue title" }, - body: { type: "string", description: "Issue body/description" }, - labels: { - type: "array", - items: { type: "string" }, - description: "Issue labels", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_discussion", - description: "Create a new GitHub discussion", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Discussion title" }, - body: { type: "string", description: "Discussion body/content" }, - category: { type: "string", description: "Discussion category" }, - }, - additionalProperties: false, - }, - }, - { - name: "add_comment", - description: "Add a comment to a GitHub issue or pull request", - inputSchema: { - type: "object", - required: ["body"], - properties: { - body: { type: "string", description: "Comment body/content" }, - issue_number: { - type: "number", - description: "Issue or PR number (optional for current context)", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_pull_request", - description: "Create a new GitHub pull request", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Pull request title" }, - body: { - type: "string", - description: "Pull request body/description", - }, - branch: { - type: "string", - description: "Optional branch name. If not provided, the current branch will be used.", - }, - labels: { - type: "array", - items: { type: "string" }, - description: "Optional labels to add to the PR", - }, - }, - additionalProperties: false, - }, - handler: createPullRequestHandler, - }, - { - name: "create_pull_request_review_comment", - description: "Create a review comment on a GitHub pull request", - inputSchema: { - type: "object", - required: ["path", "line", "body"], - properties: { - path: { - type: "string", - description: "File path for the review comment", - }, - line: { - type: ["number", "string"], - description: "Line number for the comment", - }, - body: { type: "string", description: "Comment body content" }, - start_line: { - type: ["number", "string"], - description: "Optional start line for multi-line comments", - }, - side: { - type: "string", - enum: ["LEFT", "RIGHT"], - description: "Optional side of the diff: LEFT or RIGHT", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_code_scanning_alert", - description: "Create a code scanning alert. severity MUST be one of 'error', 'warning', 'info', 'note'.", - inputSchema: { - type: "object", - required: ["file", "line", "severity", "message"], - properties: { - file: { - type: "string", - description: "File path where the issue was found", - }, - line: { - type: ["number", "string"], - description: "Line number where the issue was found", - }, - severity: { - type: "string", - enum: ["error", "warning", "info", "note"], - description: - ' Security severity levels follow the industry-standard Common Vulnerability Scoring System (CVSS) that is also used for advisories in the GitHub Advisory Database and must be one of "error", "warning", "info", "note".', - }, - message: { - type: "string", - description: "Alert message describing the issue", - }, - column: { - type: ["number", "string"], - description: "Optional column number", - }, - ruleIdSuffix: { - type: "string", - description: "Optional rule ID suffix for uniqueness", - }, - }, - additionalProperties: false, - }, - }, - { - name: "add_labels", - description: "Add labels to a GitHub issue or pull request", - inputSchema: { - type: "object", - required: ["labels"], - properties: { - labels: { - type: "array", - items: { type: "string" }, - description: "Labels to add", - }, - issue_number: { - type: "number", - description: "Issue or PR number (optional for current context)", - }, - }, - additionalProperties: false, - }, - }, - { - name: "update_issue", - description: "Update a GitHub issue", - inputSchema: { - type: "object", - properties: { - status: { - type: "string", - enum: ["open", "closed"], - description: "Optional new issue status", - }, - title: { type: "string", description: "Optional new issue title" }, - body: { type: "string", description: "Optional new issue body" }, - issue_number: { - type: ["number", "string"], - description: "Optional issue number for target '*'", - }, - }, - additionalProperties: false, - }, - }, - { - name: "push_to_pull_request_branch", - description: "Push changes to a pull request branch", - inputSchema: { - type: "object", - required: ["message"], - properties: { - branch: { - type: "string", - description: "Optional branch name. If not provided, the current branch will be used.", - }, - message: { type: "string", description: "Commit message" }, - pull_request_number: { - type: ["number", "string"], - description: "Optional pull request number for target '*'", - }, - }, - additionalProperties: false, - }, - handler: pushToPullRequestBranchHandler, - }, - { - name: "upload_asset", - description: "Publish a file as a URL-addressable asset to an orphaned git branch", - inputSchema: { - type: "object", - required: ["path"], - properties: { - path: { - type: "string", - description: - "Path to the file to publish as an asset. Must be a file under the current workspace or /tmp directory. By default, images (.png, .jpg, .jpeg) are allowed, but can be configured via workflow settings.", - }, - }, - additionalProperties: false, - }, - handler: uploadAssetHandler, - }, - { - name: "missing_tool", - description: "Report a missing tool or functionality needed to complete tasks", - inputSchema: { - type: "object", - required: ["tool", "reason"], - properties: { - tool: { type: "string", description: "Name of the missing tool" }, - reason: { type: "string", description: "Why this tool is needed" }, - alternatives: { - type: "string", - description: "Possible alternatives or workarounds", - }, - }, - additionalProperties: false, - }, - }, - ]; + const toolsEnv = process.env.GITHUB_AW_SAFE_OUTPUTS_TOOLS; + if (!toolsEnv) { + throw new Error("GITHUB_AW_SAFE_OUTPUTS_TOOLS environment variable is required"); + } + let PREDEFINED_TOOLS = {}; + debug(`Loading tools from GITHUB_AW_SAFE_OUTPUTS_TOOLS environment variable`); + try { + PREDEFINED_TOOLS = JSON.parse(toolsEnv); + debug(`Loaded ${Object.keys(PREDEFINED_TOOLS).length} predefined tools from environment`); + } catch (error) { + debug(`Error parsing GITHUB_AW_SAFE_OUTPUTS_TOOLS: ${error instanceof Error ? error.message : String(error)}`); + throw new Error(`Failed to parse GITHUB_AW_SAFE_OUTPUTS_TOOLS: ${error instanceof Error ? error.message : String(error)}`); + } debug(`v${SERVER_INFO.version} ready on stdio`); debug(` output file: ${outputFile}`); debug(` config: ${JSON.stringify(safeOutputsConfig)}`); const TOOLS = {}; - ALL_TOOLS.forEach(tool => { - if (Object.keys(safeOutputsConfig).find(config => normTool(config) === tool.name)) { - TOOLS[tool.name] = tool; + debug(`Using pre-filtered tools from environment variable`); + Object.keys(PREDEFINED_TOOLS).forEach(toolName => { + const tool = PREDEFINED_TOOLS[toolName]; + TOOLS[toolName] = tool; + if (tool.hasHandler) { + if (toolName === "create_pull_request") { + TOOLS[toolName].handler = createPullRequestHandler; + } else if (toolName === "push_to_pull_request_branch") { + TOOLS[toolName].handler = pushToPullRequestBranchHandler; + } else if (toolName === "upload_asset") { + TOOLS[toolName].handler = uploadAssetHandler; + } } }); Object.keys(safeOutputsConfig).forEach(configKey => { @@ -918,7 +702,8 @@ jobs: if (TOOLS[normalizedKey]) { return; } - if (!ALL_TOOLS.find(t => t.name === normalizedKey)) { + const isKnownTool = PREDEFINED_TOOLS[normalizedKey]; + if (!isKnownTool) { const jobConfig = safeOutputsConfig[configKey]; const dynamicTool = { name: normalizedKey, @@ -972,6 +757,7 @@ jobs: }); debug(` tools: ${Object.keys(TOOLS).join(", ")}`); if (!Object.keys(TOOLS).length) throw new Error("No tools enabled in configuration"); + console.error(`[${SERVER_INFO.name}] Filtered tools:\n${JSON.stringify(TOOLS, null, 2)}`); function handleMessage(req) { if (!req || typeof req !== "object") { debug(`Invalid message: not an object`); @@ -1082,6 +868,7 @@ jobs: env: GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }} GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"create-pull-request\":{},\"missing-tool\":{},\"push-to-pull-request-branch\":{}}" + GITHUB_AW_SAFE_OUTPUTS_TOOLS: "{\"create_pull_request\":{\"name\":\"create_pull_request\",\"description\":\"Create a new GitHub pull request\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"body\":{\"description\":\"Pull request body/description\",\"type\":\"string\"},\"branch\":{\"description\":\"Optional branch name. If not provided, the current branch will be used.\",\"type\":\"string\"},\"labels\":{\"description\":\"Optional labels to add to the PR\",\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"title\":{\"description\":\"Pull request title\",\"type\":\"string\"}},\"required\":[\"title\",\"body\"],\"type\":\"object\"},\"hasHandler\":true},\"missing_tool\":{\"name\":\"missing_tool\",\"description\":\"Report a missing tool or functionality needed to complete tasks\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"alternatives\":{\"description\":\"Possible alternatives or workarounds\",\"type\":\"string\"},\"reason\":{\"description\":\"Why this tool is needed\",\"type\":\"string\"},\"tool\":{\"description\":\"Name of the missing tool\",\"type\":\"string\"}},\"required\":[\"tool\",\"reason\"],\"type\":\"object\"}},\"push_to_pull_request_branch\":{\"name\":\"push_to_pull_request_branch\",\"description\":\"Push changes to a pull request branch\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"branch\":{\"description\":\"Optional branch name. If not provided, the current branch will be used.\",\"type\":\"string\"},\"message\":{\"description\":\"Commit message\",\"type\":\"string\"},\"pull_request_number\":{\"description\":\"Optional pull request number for target '*'\",\"type\":[\"number\",\"string\"]}},\"required\":[\"message\"],\"type\":\"object\"},\"hasHandler\":true}}" run: | mkdir -p /tmp/gh-aw/mcp-config mkdir -p /home/runner/.copilot diff --git a/.github/workflows/unbloat-docs.lock.yml b/.github/workflows/unbloat-docs.lock.yml index 5e213fd3b24..57e7c215091 100644 --- a/.github/workflows/unbloat-docs.lock.yml +++ b/.github/workflows/unbloat-docs.lock.yml @@ -753,251 +753,35 @@ jobs: }; }; const normTool = toolName => (toolName ? toolName.replace(/-/g, "_").toLowerCase() : undefined); - const ALL_TOOLS = [ - { - name: "create_issue", - description: "Create a new GitHub issue", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Issue title" }, - body: { type: "string", description: "Issue body/description" }, - labels: { - type: "array", - items: { type: "string" }, - description: "Issue labels", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_discussion", - description: "Create a new GitHub discussion", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Discussion title" }, - body: { type: "string", description: "Discussion body/content" }, - category: { type: "string", description: "Discussion category" }, - }, - additionalProperties: false, - }, - }, - { - name: "add_comment", - description: "Add a comment to a GitHub issue or pull request", - inputSchema: { - type: "object", - required: ["body"], - properties: { - body: { type: "string", description: "Comment body/content" }, - issue_number: { - type: "number", - description: "Issue or PR number (optional for current context)", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_pull_request", - description: "Create a new GitHub pull request", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Pull request title" }, - body: { - type: "string", - description: "Pull request body/description", - }, - branch: { - type: "string", - description: "Optional branch name. If not provided, the current branch will be used.", - }, - labels: { - type: "array", - items: { type: "string" }, - description: "Optional labels to add to the PR", - }, - }, - additionalProperties: false, - }, - handler: createPullRequestHandler, - }, - { - name: "create_pull_request_review_comment", - description: "Create a review comment on a GitHub pull request", - inputSchema: { - type: "object", - required: ["path", "line", "body"], - properties: { - path: { - type: "string", - description: "File path for the review comment", - }, - line: { - type: ["number", "string"], - description: "Line number for the comment", - }, - body: { type: "string", description: "Comment body content" }, - start_line: { - type: ["number", "string"], - description: "Optional start line for multi-line comments", - }, - side: { - type: "string", - enum: ["LEFT", "RIGHT"], - description: "Optional side of the diff: LEFT or RIGHT", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_code_scanning_alert", - description: "Create a code scanning alert. severity MUST be one of 'error', 'warning', 'info', 'note'.", - inputSchema: { - type: "object", - required: ["file", "line", "severity", "message"], - properties: { - file: { - type: "string", - description: "File path where the issue was found", - }, - line: { - type: ["number", "string"], - description: "Line number where the issue was found", - }, - severity: { - type: "string", - enum: ["error", "warning", "info", "note"], - description: - ' Security severity levels follow the industry-standard Common Vulnerability Scoring System (CVSS) that is also used for advisories in the GitHub Advisory Database and must be one of "error", "warning", "info", "note".', - }, - message: { - type: "string", - description: "Alert message describing the issue", - }, - column: { - type: ["number", "string"], - description: "Optional column number", - }, - ruleIdSuffix: { - type: "string", - description: "Optional rule ID suffix for uniqueness", - }, - }, - additionalProperties: false, - }, - }, - { - name: "add_labels", - description: "Add labels to a GitHub issue or pull request", - inputSchema: { - type: "object", - required: ["labels"], - properties: { - labels: { - type: "array", - items: { type: "string" }, - description: "Labels to add", - }, - issue_number: { - type: "number", - description: "Issue or PR number (optional for current context)", - }, - }, - additionalProperties: false, - }, - }, - { - name: "update_issue", - description: "Update a GitHub issue", - inputSchema: { - type: "object", - properties: { - status: { - type: "string", - enum: ["open", "closed"], - description: "Optional new issue status", - }, - title: { type: "string", description: "Optional new issue title" }, - body: { type: "string", description: "Optional new issue body" }, - issue_number: { - type: ["number", "string"], - description: "Optional issue number for target '*'", - }, - }, - additionalProperties: false, - }, - }, - { - name: "push_to_pull_request_branch", - description: "Push changes to a pull request branch", - inputSchema: { - type: "object", - required: ["message"], - properties: { - branch: { - type: "string", - description: "Optional branch name. If not provided, the current branch will be used.", - }, - message: { type: "string", description: "Commit message" }, - pull_request_number: { - type: ["number", "string"], - description: "Optional pull request number for target '*'", - }, - }, - additionalProperties: false, - }, - handler: pushToPullRequestBranchHandler, - }, - { - name: "upload_asset", - description: "Publish a file as a URL-addressable asset to an orphaned git branch", - inputSchema: { - type: "object", - required: ["path"], - properties: { - path: { - type: "string", - description: - "Path to the file to publish as an asset. Must be a file under the current workspace or /tmp directory. By default, images (.png, .jpg, .jpeg) are allowed, but can be configured via workflow settings.", - }, - }, - additionalProperties: false, - }, - handler: uploadAssetHandler, - }, - { - name: "missing_tool", - description: "Report a missing tool or functionality needed to complete tasks", - inputSchema: { - type: "object", - required: ["tool", "reason"], - properties: { - tool: { type: "string", description: "Name of the missing tool" }, - reason: { type: "string", description: "Why this tool is needed" }, - alternatives: { - type: "string", - description: "Possible alternatives or workarounds", - }, - }, - additionalProperties: false, - }, - }, - ]; + const toolsEnv = process.env.GITHUB_AW_SAFE_OUTPUTS_TOOLS; + if (!toolsEnv) { + throw new Error("GITHUB_AW_SAFE_OUTPUTS_TOOLS environment variable is required"); + } + let PREDEFINED_TOOLS = {}; + debug(`Loading tools from GITHUB_AW_SAFE_OUTPUTS_TOOLS environment variable`); + try { + PREDEFINED_TOOLS = JSON.parse(toolsEnv); + debug(`Loaded ${Object.keys(PREDEFINED_TOOLS).length} predefined tools from environment`); + } catch (error) { + debug(`Error parsing GITHUB_AW_SAFE_OUTPUTS_TOOLS: ${error instanceof Error ? error.message : String(error)}`); + throw new Error(`Failed to parse GITHUB_AW_SAFE_OUTPUTS_TOOLS: ${error instanceof Error ? error.message : String(error)}`); + } debug(`v${SERVER_INFO.version} ready on stdio`); debug(` output file: ${outputFile}`); debug(` config: ${JSON.stringify(safeOutputsConfig)}`); const TOOLS = {}; - ALL_TOOLS.forEach(tool => { - if (Object.keys(safeOutputsConfig).find(config => normTool(config) === tool.name)) { - TOOLS[tool.name] = tool; + debug(`Using pre-filtered tools from environment variable`); + Object.keys(PREDEFINED_TOOLS).forEach(toolName => { + const tool = PREDEFINED_TOOLS[toolName]; + TOOLS[toolName] = tool; + if (tool.hasHandler) { + if (toolName === "create_pull_request") { + TOOLS[toolName].handler = createPullRequestHandler; + } else if (toolName === "push_to_pull_request_branch") { + TOOLS[toolName].handler = pushToPullRequestBranchHandler; + } else if (toolName === "upload_asset") { + TOOLS[toolName].handler = uploadAssetHandler; + } } }); Object.keys(safeOutputsConfig).forEach(configKey => { @@ -1005,7 +789,8 @@ jobs: if (TOOLS[normalizedKey]) { return; } - if (!ALL_TOOLS.find(t => t.name === normalizedKey)) { + const isKnownTool = PREDEFINED_TOOLS[normalizedKey]; + if (!isKnownTool) { const jobConfig = safeOutputsConfig[configKey]; const dynamicTool = { name: normalizedKey, @@ -1059,6 +844,7 @@ jobs: }); debug(` tools: ${Object.keys(TOOLS).join(", ")}`); if (!Object.keys(TOOLS).length) throw new Error("No tools enabled in configuration"); + console.error(`[${SERVER_INFO.name}] Filtered tools:\n${JSON.stringify(TOOLS, null, 2)}`); function handleMessage(req) { if (!req || typeof req !== "object") { debug(`Invalid message: not an object`); @@ -1169,6 +955,7 @@ jobs: env: GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }} GITHUB_AW_SAFE_OUTPUTS_CONFIG: "{\"add-comment\":{\"max\":1},\"create-pull-request\":{},\"missing-tool\":{}}" + GITHUB_AW_SAFE_OUTPUTS_TOOLS: "{\"add_comment\":{\"name\":\"add_comment\",\"description\":\"Add a comment to a GitHub issue or pull request\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"body\":{\"description\":\"Comment body/content\",\"type\":\"string\"},\"issue_number\":{\"description\":\"Issue or PR number (optional for current context)\",\"type\":\"number\"}},\"required\":[\"body\"],\"type\":\"object\"}},\"create_pull_request\":{\"name\":\"create_pull_request\",\"description\":\"Create a new GitHub pull request\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"body\":{\"description\":\"Pull request body/description\",\"type\":\"string\"},\"branch\":{\"description\":\"Optional branch name. If not provided, the current branch will be used.\",\"type\":\"string\"},\"labels\":{\"description\":\"Optional labels to add to the PR\",\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"title\":{\"description\":\"Pull request title\",\"type\":\"string\"}},\"required\":[\"title\",\"body\"],\"type\":\"object\"},\"hasHandler\":true},\"missing_tool\":{\"name\":\"missing_tool\",\"description\":\"Report a missing tool or functionality needed to complete tasks\",\"inputSchema\":{\"additionalProperties\":false,\"properties\":{\"alternatives\":{\"description\":\"Possible alternatives or workarounds\",\"type\":\"string\"},\"reason\":{\"description\":\"Why this tool is needed\",\"type\":\"string\"},\"tool\":{\"description\":\"Name of the missing tool\",\"type\":\"string\"}},\"required\":[\"tool\",\"reason\"],\"type\":\"object\"}}}" run: | mkdir -p /tmp/gh-aw/mcp-config cat > /tmp/gh-aw/mcp-config/mcp-servers.json << 'EOF' @@ -1194,6 +981,7 @@ jobs: "env": { "GITHUB_AW_SAFE_OUTPUTS": "${{ env.GITHUB_AW_SAFE_OUTPUTS }}", "GITHUB_AW_SAFE_OUTPUTS_CONFIG": ${{ toJSON(env.GITHUB_AW_SAFE_OUTPUTS_CONFIG) }}, + "GITHUB_AW_SAFE_OUTPUTS_TOOLS": ${{ toJSON(env.GITHUB_AW_SAFE_OUTPUTS_TOOLS) }}, "GITHUB_AW_ASSETS_BRANCH": "${{ env.GITHUB_AW_ASSETS_BRANCH }}", "GITHUB_AW_ASSETS_MAX_SIZE_KB": "${{ env.GITHUB_AW_ASSETS_MAX_SIZE_KB }}", "GITHUB_AW_ASSETS_ALLOWED_EXTS": "${{ env.GITHUB_AW_ASSETS_ALLOWED_EXTS }}" diff --git a/pkg/cli/imports_test.go b/pkg/cli/imports_test.go index ce1c7585915..7a15cfddb96 100644 --- a/pkg/cli/imports_test.go +++ b/pkg/cli/imports_test.go @@ -217,7 +217,7 @@ func TestProcessIncludesWithWorkflowSpec_RealWorldScenario(t *testing.T) { // The workflow has: {{#import? agentics/weekly-research.config}} // Previously this would generate: githubnext/agentics/@e2770974... // Now it should generate: githubnext/agentics/agentics/weekly-research.config@e2770974... - + content := `--- on: schedule: diff --git a/pkg/workflow/claude_engine.go b/pkg/workflow/claude_engine.go index 120281d2a5f..7aa3bf255ce 100644 --- a/pkg/workflow/claude_engine.go +++ b/pkg/workflow/claude_engine.go @@ -796,6 +796,7 @@ func (e *ClaudeEngine) renderSafeOutputsMCPConfig(yaml *strings.Builder, isLast yaml.WriteString(" \"env\": {\n") yaml.WriteString(" \"GITHUB_AW_SAFE_OUTPUTS\": \"${{ env.GITHUB_AW_SAFE_OUTPUTS }}\",\n") yaml.WriteString(" \"GITHUB_AW_SAFE_OUTPUTS_CONFIG\": ${{ toJSON(env.GITHUB_AW_SAFE_OUTPUTS_CONFIG) }},\n") + yaml.WriteString(" \"GITHUB_AW_SAFE_OUTPUTS_TOOLS\": ${{ toJSON(env.GITHUB_AW_SAFE_OUTPUTS_TOOLS) }},\n") yaml.WriteString(" \"GITHUB_AW_ASSETS_BRANCH\": \"${{ env.GITHUB_AW_ASSETS_BRANCH }}\",\n") yaml.WriteString(" \"GITHUB_AW_ASSETS_MAX_SIZE_KB\": \"${{ env.GITHUB_AW_ASSETS_MAX_SIZE_KB }}\",\n") yaml.WriteString(" \"GITHUB_AW_ASSETS_ALLOWED_EXTS\": \"${{ env.GITHUB_AW_ASSETS_ALLOWED_EXTS }}\"\n") diff --git a/pkg/workflow/data/safe_outputs_tools.json b/pkg/workflow/data/safe_outputs_tools.json new file mode 100644 index 00000000000..30a39a08b1f --- /dev/null +++ b/pkg/workflow/data/safe_outputs_tools.json @@ -0,0 +1,236 @@ +[ + { + "name": "create_issue", + "description": "Create a new GitHub issue", + "inputSchema": { + "type": "object", + "required": ["title", "body"], + "properties": { + "title": { "type": "string", "description": "Issue title" }, + "body": { "type": "string", "description": "Issue body/description" }, + "labels": { + "type": "array", + "items": { "type": "string" }, + "description": "Issue labels" + } + }, + "additionalProperties": false + } + }, + { + "name": "create_discussion", + "description": "Create a new GitHub discussion", + "inputSchema": { + "type": "object", + "required": ["title", "body"], + "properties": { + "title": { "type": "string", "description": "Discussion title" }, + "body": { "type": "string", "description": "Discussion body/content" }, + "category": { "type": "string", "description": "Discussion category" } + }, + "additionalProperties": false + } + }, + { + "name": "add_comment", + "description": "Add a comment to a GitHub issue or pull request", + "inputSchema": { + "type": "object", + "required": ["body"], + "properties": { + "body": { "type": "string", "description": "Comment body/content" }, + "issue_number": { + "type": "number", + "description": "Issue or PR number (optional for current context)" + } + }, + "additionalProperties": false + } + }, + { + "name": "create_pull_request", + "description": "Create a new GitHub pull request", + "inputSchema": { + "type": "object", + "required": ["title", "body"], + "properties": { + "title": { "type": "string", "description": "Pull request title" }, + "body": { + "type": "string", + "description": "Pull request body/description" + }, + "branch": { + "type": "string", + "description": "Optional branch name. If not provided, the current branch will be used." + }, + "labels": { + "type": "array", + "items": { "type": "string" }, + "description": "Optional labels to add to the PR" + } + }, + "additionalProperties": false + }, + "hasHandler": true + }, + { + "name": "create_pull_request_review_comment", + "description": "Create a review comment on a GitHub pull request", + "inputSchema": { + "type": "object", + "required": ["path", "line", "body"], + "properties": { + "path": { + "type": "string", + "description": "File path for the review comment" + }, + "line": { + "type": ["number", "string"], + "description": "Line number for the comment" + }, + "body": { "type": "string", "description": "Comment body content" }, + "start_line": { + "type": ["number", "string"], + "description": "Optional start line for multi-line comments" + }, + "side": { + "type": "string", + "enum": ["LEFT", "RIGHT"], + "description": "Optional side of the diff: LEFT or RIGHT" + } + }, + "additionalProperties": false + } + }, + { + "name": "create_code_scanning_alert", + "description": "Create a code scanning alert. severity MUST be one of 'error', 'warning', 'info', 'note'.", + "inputSchema": { + "type": "object", + "required": ["file", "line", "severity", "message"], + "properties": { + "file": { + "type": "string", + "description": "File path where the issue was found" + }, + "line": { + "type": ["number", "string"], + "description": "Line number where the issue was found" + }, + "severity": { + "type": "string", + "enum": ["error", "warning", "info", "note"], + "description": " Security severity levels follow the industry-standard Common Vulnerability Scoring System (CVSS) that is also used for advisories in the GitHub Advisory Database and must be one of \"error\", \"warning\", \"info\", \"note\"." + }, + "message": { + "type": "string", + "description": "Alert message describing the issue" + }, + "column": { + "type": ["number", "string"], + "description": "Optional column number" + }, + "ruleIdSuffix": { + "type": "string", + "description": "Optional rule ID suffix for uniqueness" + } + }, + "additionalProperties": false + } + }, + { + "name": "add_labels", + "description": "Add labels to a GitHub issue or pull request", + "inputSchema": { + "type": "object", + "required": ["labels"], + "properties": { + "labels": { + "type": "array", + "items": { "type": "string" }, + "description": "Labels to add" + }, + "issue_number": { + "type": "number", + "description": "Issue or PR number (optional for current context)" + } + }, + "additionalProperties": false + } + }, + { + "name": "update_issue", + "description": "Update a GitHub issue", + "inputSchema": { + "type": "object", + "properties": { + "status": { + "type": "string", + "enum": ["open", "closed"], + "description": "Optional new issue status" + }, + "title": { "type": "string", "description": "Optional new issue title" }, + "body": { "type": "string", "description": "Optional new issue body" }, + "issue_number": { + "type": ["number", "string"], + "description": "Optional issue number for target '*'" + } + }, + "additionalProperties": false + } + }, + { + "name": "push_to_pull_request_branch", + "description": "Push changes to a pull request branch", + "inputSchema": { + "type": "object", + "required": ["message"], + "properties": { + "branch": { + "type": "string", + "description": "Optional branch name. If not provided, the current branch will be used." + }, + "message": { "type": "string", "description": "Commit message" }, + "pull_request_number": { + "type": ["number", "string"], + "description": "Optional pull request number for target '*'" + } + }, + "additionalProperties": false + }, + "hasHandler": true + }, + { + "name": "upload_asset", + "description": "Publish a file as a URL-addressable asset to an orphaned git branch", + "inputSchema": { + "type": "object", + "required": ["path"], + "properties": { + "path": { + "type": "string", + "description": "Path to the file to publish as an asset. Must be a file under the current workspace or /tmp directory. By default, images (.png, .jpg, .jpeg) are allowed, but can be configured via workflow settings." + } + }, + "additionalProperties": false + }, + "hasHandler": true + }, + { + "name": "missing_tool", + "description": "Report a missing tool or functionality needed to complete tasks", + "inputSchema": { + "type": "object", + "required": ["tool", "reason"], + "properties": { + "tool": { "type": "string", "description": "Name of the missing tool" }, + "reason": { "type": "string", "description": "Why this tool is needed" }, + "alternatives": { + "type": "string", + "description": "Possible alternatives or workarounds" + } + }, + "additionalProperties": false + } + } +] diff --git a/pkg/workflow/js/safe_outputs_mcp_server.cjs b/pkg/workflow/js/safe_outputs_mcp_server.cjs index 334bccd1efc..ac4efaa8c13 100644 --- a/pkg/workflow/js/safe_outputs_mcp_server.cjs +++ b/pkg/workflow/js/safe_outputs_mcp_server.cjs @@ -332,244 +332,23 @@ const pushToPullRequestBranchHandler = args => { }; const normTool = toolName => (toolName ? toolName.replace(/-/g, "_").toLowerCase() : undefined); -const ALL_TOOLS = [ - { - name: "create_issue", - description: "Create a new GitHub issue", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Issue title" }, - body: { type: "string", description: "Issue body/description" }, - labels: { - type: "array", - items: { type: "string" }, - description: "Issue labels", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_discussion", - description: "Create a new GitHub discussion", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Discussion title" }, - body: { type: "string", description: "Discussion body/content" }, - category: { type: "string", description: "Discussion category" }, - }, - additionalProperties: false, - }, - }, - { - name: "add_comment", - description: "Add a comment to a GitHub issue or pull request", - inputSchema: { - type: "object", - required: ["body"], - properties: { - body: { type: "string", description: "Comment body/content" }, - issue_number: { - type: "number", - description: "Issue or PR number (optional for current context)", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_pull_request", - description: "Create a new GitHub pull request", - inputSchema: { - type: "object", - required: ["title", "body"], - properties: { - title: { type: "string", description: "Pull request title" }, - body: { - type: "string", - description: "Pull request body/description", - }, - branch: { - type: "string", - description: "Optional branch name. If not provided, the current branch will be used.", - }, - labels: { - type: "array", - items: { type: "string" }, - description: "Optional labels to add to the PR", - }, - }, - additionalProperties: false, - }, - handler: createPullRequestHandler, - }, - { - name: "create_pull_request_review_comment", - description: "Create a review comment on a GitHub pull request", - inputSchema: { - type: "object", - required: ["path", "line", "body"], - properties: { - path: { - type: "string", - description: "File path for the review comment", - }, - line: { - type: ["number", "string"], - description: "Line number for the comment", - }, - body: { type: "string", description: "Comment body content" }, - start_line: { - type: ["number", "string"], - description: "Optional start line for multi-line comments", - }, - side: { - type: "string", - enum: ["LEFT", "RIGHT"], - description: "Optional side of the diff: LEFT or RIGHT", - }, - }, - additionalProperties: false, - }, - }, - { - name: "create_code_scanning_alert", - description: "Create a code scanning alert. severity MUST be one of 'error', 'warning', 'info', 'note'.", - inputSchema: { - type: "object", - required: ["file", "line", "severity", "message"], - properties: { - file: { - type: "string", - description: "File path where the issue was found", - }, - line: { - type: ["number", "string"], - description: "Line number where the issue was found", - }, - severity: { - type: "string", - enum: ["error", "warning", "info", "note"], - description: - ' Security severity levels follow the industry-standard Common Vulnerability Scoring System (CVSS) that is also used for advisories in the GitHub Advisory Database and must be one of "error", "warning", "info", "note".', - }, - message: { - type: "string", - description: "Alert message describing the issue", - }, - column: { - type: ["number", "string"], - description: "Optional column number", - }, - ruleIdSuffix: { - type: "string", - description: "Optional rule ID suffix for uniqueness", - }, - }, - additionalProperties: false, - }, - }, - { - name: "add_labels", - description: "Add labels to a GitHub issue or pull request", - inputSchema: { - type: "object", - required: ["labels"], - properties: { - labels: { - type: "array", - items: { type: "string" }, - description: "Labels to add", - }, - issue_number: { - type: "number", - description: "Issue or PR number (optional for current context)", - }, - }, - additionalProperties: false, - }, - }, - { - name: "update_issue", - description: "Update a GitHub issue", - inputSchema: { - type: "object", - properties: { - status: { - type: "string", - enum: ["open", "closed"], - description: "Optional new issue status", - }, - title: { type: "string", description: "Optional new issue title" }, - body: { type: "string", description: "Optional new issue body" }, - issue_number: { - type: ["number", "string"], - description: "Optional issue number for target '*'", - }, - }, - additionalProperties: false, - }, - }, - { - name: "push_to_pull_request_branch", - description: "Push changes to a pull request branch", - inputSchema: { - type: "object", - required: ["message"], - properties: { - branch: { - type: "string", - description: "Optional branch name. If not provided, the current branch will be used.", - }, - message: { type: "string", description: "Commit message" }, - pull_request_number: { - type: ["number", "string"], - description: "Optional pull request number for target '*'", - }, - }, - additionalProperties: false, - }, - handler: pushToPullRequestBranchHandler, - }, - { - name: "upload_asset", - description: "Publish a file as a URL-addressable asset to an orphaned git branch", - inputSchema: { - type: "object", - required: ["path"], - properties: { - path: { - type: "string", - description: - "Path to the file to publish as an asset. Must be a file under the current workspace or /tmp directory. By default, images (.png, .jpg, .jpeg) are allowed, but can be configured via workflow settings.", - }, - }, - additionalProperties: false, - }, - handler: uploadAssetHandler, - }, - { - name: "missing_tool", - description: "Report a missing tool or functionality needed to complete tasks", - inputSchema: { - type: "object", - required: ["tool", "reason"], - properties: { - tool: { type: "string", description: "Name of the missing tool" }, - reason: { type: "string", description: "Why this tool is needed" }, - alternatives: { - type: "string", - description: "Possible alternatives or workarounds", - }, - }, - additionalProperties: false, - }, - }, -]; + +// Load tools from environment variable +const toolsEnv = process.env.GITHUB_AW_SAFE_OUTPUTS_TOOLS; + +if (!toolsEnv) { + throw new Error("GITHUB_AW_SAFE_OUTPUTS_TOOLS environment variable is required"); +} + +let PREDEFINED_TOOLS = {}; +debug(`Loading tools from GITHUB_AW_SAFE_OUTPUTS_TOOLS environment variable`); +try { + PREDEFINED_TOOLS = JSON.parse(toolsEnv); + debug(`Loaded ${Object.keys(PREDEFINED_TOOLS).length} predefined tools from environment`); +} catch (error) { + debug(`Error parsing GITHUB_AW_SAFE_OUTPUTS_TOOLS: ${error instanceof Error ? error.message : String(error)}`); + throw new Error(`Failed to parse GITHUB_AW_SAFE_OUTPUTS_TOOLS: ${error instanceof Error ? error.message : String(error)}`); +} debug(`v${SERVER_INFO.version} ready on stdio`); debug(` output file: ${outputFile}`); @@ -578,10 +357,21 @@ debug(` config: ${JSON.stringify(safeOutputsConfig)}`); // Create a comprehensive tools map including both predefined tools and dynamic safe-jobs const TOOLS = {}; -// Add predefined tools that are enabled in configuration -ALL_TOOLS.forEach(tool => { - if (Object.keys(safeOutputsConfig).find(config => normTool(config) === tool.name)) { - TOOLS[tool.name] = tool; +// Use pre-filtered tools from environment (provided by Go) +debug(`Using pre-filtered tools from environment variable`); +Object.keys(PREDEFINED_TOOLS).forEach(toolName => { + const tool = PREDEFINED_TOOLS[toolName]; + TOOLS[toolName] = tool; + + // Attach handlers for specific tools + if (tool.hasHandler) { + if (toolName === "create_pull_request") { + TOOLS[toolName].handler = createPullRequestHandler; + } else if (toolName === "push_to_pull_request_branch") { + TOOLS[toolName].handler = pushToPullRequestBranchHandler; + } else if (toolName === "upload_asset") { + TOOLS[toolName].handler = uploadAssetHandler; + } } }); @@ -594,8 +384,9 @@ Object.keys(safeOutputsConfig).forEach(configKey => { return; } - // Check if this is a safe-job (not in ALL_TOOLS) - if (!ALL_TOOLS.find(t => t.name === normalizedKey)) { + // Check if this is a safe-job (not in PREDEFINED_TOOLS) + const isKnownTool = PREDEFINED_TOOLS[normalizedKey]; + if (!isKnownTool) { const jobConfig = safeOutputsConfig[configKey]; // Create a dynamic tool for this safe-job @@ -666,6 +457,9 @@ Object.keys(safeOutputsConfig).forEach(configKey => { debug(` tools: ${Object.keys(TOOLS).join(", ")}`); if (!Object.keys(TOOLS).length) throw new Error("No tools enabled in configuration"); +// Log the filtered tools for debugging +console.error(`[${SERVER_INFO.name}] Filtered tools:\n${JSON.stringify(TOOLS, null, 2)}`); + function handleMessage(req) { // Validate basic JSON-RPC structure if (!req || typeof req !== "object") { diff --git a/pkg/workflow/mcps.go b/pkg/workflow/mcps.go index f8ecd7fac95..f8f4b5594c3 100644 --- a/pkg/workflow/mcps.go +++ b/pkg/workflow/mcps.go @@ -163,10 +163,18 @@ func (c *Compiler) generateMCPSetup(yaml *strings.Builder, tools map[string]any, yaml.WriteString(" - name: Setup MCPs\n") if HasSafeOutputsEnabled(workflowData.SafeOutputs) { if safeOutputConfig != "" { + // Generate filtered tools JSON + filteredToolsJSON, err := c.GenerateFilteredToolsJSON(workflowData) + if err != nil { + // Log error but continue with empty tools + filteredToolsJSON = "{}" + } + // Add environment variables for JSONL validation yaml.WriteString(" env:\n") fmt.Fprintf(yaml, " GITHUB_AW_SAFE_OUTPUTS: ${{ env.GITHUB_AW_SAFE_OUTPUTS }}\n") fmt.Fprintf(yaml, " GITHUB_AW_SAFE_OUTPUTS_CONFIG: %q\n", safeOutputConfig) + fmt.Fprintf(yaml, " GITHUB_AW_SAFE_OUTPUTS_TOOLS: %q\n", filteredToolsJSON) if workflowData.SafeOutputs != nil && workflowData.SafeOutputs.UploadAssets != nil { fmt.Fprintf(yaml, " GITHUB_AW_ASSETS_BRANCH: %q\n", workflowData.SafeOutputs.UploadAssets.BranchName) fmt.Fprintf(yaml, " GITHUB_AW_ASSETS_MAX_SIZE_KB: %d\n", workflowData.SafeOutputs.UploadAssets.MaxSizeKB) diff --git a/pkg/workflow/safe_outputs_mcp_server_test.go b/pkg/workflow/safe_outputs_mcp_server_test.go index 2758a413748..9655ce5fd22 100644 --- a/pkg/workflow/safe_outputs_mcp_server_test.go +++ b/pkg/workflow/safe_outputs_mcp_server_test.go @@ -43,6 +43,41 @@ func NewMCPTestClient(t *testing.T, outputFile string, config map[string]any) *M t.Fatalf("Failed to marshal config: %v", err) } env = append(env, fmt.Sprintf("GITHUB_AW_SAFE_OUTPUTS_CONFIG=%s", string(configJSON))) + + // Generate filtered tools JSON based on the config + compiler := NewCompiler(false, "", "test") + compiler.SetSkipValidation(true) + + // Build a minimal WorkflowData with the safe outputs config + safeOutputs := &SafeOutputsConfig{} + for key := range config { + // Normalize the key to handle both dash and underscore formats + normalizedKey := strings.ReplaceAll(key, "_", "-") + switch normalizedKey { + case "create-issue": + safeOutputs.CreateIssues = &CreateIssuesConfig{} + case "create-discussion": + safeOutputs.CreateDiscussions = &CreateDiscussionsConfig{} + case "add-comment": + safeOutputs.AddComments = &AddCommentsConfig{} + case "create-pull-request": + safeOutputs.CreatePullRequests = &CreatePullRequestsConfig{} + case "upload-asset": + safeOutputs.UploadAssets = &UploadAssetsConfig{} + case "missing-tool": + safeOutputs.MissingTool = &MissingToolConfig{} + } + } + + workflowData := &WorkflowData{ + SafeOutputs: safeOutputs, + } + + toolsJSON, err := compiler.GenerateFilteredToolsJSON(workflowData) + if err != nil { + t.Fatalf("Failed to generate tools JSON: %v", err) + } + env = append(env, fmt.Sprintf("GITHUB_AW_SAFE_OUTPUTS_TOOLS=%s", toolsJSON)) } // Create command for the MCP server diff --git a/pkg/workflow/safe_outputs_tools.go b/pkg/workflow/safe_outputs_tools.go new file mode 100644 index 00000000000..ceac1205589 --- /dev/null +++ b/pkg/workflow/safe_outputs_tools.go @@ -0,0 +1,208 @@ +package workflow + +import ( + _ "embed" + "encoding/json" +) + +//go:embed data/safe_outputs_tools.json +var safeOutputsToolsJSON string + +// SafeOutputToolDefinition represents a tool definition for the safe-outputs MCP server +type SafeOutputToolDefinition struct { + Name string `json:"name"` + Description string `json:"description"` + InputSchema map[string]interface{} `json:"inputSchema"` + HasHandler bool `json:"hasHandler,omitempty"` +} + +// GetSafeOutputsToolsDefinitions returns the parsed tool definitions +func GetSafeOutputsToolsDefinitions() ([]SafeOutputToolDefinition, error) { + var tools []SafeOutputToolDefinition + if err := json.Unmarshal([]byte(safeOutputsToolsJSON), &tools); err != nil { + return nil, err + } + return tools, nil +} + +// GenerateFilteredToolsJSON generates a JSON string of tools filtered by the safe-outputs configuration +// The output is a JSON object mapping normalized tool names to tool definitions +func (c *Compiler) GenerateFilteredToolsJSON(data *WorkflowData) (string, error) { + if data == nil || data.SafeOutputs == nil { + return "{}", nil + } + + // Get all tool definitions + allTools, err := GetSafeOutputsToolsDefinitions() + if err != nil { + return "", err + } + + // Build a map of enabled tools based on safe-outputs configuration + enabledTools := make(map[string]SafeOutputToolDefinition) + + // Helper function to normalize tool names (convert dashes to underscores, lowercase) + normalizeTool := func(name string) string { + // This matches the normTool function in the JavaScript + result := "" + for _, ch := range name { + if ch == '-' { + result += "_" + } else { + result += string(ch) + } + } + // JavaScript toLowerCase is locale-independent for ASCII + return result + } + + // Check which tools are enabled in the configuration + safeOutputsConfig := c.generateSafeOutputsConfigMap(data) + + for _, tool := range allTools { + // Check if this tool is enabled in the configuration + for configKey := range safeOutputsConfig { + if normalizeTool(configKey) == tool.Name { + enabledTools[tool.Name] = tool + break + } + } + } + + // Convert to JSON + toolsJSON, err := json.Marshal(enabledTools) + if err != nil { + return "", err + } + + return string(toolsJSON), nil +} + +// generateSafeOutputsConfigMap generates a map of safe-outputs configuration +// This is similar to generateSafeOutputsConfig but returns a map instead of JSON string +func (c *Compiler) generateSafeOutputsConfigMap(data *WorkflowData) map[string]interface{} { + safeOutputsConfig := make(map[string]interface{}) + + if data.SafeOutputs == nil { + return safeOutputsConfig + } + + // Handle safe-outputs configuration if present + if data.SafeOutputs.CreateIssues != nil { + issueConfig := map[string]interface{}{} + if data.SafeOutputs.CreateIssues.Max > 0 { + issueConfig["max"] = data.SafeOutputs.CreateIssues.Max + } + if data.SafeOutputs.CreateIssues.Min > 0 { + issueConfig["min"] = data.SafeOutputs.CreateIssues.Min + } + safeOutputsConfig["create-issue"] = issueConfig + } + if data.SafeOutputs.AddComments != nil { + commentConfig := map[string]interface{}{} + if data.SafeOutputs.AddComments.Target != "" { + commentConfig["target"] = data.SafeOutputs.AddComments.Target + } + if data.SafeOutputs.AddComments.Max > 0 { + commentConfig["max"] = data.SafeOutputs.AddComments.Max + } + if data.SafeOutputs.AddComments.Min > 0 { + commentConfig["min"] = data.SafeOutputs.AddComments.Min + } + safeOutputsConfig["add-comment"] = commentConfig + } + if data.SafeOutputs.CreateDiscussions != nil { + discussionConfig := map[string]interface{}{} + if data.SafeOutputs.CreateDiscussions.Max > 0 { + discussionConfig["max"] = data.SafeOutputs.CreateDiscussions.Max + } + if data.SafeOutputs.CreateDiscussions.Min > 0 { + discussionConfig["min"] = data.SafeOutputs.CreateDiscussions.Min + } + safeOutputsConfig["create-discussion"] = discussionConfig + } + if data.SafeOutputs.CreatePullRequests != nil { + prConfig := map[string]interface{}{} + if data.SafeOutputs.CreatePullRequests.Min > 0 { + prConfig["min"] = data.SafeOutputs.CreatePullRequests.Min + } + safeOutputsConfig["create-pull-request"] = prConfig + } + if data.SafeOutputs.CreatePullRequestReviewComments != nil { + prReviewCommentConfig := map[string]interface{}{} + if data.SafeOutputs.CreatePullRequestReviewComments.Max > 0 { + prReviewCommentConfig["max"] = data.SafeOutputs.CreatePullRequestReviewComments.Max + } + if data.SafeOutputs.CreatePullRequestReviewComments.Min > 0 { + prReviewCommentConfig["min"] = data.SafeOutputs.CreatePullRequestReviewComments.Min + } + safeOutputsConfig["create-pull-request-review-comment"] = prReviewCommentConfig + } + if data.SafeOutputs.CreateCodeScanningAlerts != nil { + securityReportConfig := map[string]interface{}{} + if data.SafeOutputs.CreateCodeScanningAlerts.Max > 0 { + securityReportConfig["max"] = data.SafeOutputs.CreateCodeScanningAlerts.Max + } + if data.SafeOutputs.CreateCodeScanningAlerts.Min > 0 { + securityReportConfig["min"] = data.SafeOutputs.CreateCodeScanningAlerts.Min + } + safeOutputsConfig["create-code-scanning-alert"] = securityReportConfig + } + if data.SafeOutputs.AddLabels != nil { + labelConfig := map[string]interface{}{} + if data.SafeOutputs.AddLabels.Max > 0 { + labelConfig["max"] = data.SafeOutputs.AddLabels.Max + } + if data.SafeOutputs.AddLabels.Min > 0 { + labelConfig["min"] = data.SafeOutputs.AddLabels.Min + } + if len(data.SafeOutputs.AddLabels.Allowed) > 0 { + labelConfig["allowed"] = data.SafeOutputs.AddLabels.Allowed + } + safeOutputsConfig["add-labels"] = labelConfig + } + if data.SafeOutputs.UpdateIssues != nil { + updateConfig := map[string]interface{}{} + if data.SafeOutputs.UpdateIssues.Max > 0 { + updateConfig["max"] = data.SafeOutputs.UpdateIssues.Max + } + if data.SafeOutputs.UpdateIssues.Min > 0 { + updateConfig["min"] = data.SafeOutputs.UpdateIssues.Min + } + safeOutputsConfig["update-issue"] = updateConfig + } + if data.SafeOutputs.PushToPullRequestBranch != nil { + pushToBranchConfig := map[string]interface{}{} + if data.SafeOutputs.PushToPullRequestBranch.Target != "" { + pushToBranchConfig["target"] = data.SafeOutputs.PushToPullRequestBranch.Target + } + if data.SafeOutputs.PushToPullRequestBranch.Max > 0 { + pushToBranchConfig["max"] = data.SafeOutputs.PushToPullRequestBranch.Max + } + if data.SafeOutputs.PushToPullRequestBranch.Min > 0 { + pushToBranchConfig["min"] = data.SafeOutputs.PushToPullRequestBranch.Min + } + safeOutputsConfig["push-to-pull-request-branch"] = pushToBranchConfig + } + if data.SafeOutputs.UploadAssets != nil { + uploadConfig := map[string]interface{}{} + if data.SafeOutputs.UploadAssets.Max > 0 { + uploadConfig["max"] = data.SafeOutputs.UploadAssets.Max + } + if data.SafeOutputs.UploadAssets.Min > 0 { + uploadConfig["min"] = data.SafeOutputs.UploadAssets.Min + } + safeOutputsConfig["upload-asset"] = uploadConfig + } + if data.SafeOutputs.MissingTool != nil { + missingToolConfig := map[string]interface{}{} + safeOutputsConfig["missing-tool"] = missingToolConfig + } + + // Add safe-jobs as well + for jobName := range data.SafeOutputs.Jobs { + safeOutputsConfig[jobName] = map[string]interface{}{} + } + + return safeOutputsConfig +} diff --git a/pkg/workflow/safe_outputs_tools_test.go b/pkg/workflow/safe_outputs_tools_test.go new file mode 100644 index 00000000000..2b72a626669 --- /dev/null +++ b/pkg/workflow/safe_outputs_tools_test.go @@ -0,0 +1,197 @@ +package workflow + +import ( + "encoding/json" + "testing" +) + +func TestSafeOutputsToolsJSON(t *testing.T) { + // Test that we can parse the embedded tools JSON + tools, err := GetSafeOutputsToolsDefinitions() + if err != nil { + t.Fatalf("Failed to parse tools JSON: %v", err) + } + + // Verify we have the expected number of tools + expectedTools := []string{ + "create_issue", + "create_discussion", + "add_comment", + "create_pull_request", + "create_pull_request_review_comment", + "create_code_scanning_alert", + "add_labels", + "update_issue", + "push_to_pull_request_branch", + "upload_asset", + "missing_tool", + } + + if len(tools) != len(expectedTools) { + t.Errorf("Expected %d tools, got %d", len(expectedTools), len(tools)) + } + + // Verify each expected tool is present + toolMap := make(map[string]bool) + for _, tool := range tools { + toolMap[tool.Name] = true + } + + for _, expected := range expectedTools { + if !toolMap[expected] { + t.Errorf("Expected tool %q not found", expected) + } + } + + // Verify each tool has required fields + for _, tool := range tools { + if tool.Name == "" { + t.Errorf("Tool has empty name") + } + if tool.Description == "" { + t.Errorf("Tool %q has empty description", tool.Name) + } + if tool.InputSchema == nil { + t.Errorf("Tool %q has nil inputSchema", tool.Name) + } + } +} + +func TestGenerateFilteredToolsJSON(t *testing.T) { + tests := []struct { + name string + safeOutputs *SafeOutputsConfig + expectedTools []string + }{ + { + name: "No safe outputs", + safeOutputs: nil, + expectedTools: []string{}, + }, + { + name: "Single output - create-issue", + safeOutputs: &SafeOutputsConfig{ + CreateIssues: &CreateIssuesConfig{ + BaseSafeOutputConfig: BaseSafeOutputConfig{Max: 1}, + }, + MissingTool: &MissingToolConfig{}, + }, + expectedTools: []string{"create_issue", "missing_tool"}, + }, + { + name: "Multiple outputs", + safeOutputs: &SafeOutputsConfig{ + CreateIssues: &CreateIssuesConfig{ + BaseSafeOutputConfig: BaseSafeOutputConfig{Max: 1}, + }, + AddComments: &AddCommentsConfig{ + BaseSafeOutputConfig: BaseSafeOutputConfig{Max: 1}, + }, + CreatePullRequests: &CreatePullRequestsConfig{}, + MissingTool: &MissingToolConfig{}, + }, + expectedTools: []string{"create_issue", "add_comment", "create_pull_request", "missing_tool"}, + }, + { + name: "Upload assets", + safeOutputs: &SafeOutputsConfig{ + UploadAssets: &UploadAssetsConfig{}, + MissingTool: &MissingToolConfig{}, + }, + expectedTools: []string{"upload_asset", "missing_tool"}, + }, + } + + compiler := NewCompiler(false, "", "test") + compiler.SetSkipValidation(true) + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + data := &WorkflowData{ + SafeOutputs: tt.safeOutputs, + } + + toolsJSON, err := compiler.GenerateFilteredToolsJSON(data) + if err != nil { + t.Fatalf("Failed to generate filtered tools JSON: %v", err) + } + + // Parse the JSON + var tools map[string]SafeOutputToolDefinition + if err := json.Unmarshal([]byte(toolsJSON), &tools); err != nil { + t.Fatalf("Failed to parse tools JSON: %v", err) + } + + // Verify expected tools are present + if len(tools) != len(tt.expectedTools) { + t.Errorf("Expected %d tools, got %d", len(tt.expectedTools), len(tools)) + } + + for _, expected := range tt.expectedTools { + if _, ok := tools[expected]; !ok { + t.Errorf("Expected tool %q not found in filtered tools", expected) + } + } + + // Verify no unexpected tools + for toolName := range tools { + found := false + for _, expected := range tt.expectedTools { + if toolName == expected { + found = true + break + } + } + if !found { + t.Errorf("Unexpected tool %q found in filtered tools", toolName) + } + } + }) + } +} + +func TestGenerateFilteredToolsJSONWithHandlers(t *testing.T) { + compiler := NewCompiler(false, "", "test") + compiler.SetSkipValidation(true) + + data := &WorkflowData{ + SafeOutputs: &SafeOutputsConfig{ + CreatePullRequests: &CreatePullRequestsConfig{}, + PushToPullRequestBranch: &PushToPullRequestBranchConfig{}, + UploadAssets: &UploadAssetsConfig{}, + MissingTool: &MissingToolConfig{}, + }, + } + + toolsJSON, err := compiler.GenerateFilteredToolsJSON(data) + if err != nil { + t.Fatalf("Failed to generate filtered tools JSON: %v", err) + } + + // Parse the JSON + var tools map[string]SafeOutputToolDefinition + if err := json.Unmarshal([]byte(toolsJSON), &tools); err != nil { + t.Fatalf("Failed to parse tools JSON: %v", err) + } + + // Verify hasHandler flag is set for tools that have handlers + toolsWithHandlers := []string{"create_pull_request", "push_to_pull_request_branch", "upload_asset"} + + for _, toolName := range toolsWithHandlers { + tool, ok := tools[toolName] + if !ok { + t.Errorf("Expected tool %q not found", toolName) + continue + } + if !tool.HasHandler { + t.Errorf("Tool %q should have hasHandler=true", toolName) + } + } + + // Verify missing_tool does not have hasHandler flag + if tool, ok := tools["missing_tool"]; ok { + if tool.HasHandler { + t.Errorf("Tool missing_tool should not have hasHandler flag") + } + } +}