Skip to content

Copilot: make MCP CLI prompt list reflect actual mounted servers under --disable-builtin-mcps#46778

Merged
pelikhan merged 8 commits into
mainfrom
copilot/fix-mcp-clis-github-access
Jul 20, 2026
Merged

Copilot: make MCP CLI prompt list reflect actual mounted servers under --disable-builtin-mcps#46778
pelikhan merged 8 commits into
mainfrom
copilot/fix-mcp-clis-github-access

Conversation

Copilot AI commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Copilot runs with --disable-builtin-mcps, but the generated <mcp-clis> section only listed safeoutputs, even when github/custom MCP wrappers were mounted on PATH. This created nondeterministic GitHub access: runs succeeded only when the agent guessed CLI server binaries existed.

  • Prompt/runtime alignment for Copilot CLI mounts

    • Updated MCP CLI server list generation to include CLI-mounted servers that Copilot can actually invoke when CLI mounting is active.
    • For Copilot engine runs, the list now includes:
      • github (when GitHub MCP is configured and not in gh-proxy mode)
      • custom MCP servers with MCP config (e.g. azure-devops)
    • Non-Copilot behavior remains unchanged.
  • Regression coverage

    • Added focused tests to verify:
      • Copilot includes safeoutputs, github, and custom MCP servers in the generated CLI server list.
      • Non-Copilot retains prior list behavior for the same setup.
if len(servers) > 0 && data.EngineConfig != nil && data.EngineConfig.ID == string(constants.CopilotEngine) {
    if hasGitHubTool(data.ParsedTools) && !isGitHubCLIModeEnabled(data) && !slices.Contains(servers, "github") {
        servers = append(servers, "github")
    }

    for toolName, toolValue := range data.Tools {
        mcpConfig, ok := toolValue.(map[string]any)
        if ok {
            if hasMcp, _ := hasMCPConfig(mcpConfig); hasMcp && !slices.Contains(servers, toolName) {
                servers = append(servers, toolName)
            }
        }
    }
}

run: https://github.com/github/gh-aw/actions/runs/29735453773

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • pi.dev

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "pi.dev"

See Network Configuration for more information.

Generated by 👨‍🍳 PR Sous Chef · 7.59 AIC · ⌖ 5.86 AIC · ⊞ 5.4K ·
Comment /souschef to run again

Copilot AI and others added 2 commits July 20, 2026 09:41
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix CLI-mounted github/azure-devops in Copilot engine Copilot: make MCP CLI prompt list reflect actual mounted servers under --disable-builtin-mcps Jul 20, 2026
Copilot AI requested a review from pelikhan July 20, 2026 09:51
@pelikhan
pelikhan marked this pull request as ready for review July 20, 2026 09:51
Copilot AI review requested due to automatic review settings July 20, 2026 09:51
@github-actions

github-actions Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

@github-actions

github-actions Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

@github-actions

github-actions Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

@github-actions

Copy link
Copy Markdown
Contributor

Thanks for the fix, @app/copilot-swe-agent! 🤖 This implementation looks solid — the change ensures CLI-mounted github and azure-devops servers are properly listed in the MCP CLI prompt, addressing the intermittent GitHub access issue in #46259.

What's working well:

  • Focused diff that addresses the root cause (prompt omission)
  • Comprehensive test coverage across multiple scenarios (bash restriction, playwright CLI, GitHub proxy mode)
  • Clear documentation in code comments explaining the CLI-mount behavior

To get this across the finish line:

  • Complete the pending todo: "Run final validation (including parallel validation), secret scan, and report results" — this is essential before merge.
  • Once the WIP is resolved, update the PR title to remove the [WIP] prefix.

The implementation looks ready for final validation. Once that step completes and the WIP is cleared, this should be good to merge! 🎉

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • patchdiff.githubusercontent.com

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "patchdiff.githubusercontent.com"

See Network Configuration for more information.

Generated by ✅ Contribution Check · 64.1 AIC · ⌖ 8.09 AIC · ⊞ 6.2K ·

@github-actions

github-actions Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ completed the design decision gate check.

No ADR enforcement needed: PR does not have the 'implementation' label and has ≤100 new lines of code in business logic directories (70 additions detected, threshold is 100).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Aligns Copilot prompts with MCP servers mounted as CLI wrappers.

Changes:

  • Adds GitHub and custom MCP servers to Copilot’s CLI server list.
  • Adds regression tests for Copilot and non-Copilot behavior.
Show a summary per file
File Description
pkg/workflow/mcp_cli_mount.go Expands Copilot MCP CLI server discovery.
pkg/workflow/mcp_cli_mount_test.go Tests server-list behavior by engine.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comments suppressed due to low confidence (1)

pkg/workflow/mcp_cli_mount.go:130

  • This pass does not actually mirror every server mounted from the manifest: it only adds GitHub and map-shaped custom MCP tools. For example, with Copilot + safe outputs + Playwright MCP and no cli-proxy, safeoutputs activates the mount step, the mount script creates both wrappers, but this loop omits playwright from the prompt (the same applies to agentic-workflows). The agent is therefore still forced to guess some mounted command names. Build this augmentation from the same engine-filtered server set used to create the gateway manifest, including the manifest ID normalization for built-in servers.
		for toolName, toolValue := range data.Tools {
			if internalMCPServerNames[toolName] {
				continue
			}
			mcpConfig, ok := toolValue.(map[string]any)
			if !ok {
				continue
			}
			if hasMcp, _ := hasMCPConfig(mcpConfig); hasMcp && !slices.Contains(servers, toolName) {
				servers = append(servers, toolName)
			}
  • Files reviewed: 2/2 changed files
  • Comments generated: 1
  • Review effort level: Medium

Comment thread pkg/workflow/mcp_cli_mount.go Outdated
// custom servers such as azure-devops) and exposes wrappers on PATH. Reflect
// that runtime reality in the generated CLI server list so agents can call
// these wrappers deterministically instead of guessing command names.
if len(servers) > 0 && data.EngineConfig != nil && data.EngineConfig.ID == string(constants.CopilotEngine) {
@github-actions

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 95/100 — Excellent

Analyzed 4 test(s): 4 design, 0 implementation, 0 violation(s).

📊 Metrics (4 tests)
Metric Value
Analyzed 4 (Go: 4, JS: 0)
✅ Design 4 (100%)
⚠️ Implementation 0 (0%)
Edge/error coverage 4 (100%)
Duplicate clusters 0
Inflation No
🚨 Violations 0
Test File Classification Issues
TestHasBashRestrictedAllowlist pkg/workflow/mcp_cli_mount_test.go design_test None
TestWithMountedCLIShellCommandsInRestrictedBash_PlaywrightCLIMode pkg/workflow/mcp_cli_mount_test.go design_test None
TestBuildMCPCLIPromptSection_PromptFileUsesNonHeadingLabels pkg/workflow/mcp_cli_mount_test.go design_test None
TestGetMCPCLIServerNames_CopilotIncludesManifestServersInPromptList pkg/workflow/mcp_cli_mount_test.go design_test None

Verdict

Passed. 0% implementation tests (threshold: 30%). Build tag present. No forbidden mocks. Strong edge-case and behavioral coverage across all 4 test functions (~19 subtests).

Test highlights:

  • TestHasBashRestrictedAllowlist: 8-row table-driven test covering nil, missing key, wildcard (*, :*), and specific command lists — full boundary coverage of the allowlist detection contract.
  • TestWithMountedCLIShellCommandsInRestrictedBash_PlaywrightCLIMode: 9 subtests covering playwright CLI vs MCP mode, gh-proxy vs local mode, nil tools, wildcard preservation, and deduplication guard.
  • TestGetMCPCLIServerNames_CopilotIncludesManifestServersInPromptList: directly validates the PR core behavioral change — Copilot engine lists manifest servers; non-Copilot engines do not.

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • awmgmcpg

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "awmgmcpg"

See Network Configuration for more information.

🧪 Test quality analysis by Test Quality Sentinel · 27.9 AIC · ⌖ 9.83 AIC · ⊞ 7K ·
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Test Quality Sentinel: 95/100. 0% implementation tests (threshold: 30%).

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skills-Based Review 🧠

Applied /diagnosing-bugs, /codebase-design, and /tdd — commenting (no blocking issues, but three improvements worth addressing).

📋 Key Themes

Issues Found

  • Hardcoded "github" string (mcp_cli_mount.go:116) — inconsistent with constants-based naming elsewhere; typo risk.
  • Duplicated custom-server iteration (mcp_cli_mount.go:120–131) — same data.Tools scan already exists in the cli-proxy block; a shared helper would prevent drift.
  • Missing boundary-condition test (mcp_cli_mount_test.go:256) — the len(servers) > 0 guard silently skips the new block for Copilot workflows without safeoutputs/mcpscripts; a test should assert the intended behaviour.

Positive Highlights

  • ✅ Clear PR description with an inline code snippet explaining the fix.
  • ✅ Non-Copilot path untouched; regression test verifies this.
  • internalMCPServerNames skip guard correctly carried over to the new loop.
  • isGitHubCLIModeEnabled check prevents double-listing github in gh-proxy mode.

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • proxy.golang.org

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "proxy.golang.org"

See Network Configuration for more information.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · 39.7 AIC · ⌖ 4.75 AIC · ⊞ 6.7K
Comment /matt to run again

Comment thread pkg/workflow/mcp_cli_mount.go Outdated
// that runtime reality in the generated CLI server list so agents can call
// these wrappers deterministically instead of guessing command names.
if len(servers) > 0 && data.EngineConfig != nil && data.EngineConfig.ID == string(constants.CopilotEngine) {
if hasGitHubTool(data.ParsedTools) && !isGitHubCLIModeEnabled(data) && !slices.Contains(servers, "github") {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/diagnosing-bugs] "github" is hardcoded rather than using a named constant, while the rest of the file uses constants.SafeOutputsMCPServerID.String() etc. A typo here would silently produce a wrong server name in the generated prompt.

💡 Suggested fix

Define a constant such as constants.GitHubMCPServerID and use .String() everywhere, so a future rename cannot diverge silently.

@copilot please address this.

servers = append(servers, "github")
}

for toolName, toolValue := range data.Tools {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/codebase-design] The new Copilot loop (lines 120–131) duplicates the custom-MCP-server logic already in the cli-proxy loop (lines 55–94). For a Copilot workflow that also has cli-proxy: true, custom servers will be appended twice (deduplicated by slices.Contains, but the scan happens twice). This adds fragility: future changes to the earlier loop may not be mirrored here.

💡 Suggested refactor

Extract the "collect custom MCP servers from data.Tools" logic into a shared helper, and call it from both places. This keeps a single source of truth and makes the invariants obvious.

@copilot please address this.

assert.True(t, slices.IsSorted(servers), "server list should remain sorted")
})

t.Run("non-copilot keeps existing behavior", func(t *testing.T) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/tdd] slices.IsSorted(servers) passes because sort.Strings is called unconditionally at the end of getMCPCLIServerNames, but this test does not document that invariant explicitly. More importantly, there is no test for the boundary condition where len(servers) == 0 before the Copilot block — i.e., a Copilot workflow with the GitHub tool enabled but no safeoutputs/mcpscripts configured. In that case the new block is skipped entirely and github is silently omitted.

💡 Suggested test
t.Run("copilot without safeoutputs does not add github", func(t *testing.T) {
    data := &WorkflowData{
        EngineConfig: &EngineConfig{ID: string(constants.CopilotEngine)},
        Tools: map[string]any{"github": true},
        ParsedTools: NewTools(map[string]any{"github": true}),
        // SafeOutputs intentionally nil
    }
    servers := getMCPCLIServerNames(data)
    assert.Nil(t, servers, "no mounts active → guard prevents github from being added")
})

If this is the intended behaviour, a test documents it explicitly. If it is a gap, the guard condition needs adjusting.

@copilot please address this.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The implementation is correct and focused. The Copilot-specific block is logically sound: it uses len(servers) > 0 as a guard to ensure infrastructure mounts are active, correctly skips internalMCPServerNames (which includes github), and handles the GitHub server separately via hasGitHubTool. The test coverage validates both the new behavior and the non-regression case for other engines.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · 33.5 AIC · ⌖ 4.32 AIC · ⊞ 5K

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall

The logic is sound and the intent is well-documented. One non-blocking issue found.

What the PR does: Adds a Copilot-specific block in getMCPCLIServerNames to reflect that --disable-builtin-mcps is always active for Copilot; the mount script exposes github and custom MCP servers on PATH, so they appear in the generated <mcp-clis> prompt section.

What looks good:

  • The internalMCPServerNames guard and !slices.Contains duplicate-prevention are present.
  • Non-Copilot path is untouched.
  • Tests cover both branches.

Non-blocking: The slices.IsSorted assertion in the new test is tautological — see inline comment.

🔎 Code quality review by PR Code Quality Reviewer · 41.9 AIC · ⌖ 4.47 AIC · ⊞ 5.6K
Comment /review to run again

Comment thread pkg/workflow/mcp_cli_mount_test.go Outdated
assert.Contains(t, servers, constants.SafeOutputsMCPServerID.String(), "safeoutputs remains present")
assert.Contains(t, servers, "github", "github CLI wrapper should be listed for Copilot")
assert.Contains(t, servers, "azure-devops", "custom MCP server wrappers should be listed for Copilot")
assert.True(t, slices.IsSorted(servers), "server list should remain sorted")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tautological assertion: slices.IsSorted can never fail here because sort.Strings(servers) is called unconditionally at the end of getMCPCLIServerNames before returning. This assertion tests an implementation detail, not observable behavior, and gives false confidence that sort order is being maintained correctly.

💡 Suggested fix

Replace with an assertion that would actually catch a regression — e.g., verify the full sorted output or use assert.Equal with the expected ordered slice:

assert.Equal(t, []string{"azure-devops", "github", constants.SafeOutputsMCPServerID.String()}, servers,
    "server list order should be deterministic and sorted")

This pins both presence AND order, so a future change that skips the sort would actually fail the test.

@pelikhan

Copy link
Copy Markdown
Collaborator

@copilot run pr-finisher skill

Copilot AI and others added 2 commits July 20, 2026 10:10
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
…ation

Fixes the case where a Copilot workflow with cli-proxy: true and only a
GitHub MCP tool (no safeoutputs) would have len(servers)==0 before the
Copilot augmentation block, causing it to be skipped and `github` to
never be advertised in the CLI server list.

Include ParsedTools.CLIProxy in the activation condition alongside
len(servers) > 0, so the block runs whenever a CLI-mount trigger is
active regardless of whether any non-GitHub servers were collected first.

Also adds a regression test covering this specific case.

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

Copilot AI commented Jul 20, 2026

Copy link
Copy Markdown
Contributor Author

@copilot run pr-finisher skill

Addressed the reviewer's feedback in commit $(git -C /home/runner/work/gh-aw/gh-aw rev-parse --short HEAD). The activation condition at line 115 now reads:

isCLIMountActive := len(servers) > 0 || (data.ParsedTools != nil && data.ParsedTools.CLIProxy)
if isCLIMountActive && data.EngineConfig != nil && data.EngineConfig.ID == string(constants.CopilotEngine) {

This ensures a Copilot workflow with cli-proxy: true and only a GitHub MCP tool (no safeoutputs/mcpscripts) still enters the augmentation block and gets github advertised. A regression test covering this specific case was also added.

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

[pr-sous-chef] @copilot please run the pr-finisher skill, address the unresolved review feedback below, and rerun checks once the branch is ready.

Unresolved review feedback:

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • pi.dev

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "pi.dev"

See Network Configuration for more information.

Generated by 👨‍🍳 PR Sous Chef · 7.59 AIC · ⌖ 5.86 AIC · ⊞ 5.4K ·
Comment /souschef to run again

…est assertions

- Add constants.GitHubMCPServerID = "github" to avoid hard-coded strings
- Extract appendCustomMCPServerIfEligible() shared helper used by both the
  cli-proxy default case and the Copilot augmentation sweep, eliminating
  the duplicated custom-MCP-server detection logic
- Use constants.GitHubMCPServerID.String() throughout mcp_cli_mount.go
- Replace tautological slices.IsSorted() assertions with assert.Equal()
  pinning the full expected sorted slice in both Copilot test cases
- Add boundary-condition test: Copilot with no CLI mount trigger active
  returns nil (github is not inadvertently advertised)

Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>

Copilot AI commented Jul 20, 2026

Copy link
Copy Markdown
Contributor Author

[pr-sous-chef] @copilot please run the pr-finisher skill, address the unresolved review feedback below, and rerun checks once the branch is ready.

Unresolved review feedback:...

Addressed in commit 1fe4e86:

  1. Hardcoded "github" string — Added constants.GitHubMCPServerID MCPServerID = "github" and replaced all hardcoded "github" strings in mcp_cli_mount.go with constants.GitHubMCPServerID.String().

  2. Duplicated custom-server iteration — Extracted appendCustomMCPServerIfEligible(servers, toolName, toolValue) shared helper; both the cli-proxy default case and the Copilot augmentation sweep now call it instead of inlining the same scan logic.

  3. Tautological slices.IsSorted / missing boundary test — Replaced slices.IsSorted assertions with assert.Equal pinning the full expected sorted slice (catches both presence and order regressions). Added a boundary-condition subtest confirming that Copilot with no CLI mount trigger active returns nil and does not inadvertently advertise github.

…rIfEligible

Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>
Copilot AI requested a review from gh-aw-bot July 20, 2026 11:02
@pelikhan
pelikhan merged commit 56aed55 into main Jul 20, 2026
23 checks passed
@pelikhan
pelikhan deleted the copilot/fix-mcp-clis-github-access branch July 20, 2026 11:55
@github-actions

Copy link
Copy Markdown
Contributor

🎉 This pull request is included in a new release.

Release: v0.82.14

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

4 participants